Spring 2.5 的基于注解的依赖注入

@Autowired之外的选择——使用JSR250标注依赖注入关系

Spring2.5提供的基于注解的依赖注入,除了可以使用 Spring 提供的 @Autowired@Qualifier 来标注相应类定义之外,还可以使用 JSR250的 @Resource@PostConstruct 以及 @PreDestroy 对相应类进行标注,这同样可以达到依赖注入的目的。

@Resource@Autowired不同,它遵循的是byName自动绑定形式的行为准则,也就是说,IoC容器将根据@Resource所指定的名称,到容器中查找beanName与之对应的实例,然后将查找到的对象实例注入给@Resource所标注的对象。

同样的FXNewsProvider,如若使用@Resource进行标注以获取依赖注入的话,类似如下的样子:

 
    public class FXNewsProvider {
    	@Resource(name="djNewsListener")
      private IFXNewsListener newsListener;
      @Resource(name="djNewsPersister")
      private IFXNewsPersister newPersistener;
      ...
    }
    

JSR250规定,如果@Resource标注于属性域或者方法之上的话,相应的容器将负责把指定的资源注入给当前对象,所以,除了像我们这样直接在属性域上标注@Resource,还可以在构造方法或者普通方法定义上标注@Resource,这与@Autowired能够存在的地方大致相同。

确切地说,@PostConstruct@PreDestroy不是服务于依赖注入的,它们主要用于 标注对象生命周期管理相关方法,这与Spring的InitializingBeanDisposableBean接口,以及配置项中的init-methoddestroy-method起到类似的作用。

下方代码给出了可能使用这两个注解的示例代码。

 
    
    public class LifecycleEnabledClass {
        @PostConstruct
        public void setUp() {
            ...
        }
        @PreDestroy
        public void destroy() {
            ...
        }
    }

如果想某个方法在对象实例化之后被调用,以做某些准备工作,或者想在对象销毁之前调用某个方法清理某些资源,那么就可以像我们这样,使用@PostConstruct@PreDestroy来标注这些方法。

我们只是使用@Resource或者@PostConstruct@PreDestroy标注了相应对象,并不能给该对象带来想要的东西。所以,就像@Autowired需要AutowiredAnnotationBeanPostProcessor为它与IoC容器牵线搭桥一样,JSR250的这些注解也同样需要一个BeanPostProcessor帮助它们实现自身的价值。这个BeanPostProcessor就是org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,只有将CommonAnnotationBeanPostProcessor添加到容器,JSR250的相关注解才能发挥作用,通常如下添加相关配置即可:

 
    
    <beans>
      <!--重点在下面这行-->
    	<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
      
    	<bean id="newsProvider" class="..FXNewsProvider"/>
    	<bean id="djNewsListener" class="..DowJonesNewsListener"/>
      <bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
    </beans>

既然不管是@Autowired还是@Resource都需要添加相应的BeanPostProcessor到容器,那么我们就可以在基于XSD的配置文件中使用一个<context:annotation-config>配置搞定以上所有的BeanPostProcessor配置,如下方代码所示。

 
    
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
    
        <context:annotation-config />
        
        <bean id="newsProvider" class="..FXNewsProvider" /> 
        <!--其他bean定义-->
        ...
    </beans>
    
 

<context:annotation-config>不但帮我们把AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor注册到容器,同时还会把PersistenceAnnotationBeanPostProcessorRequiredAnnotationBeanPostProcessor一并进行注册,可谓一举四得啊!

classpath-scanning 功能介绍

好了,该来解决让我们不爽的那个问题了。到目前为止,我们还是需要将相应对象的bean定义,一个个地添加到IoC容器的配置文件中。与之前唯一的区别就是,不用在配置文件中明确指定依赖关系了(改用注解来表达了嘛)。既然使用注解来表达对象之间的依赖注入关系,那为什么不搞的彻底一点儿,将那些几乎“光秃秃”的bean定义从配置文件中彻底消灭呢?

OK,我们想到了,Spring开发团队也想到了,classpath-scanning的功能正是因此而诞生的!

使用相应的注解对组成应用程序的相关类进行标注之后,classpath-scanning功能可以从某一顶层包(basepackage)开始扫描。当扫描到某个类标注了相应的注解之后,就会提取该类的相关信息,构建对应的BeanDefinition,然后把构建完的BeanDefinition注册到容器。这之后所发生的事情就不用我说了,既然相关的类已经添加到了容器,那么后面BeanPostProcessor@Autowired或者@Resource所提供的注入肯定是有东西拿咯!

classpath-scanning 功能的触发是由 <context:component-scan> 决定的。按照如下代码,在 XSD 形式(也只能是 XSD 形式)的配置文件中添加该项配置之后,classpath-scanning 功能立即开启。

 
    <context:component-scan base-package="org.spring21"/>

