深入了解Spring

1.Bean后处理器

Spring容器提供了一个接口InitializingBean,实现这个接口的bean只要重写afterPropertiesSet()或者在XML中添加init-method属性,就可以在Bean初始化前后执行特定行为。

InitializingBean是针对单个Bean起作用的,Spring还提供了另外一个接口叫BeanPostProcessor,这个接口是针对容器中所有Bean起作用的。

只要定义个普通的Bean实现这个接口,并实现postProcessBeforeInitialization()和postProcessAfterInitialization()两个方法,容器中所有的Bean都会受影响。

下面是一个实现了BeanPostProcessor的普通bean类,

 1 package spi;
 2 
 3 import org.springframework.beans.BeansException;
 4 import org.springframework.beans.factory.config.BeanPostProcessor;
 5 
 6 public class MyBeanPostProcessor implements BeanPostProcessor {
 7 
 8     @Override
 9     public Object postProcessBeforeInitialization(Object bean, String beanName)
10             throws BeansException {
11         System.out.println("Bean后处理器在初始化之前对 "+beanName+" 进行增强处理...");
12         return bean;
13     }
14 
15     @Override
16     public Object postProcessAfterInitialization(Object bean, String beanName)
17             throws BeansException {
18         System.out.println("Bean后处理器在初始化之后对 "+beanName+" 进行增强处理...");
19         if (bean instanceof Chinese) {
20             System.out.println("Bean后处理器将Chinese.beanName修改为'Java编程'.");
21             Chinese c = (Chinese)bean;
22             c.setBeanName("Java编程");
23         }
24         return bean;
25     }
26     
27 }

特意让这个bean修改chinese这个bean的属性,下面是chinese Bean的代码,

 1 package spi;
 2 
 3 import org.springframework.beans.factory.BeanNameAware;
 4 import org.springframework.beans.factory.InitializingBean;
 5 
 6 public class Chinese implements Person, InitializingBean {
 7     private Son son;
 8     private String beanName;
 9 
10     public void setBeanName(String beanName) {
11         System.out.println(beanName+":Spring正在执行setBeanName()方法注入依赖关系...");
12         this.beanName = beanName;
13         
14     }
15     public void info(){
16         System.out.println("我在XML中的id名称为:"+beanName);
17     }
18     public int age;
19     private Axe axe;
20     public int getAge() {
21         return age;
22     }
23     public void setAge(int age) {
24         this.age = age;
25     }
26     public Axe getAxe() {
27         return axe;
28     }
29     public void setAxe(Axe axe) {
30         this.axe = axe;
31     }
32     public Chinese() {
33         System.out.println("Spring正在执行默认构造函数构造Chinese实例...");
34     }
35     public Chinese(Axe axe) {
36         this.axe = axe;
37     }
38     public void useAxe() {
39         System.out.println("我打算去砍点柴火");
40         System.out.println("修改beanName="+beanName+","+axe.chop());
41     }
42     public void close() {
43         System.out.println("正在执行销毁前的方法 close ...");
44     }
45     public Son getSon() {
46         return son;
47     }
48     public void setSon(Son son) {
49         this.son = son;
50     }
51     public void init() {
52         System.out.println("正在执行初始化方法init...");
53     }
54     @Override
55     public void afterPropertiesSet() throws Exception {
56         // TODO Auto-generated method stub
57         System.out.println("正在执行初始化方法afterPropertiesSet...");
58     }
59 
60 }

Chinese Bean实现了InitializingBean接口,添加了init()(需要XML配置)方法和afterPropertiesSet()方法,那么它在初始化前后会被插入特定行为,行为由这两个方法决定。 同时,由于容器中存在实现了BeanPostProcessor的Bean,那么所有Bean包括chinese,都会在初始化前后批量被插入特定行为。

XML配置如下,

1     <bean id="chinese" class="spi.Chinese" 
2     init-method="init" p:axe-ref="steelAxe" p:beanName="依赖注入的值" />
3     <bean class="spi.MyBeanPostProcessor" />

测试代码,

1     public static void test8() {
2         ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
3         Person p = ctx.getBean("chinese", Person.class);
4         p.useAxe();
5     }

 

执行结果,

 1 Bean后处理器在初始化之前对 messageSource 进行增强处理...
 2 Bean后处理器在初始化之后对 messageSource 进行增强处理...
 3 Bean后处理器在初始化之前对 stoneAxe 进行增强处理...
 4 Bean后处理器在初始化之后对 stoneAxe 进行增强处理...
 5 Bean后处理器在初始化之前对 steelAxe 进行增强处理...
 6 Bean后处理器在初始化之后对 steelAxe 进行增强处理...
 7 Bean后处理器在初始化之前对 spi.EmailNotifier#0 进行增强处理...
 8 Bean后处理器在初始化之后对 spi.EmailNotifier#0 进行增强处理...
 9 Bean后处理器在初始化之前对 getContextViaBean 进行增强处理...
