AOP 面向切面的编程

  一、面向切面的编程需求的产生

    1. 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
    2. 代码分散: 以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。

  二、实现面向切面的编程

    1. 将需要实现AOP的类注入到Spring容器中,例如:
       1 package com.neuedu.aop;
       2 
       3 import org.springframework.stereotype.Component;
       4 
       5 @Component
       6 public class RawCaculator implements MathCaculator{
       7 
       8     @Override
       9     public int add(int i, int j) {
      10         int rs=i+j;
      11         System.out.println(i+"+"+j+"="+rs);
      12         return rs;
      13     }
      14 
      15     @Override
      16     public int sub(int i, int j) {
      17         int rs=i-j;
      18         System.out.println(i+"-"+j+"="+rs);
      19         return rs;
      20     }
      21 
      22     @Override
      23     public int mul(int i, int j) {
      24         int rs=i*j;
      25         System.out.println(i+"*"+j+"="+rs);
      26         return rs;
      27     }
      28 
      29     @Override
      30     public int div(int i, int j) {
      31         int rs=i/j;
      32         System.out.println(i+"/"+j+"="+rs);
      33         return rs;
      34     }
      35 
      36 }
      要实现AOP的计算方法类

       

    2. 实现切面类
      1. 使用前置通知、后置通知、返回通知、异常通知实现切面类,注入到Spring容器中
         1 package com.neuedu.aop;
         2 
         3 import static org.hamcrest.CoreMatchers.nullValue;
         4 
         5 import java.util.Arrays;
         6 import java.util.List;
         7 
         8 import org.aspectj.lang.JoinPoint;
         9 import org.aspectj.lang.ProceedingJoinPoint;
        10 import org.aspectj.lang.Signature;
        11 import org.aspectj.lang.annotation.After;
        12 import org.aspectj.lang.annotation.AfterReturning;
        13 import org.aspectj.lang.annotation.AfterThrowing;
        14 import org.aspectj.lang.annotation.Around;
        15 import org.aspectj.lang.annotation.Aspect;
        16 import org.aspectj.lang.annotation.Before;
        17 import org.springframework.core.annotation.Order;
        18 import org.springframework.stereotype.Component;
        19 
        20 @Component
        21 //@Aspect表明当前类是一个切面类
        22 @Aspect
        23 //@Order表示切面执行顺序,value值越小优先级越高
        24 @Order(value=50)
        25 public class CaculatorAspect {
        26         @Before(value = "execution(public int com.neuedu.aop.RawCaculator.*(int, int))")
        27         public void showBeginLog(JoinPoint point){
        28             //System.out.println("【日志】【前置通知】");
        29             //获得参数列表:
        30             Object[] args = point.getArgs();
        31             List<Object> asList = Arrays.asList(args);
        32             //获得方法名:
        33             Signature signature = point.getSignature();
        34             String name = signature.getName();
        35             System.out.println("【日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);
        36         }
        37         @AfterThrowing(value = "execution(public int com.neuedu.aop.RawCaculator.*(..))" ,throwing="ex")
        38         public void showThrowing(JoinPoint point,Exception ex){
        39             //System.out.println("【日志】【异常通知】");
        40             System.out.println("【日志】【异常通知】 异常信息:"+ex);
        41         }
        42         @After(value = "execution(public int com.neuedu.aop.RawCaculator.*(..))")
        43         public void showAfter(){
        44             System.out.println("【日志】【后置通知】");
        45         }
        46         @AfterReturning(value = "execution(* * .*(..))",returning="result")
        47         public void showAfterReturning(JoinPoint point,Object result){
        48             //System.out.println("【日志】【返回通知】");
        49             System.out.println("【日志】【返回通知】 目标方法的返回值为"+result);
        50         }
        51     }

         

 

           使用黄色背景标注的代码是声明的通知,其中包括通知类型切入点表达式,以下就是对通知类型的介绍,切入点表达式在最后

            @Before  前置通知:在方法执行之前执行的通知

            @After 后置通知:后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候

            @AfterReturning   返回通知:

          • 无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。
          • 在返回通知中访问连接点的返回值:
            • 在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称
            • 必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值
            • 原始的切点表达式需要出现在pointcut属性中 

            @AfterThrowing 异常通知:只在连接点抛出异常时才执行异常通知.:

        • 将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。
            • 如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行

           

          2.使用环绕通知实现切面类,注入到Spring容器中

 1   package com.neuedu.aop;
 2 
 3   import java.util.Arrays;
 4   import java.util.List;
 5 
 6   import org.aspectj.lang.ProceedingJoinPoint;
 7   import org.aspectj.lang.Signature;
 8   import org.aspectj.lang.annotation.Around;
 9   import org.aspectj.lang.annotation.Aspect;
10   import org.springframework.core.annotation.Order;
11   import org.springframework.stereotype.Component;
12 
13   @Component
14   //@Aspect表明当前类是一个切面类
15   @Aspect
16   @Order(value=40)
17     public class secondAspect {
18       @Around(value = "execution(* * .*(..))")
19       public Object Around(ProceedingJoinPoint point){
20           Object result=null;
21           Object[] args = point.getArgs();
22           List<Object> asList = Arrays.asList(args);
23           Signature signature = point.getSignature();
24           String name = signature.getName();
25           try{
26               System.out.println("【事物日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);
27               try {
28                   result=point.proceed(args);
29                 } finally{
30                     System.out.println("【事物日志】【后置通知】");
31                 }    
32               System.out.println("【事物日志】【返回通知】 目标方法的返回值为"+result);    
33           }catch (Throwable e) {
34               System.out.println("【事物日志】【异常通知】 异常信息:"+e);
35           }
36           return result;
37       }
38   }

 

            @Around  环绕通知:

          • 环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
          • 对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。

          • 在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。

          • 注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。

     

       3.实现测试类

 

package junit.test;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.neuedu.aop.MathCaculator;
import com.neuedu.aop.RawCaculator;

public class TestCaculator {

    @Test
    public void test() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        //MathCaculator bean = ioc.getBean(RawCaculator.class);   不可用 (代表类 类型不一样)
        //rawCaculator即使使用注解时代表类的id;又是xml配置文件里的id
        MathCaculator bean = (MathCaculator) ioc.getBean("rawCaculator");
        bean.add(10, 5);
        System.out.println();
        bean.sub(14, 5);
        System.out.println();
        bean.mul(8, 7);
        System.out.println();
        bean.div(10, 0);
    }

}
测试类

 

  

  三、关于切入点表达式

    1.作用:

      通过表达式的方式定位一个或多个具体的连接点。

    2.语法细节:

      1)切入点表达式的语法格式:execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

      2)举例说明:

          

