如今作为一个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来申明事务。