Spring学习笔记(二)后处理器与AOP

2023年5月16日13:06:54

1.后处理器

后处理器是对IOC容器功能的扩展。按我的理解,扩展的原理是在某动作结束后再次调用指定的方法进行扩展处理。有点类似于AOP。

后处理器分两种:Bean后处理器容器后处理器

1.1 Bean后处理器

Bean后处理器会在Bean实例化结束后(注意,该实例化是指Bean类的实例化,还没有进行Spring中的注入等操作,并不是Spring最终返回的Bean),对其进行近一步的增强处理,例如返回一个Bean的代理类。

Bean后处理器需要实现BeanPostProcessor接口,该接口包含的postProcessBeforeInitializationpostProcessAfterInitialization分别在Bean初始化之前和之后回调。
Spring学习笔记(二)后处理器与AOP

如上图,增强处理与init-methodInitializingBeandestory-methodDisposableBean的执行顺序,增强处理永远在最外围的

实现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的方式,是将同一段代码复制粘贴三遍:
Spring学习笔记(二)后处理器与AOP

改进的方式是,我把这段代码抽离到一个方法中,然后在三个方法中手动调用这个抽离方法:
Spring学习笔记(二)后处理器与AOP

但是上面的方法仍然有些不方便。如果不是三个方法,是十个,二十个,那一个个的在里面写方法的调用很麻烦。而且,如果增加需求,例如再次为方法一、二、三增加日志打印,再次为方法一、二、三增加参数检验,那么每次都得加个抽离方法,然后在方法一二三里面加调用。

AOP就是针对这些不便的进一步优化。我们将方法一二三看成一个切面,然后在这个切面上进行增强处理。不需要方法一二三手动调用抽离方法,抽离方法“自动”进行了调用:
Spring学习笔记(二)后处理器与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-----");
    }

注意,几种切面方法的执行顺序如下:

Spring学习笔记(二)后处理器与AOP

2.2.2 execution的规则

补充下切面execution的规则:

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

举一个例子说明:
Spring学习笔记(二)后处理器与AOP

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》

  • 作者:大树先生
  • 原文链接:https://blog.csdn.net/z55887/article/details/52684965
    更新时间:2023年5月16日13:06:54 ,共 6081 字。