后处理器是对IOC容器功能的扩展。按我的理解,扩展的原理是在某动作结束后再次调用指定的方法进行扩展处理。有点类似于AOP。
后处理器分两种: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; } }
<!--bean后处理器--> <bean id="beanproc" class="test.wsz.spring.postprocessor.BeanPostProc" />
Bean后处理器是对Bean实例化后进行后处理的,而容器后处理器,顾名思义,就是对Spring容器进行后处理,通常用于Spring容器实例化Bean之前,读取配置文件元数据,对其进行修改。
容器后处理器需要实现BeanFactoryPostProcessor
接口,重写该接口包含的postProcessBeanFactory
方法。
Spring中已提供了几个常用的容器后处理器:
下面给出容器后处理器的Demo:
/** * 容器后处理器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的初始化做修改"); } }
<!--容器后处理器--> <bean id="beanfactoryproc" class="test.wsz.spring.postprocessor.BeanFactoryProc" />
Aspect Orient Pragramming:面向切面编程。
这个术语不太好理解,下面我们用图来一步步阐述它演变的过程。
现在有三个方法,我要在里面添加同一段代码,比较low的方式,是将同一段代码复制粘贴三遍:
改进的方式是,我把这段代码抽离到一个方法中,然后在三个方法中手动调用这个抽离方法:
但是上面的方法仍然有些不方便。如果不是三个方法,是十个,二十个,那一个个的在里面写方法的调用很麻烦。而且,如果增加需求,例如再次为方法一、二、三增加日志打印,再次为方法一、二、三增加参数检验,那么每次都得加个抽离方法,然后在方法一二三里面加调用。
AOP就是针对这些不便的进一步优化。我们将方法一二三看成一个切面,然后在这个切面上进行增强处理。不需要方法一二三手动调用抽离方法,抽离方法“自动”进行了调用:
通过上面的图我们可以进行一个总结:AOP其实就是代理模式的一种体现,将程序运行过程看成一个切面,在切面上进行公共的处理。
现在版本的Spring的AOP一般都是整合的AspectJ实现的。AspectJ框架是最早,功能比较强大的AOP实现之一,Spring中只是用到了它部分的功能,有兴趣的朋友可以百度了解一下。
值得注意的是,AspectJ和Spring的实现方式的不同,AspectJ是编译时对目标类进行增强(反编译目标类可发现多了内容),而Spring是生成一个代理类进行增强。
下面我们开始在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>
<!--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 //声明该类为切面类,在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-----"); }
注意,几种切面方法的执行顺序如下:
execution
的规则补充下切面execution
的规则:
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
举一个例子说明:
使用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》