Spring

第 3 章 Spring

有了spring,我们就不用再写工厂方法,不用发愁怎么把写出来的各个代码装配在一起,不过我们选择Spring的原因也不光是为了IOC,而是考虑到现在JavaEE中很多开源项目都提供了Spring整合使用的方法,可以为我们节省相当多的精力和时间。

3.1. 在项目中使用Spring

3.1.1. 直接构造ApplicationContext

如果我们可以applicationContext.xml放到classpath下,我们可以使用ClasspathXmlApplicationContext。这里传入参数的路径是相对于classpath的配置的,对于web项目就是WEB-INF/classes这个目录,或者WEB-INF/lib下任意一个jar文件下了。

ApplicationContext ctx = new ClasspathXmlApplicationContext("applicationContext.xml");
            

3.1.2. 使用ContextLoaderListener

从ServletContext取得web.xml中初始化的ApplicationContext

首先在web.xml中配置listener。

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:spring/*.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
            

然后从ServletContext中获得ApplicationContext。

ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(application);
            

对于无法获得ServletContext的环境,最好自定义一个listener,将生成的ApplicationContext放入一个单例中,以便日后使用。

3.2. Spring配置要点

3.2.1. 在xml中使用spring-2.x的DTD。

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

相比schema真是方便了很多,这样可以在必须使用特定schema的时候,再让他重装上阵。

spring-2.x中去掉了singleton属性,使用scope属性做替代。如果还想使用singleton属性,必须配置成spring-1.x格式的DTD。

提示

spring中已不再推荐使用singleton属性,因为单例在多jvm,远程调用,集群的情况下难以掌控,还是为实例指明生存的scope比较好。

3.2.2. default-lazy-init

懒惰加载,系统启动的时候并不加载xml中定义的bean,而是等到实际调用的时候才去加载,这样可以缩短系统初始化时间,在测试系统部分功能的情况下有极大的好处。

注意,PropertityPlaceHolderConfigurer,springmvc,xfire,quartz等的配置文件不能声明为懒惰加载,否则会出问题。

3.2.3. default-autowire="byName"

按名称自动绑定。设置了这个,只要定义bean的时候名称与需要绑定的属性名相同,在实例化对象的时候,spring就会将这些实例自动绑定,不需要再去声明绑定哪些property。减少xml代码量,使得结构更清晰。

在使用compass的时候要注意,不能使用按名称自动绑定,会自动为compass绑定dataSource导致错误。

3.2.4. import

将xml统一放在classpath下,这样更有利于进行单元测试,对于多个模块的xml使用import进行导入,层次更清晰。

<import resource="classpath:jbpm/applicationContext-jbpm4.xml"/>
            

3.2.5. CharacterEncodingFilter

spring提供的编码过滤器,好处一是不用自己动手写了,好处二是保证每次请求只过滤一次。配置如下:(web.xml)

<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
            

3.2.6. IntrospectorCleanupListener

spring提供的监听器,避免Struts,Quartz的内存泄露导致ClassLoader不能加载。配置如下:(web.xml)

<listener>
    <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
            

3.2.7. PropertyPlaceholderConfigurer

读取properties中的变量,在xml中可以通过${变量名}的方式调用。配置如下:

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath*:conf/jdbc.properties</value>
        </list>
    </property>
</bean>

<bean id="userManager" class="com.family168.manager.UserManager">
    <property name="username" value="${jdbc.username}"/>
</bean>
            

3.2.8. PropertyOverrideConfigurer

与PropertyPlaceholderConfigurer不同,PropertyOverrideConfigurer会在ApplicationContext初始化后,根据properties中的定义,修改对应属性的值。配置如下:

<bean id="testPropertyConfigurer" class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
    <property name="locations" value="classpath*:override.properties"/>
    <property name="ignoreInvalidKeys" value="true"/>
</bean>
            

用来在测试环境下覆盖已有的配置,比如在override.properties中有userManager.username=111,那么id="userManager"的bean的username属性就会被修改为111。

3.3. Spring-2.x对AOP和事务管理的简化配置

  1. 首先要使用schema

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
                        
  2. 然后要声明标记(不知道是不是必要的)

    <!-- 支持 @Transactional 标记 -->
    <tx:annotation-driven/>
    
    <!-- 支持 @AspectJ 标记-->
    <aop:aspectj-autoproxy/>
                    
  3. 配置aop

    <aop:config proxy-target-class="true">
        <aop:advisor pointcut="execution(* com.family168.manager..*Manager.*(..))" advice-ref="txAdvice"/>
    </aop:config>
                    
  4. 配置txAdvice处理事务

    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="pagedQuery*" read-only="true"/>
            <tx:method name="load*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
                    

提示

name-pattern千万不要写成*..*Manager ,这样子会把所有第三方类库的Manager比如Spring的PlatformTranstationManager 也加入aop,非常危险。所以最好还是加上项目的package前缀,如"com.family168.manager..*Manager"

因为有*,会修饰所有方法,有些hibernateTemplate的final的方法不能被cglib修改,会抛warning,无害。

事务定义一般默认的PROPAGATION_REQUIRED即可,另提供的几个选择很少使用。值得注意的是一个PROPAGATION_NESTED,嵌入式事务的意义在于多级事务,如果出错只rollback子事务自己,不rollback主事务的所有操作。这需要JDBC3.0 SavePoint功能的支持。 而一般service间互相嵌入调用时,如果都定义为PROPAGATION_REQUIRED,有其中一个操作出错,rollback全部操作。

提示

tx这个命名空间会要求咱们提供一个名字为transactionManager的bean,用这个来作为默认的事务管理器。

Spring参考文档 7.3 chema-based AOP support 提供了aspect,advisor,advide三种组装方法的解释,其中aspect是aspectJ原装,但稍复杂,

这里唯一有点难懂的是pointcut里的语法,其实也很好学,Spring参考文档7.2.3.4里有完整说明 ,其实一排子过去是

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
      throws-pattern?)
        

其中modifiers-pattern?(public/protected) 和 declaring-type-pattern? throws-pattern? 可以不填

可见execution(* *..BookManager.save(..))

  • 第一颗* 代表ret-type-pattern 返回值可任意,

  • *..*Manager 代表任意Package里的以Manager结尾的类。

    如果写成com.xyz.service.* 则代表com.xyz.service下的任意类

    com.xyz.service..* com.xyz.service则代表com.xyz.service及其子package下的任意类

  • save代表save方法,也可以写save* 代表saveBook()等方法

    (..) 匹配0个参数或者多个参数的,任意类型

    (x,..) 第一个参数的类型必须是X

    (x,*,*,s,..) 匹配至少4个参数,第一个参数必须是x类型,第二个和第三个参数可以任意,第四个必须是s类型。

3.4. Spring中的零配置

其实也不是完全的零配置,我们需要在xml中制定规范,然后在java中使用注解进行标注。

首先要在xml中配置如下标记:

<context:component-scan base-package="com.family168.manager" />
        

这里需要使用Spring的schema命名空间进行配置,例子中使用的是springside中提供的例子,指定了默认在哪个包下查找需要进行注入的类。在Service类中使用@Service注释, Dao类中使用@Repository注释,通过pacakge扫描加入Spring的applicationContext。

在私有属性或注入方法(不需要严格按setter命名)上 使用@Autowired 注释 进行byType注入,如果需要byName注入,增加@Qualifier注释。另@auwowired默认隐含了@Required特性

使用@Required注释非@Autowired的属性,保证autowired下对象必然被注入,如果对象没有被注入则报错。使用JSR250的@PostConstruct来定义在执行完所有setter注入后必须执行的函数,比以往的实现接口或者在applicationContext.xml中配置init-method的方式更为标准。

为提高效率,还是需要在xml中默认配置default-lazy-load和default-autowire byName。

3.5. Spring中的资源访问

3.5.1. ResourceLoader

ResourceLoader 可以获得ClassPath, File,URL中的文件,返回类型为Resource的统一资源定义。

ApplicationContext中就拥有ResourceLoader。

实现resourceLoaderAware接口可以获得ResourceLoader。

3.5.2. ResourcePatternResolver

ResourcePatternResolver 是ResourceLoader的升级版子接口,能解决ant-style的文件模糊批量定义,如applicationContext-*.xml。

ApplicationContext中的ResourceLoader其实是ResourcePatternResolver ,可以通过强制转型获得,友好一点的做法是使用ResourcePatternUtils来转换。

public class RescManager implements InitializingBean, BeanNameAware,
    ResourceLoaderAware {
    private ResourceLoader resourceLoader;

    public void demo() throws IOException {
        ResourcePatternResolver resolver = ResourcePatternUtils
            .getResourcePatternResolver(resourceLoader);
        Resource[] rescs = resolver.getResources(
                "classpath*:spring/applicationContext*.xml");
        System.out.println(rescs);
    }
            

3.6. Spring的微内核与FactoryBean扩展机制

3.6.1. 微内核的功能

3.6.1.1. DI(依赖注入)与Singleton管理

利用POJO setter的DI机制,估计每位同学随手都能写一个简单版本,不多说了。

Singleton管理说白了就是先到一个map中按id找找看有没有已存在的实例。

3.6.1.2. BeanName与BeanFactory注入

除了DI注入的属性,微内核还有什么能卖给POJO呢?就是Bean在xml 定义里的id和BeanFactory自己了。

卖的机制是让POJO 实现 BeanNameAware和BeanFactoryAware接口。BeanFactory用 if(pojo instance of BeanFactoryAware)判断到POJO需要注入BeanFactory,就调用setBeanFactory(this)将自己注入。

3.6.1.3. DI后的初始化函数调用

比如属性A,B注入之后,需要同时根据A和B来对A,B进行加工或者装配一个内部属性C,这样就需要在所有属性注入后再跑一个init()函数。

Spring提供两种方式,一种是和上面的原理一样,实现InitializingBean接口的afterPropertiesSet()函数供Spring调用。

一种是在xml定义文件里面自行定义init函数名。

懒得每次在xml文件里定义的就采用第1种方式,不想与spring耦合的pojo就采用第2种方式。本来就是为了扩展Spring而存在的FactoryBean多采用第一种。

所谓微内核,就是仅提供以上三种功能的DI容器。

但作为轻量级容器,还需要以下两种方式,向容器内的POJO 附加各种服务。

3.6.2. FactoryBean扩展机制

Spring的AOP、ORM、事务管理、JMX、Quartz、Remoting、Freemarker、Velocity,都靠FacotryBean的扩展,FacotryBean几乎遍布地上:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"/>
<bean id="baseDAOService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"/>
            

这原理说出来好简单,所有FactoryBean 实现FactoryBean接口的getObject()函数。Spring容器getBean(id)时见到bean的定义是普通class时,就会构造该class的实例来获得bean,而如果发现是FactoryBean接口的实例时,就通过调用它的getObject()函数来获得bean,仅此而以.......可见,很重要的思想,可以用很简单的设计来实现。

考察一个典型的FactoryBean:

一般会有两个变量,三个接口:

一个setter函数注入需要改装的pojo,一个内部变量保持装配后的对象returnOjbect。

implements三个接口 :FactoryBean,InitializingBean和BeanFactoryAware 。

各接口的意义之前都讲过了。factoryBean会在afterPropertiesSet()里把pojo改装成returnObject,需要用到beanfactory进行天马行空的动作时就靠BeanFactoryAware注入。最后在getObject()里把returnObject返回。

3.6.3. Bean Post-Processor扩展机制

如果说FactoryBean 是一种Factory、Wrapper式的扩展,Bean Post-Processor就是另一种AOP、visitor式的机制,所以也多用于spring的AOP架构。

Post-Processor的原理就是BeanFactory在前文里的调用afterPropertiesSet()/init-method前后,调用在工厂里注册了的post-processor的postProcessBeforeInitialization()和postProcessAfterInitialization()。

那怎么注册登记呢?又分请不请礼仪公司两类。如果是ApplicationContext,你把继承BeanPostProcessor 的bean往xml里一搁就行了,application context自会打理。如果是BeanFacotry,就要显式的注册,代码大概像:

XmlBeanFactory factory = new XmlBeanFactory("C:/beans.xml");
BeanPostLogger logger = new BeanPostLogger();
factory.addBeanPostProcessor(logger);
            

posted on 2010-05-24 10:45  小芹菜  阅读(2214)  评论(0编辑  收藏  举报

导航