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

1.后处理器

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

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

1.1 Bean后处理器

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

Bean后处理器需要实现BeanPostProcessor接口,该接口包含的postProcessBeforeInitializationpostProcessAfterInitialization分别在Bean初始化之前和之后回调。


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

下面给出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是生成一个代理类进行增强。

下面我们开始在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-----");
    }

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

最后补充下切面execution的规则
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
举一个例子说明:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,699评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,124评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,127评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,342评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,356评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,057评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,654评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,572评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,095评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,205评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,343评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,015评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,704评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,196评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,320评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,690评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,348评论 2 358

推荐阅读更多精彩内容