• Spring4笔记7--AspectJ 对 AOP 的实现


    AspectJ 对 AOP 的实现:

      对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中
      在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式
      AspectJ 中常用的通知有五种类型:  
        (1)前置通知
        (2)后置通知
        (3)环绕通知  
        (4)异常通知
        (5)最终通知  
      其中最终通知是指,无论程序执行是否正常,该通知都会执行。类似于 try..catch 中的finally 代码块。

      AspectJ 的切入点表达式 execution():

        AspectJ 除了提供了六种通知(还有一种引入通知???)外,还定义了专门的表达式用于指定切入点。表达式的原型是:
          execution ( [modifiers-pattern]   访问权限类型
                ret-type-pattern   返回值类型
                [declaring-type-pattern]   全限定性类名
                name-pattern(param-pattern)  方法名(参数名)
                [throws-pattern]   抛出异常类型
               )
        切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

        * 0至多个任意字符

        .. 用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径

        + 用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类

      AspectJ 对于 AOP 的实现有两种方式:  
        (1)注解方式  
        (2)XML 方式

      
      AspectJ 基于注解的 AOP 实现:

        Step1:定义业务接口与实现类:

    1 package com.tongji.service;
    2 
    3 //主业务接口
    4 public interface ISomeService {
    5     public void doSome();
    6     public String doSecond();
    7     public String doThird();
    8 }
     1 package com.tongji.service;
     2 
     3 //目标类
     4 public class SomeServiceImpl implements ISomeService {
     5 
     6     @Override
     7     public void doSome() {
     8         System.out.println("执行doSome()方法");
     9         //System.out.println("执行doSome()方法" + 3/0);
    10     }
    11 
    12     @Override
    13     public String doSecond() {
    14         System.out.println("执行doSecond()方法");
    15         return "China";
    16     }
    17 
    18     @Override
    19     public String doThird() {
    20         System.out.println("执行doThird()方法");
    21         return "abcde";
    22     }
    23 
    24 }

        Step2:定义切面 POJO 类
           该类为一个 POJO 类,将作为切面出现。其中定义了若干普通方法,将作为不同的通知方法。

        Step3:在切面类上添加@Aspect 注解
           在定义的 POJO 类上添加@Aspect 注解,指定当前 POJO 类将作为切面。
        Step4:在 POJO 类的普通方法上添加通知注解
          切面类是用于定义增强代码的,即用于定义增强目标类中目标方法的增强方法。这些增强方法使用不同的“通知”注解,会在不同的时间点完成织入。当然,对于增强代码,还要通过 execution 表达式指定具体应用的目标类与目标方法,即切入点

     1 package com.tongji.aspects;
     2 
     3 import org.aspectj.lang.JoinPoint;
     4 import org.aspectj.lang.ProceedingJoinPoint;
     5 import org.aspectj.lang.annotation.After;
     6 import org.aspectj.lang.annotation.AfterReturning;
     7 import org.aspectj.lang.annotation.AfterThrowing;
     8 import org.aspectj.lang.annotation.Around;
     9 import org.aspectj.lang.annotation.Aspect;
    10 import org.aspectj.lang.annotation.Before;
    11 
    12 @Aspect //表示当前POJO类为切面
    13 public class MyAspect {
    14     
    15     //定义前置通知方法
    16     @Before("execution(* *..service.*.doSome(..))")
    17     public void myBefore() {
    18         System.out.println("执行前置通知方法myBefore()");
    19     }
    20     
    21     @Before("execution(* *..ISomeService.doSecond(..))")
    22     public void myBefore(JoinPoint jp) {
    23         System.out.println("执行前置通知方法myBefore() jp = " + jp);
    24     }
    25     
    26     //定义后置通知方法
    27     @AfterReturning("execution(* *..ISomeService.doSome(..))")
    28     public void myAfterReturning() {
    29         System.out.println("执行后置通知方法myAfterReturning()");
    30     }
    31     
    32     @AfterReturning(value="execution(* *..ISomeService.doSecond(..))", returning="result")
    33     public void myAfterReturning(Object result) {
    34         System.out.println("执行后置通知方法myAfterReturning() result = " + result);
    35     }
    36     
    37     //定义环绕通知方法
    38     @Around("execution(* *..ISomeService.doThird(..))")
    39     public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
    40         System.out.println("执行环绕通知方法myAfter() 目标方法执行之前");
    41         //执行目标方法
    42         Object result = pjp.proceed();
    43         System.out.println("执行环绕通知方法myAfter() 目标方法执行之后");
    44         
    45         if (result != null) {
    46             result = ((String)result).toUpperCase();
    47         }
    48         return result;
    49     }
    50     
    51     //定义异常通知方法
    52     @AfterThrowing("execution(* *..service.*.doSome(..))")
    53     public void myAfterThrowing() {
    54         System.out.println("执行异常通知方法myAfterThrowing()");
    55     }
    56     
    57     @AfterThrowing(value="execution(* *..service.*.doSome(..))", throwing="ex")
    58     public void myAfterThrowing(Exception ex) {
    59         System.out.println("执行异常通知方法myAfterThrowing() ex = " + ex.getMessage());
    60     }
    61     
    62     //定义最终通知方法
    63     @After("execution(* *..service.*.doSome(..))")
    64     public void myAfter() {
    65         System.out.println("执行最终通知方法myAfter()");
    66     }
    67     
    68     @After("mySomePointCut()")
    69     public void myAfter2() {
    70         System.out.println("执行最终通知方法myAfter2()");
    71     }
    72     
    73     //定义切入点方法
    74     @Pointcut("execution(* *..service.*.doSome(..))")
    75     public void mySomePointCut() {
    76     }
    77 }

        Step5:注册目标对象与 POJO 切面类

        Step6:注册 AspectJ 的自动代理
          在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类  +  切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj的自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。 

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4     xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
     5         http://www.springframework.org/schema/beans 
     6         http://www.springframework.org/schema/beans/spring-beans.xsd
     7         http://www.springframework.org/schema/aop 
     8         http://www.springframework.org/schema/aop/spring-aop.xsd"> 
     9 
    10     <!-- 目标对象 -->
    11     <bean id="someService" class="com.tongji.service.SomeServiceImpl"/>
    12     <!-- 切面 -->
    13     <bean id="myAspect" class="com.tongji.aspects.MyAspect"/>
    14     
    15     <!-- AspectJ的自动代理 -->
    16     <aop:aspectj-autoproxy/>
    17 
    18 </beans>

        <aop:aspectj-autoproxy/>的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。其工作原理是,<aop:aspectj-autoproxy/>通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。

        Step7:测试类中使用目标对象的 id

     1 package com.tongji.test;
     2 
     3 import org.junit.Test;
     4 import org.springframework.context.ApplicationContext;
     5 import org.springframework.context.support.ClassPathXmlApplicationContext;
     6 
     7 import com.tongji.service.ISomeService;
     8 
     9 public class MyTest {
    10     
    11     @Test
    12     public void test01() {
    13         String resource = "applicationContext.xml";
    14         @SuppressWarnings("resource")
    15         ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
    16         ISomeService service = (ISomeService) ac.getBean("someService");
    17         service.doSome();
    18         System.out.println("---------------------");
    19         service.doSecond();
    20         System.out.println("---------------------");
    21         String result = service.doThird();
    22         System.out.println(result);
    23     }
    24     
    25 } 

        

        关于切面类的补充:

          (1)@Before 前置通知-增强方法有 JoinPoint 参数
            在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。

          (2)@AfterReturning 后置通知-注解有 returning 属性
            在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

          (3)@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数
            在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

          (4)@AfterThrowing 异常通知-注解中有 throwing 属性
            在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。 

          (5)@After 最终通知
            无论目标方法是否抛出异常,该增强均会被执行。 

          (6)@Pointcut 定义切入点
            当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。  
            其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcute注解的方法一般使用 private 的标识方法,即没有实际作用的方法

      

      AspectJ基于XML的AOP实现(这个方式是Spring实现AOP的最常用的方式):

        AspectJ 除了提供了基于注解的 AOP 的实现外,还提供了以 XML 方式的实现。切面就是一个 POJO 类,而用于增强的方法就是普通的方法。通过配置文件,将切面中的功能增强织入到了目标类的目标方法中。

          Step1:定义业务接口与实现类(同上)

          Step2:定义切面 POJO 类(没有注解)

          Step3:注册目标对象与 POJO 切面类

          Step4:在容器中定义 AOP 配置

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4     xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
     5         http://www.springframework.org/schema/beans 
     6         http://www.springframework.org/schema/beans/spring-beans.xsd
     7         http://www.springframework.org/schema/aop 
     8         http://www.springframework.org/schema/aop/spring-aop.xsd"> 
     9 
    10     <!-- 目标对象 -->
    11     <bean id="someService" class="com.tongji.service.SomeServiceImpl"/>
    12     <!-- 切面 -->
    13     <bean id="myAspect" class="com.tongji.aspects.MyAspect"/>
    14     
    15     <!-- Aop配置 -->
    16     <aop:config>
    17         <aop:pointcut expression="execution(* *..service.*.doSome(..))" id="doSomePointCut"/>
    18         <aop:pointcut expression="execution(* *..service.*.doSecond(..))" id="doSecondPointCut"/>
    19         <aop:pointcut expression="execution(* *..service.*.doThird(..))" id="doThirdPointCut"/>
    20         <aop:aspect ref="myAspect">
    21             <aop:before method="myBefore" pointcut-ref="doSomePointCut"/>
    22             <aop:before method="myBefore(org.aspectj.lang.JoinPoint)" pointcut-ref="doSomePointCut"/>
    23             <aop:after-returning method="myAfterReturning" pointcut-ref="doSecondPointCut"/>
    24             <aop:after-returning method="myAfterReturning(java.lang.Object)" pointcut-ref="doSecondPointCut" returning="result"/>
    25             <aop:around method="myAround" pointcut-ref="doSecondPointCut"/>
    26             <aop:after-throwing method="myAfterThrowing" pointcut-ref="doSomePointCut"/>
    27             <aop:after-throwing method="myAfterThrowing(java.lang.Exception)" pointcut-ref="doSomePointCut" throwing="ex"/>
    28             <aop:after method="myAfter" pointcut-ref="doSomePointCut"/>
    29         </aop:aspect>
    30     </aop:config>
    31 </beans>

          配置文件中,除了要定义目标类与切面的 Bean 外,最主要的是在<aop:config/>中进行aop 的配置。而该标签的底层,会根据其子标签的配置,生成自动代理。
          通过其子标签<aop:pointcut/>定义切入点,该标签有两个属性,id 与 expression。分别用于指定该切入点的名称及切入点的值。expression 的值为 execution 表达式。

          通过子标签<aop:aspect/>定义具体的织入规则:根据不同的通知类型,确定不同的织入时间;将 method 指定的增强方法,按照指定织入时间,织入到切入点指定的目标方法中。  
            <aop:aspect/>的 ref 属性用于指定使用哪个切面类。  
            <aop:aspect/>的子标签是各种不同的通知类型。不同的通知所包含的属性是不同的,但也有共同的属性。  
              method:指定该通知使用的切面中的增强方法。  
              pointcut-ref:指定该通知要应用的切入点。

          AspectJ 的 6 种通知的 XML 标签如下:  
            <aop:before/>:前置通知,方法的参数类型必须是全限定类名。
            <aop:after-returning/>:  后置通知,有一个属性 returning,指定用于接收目标方法的返回值所使用的变量名
            <aop:around/>:环绕通知  
            <aop:after-throwing/>:异常通知,有一个属性 throwing,指定用于接收目标方法所抛出异常的变量名。
            <aop:after/>:最终通知
            <aop:declare-parents/>:引入通知???

         Step5:测试类中使用目标对象的 id(同上)

  • 相关阅读:
    【火炉炼AI】机器学习028-五分钟教你打造机器学习流水线
    【火炉炼AI】机器学习027-项目案例:用聚类算法建立客户细分模型
    【火炉炼AI】机器学习026-股票数据聚类分析-近邻传播算法
    【火炉炼AI】机器学习024-无监督学习模型的性能评估--轮廓系数
    【火炉炼AI】机器学习025-自动估算集群数量-DBSCAN算法
    【火炉炼AI】机器学习023-使用层次聚类算法构建模型
    【火炉炼AI】机器学习022-使用均值漂移聚类算法构建模型
    【火炉炼AI】机器学习021-使用K-means进行图片的矢量量化操作
    A Secret hdu 6153
    Fleet of the Eternal Throne HDU6138
  • 原文地址:https://www.cnblogs.com/qjjazry/p/6364743.html
Copyright © 2020-2023  润新知