spring 的继续学习

spring 除了基本的 IoC container 以外还有许许多多其他的 feature,这样才构成了我们看到的 framework,这里我们依照 Spring in Action 一书的套路分析几个比较常用的 feature:这包括 AOP、persistence 和 transaction,这也是最近需要用的几个玩意了。在此之前稍微提一下 Java 里面一个称为  anonymous inner class 的概念。前面提到了 Java 的一个缺陷,就是虽然要用 interface,但是某些很简单的事情,比如传递一个 functor 类似的事情我们都必须为其写一个文件实现某个 interface 这样来做,这样实在是太过于麻烦。因此 anonymous inner class 就能很好的解决这类问题(常出现在需要 callback function 的地方),这时我们直接 new 这个类,而把实现写在后面,比如

public interface Foo {
  void doSomething (int a) ;
}

public class Bar {
  void register (Foo f) {
  }
}

// when you are using them
Bar b ;
b.register ( new Foo () {
    void doSomething (int a) {
      // implementation goes here
    }
  }
) ;

这种写法看起来似乎一个匿名函数,但是本质上是一个 anonymous inner class。

IoC Container 一些技巧

前面关于 IoC container 我们提供了 XML 的方式,这也是最常用的一种,通常我们可以对 constructor hijack(通过 bean 的 constructor-arg 子节点传递参数),使用 factory method(bean 的 factory-method 属性指定对应的方法名),通过 init-method 和 destroy-method 在创立 bean 是释放 bean 时候执行指定的方法,property injection(对 POJO 类型的最合适),对于 public 的成员直接使用 property 子节点即可。通过 ref 可以在一个 bean 里面嵌入另一个 bean 的引用,另一种是直接在 property 里面嵌入新的 bean 子节点表示对象的关系(对一些只供某个对象使用的对象比较合适)。

除了 property 子节点以外,还可以使用 p namespace 在 bean 的属性里面添加成员的值,这样显得稍微简洁一些。比如原先需要写 <propert name=”song” value=”123″ /> 的现在就能简化到 <bean name=”something” p:song=”123″ /> 里面去了。

Spring 对一些标准容器接口进行了封装,如 list、set、map,我们可以为前两者直接使用 ref 子节点或者 value、null 子节点表示对应的意思,map 使用的是 entry 子节点,我们通过对应的 key/value(后者加 -ref)属性表示内容。

Spring 也对 properties 进行了封装,我们使用 props 获得一个 property set,里面子节点为 prop,属性 key 表示键名,内容表示属性值。

Spring 提供了 SpEL 如果你希望在这个 XML 里面执行某些函数获得结果的话。通常 SpEL 以 #{} 标识,我们可以在里面放 Java 的常值,同时写 value=”#{beanname}” 和 ref=”beanname” 是一样的。但是如果希望获得某个对象的成员或者调用对应的方法的话,就只能通过 SpEL 了。这些写法与 Java 语句类似,但是如果希望避免某些 null pointer exception 的话可以用 ?. 的方式调用对应的方法。SpEL 里面如果希望获得某个类的 Class 对象,一般使用 T(),这样才能使用这个类的 static 方法和常量等。对于 list、map 我们可以用 [] 来访问其成员,.? 用来选择其中的一部分,.! 对他们进行 projection。

为了减少 XML 文件里面一些繁琐的重复,Spring 也提供了一些自动将对象联系起来的机制,这分别是

  • 通过名字,某个 bean 的成员的 name 属性与另一个 bean 的 name match 的话可以通过在前者的属性里面加上 autowire=”byName”,这样对应的成员就会被自动关联到有着同名的 bean 上了。
  • 通过类型,在后者的属性里面写 primary=false 表示这是一个被 auto-wired 的 bean(默认是 true)
  • 构造函数,在前者的属性里面写 autowire=”constructor”
  • 自动发现,在 beans 的属性里面加上 default-autowire=”类型” 就可以打开默认 autowire 功能

另外我们可以通过 anotation 来实现。但是估计不大会用到这个功能。

AOP

