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" />