Spring学习(四)搞定切面(AOP)

Spring

导语:

面向切面编程(AOP) 所要解决的问题是把横切关注点与业务逻辑相分离。
DI有助于应用对象之间的解耦, 而AOP可以实现横切关注点与它们所影响的对象之间的解耦。

日志是应用切面的常见范例,除此之外,切面还适用声明式事务、 安全和缓存。
主要内容:

  • 面向切面编程的基本原理
  • 通过POJO创建切面
  • 使用@AspectJ注解
  • 为AspectJ切面注入依赖

一、面向切面编程的基本原理

横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect) 。

  1. AOP术语
    通知包含了需要用于多个应用对象的横切行为; 连接点是程序执行过程中能够应用通知的所有点; 切点定义了通知被应用的具体位置(在哪些连接点) 。 其中关键的概念是切点定义了哪些连接点会得到通知。
  • 通知(Advice)
    在AOP术语中,切面的工作被称为通知。通知定义了切面是什么以及何时使用。
    Spring切面可以应用5种类型的通知:

    前置通知(Before) : 在目标方法被调用之前调用通知功能;
    后置通知(After) : 在目标方法完成之后调用通知, 此时不会关心方法的输出是什么;
    返回通知(After-returning) : 在目标方法成功执行之后调用通知;
    异常通知(After-throwing) : 在目标方法抛出异常后调用通知;
    环绕通知(Around) : 通知包裹了被通知的方法, 在被通知的方法调用之前和调用之后执行自定义的行为。

  • 连接点(Join point)(被切的类上的点)
    连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、 抛出异常时、 甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中, 并添加新的行为。
  • 切点(Poincut)(在切面上定义要切的类)
    切点有助于缩小切面所通知的连接点的范围。
  • 切面(Aspect)
    切面是通知和切点的结合。 通知和切点共同定义了切面的全部内容——它是什么, 在何时和何处完成其功能。
  • 引入(Introduction)
    引入允许我们向现有的类添加新方法或属性。
  • 织入(Weaving)
    织入是把切面应用到目标对象并创建新的代理对象的过程。

    在目标对象的生命周期里有多个点可以进行织入:
    编译期: 切面在目标类编译时被织入。 这种方式需要特殊的编译器。 AspectJ的织入编译器就是以这种方式织入切面的。
    类加载期: 切面在目标类加载到JVM时被织入。 这种方式需要特殊的类加载器(ClassLoader) ,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入 (load-timeweaving, LTW) 就支持以这种方式织入切面。
    运行期:切面在应用运行的某个时刻被织入。 一般情况下,在织入切面时, AOP容器会为目标对象动 态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。

  1. Spring对AOP的支持
    创建切点来定义切面所织入的连接点是AOP框架的基本功能,而在SpringAOP中,spring提供了4种类型的AOP支持:
  • 基于代理的经典Spring AOP
  • 纯POJO切面
  • @AspectJ注解驱动的切面
  • 注入式AspectJ切面(适用于Spring各版本)
    前三种都是Spring AOP实现的变体, Spring AOP构建在动态代理基础之上, 因此, Spring对AOP的支持局限于方法拦截。

Spring AOP框架的一些关键知识:

  • Spring通知是Java编写的
  • Spring在运行时通知对象
  • Spring只支持方法级别的连接点

二、通过切点来选择连接点


切点指示器

SpringAOP中,execution指示器是我们在编写切点定义时最主要使用的指示器。

  1. 编写切点
    要想编写切点,必须先创建一个“被切”的类,也叫主题:
public interface Performance {
    public void perform();
}

Performance可以代表任何类型的现场表演, 如舞台剧、 电影或音乐会。 假设我们想编写Performance的perform()方法触发的通知。

我们使用execution()指示器选择Performance的perform()方法。 方法表达式以“*”号开始, 表明了我们不关心方法返回值的类型。 然后, 我们指定了全限定类名和方法名。 对于方法参数列表, 我
们使用两个点号(..) 表明切点要选择任意的perform()方法, 无论该方法的入参是什么。