现在<context:component-scan>将遍历扫描org.spring21路径下的所有类型定义,寻找标注了相应注解的类,并添加到IoC容器。如果要扫描的类定义存在于不同的源码包下面,也可以为base-package指定多个以逗号分隔的扫描路径。

<context:component-scan>默认扫描的注解类型是@Component。不过,在@Component语义基础上细化后的@Repository@Service@Controller也同样可以获得<context:component-scan>的青睐。

同样对于服务层的类定义来说,使用@Service标注它,要比使用@Component更为确切。对于其他两种注解也是同样道理。

我们暂且使用语义更广的@Component来标注FXNews相关类,以便摆脱每次都要向IoC容器配置添加bean定义的苦恼。

    
    @Component
    public class FXNewsProvider {
    	@Autowired
    	private IFXNewsListener newsListener;
      @Autowired
     	private IFXNewsPersister newPersistener;
      ...
    }
 
    
    @Component("djNewsListener")
    public class DowJonesNewsListener implements IFXNewsListener {
      ...
    }
    
 
    @Component
    public class DowJonesNewsPersister implements IFXNewsPersister {
      ...
    }

<context:component-scan>在扫描相关类定义并将它们添加到容器的时候,会使用一种默认的命名规则,来生成那些添加到容器的bean定义的名称(beanName)。

比如DowJonesNewsPersister通过默认命名规则将获得dowJonesNewsPersister作为bean定义名称。如果想改变这一默认行为,就可以像以上DowJonesNewsListener所对应的@Component那样,指定一个自定义的名称。

现在,除了<context:component-scan>是唯一需要添加到IoC容器的配置内容,所有的工作都可以围绕着使用注解的Java源代码来完成了。如果现在加载配置文件,启动FXNewProvider来处理外汇新闻的话,我们可以得到预期的运行效果,运行的代码如下所示:

ApplicationContextctx=newClassPathXmlApplicationContext("../conf.xml");
    FXNewProviderprovider=(FXNewProvider)ctx.getBean("FXNewsProvider");
    provider.getAndPersistNews();

你或许会觉得有些诧异,因为我们并没有使用<context:annotation-config>甚至直接将相应的BeanPostProcessor添加到容器中,而FXNewsProvider怎么会获得相应的依赖注入呢?这个得怪<context:component-scan>“多管闲事”,它同时将AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor一并注册到了容器中,所以,依赖注入的需求得以满足。

如果你不喜欢,非要自己通过<context:annotation-config>或者直接添加相关BeanPostProcessor的方式来满足@Autowired或者@Resource的需求,可以将<context:component-scan>annotation-config属性值从默认的true改为false。不过,我想没有太好的理由非要这么做吧?

<context:component-scan>的扫描行为可以进一步定制,默认情况下它只关心@Component@Repository@Service@Controller四位大员,但我们可以丰富这一范围,或者对默认的扫描结果进行过滤以排除某些类,<context:component-scan>的嵌套配置项可以帮我们达到这一目的。下方代码演示了<context:component-scan>部分嵌套配置项的使用。

 
    
    <context:component-scan base-package="org.spring21">
      <context:include-filter type="annotation" expression="cn.spring21.annotation.FXService"/>
      <context:exclude-filter type="aspectj" expression=".."/>
    </context:component-scan>
    

include-filterexclude-filter可以使用的type类型有annotationassignableregexaspectj四种。它们的更多信息可以参考最新的Spring2.5参考文档。上例中,我们增加了@FXService作为新的被扫描注解对象,并使用aspectj表达式排除某些扫描结果。

Spring3.0 展望

(这里不做介绍,有兴趣可以看原文)

本章小结

Spring最初并不支持基于注解的依赖注入方式。所以,在Spring2.5中引入这一依赖注入方式的时候,肯定要在维护整个框架设计与实现的一致性和引入这种依赖注入方式对整个框架的冲击之间做出权衡。最终的结果我们已经看到了,Spring2.5中引入的基于注解的依赖注入从整体上保持了框架内的一致性,同时又提供了足够的基于注解的依赖注入表达能力。我想,最初的决定和最终的效果都是令人满意的。虽然我们还会部分地依赖于容器的配置文件,但通过20%的工作却可以带来80%的效果,这本身已经是最好的结果了。

不过,从实际开发角度看,如果非要使用完全基于注解的依赖注入的话,或许会遇到一些过不去的坎儿。比如,对于第三方提供的类库,肯定没法给其中的相关类标注@Component之类的注解。这时,我们可以结合使用基于配置文件的依赖注入方式。毕竟,基于XML的依赖注入方式是Spring提供的最基本、也最为强大的表达方式了!

到目前为止,我们已经结束了SpringIoC容器的旅程。接下来的第三部分将带领读者探索SpringAOP框架的精彩世界。