WsztRush

Spring

如今作为一个Java程序员,如果没用过Spring的话基本上没法混了~ 这里就不再啰嗦如何如何从EJB走到了Spring(毕竟咱也没经历过那些个艰难的时代),直接来看它带给我们哪些东西:

  1. IoC
  2. Aop
  3. SpringMVC

下面分几个部分很粗地描述一下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提供的功能可能远超过了你需要的:

  1. BeanFactoryAware:获取BeanFactory实例
  2. BeanPostProcessor:在afterPropertiesSet的方法前后执行
  3. InitializingBean:完成一些初始化动作
  4. BeanFactoryPostProcessor:处理Bean的定义,也就是BeanDefinition

还有很多其他的扩展点,下面重点看下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 */ }
}

在使用前需要配置,第二种方式确实有一些进步,但是考虑注解之后,AOP的功能开始变得有点IMBA了:

@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利用了两种技术:

  1. JDK动态代理
  2. CGLIB字节码生成

如果你想选择cglib需要配置文件中加上<aop:aspectj-autoproxy proxy-target-class=”true”/>。 AOP中涉及到的细节整理如下(不断更新中):

关键字 作用
Pointcut 通常使用正则表达式来描述切入的点(拦截哪些方法)
Advice 在特定的Pointcut执行的动作:around、before、throws等
Joinpoint 具体运行时拦截到的信息

MVC

细想一下大部分的Java程序员应该都在直接或者间接的做页面开发,那么这里就涉及到分层的概念了,当然这里不会讲这些东西。在WEB应用作用,不能不提web.xml,其中需要清楚

  1. filter
  2. listener
  3. servlet

它们的作用以及运行机制,在通过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的方式有:

  1. SimpleUrlHandlerMapping
  2. DefaultAnnotationHandlerMapping
  3. AnnotationMethodHandlerAdapter

用注解的方式更直观一些,在代码中直接就可以明白你正在处理什么样的请求,最简单的一个例子:

@Controller
@RequestMapping("/helloAnnoController.html")
public class AnyTypeYouLikeController{
    @RequestMapping(method={RequestMethod.GET,RequestMethod.POST})
    public String processWebRequest(){
        return "anno/helloAnnController";
    }
}

PS:现在各种WEB框架非常多,关键是了解它们运行机制,在遇到问题的时候可以DEBUG去找到解决办法,对某个框架有哪些“奇技淫巧”就不说了,细节太多了···

数据访问

首先想想要想是的访问数据更方便,Spring应该做什么事情?

  1. 统一的异常处理
  2. 将相同(类似)操作进行封装

如果直接用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,比如iBatisHibernate,在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对事务的管理进行模板化。而 基于申明式的事务有四种方式:

  1. ProxyFactory + TransactionInterceptor
  2. TransactionProxyFactoryBean
  3. BeanNameAutoProxyCreator
  4. Spring 2.x申明事务配置方式

当然也可以通过注解@Transactional来申明事务。