表达式

execution(* com.atguigu.spring.ArithmeticCalculator.*(..))

含义

ArithmeticCalculator接口中声明的所有方法。

第一个“*”代表任意修饰符及任意返回值。

第二个“*”代表任意方法。

“..”匹配任意数量、任意类型的参数。

若目标类、接口与该切面类在同一个包中可以省略包名。

 

表达式

execution(public * ArithmeticCalculator.*(..))

含义

ArithmeticCalculator接口的所有公有方法

 

表达式

execution(public double ArithmeticCalculator.*(..))

含义

ArithmeticCalculator接口中返回double类型数值的方法

 

表达式

execution(public double ArithmeticCalculator.*(double, ..))

含义

第一个参数为double类型的方法。

“..” 匹配任意数量、任意类型的参数。

 

表达式

execution(public double ArithmeticCalculator.*(double, double))

含义

参数类型为double,double类型的方法

    3.重用切入点:

      1)

      • 在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现

      • 在Aspect切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的。 
      • 切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。  

      2)示例代码:

 1   @Component
 2   //@Aspect表明当前类是一个切面类
 3   @Aspect
 4   //@Order表示切面执行顺序,value值越小优先级越高
 5   @Order(value=50)
 6   public class CaculatorAspect {
 7           //重用切入点
 8           @Pointcut("execution(* * .*(..))")
 9           private void LoggingOperation(){
10               
11           }
12           @Before(value = "LoggingOperation()")
13           public void showBeginLog(JoinPoint point){
14               //获得参数列表:
15               Object[] args = point.getArgs();
16               List<Object> asList = Arrays.asList(args);
17               //获得方法名:
18               Signature signature = point.getSignature();
19               String name = signature.getName();
20               System.out.println("【日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);
21           }
22           @AfterThrowing(value = "LoggingOperation()" ,throwing="ex")
23           public void showThrowing(JoinPoint point,Exception ex){
24               System.out.println("【日志】【异常通知】 异常信息:"+ex);
25           }
26           @After(value = "LoggingOperation()")
27           public void showAfter(){
28               System.out.println("【日志】【后置通知】");
29           }
30           @AfterReturning(value = "LoggingOperation()",returning="result")
31           public void showAfterReturning(JoinPoint point,Object result){
32               System.out.println("【日志】【返回通知】 目标方法的返回值为"+result);
33           }
34       }

 

 

 



posted @ 2017-08-29 20:59  戏路很宽丶  阅读(723)  评论(2编辑  收藏  举报