zoukankan      html  css  js  c++  java
  • 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)则可以

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

      

      

  • 相关阅读:
    Java线上应用故障排查之一:高CPU占用【转】
    JAVA 之 Tomcat知识框架【转】
    FileZilla 配置备份与还原【转】
    linux删除第几天日志【原创】
    sar命令使用【转】
    Mysql Group Replication 简介及单主模式组复制配置【转】
    grep 中的正则表达式【转】
    java程序out of memory【转】
    Java笔记18:JUnit单元测试
    Java笔记17:导出可执行jar包
  • 原文地址:https://www.cnblogs.com/heyboom/p/11354696.html
Copyright © 2011-2022 走看看