10 Bean后处理器在初始化之后对 getContextViaBean 进行增强处理...
11 Bean后处理器在初始化之前对 getField 进行增强处理...
12 Bean后处理器在初始化之后对 getField 进行增强处理...
13 Spring正在执行默认构造函数构造Chinese实例...
14 依赖注入的值:Spring正在执行setBeanName()方法注入依赖关系...
15 Bean后处理器在初始化之前对 chinese 进行增强处理...
16 正在执行初始化方法afterPropertiesSet...
17 正在执行初始化方法init...
18 Bean后处理器在初始化之后对 chinese 进行增强处理...
19 Bean后处理器将Chinese.beanName修改为'Java编程'.
20 Java编程:Spring正在执行setBeanName()方法注入依赖关系...
21 我打算去砍点柴火
22 修改beanName=Java编程,钢斧砍柴好快

 

 从执行结果可以看到,受BeanPostProcessor影响,所有Bean都被插入了两条特定行为,chinese Bean由于额外实现了InitializingBean接口而多了两条额外行为。

另外可以看到虽然在配置文件中chinese的beanName被注入的是“依赖注入的值”,但是初始化之后它又被修改成了“Java编程”,所以setBeanName也被调用了两次。

从上面的例子可以看出,BeanPostProcessor接口的作用就是对容器bean进行批量处理,实际中Spring的这种后处理很有用,通常用来实现代理器,

例如BeanNameAutoProxyCreator是根据Bean实例的name属性,创建Bean实例的代理。

DefaultAdvisorProxyCreator是根据提供的Advisor对容器所有Bean实例创建代理。

2.容器后处理器

类似的,容器也有后处理器,专门用来扩展容器的功能。容器后处理器需要实现BeanFactoryPostProcesser接口。

Spring为我们实现了几个容器后处理器,常用的有PropertyPlaceholderConfigurer, PropertyOverrideConfigurer等。

PropertyPlaceholderConfigurer可以用property文件来替换Spring配置文件中的占位符变量,典型用法如,

 1     <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 2         <property name="locations">
 3             <list>
 4                 <value>dbconn.properties</value>
 5                 <value>dbconn.properties2</value>
 6                 <value>dbconn.properties3</value>
 7             </list>
 8         </property>
 9     </bean>
10     
11     <bean id="dataSource" class="conn.machange.v2.c3p0.ComboPooledDataSource" destroy-method="close"
12         p:driverClass="${jdbc.driverClassName}"
13         p:jdbcUrl="${jdbc.url}"
14         p:user="${jdbc.username}"
15         p:password="${jdbc.password}" />

 

上面定义的dataSource Bean,并没有直接将值注入driverClass, jdbcUrl等属性中,而是使用${jdbc.driverClassName},${jdbc.url},

因为有了容器后处理器PropertyPlaceholderConfigurer的存在,Spring会查找对应的外部properties文件并用里面设置的变量替换Spring中的变量,外部properties文件内容如下,

dbconn.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.pawword=123456

 

另外还有PropertyOverrideConfigurer容器后处理器,这个跟前面有点类似都是用来将Spring配置提取到外部properties文件中,但是功能更强大。

PropertyOverrideConfigurer属性文件指定的信息可以直接覆盖Spring配置的元数据,还是上面的例子,

 1     <bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
 2         <property name="locations">
 3             <list>
 4                 <value>dbconn.properties</value>
 5                 <value>dbconn.properties2</value>
 6                 <value>dbconn.properties3</value>
 7             </list>
 8         </property>
 9     </bean>
10     
11     <bean id="dataSource" class="conn.machange.v2.c3p0.ComboPooledDataSource" 
12     destroy-method="close" />

 

可以看到dataSource这个bean没有最注入任何属依赖属性,但是在下面的外部properties文件中,却配置了dataSource Bean的属性,注意这里的属性名称必须是Bean确实拥有的才行。

dbconn.properties
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/test
dataSource.username=root
dataSource.pawword=123456

 

3.Spring的Annotation

使用Spring的Annotation可以不用再在XML文件中进行bean的配置,只需要在XML中设置好bean的搜索路径,然后在bean上使用相应的Annotation就行了。

XML配置Bean搜索路径如下,使用<context:component-scan的base-package属性进行配置,