所谓的 AoP 指的是通过 aspect 这种概念来分离软件 component 的设计,每种 aspect 相当于负责某个方面的事情,一个正常的业务可能会涉及到很多 aspects,那么我们只需要将这些 aspect hook 到合适的位置就行了,这样我们就可以全心全意仅仅关心自己的业务而不会被一些更 general 的业务所烦恼。这个想法是很好的,但是如何实现分很多,

  • 有编译时将某些东西参合进去的(需要特定的编译器支持),
  • 还有 class load time,如 aspectj
  • 也有通过代理类来实现的(Spring 的实现算是这类,将我们放在 bean 里面的对象封装在 proxy 里面),这是纯粹 runtime 实现的。

那么我们一般需要搞清楚几个 AoP 的概念:

  • advice,即 aspect 是干什么的,什么时候干(现在能做到的一般就是在某些方法之前之后,返回后或者抛出异常后或者 around)
  • join point 所有可能被 advice 接入的地方
  • poincuts 某个 advice 所关心的一个 join point 的子集,也就是 advice 在哪里发挥作用(具体到什么方法之前之后或者什么)
  • aspect = advice + pointcuts
  • introduction 对已有类添加新方法或成员
  • weaving 即最后将 aspects 缝合到目标上的过程

Spring 的 AOP 一般如下实现,通过 Java 实现对应的 advice,然后在 XML 文件里面声明 pointcuts,并且在 aop namespace 里面完成 weaving 的细节。下面是一个 AoP 的例子,对应的 advice 其实可以是任意的 POJO 与 spring 本身没有直接的关系都可以,我们需要在 XML 文件里面将它放在 AOP 的部分:

<beans>
  <bean id="audience" class="demo.Audience" />
  <aop:config>
    <aop:aspect ref="audience">
      <aop:before pointcut="execution(* demo.Performer.perform (..))" method="takeSeat" />
    </aop:aspect>
  </aop:config>
</beans>

我们大致能从以上声明里面看到一些简单的概念,我们将一个 POJO 类型 Audience 在 aop:config 里面放在了 aop:aspect 中表示这是一个 aspect,然后我们在 aop:position 这种里面表明了这个 advice 作用的位置,对应的 pointcut 是 demo.Performer 类调用 perform 方法,也就是说 spring 管理的任意一个 Performer 对象调用 perform 之前都会调用这个 audience 的 takeSeat 方法。这也就是 spring 的 AOP 的核心用法。比较不一样的可能就是 aop:around。这里我们简略了 aop:pointcut,复杂的情况(如传递参数),我们需要为其单独创建一个子节点,然后在 aop:position 里面引用这个 id,同时前者可以通过 args 之类的将参数传递给 advice。如此一来我们相当于能够获得类似 decorator 能力的 aspect。

当然通过 annotation 也能在某些类上面加上需要的 aspect 了。这通常是一些别的已经很常用的 aspect,如果是一些临时的,感觉 annotator 不是很方便(需要重新编译)。

Spring 的 AOP 相对来说还是比较弱的,正因为如此,它还提供了对 AspectJ 的支持,这里不再讨论。

Persistence

Java 的 persistence 一般常见的有两种策略,JDBC、ORM(可通过 hibernate 或者 JPA)。我们这里主要会用 hibernate,稍微讲一些 spring 对 JDBC 的简化。

JDBC 使用需要一个 data source(支持 JNDI-Java Naming and Directory Interface、Jakarta Common DBCP-Database Connection Pooling、或者是 JDBC 驱动)。每种都需要配置一些不同的属性。这个看需要什么查什么文档了。

Spring 对 JDBC 的支持体现在两个方面:通过 IoC container 我们可以初始化到数据库的连接,通过 template 能够简化代码(避免冗余的 try/catch block 来处理 SQLException)。这两者前面一个我们通过一个 data source 搞定,后一个通过三个模板(JdbcTemplate,NamedParameterJdbcTemplate 和 SimpleJdbcTemplate)之中一个(使用 data source 传入构造函数),这样我们将 template 嵌入到实际干活的那个类里面,写干活的代码时就简单的利用 template 提供的功能(传入 SQL 语句和对应的值)即可。

