Spring_AOP_AspectJ支持的通知注解

1.AOP前奏: 使用动态代理解决日志需求

ArithmeticCalculator.java

package com.aff.spring.aop.helloworld;
public interface ArithmeticCalculator {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}

ArithmeticCalculatorImpl.java

package com.aff.spring.aop.helloworld;
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}

ArithmeticCalculatorLoggingProxy.java

package com.aff.spring.aop.helloworld;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ArithmeticCalculatorLoggingProxy {
    //要代理的对象
    private ArithmeticCalculator target;
    
    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
        super();
        this.target = target;
    }

    //返回代理对象
    public ArithmeticCalculator getLoggingProxy(){
        ArithmeticCalculator proxy = null;
        
        ClassLoader loader = target.getClass().getClassLoader();
        Class [] interfaces = new Class[]{ArithmeticCalculator.class};
        InvocationHandler h = new InvocationHandler() {
            /**
             * proxy: 代理对象。 一般不使用该对象
             * method: 正在被调用的方法
             * args: 调用方法传入的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                String methodName = method.getName();
                //打印日志
                System.out.println("[before] The method " + methodName + " begins with " + Arrays.asList(args));
                
                //调用目标方法
                Object result = null;
                
                try {
                    //前置通知
                    result = method.invoke(target, args);
                    //返回通知, 可以访问到方法的返回值
                } catch (NullPointerException e) {
                    e.printStackTrace();
                    //异常通知, 可以访问到方法出现的异常
                }
                
                //后置通知. 因为方法可以能会出异常, 所以访问不到方法的返回值
                
                //打印日志
                System.out.println("[after] The method ends with " + result);
                
                return result;
            }
        };
        
        /**
         * loader: 代理对象使用的类加载器。 
         * interfaces: 指定代理对象的类型. 即代理代理对象中可以有哪些方法. 
         * h: 当具体调用代理对象的方法时, 应该如何进行响应, 实际上就是调用 InvocationHandler 的 invoke 方法
         */
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
        
        return proxy;
    }
}

Main.java

package com.aff.spring.aop.helloworld;

public class Main {
    public static void main(String[] args) {

        ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorImpl();

        arithmeticCalculator = new ArithmeticCalculatorLoggingProxy(arithmeticCalculator).getLoggingProxy();

        int result = arithmeticCalculator.add(11, 12);
        System.out.println("result:" + result);

        result = arithmeticCalculator.div(21, 3);
        System.out.println("result:" + result);

    }
}

结果如下

[before] The method add begins with [11, 12]
[after] The method ends with 23
result:23
[before] The method div begins with [21, 3]
[after] The method ends with 7
result:7

2.AOP简介

①AOP 的好处:
    每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
    业务模块更简洁, 只包含核心业务代码

②.AOP术语

切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice): 切面必须要完成的工作
目标(Target): 被通知的对象
代理(Proxy): 向目标对象应用通知之后创建的对象
连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。

连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice): 切面必须要完成的工作
目标(Target): 被通知的对象
代理(Proxy): 向目标对象应用通知之后创建的对象
连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

③用 AspectJ 注解声明切面

要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.
在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.
通知是标注有某种注解的简单的 Java 方法.
AspectJ 支持 5 种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行

LoggingAspect.java

package com.aff.spring.aop.imlp;

import java.util.Arrays;
import java.util.List;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