还可以使用<context:include-filter和<context:exclude-filter进行Bean路径的配置,这种配置不需要在Bean类名上加注解,容器就能自动识别出bean

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:context="http://www.springframework.org/schema/context"
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans
 6     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 7     http://www.springframework.org/schema/context
 8     http://www.springframework.org/schema/context/spring-context-4.0.xsd">
 9     <!-- 自动扫描指定包及其子包下的所有Bean类 -->
10     <context:component-scan base-package="spi" />
11     <context:include-filter type="regex" expression=".*Chinese" />
12     <context:exclude-filter type="regex" expression="Stone*" />
13 </beans>

3.1@Componnet

接着我们只需要将Bean类名加上Annotation注解就行了,Spring可以使用@Component, @Controller, @Service, @Repository四种注解将一个类标识为容器中的bean,最常用的是@Component.

 1 @Component
 2 public class Chinese implements Person, InitializingBean {
 3 ...
 4 
 5 @Component
 6 public class SteelAxe implements Axe { 
 7 ...
 8 
 9 @Component
10 public class StoneAxe implements Axe { 
11 ...

3.2@Scope

@Scope用来指定Bean的作用域,与XML配置中的scope属性意义一样

1 @Scope("prototype")
2 @Component
3 public class Chinese implements Person, InitializingBean {
4 ...

3.3@Resource

Spring通过@Resource设置依赖关系,用来修饰setter方法,相当于XML配置中的<property>元素,@Resource中有一个name属性,为name属性设置值就相当于设置ref属性。

1     @Resource(name="stoneAxe")
2     public void setAge(int age) {
3         this.age = age;
4     }

 

@Resource也可以直接修饰实例变量,一样会进行依赖注入,并且括号中的name可以省略,

1     @Resource(name="stoneAxe")
2     private Axe axe;
3 //或者
4     @Resource(”stoneAxe")
5     private Axe axe;

3.4@PostConstruct和@PreDestroy

这两个注解相当于XML中的init-method和destroy-method属性,会在bean初始化期间添加额外行为,只不过这两个注解不需要任何属性值,直接将注解放在对应的方法上即可

1     @PostConstruct
2     public void init() {
3         System.out.println("正在执行初始化方法init...");
4     }
5     @PreDestroy
6     public void afterPropertiesSet() throws Exception {
7         // TODO Auto-generated method stub
8         System.out.println("正在执行初始化方法afterPropertiesSet...");
9     }

3.5@DependsOn和@Lazy

@DependsOn用于强制初始化其他(依赖的)多个bean,以数组形式作为此注解的参数值,

1 @DependsOn({"steelAxe","stoneAxe"})
2 @Scope("prototype")
3 @Component
4 public class Chinese implements Person, InitializingBean {
5 ...

3.6@Autowired

Spring4.0中的@Autowired进行了功能增强,可以用来修饰setter方法,普通方法,实例变量和构造器,甚至是数组变量。默认使用byType(按参数类型)自动装配策略。

 1     // 修饰setter方法
 2     @Autowired
 3     public void setAge(int age) {
 4         this.age = age;
 5     }
 6 ...
 7     //修饰普通方法,支持多个参数
 8     @Autowired
 9     public void test(Axe axe, Person person) {
10         
11     }
12 ...
13     //修饰成员变量
14     @Autowired
15     private Axe axe;
16 ...
17     //修饰构造器
18     @Autowired
19     public Chinese(Axe axe) {
20         this.axe = axe;
21     }
22 ...
23     //修饰数组,Spring会先收集类型相符的bean,构造数组,然后进行依赖注入
24     @Autowired
25     private Axe[] axes;
26 ...

需要注意的是,@Autowired除了修饰数组之外,对于其他情况(setter方法,普通方法,成员变量,构造函数),如果Spring在容器中找到超过一个符合类型的bean,将会报错

@Autowired默认使用byType策略进行依赖注入时bean的查找,如果希望指定依赖注入的bean id,则需要使用@Qualifier

1     @Qualifier("steelAxe")
2     private Axe axe;

当然,这么做就没什么意义了,因为@Resource完全可以实现这个功能,@Qualifier还有个用处就是可以指定方法形参时的依赖注入,

1     public void setAxe(@Qualifier("steelAxe") Axe axe) {
2         this.axe = axe;
3     }

 4.Resource接口

Spring提供了一个Resource接口用来访问资源,其底层是封装了各种各种资源的访问方式,例如网络资源,本地资源,二进制数据等等,

通过Resource实现类的对象,可以对资源进行统一方式的操作,还可以通过Rescource实现类的对象获取资源的File实例,以java IO的方式操作资源。

Resource提供如下对资源的统一访问方法:getInputStream(), exists(), isOpen(), getDescription(), getFile(), getUrl()。

Spring为Resource默认已经实现了如下类:UrlResource, ClassPathResource, FileSystemResource, ServletContextResource, InputStreamResource, ByteArrayResource.

下面是几个实现类的用法,

 1     public static void test10() throws IOException {
 2         UrlResource ur = new UrlResource("file:book.xml");
 3         //ClassPathResource ur = new ClassPathResource("book.xml");
 4         //FileSystemResource ur = new FileSystemResource("book.xml");
 5         //ServletContextResource ur = new ServletContextResource(application,"WEB-INF/book.xml");
 6         //String file="Java编程核心思想";
 7         //byte[] fileBytes = file.getBytes();
 8         //ByteArrayResource ur = new ByteArrayResource(fileBytes);
 9         System.out.println(ur.getFilename());
10         System.out.println(ur.getDescription());
11         File file = ur.getFile();
12         System.out.println(file.getName());
13     }

 

可以看到上面代码中,对于每一种Resource实现类,在Spring使用时仅仅是获取资源的方式不同,但是操作文件和数据一模一样,都是通过Resourcce实现类获取资源对象,以相同方法获取资源信息,都可以转化为File实例进行后续操作,当然对于二进制数据就没有文件的说法,所以getFileName之类的方法是不起作用的。

另外还注意到,不同的Resource实现类在获取资源时,传入的参数类型不尽相同,有的需要http:前缀,有的需要file:前缀,有的不需要前缀,这是由实现类的底层性质针对不同类型的资源决定的。

5.ResourceLoader和ResourceLoaderAware接口

在上面的例子中,我们总是需要显式地写出要使用哪个Resource的实现类来获取资源,这显得比较麻烦,Spring又为我们提供了两个接口,

ResourceLoader 用来获取一个Resource实例

ResourceLoaderAware 获取Resource实例的引用

通过ResourceLoader,我们可以实现面向接口的编程,直接获取一个资源,

由于ApplicationContext类也实现了ResourceLoader接口,所以可以直接把ApplicationContext当作一个ResourceLoader,用ApplicationContext实例去获取一个资源,像这样:

1 Resource res =ctx.getResource("beans2.xml");

 

这样就直接获取了一个资源实例,简化了编程,不过这样获取的资源,其底层是通过什么方式(即由哪个Resource的实现类)来获取资源的呢,

Spring采用的是典型的策略模式,根据ApplicationContext实例化时候的策略,来决定这里获取Resource的策略。应用程序只需要使用统一的接口,调用相同的实现方法,而不需要关系底层到底是用了哪个实现类。

如果ApplicationContext实例化使用的是ClassPathXmlApplicationContext方式,那么Resource就是采用ClassPathResource实现类;

如果ApplicationContext实例化使用的是FileSystemXmlApplicationContext方式,那么Resource就是采用FileSystemResource实现类,

以此类推。

因此前面获取资源的代码我们就可以简化如下:

1     public static void test11() throws IOException {
2         ApplicationContext ctx = new ClassPathXmlApplicationContext("beans2.xml");
3         //ApplicationContext ctx = new FileSystemXmlApplicationContext("beans2.xml");
4         Resource res =ctx.getResource("beans2.xml");
5         System.out.println(res.getFilename());
6         System.out.println(res.getDescription());
7         File file = res.getFile();
8         System.out.println(file.getName());
9     }

 

这样就将获取资源的Resource实现类解耦了,我们只需要直接使用Resource接口即可。

当然,如果你非要指定具体的Resource实现类也是可以的,只需要在ctx.getResource(..)时,在传入的参数前加不同的前缀,例如

1         //无前缀表示由ApplicationContext来决定加载策略
2         Resource res =ctx.getResource("beans2.xml");
3         //以ClassPathResource访问类加载路径下的资源
4         Resource res =ctx.getResource("classpath:beans2.xml");
5         //以UrlResource实例访问本地系统
6         Resource res =ctx.getResource("file:beans2.xml");
7         //以UrlResource实例访问基于HTTP的网络资源
8         Resource res =ctx.getResource("http:beans2.xml");

 

6.将Resource实例作为Bean属性

比ResourceLoader更能解耦的方式是直接将Resource的实例作为Bean的属性,这样就能直接在XML文件中配置需要访问的资源名称,例如这样,

1 public class Chinese implements Person, InitializingBean {
2     private Resource res;
3     public Resource getRes() {
4         return res;
5     }
6     public void setRes(Resource res) {
7         this.res = res;
8     }
9 ...

 

这样便可以将具体的资源名称写入配置文件,使得能最大显得地将资源名称与具体java代码解耦

<bean id="chinese" class="spi.Chinese" p:res="classpath:book.xml" />

 

posted @ 2017-02-04 19:39  fysola  阅读(1589)  评论(0编辑  收藏  举报