切点表达式

现在假设我们需要配置的切点仅匹配concert包。 在此场景下, 可以使用within()指示器来限制匹配, 如图所示:


within()指示器

因为“&”在XML中有特殊含义, 所以在Spring的XML配置里面描述切点时, 我们可以使用and来代替“&&”。 同样, or和not可以分别用来代替“||”和“!”。

  • 在切点中选择bean
    Spring还引入了一个新的bean()指示器, 它允许我们在切点表达式中使用bean的ID来标识bean。 bean()使用bean ID或bean名称作为参数来限制切点只匹配特定的bean。
    //在执行Performance的perform()方法时应用通知, 但限定bean的ID为woodstock:
    execution(* concert.Performance.perform()) and bean('woodstock')
    //使用非操作为除了特定ID以外的其他bean应用通知:
    execution(* concert.Performance.perform()) and !bean('woodstock')
    

三、使用注解创建切面

  1. 定义切面
//以下是一个切面:
@Aspect
public class Audience {
      //先使用 @Pointcut在performance()方法上添加 @Pointcut注解,其值是一个切点表达式。
  @Pointcut("execution(** aopdemo.Performance.perform(..))")
  public void performance() {}
  //使用环绕通知实现Audience切面
  @Around("performance()")
  public void watchPerformance(ProceedingJoinPoint jp) {
      try {
          System.out.println("观众1");
          //System.out.println("开场前通知");
          //System.out.println("入座");
          jp.proceed();
          System.out.println("观众1鼓掌");
      } catch (Throwable e) {
          System.out.println("发生演出事故了。。。");
      }
  }
}
  • performance()方法的实际内容并不重要, 在这里它实际上应该是空的。 其实该方法本身只是一个标识, 供 @Pointcut注解依附。
  • 环绕通知是最为强大的通知类型。 它能够让你所编写的逻辑将被通知的目标方法完全包装起来。实际上就像在一个通知方法中同时编写前置通知和后置通知。
  1. 启用自动代理功能
  • JavaConfig中启用AspectJ注解的自动代理
@Configuration
@EnableAspectJAutoProxy
public class PerformanceConfig {
    
    @Bean
    public Performance getPer() {
        return new NCYanChu();
    }
    
    @Bean
    public Audience audience() {
        return new Audience();
    }
    
    @Bean
    public Audience2 audience2() {
        return new Audience2();
    }
}
  • 在XML中, 通过Spring的aop命名空间启用AspectJ自动代理


    xml启动aop代理
  1. 处理通知中的参数
//切面所通知的方法有参数
@Pointcut("execution(**aopdemo.Performance.perform(String))&&args(programName)")
public void performance(String programName) {}
@Around("performance(programName)")
public void watchPerformance(ProceedingJoinPoint jp,String programName) {
        try {
            System.out.println("观众2");
            //System.out.println("开场前通知");
            //System.out.println("入座");
            jp.proceed();
            System.out.println("表演者:"+programName);
            System.out.println("观众2鼓掌");
        } catch (Throwable e) {
            System.out.println("发生演出事故了。。。");
        }
    }

4.通过注解引入新功能

@Aspect
public class Audience3 {
    @DeclareParents(value="aopdemo.Performance+",defaultImpl=DefaultEncoreable.class)
    public static Encoreable encoreable;
}

@DeclareParents注解由三部分组成:

  • value属性指定了哪种类型的bean要引入该接口。 在本例中, 也就是所有实现Performance的类型。 (标记符后面的加号表示是Performance的所有子类型, 而不是Performance本身。 )
  • defaultImpl属性指定了为引入功能提供实现的类。 在这里,我们指定的是DefaultEncoreable提供实现。
  • @DeclareParents注解所标注的静态属性指明了要引入了接
    口。 在这里, 我们所引入的是Encoreable接口。