//把这个类声明为切面,需要把这个类放到IOC容器中, 再声明为一个切面
@Aspect
@Component
public class LoggingAspect {
    //声明该方法是一个前置通知, 在目标方法开始之前执行
    @Before("execution(* com.aff.spring.aop.imlp.*.*(int , int ))")
    public void  beforeMethod(JoinPoint joinPoint){
        String methodName =  joinPoint.getSignature().getName();
        List<Object> args =  Arrays.asList(joinPoint.getArgs());
        System.out.println("The method " +methodName+"begins  with"+args);
        
    }
    //后置通知:在目标方法执行后(无论 是否发生异常) ,执行的通知
    //在后置通知中还不能访问目标方法执行的结果
    //第一个*:任意返回值类型;   第二个*:包下的任意类;  第三个*:任意方法; .. : 任意参数
    @After("execution(* com.aff.spring.aop.imlp.*.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName =  joinPoint.getSignature().getName();
        System.out.println("The method " +methodName+"ends ");
    }
    
    
    /**
    *  返回通知
     * 在方法正常结束后执行的代码
     * 返回通知是可以访问到方法的返回值
     * @param joinPoint
     */
    @AfterReturning(value="execution(* com.aff.spring.aop.imlp.*.*(..))",returning="result")
    public  void  afterReturning(JoinPoint joinPoint,Object result ){
        String methodName =  joinPoint.getSignature().getName();
        System.out.println("The method " +methodName+"ends with "+result);
    }
    
    /**
     * 异常通知
     * 在目标方法出现异常时 会执行的代码
     * 可以访问到异常对象,且可以指定出现特定异常时在执行通知代码
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value="execution(* com.aff.spring.aop.imlp.*.*(..))",throwing="e")
    public void  afterThrowing(JoinPoint joinPoint,Exception e){
        String methodName =  joinPoint.getSignature().getName();
        System.out.println("The method " +methodName+"ocurs  exception :"+e);
    }        
    
    /**
     * 转绕通知需要携带 ProceedingJoinPoint 类型的参数
     * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法
     * 且环绕通知必须有返回值
     * @param pjd
     */
    @Around("execution(* com.aff.spring.aop.imlp.*.*(..))")
    public  Object  aroundMethod(ProceedingJoinPoint pjd){
        Object result =null;
        String methodName=   pjd.getSignature().getName();
        //执行目标方法
        try {
            //前置通知
            System.out.println("The method " +methodName+"begins  with"+Arrays.asList(pjd.getArgs()));
            
            //执行目标方法
            result = pjd.proceed();
            
            //返回通知
            System.out.println("The method "+methodName +"end with"+result);
        } catch (Throwable e) {
            //异常通知
            System.out.println("The method"+methodName+" ocurs  exception :"+e);
            throw new RuntimeException(e);
        }
        System.out.println("The method "+methodName +"ends");
        return result;
    }
    
}

Main

package com.aff.spring.aop.imlp;

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

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);
        
        int result = arithmeticCalculator.add(2, 3);
        System.out.println("result:"+result);
        
        int result2 = arithmeticCalculator.div(2, 0);
        System.out.println("result2:"+result2);
        
    }
}

效果如下

The method addbegins  with[2, 3]
The method addends 
The method addends with 5
result:5
The method divbegins  with[2, 1]
The method divends 
The method divends with 2
result2:2

 目录

3.@Order()注解指定切面的优先级,值越小优先级越高

   如:@Order(1)高于@Order(2)

4.重用切点表达式

//把这个类声明为切面,需要把这个类放到IOC容器中, 再声明为一个切面
@Aspect
@Component
public class LoggingAspect {
    
    /**
     * 定义一个方法, 用于声明切入点表达式,一般,该方法中不再需要添加其他代码
     * 
     * @Pointut 声明切点表达式
     *    后面的其他通知 直接使用方法名来引用当前的切点表达式 
     */
    @Pointcut("execution(* com.aff.spring.aop.imlp.*.*(int , int ))")
    public void  declareJointPointExpression(){
        
    }
    
    //声明该方法是一个前置通知, 在目标方法开始之前执行
    @Before("declareJointPointExpression()")
    public void  beforeMethod(JoinPoint joinPoint){
        String methodName =  joinPoint.getSignature().getName();
        List<Object> args =  Arrays.asList(joinPoint.getArgs());
        System.out.println("The method " +methodName+"begins  with"+args);
    }
}
All that work will definitely pay off
原文地址:https://www.cnblogs.com/afangfang/p/12986741.html