深入了解Spring
1.Bean后处理器
Spring容器提供了一个接口InitializingBean,实现这个接口的bean只要重写afterPropertiesSet()或者在XML中添加init-method属性,就可以在Bean初始化前后执行特定行为。
InitializingBean是针对单个Bean起作用的,Spring还提供了另外一个接口叫BeanPostProcessor,这个接口是针对容器中所有Bean起作用的。
只要定义个普通的Bean实现这个接口,并实现postProcessBeforeInitialization()和postProcessAfterInitialization()两个方法,容器中所有的Bean都会受影响。
下面是一个实现了BeanPostProcessor的普通bean类,
package spi; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPostProcessor implements BeanPostProcessor { @Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("Bean后处理器在初始化之前对 "+beanName+" 进行增强处理...");
return bean;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("Bean后处理器在初始化之后对 "+beanName+" 进行增强处理...");
if (bean instanceof Chinese) {
System.out.println("Bean后处理器将Chinese.beanName修改为'Java编程'.");
Chinese c = (Chinese)bean;
c.setBeanName("Java编程");
}
return bean;
} }
特意让这个bean修改chinese这个bean的属性,下面是chinese Bean的代码,
package spi; import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean; public class Chinese implements Person, InitializingBean {
private Son son;
private String beanName; public void setBeanName(String beanName) {
System.out.println(beanName+":Spring正在执行setBeanName()方法注入依赖关系...");
this.beanName = beanName; }
public void info(){
System.out.println("我在XML中的id名称为:"+beanName);
}
public int age;
private Axe axe;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Axe getAxe() {
return axe;
}
public void setAxe(Axe axe) {
this.axe = axe;
}
public Chinese() {
System.out.println("Spring正在执行默认构造函数构造Chinese实例...");
}
public Chinese(Axe axe) {
this.axe = axe;
}
public void useAxe() {
System.out.println("我打算去砍点柴火");
System.out.println("修改beanName="+beanName+","+axe.chop());
}
public void close() {
System.out.println("正在执行销毁前的方法 close ...");
}
public Son getSon() {
return son;
}
public void setSon(Son son) {
this.son = son;
}
public void init() {
System.out.println("正在执行初始化方法init...");
}
@Override
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
System.out.println("正在执行初始化方法afterPropertiesSet...");
} }
Chinese Bean实现了InitializingBean接口,添加了init()(需要XML配置)方法和afterPropertiesSet()方法,那么它在初始化前后会被插入特定行为,行为由这两个方法决定。 同时,由于容器中存在实现了BeanPostProcessor的Bean,那么所有Bean包括chinese,都会在初始化前后批量被插入特定行为。
XML配置如下,
<bean id="chinese" class="spi.Chinese"
init-method="init" p:axe-ref="steelAxe" p:beanName="依赖注入的值" />
<bean class="spi.MyBeanPostProcessor" />
测试代码,
public static void test8() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Person p = ctx.getBean("chinese", Person.class);
p.useAxe();
}
执行结果,
Bean后处理器在初始化之前对 messageSource 进行增强处理...
Bean后处理器在初始化之后对 messageSource 进行增强处理...
Bean后处理器在初始化之前对 stoneAxe 进行增强处理...
Bean后处理器在初始化之后对 stoneAxe 进行增强处理...
Bean后处理器在初始化之前对 steelAxe 进行增强处理...
Bean后处理器在初始化之后对 steelAxe 进行增强处理...
Bean后处理器在初始化之前对 spi.EmailNotifier#0 进行增强处理...
Bean后处理器在初始化之后对 spi.EmailNotifier#0 进行增强处理...
Bean后处理器在初始化之前对 getContextViaBean 进行增强处理...
Bean后处理器在初始化之后对 getContextViaBean 进行增强处理...
Bean后处理器在初始化之前对 getField 进行增强处理...
Bean后处理器在初始化之后对 getField 进行增强处理...
Spring正在执行默认构造函数构造Chinese实例...
14 依赖注入的值:Spring正在执行setBeanName()方法注入依赖关系...
15 Bean后处理器在初始化之前对 chinese 进行增强处理...
16 正在执行初始化方法afterPropertiesSet...
17 正在执行初始化方法init...
18 Bean后处理器在初始化之后对 chinese 进行增强处理...
19 Bean后处理器将Chinese.beanName修改为'Java编程'.
20 Java编程:Spring正在执行setBeanName()方法注入依赖关系...
我打算去砍点柴火
修改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配置文件中的占位符变量,典型用法如,
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>dbconn.properties</value>
<value>dbconn.properties2</value>
<value>dbconn.properties3</value>
</list>
</property>
</bean> <bean id="dataSource" class="conn.machange.v2.c3p0.ComboPooledDataSource" destroy-method="close"
p:driverClass="${jdbc.driverClassName}"
p:jdbcUrl="${jdbc.url}"
p:user="${jdbc.username}"
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配置的元数据,还是上面的例子,
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="locations">
<list>
<value>dbconn.properties</value>
<value>dbconn.properties2</value>
<value>dbconn.properties3</value>
</list>
</property>
</bean> <bean id="dataSource" class="conn.machange.v2.c3p0.ComboPooledDataSource"
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
<?xml version="1.0" encoding="UTF-8"?>
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 自动扫描指定包及其子包下的所有Bean类 -->
<context:component-scan base-package="spi" />
<context:include-filter type="regex" expression=".*Chinese" />
<context:exclude-filter type="regex" expression="Stone*" />
</beans>
3.1@Componnet
接着我们只需要将Bean类名加上Annotation注解就行了,Spring可以使用@Component, @Controller, @Service, @Repository四种注解将一个类标识为容器中的bean,最常用的是@Component.
@Component
public class Chinese implements Person, InitializingBean {
... @Component
public class SteelAxe implements Axe {
... @Component
public class StoneAxe implements Axe {
...
3.2@Scope
@Scope用来指定Bean的作用域,与XML配置中的scope属性意义一样
@Scope("prototype")
@Component
public class Chinese implements Person, InitializingBean {
...
3.3@Resource
Spring通过@Resource设置依赖关系,用来修饰setter方法,相当于XML配置中的<property>元素,@Resource中有一个name属性,为name属性设置值就相当于设置ref属性。
@Resource(name="stoneAxe")
public void setAge(int age) {
this.age = age;
}
@Resource也可以直接修饰实例变量,一样会进行依赖注入,并且括号中的name可以省略,
@Resource(name="stoneAxe")
private Axe axe;
//或者
@Resource(”stoneAxe")
private Axe axe;
3.4@PostConstruct和@PreDestroy
这两个注解相当于XML中的init-method和destroy-method属性,会在bean初始化期间添加额外行为,只不过这两个注解不需要任何属性值,直接将注解放在对应的方法上即可
@PostConstruct
public void init() {
System.out.println("正在执行初始化方法init...");
}
@PreDestroy
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
System.out.println("正在执行初始化方法afterPropertiesSet...");
}
3.5@DependsOn和@Lazy
@DependsOn用于强制初始化其他(依赖的)多个bean,以数组形式作为此注解的参数值,
@DependsOn({"steelAxe","stoneAxe"})
@Scope("prototype")
@Component
public class Chinese implements Person, InitializingBean {
...
3.6@Autowired
Spring4.0中的@Autowired进行了功能增强,可以用来修饰setter方法,普通方法,实例变量和构造器,甚至是数组变量。默认使用byType(按参数类型)自动装配策略。
// 修饰setter方法
@Autowired
public void setAge(int age) {
this.age = age;
}
...
//修饰普通方法,支持多个参数
@Autowired
public void test(Axe axe, Person person) { }
...
//修饰成员变量
@Autowired
private Axe axe;
...
//修饰构造器
@Autowired
public Chinese(Axe axe) {
this.axe = axe;
}
...
//修饰数组,Spring会先收集类型相符的bean,构造数组,然后进行依赖注入
@Autowired
private Axe[] axes;
...
需要注意的是,@Autowired除了修饰数组之外,对于其他情况(setter方法,普通方法,成员变量,构造函数),如果Spring在容器中找到超过一个符合类型的bean,将会报错
@Autowired默认使用byType策略进行依赖注入时bean的查找,如果希望指定依赖注入的bean id,则需要使用@Qualifier
@Qualifier("steelAxe")
private Axe axe;
当然,这么做就没什么意义了,因为@Resource完全可以实现这个功能,@Qualifier还有个用处就是可以指定方法形参时的依赖注入,
public void setAxe(@Qualifier("steelAxe") Axe axe) {
this.axe = axe;
}
4.Resource接口
Spring提供了一个Resource接口用来访问资源,其底层是封装了各种各种资源的访问方式,例如网络资源,本地资源,二进制数据等等,
通过Resource实现类的对象,可以对资源进行统一方式的操作,还可以通过Rescource实现类的对象获取资源的File实例,以java IO的方式操作资源。
Resource提供如下对资源的统一访问方法:getInputStream(), exists(), isOpen(), getDescription(), getFile(), getUrl()。
Spring为Resource默认已经实现了如下类:UrlResource, ClassPathResource, FileSystemResource, ServletContextResource, InputStreamResource, ByteArrayResource.
下面是几个实现类的用法,
public static void test10() throws IOException {
UrlResource ur = new UrlResource("file:book.xml");
//ClassPathResource ur = new ClassPathResource("book.xml");
//FileSystemResource ur = new FileSystemResource("book.xml");
//ServletContextResource ur = new ServletContextResource(application,"WEB-INF/book.xml");
//String file="Java编程核心思想";
//byte[] fileBytes = file.getBytes();
//ByteArrayResource ur = new ByteArrayResource(fileBytes);
System.out.println(ur.getFilename());
System.out.println(ur.getDescription());
File file = ur.getFile();
System.out.println(file.getName());
}
可以看到上面代码中,对于每一种Resource实现类,在Spring使用时仅仅是获取资源的方式不同,但是操作文件和数据一模一样,都是通过Resourcce实现类获取资源对象,以相同方法获取资源信息,都可以转化为File实例进行后续操作,当然对于二进制数据就没有文件的说法,所以getFileName之类的方法是不起作用的。
另外还注意到,不同的Resource实现类在获取资源时,传入的参数类型不尽相同,有的需要http:前缀,有的需要file:前缀,有的不需要前缀,这是由实现类的底层性质针对不同类型的资源决定的。
5.ResourceLoader和ResourceLoaderAware接口
在上面的例子中,我们总是需要显式地写出要使用哪个Resource的实现类来获取资源,这显得比较麻烦,Spring又为我们提供了两个接口,
ResourceLoader 用来获取一个Resource实例
ResourceLoaderAware 获取Resource实例的引用
通过ResourceLoader,我们可以实现面向接口的编程,直接获取一个资源,
由于ApplicationContext类也实现了ResourceLoader接口,所以可以直接把ApplicationContext当作一个ResourceLoader,用ApplicationContext实例去获取一个资源,像这样:
Resource res =ctx.getResource("beans2.xml");
这样就直接获取了一个资源实例,简化了编程,不过这样获取的资源,其底层是通过什么方式(即由哪个Resource的实现类)来获取资源的呢,
Spring采用的是典型的策略模式,根据ApplicationContext实例化时候的策略,来决定这里获取Resource的策略。应用程序只需要使用统一的接口,调用相同的实现方法,而不需要关系底层到底是用了哪个实现类。
如果ApplicationContext实例化使用的是ClassPathXmlApplicationContext方式,那么Resource就是采用ClassPathResource实现类;
如果ApplicationContext实例化使用的是FileSystemXmlApplicationContext方式,那么Resource就是采用FileSystemResource实现类,
以此类推。
因此前面获取资源的代码我们就可以简化如下:
public static void test11() throws IOException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans2.xml");
//ApplicationContext ctx = new FileSystemXmlApplicationContext("beans2.xml");
Resource res =ctx.getResource("beans2.xml");
System.out.println(res.getFilename());
System.out.println(res.getDescription());
File file = res.getFile();
System.out.println(file.getName());
}
这样就将获取资源的Resource实现类解耦了,我们只需要直接使用Resource接口即可。
当然,如果你非要指定具体的Resource实现类也是可以的,只需要在ctx.getResource(..)时,在传入的参数前加不同的前缀,例如
//无前缀表示由ApplicationContext来决定加载策略
Resource res =ctx.getResource("beans2.xml");
//以ClassPathResource访问类加载路径下的资源
Resource res =ctx.getResource("classpath:beans2.xml");
//以UrlResource实例访问本地系统
Resource res =ctx.getResource("file:beans2.xml");
//以UrlResource实例访问基于HTTP的网络资源
Resource res =ctx.getResource("http:beans2.xml");
6.将Resource实例作为Bean属性
比ResourceLoader更能解耦的方式是直接将Resource的实例作为Bean的属性,这样就能直接在XML文件中配置需要访问的资源名称,例如这样,
public class Chinese implements Person, InitializingBean {
private Resource res;
public Resource getRes() {
return res;
}
public void setRes(Resource res) {
this.res = res;
}
...
这样便可以将具体的资源名称写入配置文件,使得能最大显得地将资源名称与具体java代码解耦
<bean id="chinese" class="spi.Chinese" p:res="classpath:book.xml" />
深入了解Spring的更多相关文章
- 基于spring注解AOP的异常处理
一.前言 项目刚刚开发的时候,并没有做好充足的准备.开发到一定程度的时候才会想到还有一些问题没有解决.就比如今天我要说的一个问题:异常的处理.写程序的时候一般都会通过try...catch...fin ...
- 玩转spring boot——快速开始
开发环境: IED环境:Eclipse JDK版本:1.8 maven版本:3.3.9 一.创建一个spring boot的mcv web应用程序 打开Eclipse,新建Maven项目 选择quic ...
- Spring基于AOP的事务管理
Spring基于AOP的事务管理 事务 事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务 ...
- [Spring]IoC容器之进击的注解
先啰嗦两句: 第一次在博客园使用markdown编辑,感觉渲染样式差强人意,还是github的样式比较顺眼. 概述 Spring2.5 引入了注解. 于是,一个问题产生了:使用注解方式注入 JavaB ...
- 学习AOP之透过Spring的Ioc理解Advisor
花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...
- 学习AOP之深入一点Spring Aop
上一篇<学习AOP之认识一下SpringAOP>中大体的了解了代理.动态代理及SpringAop的知识.因为写的篇幅长了点所以还是再写一篇吧.接下来开始深入一点Spring aop的一些实 ...
- 学习AOP之认识一下Spring AOP
心碎之事 要说知道AOP这个词倒是很久很久以前了,但是直到今天我也不敢说非常的理解它,其中的各种概念即抽象又太拗口. 在几次面试中都被问及AOP,但是真的没有答上来,或者都在面上,这给面试官的感觉就是 ...
- 为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?
今年我一直在思考web开发里的前后端分离的问题,到了现在也颇有点心得了,随着这个问题的深入,再加以现在公司很多web项目的控制层的技术框架由struts2迁移到springMVC,我突然有了一个新的疑 ...
- Spring之旅(2)
Spring简化Java的下一个理念:基于切面的声明式编程 3.应用切面 依赖注入的目的是让相互协作的组件保持松散耦合:而AOP编程允许你把遍布应用各处的功能分离出来形成可重用的组件. AOP面向切面 ...
- Spring之旅
Java使得以模块化构建复杂应用系统成为可能,它为Applet而来,但为组件化而留. Spring是一个开源的框架,最早由Rod Johnson创建.Spring是为了解决企业级应用开发的复杂性而创建 ...
随机推荐
- HDU 4515
刷水完毕,年月日,日日日日日日日日日日日日日日日日日日 #include <stdio.h> ,,,,,,,,,,,,}; ,M = ,D = ; int leap(int y) { == ...
- boost中的有用工具assign和uuid
assign assign重载'+'=和','实现连续赋值 assign不仅支持所有8个STL标准容器(vector.string.deque.list.set.multiset.map.multim ...
- 黑马day07 登录注冊案例(一)
简单介绍:依据三层架构的思想设计本案例. 1.搭建好开发环境 准备好须要的包和模拟数据库配置文件users.xml -->cn.itheima.dao -->cn.itheima.serv ...
- Maximal Rectangle [leetcode] 的三种思路
第一种方法是利用DP.时间复杂度是 O(m * m * n) dp(i,j):矩阵中同一行以(i,j)结尾的所有为1的最长子串长度 代码例如以下: int maximalRectangle(vecto ...
- Mac上搭建android环境:Android Studio+GreenVPN
1.下载Android Studio,https://developer.android.com/sdk/index.html 2.使用GreenVPN,感觉还能够.18/月.http://www.g ...
- PHP独立操作符
& 与 ^ 位逻辑异或 $ # ! 逻辑或 ~ 按位取反
- 安卓离线SDK Windows版 资源包下载地址全集
1.Tools https://dl-ssl.google.com/android/repository/platform-tools_r19.0.1-windows.zip https://d ...
- 使用串口终端安装AIX操作系统
使用串口终端安装AIX操作系统 一.配置超级终端 首先,配置超级终端:在笔记本电脑上(Windows XP系统),点击开始à程序->附件->通讯->超级终端,配置名称为test的超级 ...
- [Pulgin] 前端上传组件Plupload使用指南
我之前写过一篇文章<文件上传利器SWFUpload使用指南>,里面介绍了上传组件SWFUpload的使用方法,但现在随着html5技术的逐渐推广和普及,再去使用以flash为上传手段的SW ...
- VirtualBox里如何正确安装增强工具(图文详解)
不多说,直接上干货! 找到 复制到