Spring听课笔记(tg)AOP

好文:https://blog.csdn.net/javazejian/article/details/56267036

通过一个实例来理解

1.  需求:实现算术计算器,可以加减乘除,同时记录日志

     

2. 实现方式:

  ① 高度耦合(直接pass)

  ② 自己实现动态代理

  ③ 利用Spring AOP框架

二. 自己实现动态代理

1. 定义接口及实现类:

  -- 接口:ArithmeticCalculator  

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);
    
}

  -- 接口的实现类

public class ArithmeticCalculatorImpl implements ArithmeticCalculator{
    
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("[add] " + i + " + " + j + " = " + result);
        return result;
    }

    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("[sub] " + i + " - " + j + " = " + result);
        return result;
    }

    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("[mul] " + i + " * " + j + " = " + result);
        return result;
    }

    public int div(int i, int j) {
        int result = i / j;
        System.out.println("[div] " + i + " / " + j + " = " + result);
        return result;
    }
}

  -- 返回动态代理类

  关键代码已经标红,利用JDK的Proxy类,加入参数,返回代理类

  try-catch-finally分别对应四种通知

public class ArithmeticCalculatorLoggingProxy {

    private ArithmeticCalculator target;
    
    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
        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: 一般不用proxy中的方法,容易死循环
            //method: 目标类中的方法
            //args: 目标类方法的参数
            public Object invoke(Object proxy, Method method, Object[] args) {
                Object result = null;
                try {
                    System.out.println("这是前置通知...");
                    result = method.invoke(target, args);
                    System.out.println("这是返回通知,方法正常执行时执行...");
                } catch(Exception e) {
                    System.out.println("这是异常通知,方法异常时执行...");
                    e.printStackTrace();
                } finally {
                    System.out.println("这是后置通知,不论是否异常,都会执行");
                }
                return result;
            }
            
        };
        
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
        
        return proxy;
    }
}

  

  -- 调用

public class App 
{
    public static void main( String[] args )
    {
        ArithmeticCalculator target = new ArithmeticCalculatorImpl();
        ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();
        proxy.add(1, 3);
        System.out.println();
        proxy.div(4, 2);
    }
}

  -- 结果(后置通知的执行顺序好像和spring aop不太一样)

    

三  通过Spring AOP + AspectJ注解方式

  -- 配置文件(利用context和aop命名空间)

    <!-- 配置bean自动扫描 -->
    <context:component-scan base-package="com.atguigu.spring_1.aop"></context:component-scan>
    
    <!-- 配置aspectj起作用 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

  

  -- 接口,实现类同前,需要注意,实现类要加到spring容器中

  

  

  -- 日志切面类

   需要注意,用@Component 加入到Spring IOC容器中, 用 @Aspect 让AspectJ自动扫描

@Component
@Aspect
public class LogginAspect {

    /**
     * 定义一个方法,用于声明切入点表达式,一般的,方法中不需要其他代码
     */
    @Pointcut("execution(* com.atguigu.spring_1.aop.ArithmeticCalculator.*(..))")
    public void declareJointPointExpression() {};
    
    /**
     * 前置通知
     */
    @Before("declareJointPointExpression()")
    public void beforeMethod(JoinPoint joinPoint) {
        // JoinPoint:链接点可以访问到方法的具体信息
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("前置通知: method " + methodName + " begin with arguments:" + args +"");
    }
    
    /**
     * 后置通知: 不论是否有异常,都会如期执行
     * 但是无法访问到方法的返回值
     */
    @After("declareJointPointExpression()")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知: method " + methodName + " end");
    }
    
    /**
     * 返回通知:只有正常执行时,才可以执行
     * 能够访问到方法的返回值
     */
    @AfterReturning(value="declareJointPointExpression()",
            returning="result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("返回通知: method " + methodName + " end with result: " + result +"");
    }
    
    /**
     * 异常通知:抛出异常时执行
     */
    @AfterThrowing(value="declareJointPointExpression()",
            throwing="ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("异常通知: method " + methodName + " throw an exception " + ex +"");
    }
}

  -- 调用

public class App 
{
    public static void main( String[] args )
    {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
        ArithmeticCalculator target = ctx.getBean(ArithmeticCalculator.class);
        target.add(1, 3);
        System.out.println();
        target.div(4, 2);
    }
}

  -- 结果

    

四)四种通知的执行顺序

  没有异常:前置通知->目标方法->后置通知->返回通知

  有异常: 前置通知->目标方法->后置通知->异常通知

五)后置通知和返回通知的区别

  -- 后置通知(@After)不能访问到目标方法的结果,而返回通知(@AfterReturning)则可以

六)切面等基本概念的理解

  

  

原文地址:https://www.cnblogs.com/heyboom/p/11354696.html