hibernate 前面讲过需要 SessionFactory,那个蹩脚的例子里面我们很 bc 的通过 Configuration 对象获得了 SessionFactory,实际上 Spring 为我们准备了更简单的 org.springframework.orm.hibernate3.LocalSessionFactoryBean,它指定 dataSource,mapping 关系的 XML(mappingResources,里面是个 list),hibernateProperties 里面是 props 表示的 hibernate 的配置。如果喜欢用 annotation 来做 ORM 而不是 XML,可以用 org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean,这里面指定 packageToScan 就会将里面的有相关 annotation 的类找到了。当然你也可以用 annotatedClasses 指定一个 list。

实际上 hibernate 和 JDBC 不大一样的是,后者只用了一个 SQLException 让人无法区分,而 hibernate 使用了自己的 exception 集合,spring 为了让这些 exception 与 hibernate (或者别的 ORM framework)本身无关,提供了一套 unchecked exception。那么怎样能在实现 DAO 的时候自动的完成这套逻辑的转换呢?第一需要用 org.pringframework.stereotype.Repository 来 annotate 这个 DAO,然后在 XML 中使用 context:component-scan 对这个 package 扫描,并增加一个 bean,来自org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor 类,通过 AOP 就能解决这个问题了。如此一来我们甚至不需要前面的 template 了。

Transaction

所谓的 transaction 其实字面上的意思就是几个 action 之间(trans 前缀),本质的意思就是某些业务涉及到几个行为,他们要发生或者全部成功,或者其中一个有问题就必须回退到什么 action 都没有的情况。一个简单的例子就是银行转账,这里涉及到两个操作:从一个账户去掉一笔钱,在另一个账户里面加上这笔钱。这个业务的逻辑就是所有账户的和一定,所有的操作都需要保证这个约束。更一般的,对于 transaction 有 4 条准则,一般简称为 ACID(atomic,consistent,isolate and durable)。

为了使用 transaction,我们一般需要一个对应的 transaction manager,这里我们以 hibernate 为例,为此我们需要org.springframework.orm.hibernate3.HibernateTransactionManager。这个 bean 需要 session factory 作为成员。实现 transaction 有两套方法,所谓的 programmatic 和 declarative 两种,功能上自然是前者强大,后者简单。spring 提供了一个 template 来帮助我们实现前者,我们可以在 XML 里面添加 TransactionTemplate(构造时传入 transaction manager),并将其作为需要实现 transaction 类的成员,这个类需要实现 transaction 的方法中可以使用 txTemplate.execute ( new TransactionCallback<void> () {//…}),这里实现的时候可以 try/catch,在出现异常后调用 txStatus.setRollbackOnly ()。

declarative 方法就是写 XML 了,结合 AOP 实现的,有兴趣可以再看看。

================= 2013.02.14 补充

其实 transaction 应用还是很普遍的,一般一个数据库的写操作如果失败,为了清理可能的东西应该进行 rollback。pragmatic 的形式是非常累赘的,一方面需要引入这个 template 以及对应的 interface 和 status 类,另一方面使得 transaction 的代码与很多事情耦合起来了。更合理的方式是为每种可能改变数据库的途径提供单独的方法,每个方法都可以被 spring 的 AOP 添加需要的 transaction 保护(比如说这些方法都叫 setXXX,这样 AOP 就能匹配上)。declarative 方法的优点就是写程序的时候可以“按部就班”,最后通过 XML 将需要关注的函数、需要捕获的异常设定好,就行了,在原先的代码里面甚至不需要引入任何 Transaction 相关的东西。

当然除了在 XML 里面 AOP 以外还可以用 annotation 来标注某些类,但这样也就需要在对应类代码里面 import 对应的 annotation class 了。

——————
Behold, I stand by the well of water; and it shall come to pass, that when the virgin comes forth to draw water, and I say to her, Give me, I pray you, a little water of your pitcher to drink;

Advertisements
spring 的继续学习

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s