如今作为一个Java程序员,如果没用过Spring的话基本上没法混了~ 这里就不再啰嗦如何如何从EJB走到了Spring(毕竟咱也没经历过那些个艰难的时代),直接来看它带给我们哪些东西:
下面分几个部分很粗地描述一下Spring~
要看控制反转,那么我们先看看没有反转的时候是什么样子的,当自己的服务依赖于别人的实现时,在其使用之前选择合适的服务实现对其进行初始化:
public class MyService { private XXService xxService; public MyService(){ this.xxService = new XXServiceImpl();/* 利用合适的实现进行初始化 */ } // ... }
程序可以正常运行,但是当你有几十个类中使用了XXService,而某一天发现XXServiceImpl是有BUG的,你想换个实现类,那就傻逼了~~
用IoC最原始的配置方式就可以将这个问题引刃而解:
<bean id="myService" class="com.test.MyService"> <property name="xxService" ref="xxService"/> </bean> <bean id="xxService" class="com.test.XXServiceImpl"/><!-- 改这里 -->
在配置文件中搞定他们之间的依赖关系,在使用前,需要用BeanFactory来构建其中的Bean,在需要时,调用getBean取得实例,然后就可以继续后面的操作了:
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml"); MyService myService = (MyService)factory.getBean("myService");
可能“顽固派”会说:干嘛要怎么麻烦,把你要使用的类包装一下再交给MyService使用就可以了啊!确实可以解决问题,但是思路决定出路,这种解决方法确实解决了上面的问题,但是下面ApplicationContext的登场,使得这种依赖管理变得异常简单:
public class MyService { @Resource /* 利用注解完成注入 */ private XXService xxService; // ... } @Component("xxService") /* 声明一个Bean */ public class XXServiceImpl{ // ... }
一直感觉注解在Java中的作用就是将配置信息植入到代码文件里面,现在在依赖管理中注解再一次发挥了巨大的威力。到这里基本上满足大部分的需求了,但是Spring提供的功能可能远超过了你需要的:
还有很多其他的扩展点,下面重点看下Bean的生命周期:
另外IoC里面有很多的细节点,逐步整理中:
关键字 | 作用 |
---|---|
byType/byName | 根据类型/名字注入 |
scope(singleton/prototype) | 单例还是每次都生成一个新的Bean |
factory-bean/FactoryBean | 工厂类型的Bean,getBean时会调用其getObject方法 |
@Resource | 默认byName,找不到的时候就byType(指定name就只能byName了) |
@Autowire | 默认byType,可以配合@Qualifier变为byName |
lazy-init | 只对singleton的Bean起作用,效果是在getBean的时候才初始化 |
面向切面编程(Aspect Oriented Programming)提供了另外一个角度来考虑程序结构,在介绍AOP的时候几乎都会介绍打日志的需求,当然除此之外还有很多的场景,这里就不赘述。简单来说Spring的AOP提供了拦截一批方法的手段。
其实我们自己也可以利用BeanFactoryPostProcessor来实现类似的功能,但在Spring中AOP强大的地方是提供了很多种方便的配置方式。第一种是用XML的配置方式:
<aop:config> <aop:aspect id="logaop" ref="allLogAdvice"><!--处理类 --> <aop:pointcut id="pointcut" expression="execution(* com.test.MyService.*(..))" /><!-- 方法 --> <aop:around method="aroundMethod" pointcut-ref="pointcut"/><!-- 方式及调用方法 --> </aop:aspect> </aop:config>
另外一种个人感觉比较好用的就是@Aspect方式,这种方式把方法和它要拦截的方法放在一个地方,比较自然一点:
@Aspect public class MyAspect { @Pointcut("execution(* com.test.MyService.*(..))") private void aspectjMethod(){}; @Before("service()") private void aroundMethod(){ /* code */ } }
在使用前需要配置
@Aspect public class LocalCacheAspect { @Pointcut("@annotation(localCache)") public void getLocalCacheAnnotation(LocalCache localCache) {} @Around("getLocalCacheAnnotation(localCache)") public Object handleCache(ProceedingJoinPoint joinPoint, LocalCache localCache) throws Throwable { /* code */ } }
这样当你在Spring的某个方法上加注解@LocalCache后,就可以被handleCache拦截了,在这里需要注意一下大小写。在具体实现的时候AOP利用了两种技术:
如果你想选择cglib需要配置文件中加上<aop:aspectj-autoproxy proxy-target-class=”true”/>。 AOP中涉及到的细节整理如下(不断更新中):
关键字 | 作用 |
---|---|
Pointcut | 通常使用正则表达式来描述切入的点(拦截哪些方法) |
Advice | 在特定的Pointcut执行的动作:around、before、throws等 |
Joinpoint | 具体运行时拦截到的信息 |
细想一下大部分的Java程序员应该都在直接或者间接的做页面开发,那么这里就涉及到分层的概念了,当然这里不会讲这些东西。在WEB应用作用,不能不提web.xml,其中需要清楚
它们的作用以及运行机制,在通过SpringMVC来开发Web应用前,需要配置:
<servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping>
这样就把所有以.htm结尾的请求都交给DispatcherServlet进行具体的处理并返回,过程如下:
常用的handleMapping的方式有:
用注解的方式更直观一些,在代码中直接就可以明白你正在处理什么样的请求,最简单的一个例子:
@Controller @RequestMapping("/helloAnnoController.html") public class AnyTypeYouLikeController{ @RequestMapping(method={RequestMethod.GET,RequestMethod.POST}) public String processWebRequest(){ return "anno/helloAnnController"; } }
PS:现在各种WEB框架非常多,关键是了解它们运行机制,在遇到问题的时候可以DEBUG去找到解决办法,对某个框架有哪些“奇技淫巧”就不说了,细节太多了···
首先想想要想是的访问数据更方便,Spring应该做什么事情?
如果直接用JDBC来完成数据访问,大致如下:
try{ Connection connection = getDataSource().getConnection(); Statement statement = connection.createStatement(); // TODO 执行数据库操作 statement.executeUpdate("sql..."); statement.close(); }finally{ statement.close(); connection.close(); }
显然在使用JDBC API处理数据的时候会有大量相似的代码,不仅增加了代码量,而且使得业务 逻辑不清晰、工程难维护。在Spring中用JdbcTemplate通过模板方式来解决这些问题:
public class JdbcTemplate { public Object execute(StatementCallback action) throws DataAccessException { // 将连接数据库等操作抽取出来 } }
在JdbcTemplate中定义了很多queryXXX和updateXXX的方法,其本质上还是Callback的方 式实现的,定义出来方便我们使用:
JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(ds); jdbcTemplate.execute(new StatementCallback(){ public Object doInStatement(Statement stmt) throws SQLException { stmt.execute("your sql"); return ret; } }
这样编码起来方便了很多,但是接着会想如果我们把SQL写到单独的文件里面,这样剥离开应该 会更好一下吧,这样就有了各种ORM,比如iBatis和Hibernate,在iBatis中用SqlMapClient 来访问数据,通常访问的方式有三种:
//1、基于SqlMapCLient的自动提交事务型简单数据访问 Map parameters = new HashMap(); parameters.put("param1", value); // .. Object ret = sqlMap.queryForObject("sql_id", parameters); // 2、基于SqlMapClient的非自动提交事务型数据访问 try{ sqlMap.startTransaction(); sqlMap.update("...."); sqlMap.commitTransaction(); } finally { sqlMap.endTransaction(); } // 3、基于SqlMapSession的数据访问 SqlMapSession session = null; try{ session = sqlMap.openSession(); session.startTransaction(); session.update("..."); session.commitTransaction(); } finally { session.endTransaction(); }
因为Spring在集成iBatis的时候要考虑将事务控制也纳入进来,所以使用基于SqlMapSession 的数据访问方式对iBatis进行集成,这种方式更灵活,可以将iBatis内部直接指定的数据源和事 务管理器等转由外部提供(IoC),SqlMapClientTemplate是Spring为iBatis的数据访问操 作提供的模板方法类:
public class SqlMapClientTemplate { public Object execute(SqlMapClientCallback action) throws DataAccessException { // ... } }
SqlMapClientCallback可以完成任何基于iBatis的数据访问操作,比如要向一个数据库批量提 交更新数据:
protected void batchInsert(final List beans) { sqlMapClientTemplate.execute(new SqlMapClientCallback() { public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException { executor.startBatch(); Interator iter = beans.iterator(); while(iter.hasNext()) { Bean bean = (Bean) iter.next(); executor.insert("insert_name", bean); } executor.executeBatch(); return null; } }); }
最后来看事务处理,局部事务的话用法如下:
try{ transaction = session.beginTransaction(); // TODO 操作数据 session.flush(); transaction.commit(); } catch (Exception e){ transaction.rollback(); // 回滚 } finally { session.close(); }
因为JDBC的局部事务控制是由同一个Connection来完成的,所以要保证两个DAO的数据访问 方式处于一个事务中,我们就得保证他们使用的是同一个Connection,要做到这一点,通常采 用称为connection-passing的方式,即为同一个事务中的各个DAO的数据访问传递当前事务对 应的同一个Connection。
我们可以直接使用PlatformTransactionManager,如下:
DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); definition.setXXX(); definition.setXXX(); TransactionStatus status = transactionManager.getTransaction(definition); try { // TODO 业务逻辑 } catch (Exception e) { transactionManager.roolback(status); } transactionManager.commit(status);
可以看到上面有很多重复的操作,接着用TransactionTemplate对事务的管理进行模板化。而 基于申明式的事务有四种方式:
当然也可以通过注解@Transactional来申明事务。