四、在XML中声明切面

如果需要声明切面, 但是又不能为通知类添加注解的时候, 那么就必须转向XML配置了。
优点:Spring的AOP配置元素能够以非侵入性的方式声明切面

AOP配置元素                              用 途
<aop:advisor>                           定义AOP通知器
<aop:after>                             定义AOP后置通知(不管被通知的方法是否执行成功)
<aop:after-returning>                   定义AOP返回通知
<aop:after-throwing>                    定义AOP异常通知
<aop:around>                            定义AOP环绕通知
<aop:aspect>                            定义一个切面
<aop:aspectj-autoproxy>                 启用 @AspectJ注解驱动的切面
<aop:before>                            定义一个AOP前置通知
<aop:config>                            顶层的AOP配置元素。 大多数的<aop:*>元素必须包含在<aop:config>元素内
<aop:declare-parents>                   以透明的方式为被通知的对象引入额外的接口
<aop:pointcut>                          定义一个切点
<?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:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <aop:config>
    <aop:aspect ref="audience">
        <aop:pointcut 
            id="performance" 
            expression="execution(** aopdemo.Performance.perform(..))" />
        <aop:around
             pointcut-ref="performance"
             method="watchPerformance" />
    </aop:aspect>
  </aop:config>
</beans>

五、注入AspectJ切面

切面很可能依赖其他类来完成它们的工作。如果在执行通知时, 切面依赖于一个或多个类,我们可以在切面内部实例化这些协作的对象。 但更好的方式是, 我们可以借助Spring的依赖注入把bean装配进AspectJ切面中。

  1. 定义切面:
  • java类定义
package aopdemo;
@Aspect                                   
public class CriticAspect { //这里也可以使用package aopdemo;
                                           //无@Aspect注解
                                           public aspect CriticAspect{}
    public CriticAspect() {}
    public static CriticAspect aspectOf() {
        return new CriticAspect();
    }
    @Pointcut ("execution(* perform(..))")
    public void performance() {}
    @AfterReturning("performance()")
    public void perFinish() {
        System.out.println(123);
        System.out.println(criticismEngine.getCriticism());
    }
//  Pointcut performance()  "execution(* perform(..))";
    private CriticismEngine criticismEngine;
    public void setCriticismEngine(CriticismEngine criticismEngine) {
        this.criticismEngine = criticismEngine;
    }
    public CriticismEngine getCriticismEngine() {
        return criticismEngine;
    }   
}
  • xml配置定义:
<aop:aspectj-autoproxy />
    <bean id="criticismEngine" class="aopdemo.CriticismEngineImpl">
        <property name="criticismPool">
            <list>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </list>
        </property>
    </bean>
    <bean class="aopdemo.CriticAspect" factory-method="aspectOf">
        <property name="criticismEngine" ref="criticismEngine"></property>
    </bean>
    <bean class="aopdemo.NCYanChu">
    </bean>
 //其中aspectOf方法是CriticAspect类中生成CriticAspect实例的静态工厂方法:
 public static CriticAspect aspectOf() {
    return new CriticAspect();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 本章内容: 面向切面编程的基本原理 通过POJO创建切面 使用@AspectJ注解 为AspectJ切面注入依赖 ...
    谢随安阅读 3,132评论 0 9
  • 在生活中,监控用电量是一个很重要的功能,但并不是大多数家庭重点关注的问题。软件系统的一些功能就像家里的电表一样,这...
    yjaal阅读 569评论 0 3
  • 前几天公司开半年总结会。开到下午的时候我特别困,一直打瞌睡。 看着公司满怀激情与希望的说愿景,谈梦想,表彰先进个人...
    末行阅读 288评论 1 1
  • 在2004年以前中国白领的工资相对于房价有很强的优势,毫不客气的说,工作可以当事业来做,工资的购买力确实很强。 我...
    西瓜like夏天阅读 158评论 0 0