1.后处理器
后处理器是对IOC容器功能的扩展。按我的理解,扩展的原理是在某动作结束后再次调用指定的方法进行扩展处理。有点类似于AOP。
后处理器分两种:Bean后处理器和容器后处理器。
1.1 Bean后处理器
Bean后处理器会在Bean实例化结束后(注意,该实例化是指Bean类的实例化,还没有进行Spring中的注入等操作,并不是Spring最终返回的Bean),对其进行近一步的增强处理,例如返回一个Bean的代理类。
Bean后处理器需要实现BeanPostProcessor
接口,该接口包含的postProcessBeforeInitialization
,postProcessAfterInitialization
分别在Bean初始化之前和之后回调。
如上图,增强处理与init-method
,InitializingBean
,destory-method
,DisposableBean
的执行顺序,增强处理永远在最外围的。
实现InitializingBean
接口的afterPropertiesSet
方法、配置<bean init-method="method">
都是在Bean的全部属性设置成功后执行的方法。而Bean后处理器是在属性注入之前和之后执行的方法。
下面给出Bean后处理器的Demo:
- 首先实现创建一个实现
BeanPostProcessor
的后处理器类
/**
* Bean后处理器Demo类,该处理类会对容器里面的所有Bean进行后处理
* @author wangsz
*/
public class BeanPostProc implements BeanPostProcessor{
/**
* 在Bean初始化后,对容器中的bean进行后处理,返回处理后的bean
*/
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean后处理器在["+beanName+"]初始化后对其进行增强处理");
if(bean instanceof Person){
((Person) bean).setName("akq");
}
//该bean可以与旧bean截然不同,如返回一个该Bean的代理类
return bean;
}
/**
* 在Bean初始化前,对容器中的bean进行后处理,返回处理后的bean
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean后处理器在["+beanName+"]初始化前对其进行增强处理");
//该bean可以与旧bean截然不同
return bean;
}
}
- 然后在Spring配置文件中加上这个Bean。这样,该后处理类就会对容器里面的所有Bean进行后处理。
<!--bean后处理器-->
<bean id="beanproc" class="test.wsz.spring.postprocessor.BeanPostProc" />
1.2 容器后处理器
Bean后处理器是对Bean实例化后进行后处理的,而容器后处理器,顾名思义,就是对Spring容器进行后处理,通常用于Spring容器实例化Bean之前,读取配置文件元数据,对其进行修改。
容器后处理器需要实现BeanFactoryPostProcessor
接口,重写该接口包含的postProcessBeanFactory
方法。
Spring中已提供了几个常用的容器后处理器:
- PropertyPlaceholderConfigurer:属性占位符配置器
- PropertyOverrideConfigurer:重写占位符配置器
- CustomAutowireConfigurer:自定义自动装配的配置器
- CustomScopeConfigurer:自定义作用域的配置器
下面给出容器后处理器的Demo:
- 首先实现创建一个实现BeanFactoryPostProcessor的容器后处理器类
/**
* 容器后处理器Demo类,在容器实例化bean之前,读取配置文件的元数据,并修改
* @author wangsz
*/
public class BeanFactoryProc implements BeanFactoryPostProcessor{
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("Spring的容器是:"+beanFactory);
System.out.println("容器后处理器并没有对BeanFactory的初始化做修改");
}
}
- 然后在Spring配置文件中加上这个Bean。
<!--容器后处理器-->
<bean id="beanfactoryproc" class="test.wsz.spring.postprocessor.BeanFactoryProc" />
2.AOP
Aspect Orient Pragramming:面向切面编程。
2.1 AOP的概念
这个术语不太好理解,下面我们用图来一步步阐述它演变的过程。
现在有三个方法,我要在里面添加同一段代码,比较low的方式,是将同一段代码复制粘贴三遍:
改进的方式是,我把这段代码抽离到一个方法中,然后在三个方法中手动调用这个抽离方法:
但是上面的方法仍然有些不方便。如果不是三个方法,是十个,二十个,那一个个的在里面写方法的调用很麻烦。而且,如果增加需求,例如再次为方法一、二、三增加日志打印,再次为方法一、二、三增加参数检验,那么每次都得加个抽离方法,然后在方法一二三里面加调用。
AOP就是针对这些不便的进一步优化。我们将方法一二三看成一个切面,然后在这个切面上进行增强处理。不需要方法一二三手动调用抽离方法,抽离方法“自动”进行了调用:
通过上面的图我们可以进行一个总结:AOP其实就是代理模式的一种体现,将程序运行过程看成一个切面,在切面上进行公共的处理。
2.2 AOP的应用
现在版本的Spring的AOP一般都是整合的AspectJ实现的。AspectJ框架是最早,功能比较强大的AOP实现之一,Spring中只是用到了它部分的功能,有兴趣的朋友可以百度了解一下。
值得注意的是,AspectJ和Spring的实现方式的不同,AspectJ是编译时对目标类进行增强(反编译目标类可发现多了内容),而Spring是生成一个代理类进行增强。
2.2.1 AOP的几种注解配置
下面我们开始在Spring中配置AOP
- 首先在Maven中增加AspectJ的支持jar包,注意版本要和jdk符合,我之前用的jar包过老,导致aop测试时莫名出现一系列找不到包的异常。
<!--aop支持jar包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
- 在Spring的配置文件中增加内容:
<!--beans中增加如下三个配置-->
<beans xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!--aspect配置
如果proxy-target-class 属性值被设置为true,那么基于类的代理将起作用(这时需要cglib库)。
如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理将起作用-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--Aspect Demo类-->
<bean class="test.wsz.spring.aop.AspectDemo" />
</beans>
如果不采用Spring的XML Schema的方法,也可以去除<beans ……>
对应配置,增加:
<!--启动AspectJ支持-->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
- 然后我们创建一个Aspect的测试Demo类:
@Aspect //声明该类为切面类,在spring配置中加入该类的bean,ApplicationContext会自动加载,将该Bean作为切面处理
public class AspectDemo {
/**
* 在方法执行前进行调用,value指定切入点
*/
@Before(value = "execution(* test.wsz.spring.bean..StoneAxe.useAxe(..))")
public void beforeTest() {
System.out.println("-----before stoneAxe.useAxe()-----");
}
/**
* 在方法正常执行完成后进行调用,value指定切入点,也可用pointcut。returning指定返回形参
*/
@AfterReturning(returning = "returnValue", pointcut = "execution(* test.wsz.spring.bean..StoneAxe.useAxeTest(..))")
public void afterReturnTest(Object returnValue) {
System.out.println("-----after stoneAxe.useAxe()-----");
System.out.println("返回值是:" + returnValue);
}
/**
* 无论方法是否正常结束,只要完成后调用该方法
*/
@After(value = "execution(* test.wsz.spring.bean..StoneAxe.useAxeTest(..))")
public void afterTest() {
System.out.println("方法执行完成,无论是正常完成还是异常终止执行");
}
/**
* 在方法异常后调用,但并不能像catch一样捕获异常,异常仍然会抛给上级进行处理
*
* @param e
* 方法中抛出的异常
*/
@AfterThrowing(throwing = "e", pointcut = "execution(* test.wsz.spring.bean..StoneAxe.useAxeTest(..))")
public void afterThrowingTest(Throwable e) {
System.out.println("方法抛出异常:" + e.getMessage());
}
/**
* 功能较强的增强方法,类似before和afterReturning的集合
* @param pjp 方法信息对象
* @return
* @throws Throwable
*/
@Around("execution(* test.wsz.spring.bean..StoneAxe.useAxeTest(..))")
public Object aroundTest(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----around-------");
System.out.println("方法执行前");
Object object = pjp.proceed();
System.out.println("方法执行后");
return object;
}
}
这个Demo类中演示了几种切面的注解方法。xml的配置方法就不贴出来了,可自行百度。
为了方便,我们还可设定一个切点,然后进行引用:
// 定义一个切入点,该切入点方法体中的代码无效
@Pointcut("execution(* test.wsz.spring.bean..IronAxe.useAxe(..))") // 方法体中的代码无效
public void mypointcut() {
System.out.println("-----pointcout-----");
}
/**
* 在方法执行前进行调用
*/
@Before(value = "mypointcut()")
public void before() {
System.out.println("-----before-----");
}
注意,几种切面方法的执行顺序如下:
2.2.2 execution
的规则
补充下切面execution
的规则:
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
举一个例子说明:
2.2.3 AOP要注意的问题
使用Spring AOP拦截时要注意:内部方法调用是无法被拦截的。
例如,方法A中调用了方法B,此时方法B就算加了拦截配置也是无法被拦截的。
原理跟Spring的代理类有关。Spring拦截类的方法时,其实都是拦截的Spring经过增强后处理的代理类的方法。:
proxybean:
before
invoke(bean,A)
after
在invoke(bean,A)
中调用方法B时,此时是原类进行的调用,Spring无法拦截。
查了资料,目前解决方案有三:
1.修改代码,手动调用代理类运行方法B:
if (null != AopContext.currentProxy()) {
rs=((Bean)AopContext.currentProxy()).method(...);
} else {
rs=method(...);
}
在配置文件加入如下配置,使代理类暴露给线程。注意该配置要spring3.0以上:
<aop:aspectj-autoproxy expose-proxy="true"/>
2. 将内部调用的方法放入其他类
3. 使用aspectj拦截
本文总结于
《疯狂JavaEE》的第八章《深入使用Spring》