zoukankan      html  css  js  c++  java
  • Spring4 AOP详解

    Spring4 AOP详解

    第一章Spring 快速入门并没有对Spring4 的 AOP 做太多的描述,是因为AOP切面编程概念不好理解。所以这章主要从三个方面详解AOP:AOP简介(了解),基于注解的AOP编程(重点)和基于xml的AOP编程。


    AOP简介

    什么是AOP

    AOP(Aspect Oriented Programming)面向切面编程,是对传统的OOP(ObjectOriented Programming)面向对象编程的补充。

    AOP的作用

    如果A,B,C三个方法都要在执行前做验证操作,执行后做日志打印操作。肿么办?

    排版好丑。。。。。。

    AOP专业术语

    ** 切面(Aspect) ** : A,B,C,方法执行前都要调用的验证逻辑和执行后都要调用的日志逻辑,这两层业务逻辑就是切面。
    ** 通知(Advice) ** : 有五种通知,执行前,执行后,执行成功后,执行抛出异常后,环绕通知。就是切面执行的方法。
    ** 目标(Target) ** : 被通知的对象,这里就是A,B,C三个方法。
    ** 连接点(Joinpoint) **:连接点是一个应用执行过程中能够插入一个切面的点。
    ** 切点(pointcut) **:每个类都拥有多个连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点
    打个比方:一天,三位侠客(被通知的对象Target)来我府上做客,被大门(切面Aspect)拦住,门前有五个保安(负责通知的Advice),因为其中一位侠客会降龙十八掌(满足被通知的一个条件Joinpoint),其中一位保安告知他:"你可以进去了"。另外两个侠客因为武艺超群(满足被通知的统一标准poincut)也都进去了。


    基于注解的AOP编程

    基于注解的编程,需要依赖AspectJ框架(java中最流行的aop框架)。
    第一步:导入AspectJ的jar包,该框架只有Spring 2.0以上才支持。

    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-aspects</artifactId>  
        <version>4.2.2.RELEASE</version>  
    </dependency>
    

    第二步:核心文件applicationContext.xml,里面需要配置自动扫描包(用于IOC注解)和配置启用AspectJ注解

    <?xml version="1.0" encoding="UTF-8"?>  
    <beans xmlns="http://www.springframework.org/schema/beans"  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
        xmlns:aop="http://www.springframework.org/schema/aop"  
        xmlns:context="http://www.springframework.org/schema/context"  
        xsi:schemaLocation="  
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd  
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">  
      
        <!-- 自动扫描的包 -->  
        <context:component-scan base-package="com.itdragon.spring.*" ></context:component-scan>  
      
        <!-- 使 AspectJ 的注解起作用 -->  
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  
          
    </beans>  
    

    第三步:切面类,该类有什么特点?首先它必须是IOC的bean,还要声明它是AspectJ切面,最后还可以定义切面的优先级Order(非必填)
    通知有五种注解
    ** @Before ** :前置通知的注解,在目标方法执行前调用
    ** @After **:后置通知的注解, 在目标方法执行后调用,即使程序抛出异常都会调用
    ** @AfterReturning **:返回通知的注解, 在目标方法成功执行后调用,如果程序出错则不会调用
    ** @AfterThrowing **:异常通知的注解, 在目标方法出现指定异常时调用
    ** @Around **:环绕通知的注解,很强大(相当于前四个通知的组合),但用的不多,
    还有为了简化开发的重用切入点@Pointcut,以及抽象表达"*"

    public interface Calculator {  
          
        public int add(int a, int b);  
        public int division(int a, int b);  
      
    }  
    
    import org.springframework.stereotype.Repository;  
    @Repository("calculator")  
    public class CalculatorImp implements Calculator {  
      
        @Override  
        public int add(int a, int b) {  
            System.out.println("add 方法执行了 ----> " + (a + b));  
            return (a + b);  
        }  
      
        @Override  
        public int division(int a, int b) {  
            System.out.println("division 方法执行了 ----> " + (a / b));  
            return (a / b);  
        }  
      
    }  
    
    import java.util.Arrays;  
    import java.util.List;  
    import org.aspectj.lang.JoinPoint;  
    import org.aspectj.lang.annotation.After;  
    import org.aspectj.lang.annotation.AfterReturning;  
    import org.aspectj.lang.annotation.AfterThrowing;  
    import org.aspectj.lang.annotation.Aspect;  
    import org.aspectj.lang.annotation.Before;  
    import org.aspectj.lang.annotation.Pointcut;  
    import org.springframework.core.annotation.Order;  
    import org.springframework.stereotype.Component;  
      
    /** 
     * @Order(n) : 切面的优先级,n越小,级别越高 
     * @Aspect:声明该类是一个切面 
     * @Component:切面必须是 IOC 中的 bean 
     */  
    @Order(2)  
    @Aspect  
    @Component  
    public class LoggerAspect {  
          
        /** 
         * 前置通知的注解,在目标方法执行前调用 
         * execution最基础的表达式语法。 
         * 注意点: 
         * 1. 方法里面不能有行参,及add(int a, int b) 这是会报错的。 
         * 2. int(方法的返回值),add(方法名) 可以用 * 抽象化。甚至可以将类名抽象,指定该包下的类。 
         * 3. (int, int) 可以用(..)代替,表示匹配任意数量的参数 
         * 4. 被通知的对象(Target),建议加上包的路径 
         */  
        @Before("execution(int com.atguigu.spring.my.aop.CalculatorImp.add(int , int))")  
        public void beforeAdvice(JoinPoint joinPoint) {  
            /** 
             * 连接点 joinPoint:add方法就是连接点 
             * getName获取的是方法名,是英文的,可以通过国际化转换对应的中文比较好。 
             */  
            String methodName = joinPoint.getSignature().getName();   
            List<Object> args = Arrays.asList(joinPoint.getArgs());  
            System.out.println("@Before 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
        }  
          
        /** 
         * 后置通知的注解, 在目标方法执行后调用,即使是程序出错都会调用 
         * 这里将 方法的返回值 和 CalculatorImp类下所有的方法,以及方法的形参 都抽象了 
         */  
        @After("execution(* com.atguigu.spring.my.aop.CalculatorImp.*(..))")  
        public void afterAdvice(JoinPoint joinPoint) {  
            String methodName = joinPoint.getSignature().getName();   
            List<Object> args = Arrays.asList(joinPoint.getArgs());  
            System.out.println("@After 后置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
        }  
          
        /** 
         * 重用切入点定义:声明切入点表达式。该方法里面不建议添加其他代码 
         */  
        @Pointcut("execution(* com.atguigu.spring.my.aop.CalculatorImp.*(..))")  
        public void declareExecutionExpression(){}  
          
        /** 
         * 返回通知的注解, 在目标方法成功执行后调用,如果程序出错则不会调用 
         * returning="result" 和 形参 result 保持一致 ,获取函数的返回值
         */  
        @AfterReturning(value="declareExecutionExpression()", returning="result")  
        public void afterRunningAdvice(JoinPoint joinPoint, Object result) {  
            String methodName = joinPoint.getSignature().getName();   
            List<Object> args = Arrays.asList(joinPoint.getArgs());  
            System.out.println("@AfterReturning 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);  
        }  
          
        /** 
         * 异常通知的注解, 在目标方法出现指定异常时调用 
         * throwing="exception" 和 形参 exception 保持一致 , 且目标方法出了Exception(可以是其他异常)异常才会调用。 
         */  
        @AfterThrowing(value="declareExecutionExpression()", throwing="exception")  
        public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {  
            String methodName = joinPoint.getSignature().getName();   
            System.out.println("@AfterThrowing 异常通知 : 方法名 【 " + methodName + " 】and  exception is " + exception);  
        } 
    /**
     * 公用多个切面
     */ 
      @AfterReturning("execution(* com.xxx.xx.baserver.operation.service.WorkOrderService.createWorkOrder(..))" +
                " || execution(* com.xxx.xx.baserver.operation.service.WorkOrderService.updateWorkOrder(..))")
    } 
    
    import java.util.Arrays;  
    import java.util.List;  
    import org.aspectj.lang.ProceedingJoinPoint;  
    import org.aspectj.lang.annotation.Around;  
    import org.aspectj.lang.annotation.Aspect;  
    import org.springframework.core.annotation.Order;  
    import org.springframework.stereotype.Component;  
      
    @Order(1)  
    @Aspect  
    @Component  
    public class AroundAspect {  
      
        /** 
         * 环绕通知,很强大,但用的不多。 用环绕通知测试Order的优先级看的不明显(这里是笔者的失误) 
         * 环绕通知需要用ProceedingJoinPoint 类型的参数 
         */  
        @Around("execution(* com.atguigu.spring.my.aop.CalculatorImp.*(..))")  
        public Object aroundAdvice(ProceedingJoinPoint joinPoint) {  
            Object result = null;  
            String methodName = joinPoint.getSignature().getName();   
            List<Object> args = Arrays.asList(joinPoint.getArgs());  
            try {  
                System.out.println("@Around 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
                result = joinPoint.proceed();  
                System.out.println("@Around 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);  
            } catch (Throwable e) {  
                e.printStackTrace();  
                System.out.println("@Around 异常通知 : 方法名 【 " + methodName + " 】and  exception is " + e);  
            }  
            System.out.println("@Around 后置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
              
            return result;  
        }  
          
    }  
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;  
    public class Main {  
          
        public static void main(String[] args) {  
              
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
            Calculator calculator = (Calculator) ctx.getBean("calculator");  
              
            calculator.add(11, 12);  
            calculator.division(21, 3); // 测试时,将被除数换成0,可以测试@AfterReturning , @After 和 @AfterThrowing  
              
            ctx.close();  
        }  
      
    } 
    

    第四步:执行看结果。这里没有做环绕通知的打印。将被除数设置为零,可以测试 返回通知,后置通知 和 异常通知。

    @Before 前置通知 : 方法名 【 add 】and args are [11, 12]  
    add 方法执行了 ----> 23  
    @After 后置通知 : 方法名 【 add 】and args are [11, 12]  
    @AfterReturning 返回通知 : 方法名 【 add 】and args are [11, 12] , result is 23  
    division 方法执行了 ----> 7  
    @After 后置通知 : 方法名 【 division 】and args are [21, 3]  
    @AfterReturning 返回通知 : 方法名 【 division 】and args are [21, 3] , result is 7  
    

    很简单对吧,用到的注解其实并不是很多。
    以上代码有一个不足之处,就是测试Order优先级的时候,效果不明显。AroundAspect的优先级高于LoggerAspect,从打印的日志中发现,只有AroundAspect的前置通知在LoggerAspect前面打印,其他通知均在后面。
    因为博客和课堂不同,如果把每个知识点都单独写出来,篇幅可能太长。笔者尽可能将所有知识点都写在一起,学A知识的同时将B,C,D的知识一起学习。但难免会有一些不听话的知识点。所以请各位读者见谅。


    基于xml的AOP编程

    上一篇文章讲到了基于xml的IOC设置bean,篇幅较长,内容较复杂。但配置AOP不同,它简单了很多。
    第一步:核心文件applicationContext.xml,
    首先是配置三个bean,方便是两个切面类,和一个方法类。
    然后配置AOP,
    aop:config:注明开始配置AOP了,
    aop:pointcut:配置切点重用表达式,expression的值是具体的表达式,id 该aop:pointcut的唯一标识,
    aop:aspect:配置切面,ref的值引用相关切面类的bean,order设置优先级(也可以不设置)。
    五种通知的配置:aop:before,aop:after,aop:after-returning,aop:after-throwing,aop:around。method的值就是对应的方法,poincut-ref的值要引用 aop:pointcut 的id。其中有两个比较特殊:aop:after-returning 要多配置一个returning,其中returning的值要和对应方法的形参保持一致。同理aop:after-throwing 也要多配置一个throwing,其中throwing的值也要和对应方法的形参保持一致。不然执行程序会报错。

    <?xml version="1.0" encoding="UTF-8"?>  
    <beans xmlns="http://www.springframework.org/schema/beans"  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
        xmlns:aop="http://www.springframework.org/schema/aop"  
        xmlns:context="http://www.springframework.org/schema/context"  
        xsi:schemaLocation="  
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd  
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">  
      
        <bean id="calculator" class="com.atguigu.spring.my.xml.CalculatorImp"></bean>  
        <bean id="loggerAspect" class="com.atguigu.spring.my.xml.LoggerAspect"></bean>  
        <bean id="aroundAspect" class="com.atguigu.spring.my.xml.AroundAspect"></bean>  
          
        <!-- AOP配置 -->  
        <aop:config>  
            <!-- 配置切点表达式 类似注解的重用表达式-->  
            <aop:pointcut expression="execution(* com.atguigu.spring.my.xml.CalculatorImp.*(..))"   
                id="pointcut"/>  
            <!-- 配置切面及通知  method的值就是 loggerAspect类中的值-->  
            <aop:aspect ref="loggerAspect" order="2">  
                <aop:before method="beforeAdvice" pointcut-ref="pointcut"/>  
                <aop:after method="afterAdvice" pointcut-ref="pointcut"/>  
                <aop:after-returning method="afterRunningAdvice" pointcut-ref="pointcut" returning="result"/>  
                <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pointcut" throwing="exception"/>  
            </aop:aspect>  
            <aop:aspect ref="aroundAspect" order="1">  
                <!-- <aop:around method="aroundAdvice" pointcut-ref="pointcut"/>  -->  
            </aop:aspect>  
        </aop:config>  
      
    </beans>  
    

    第二步:下面几个类,就是脱去了所有注解的外衣,采用通过配置的xml,实现AOP编程。

    public interface Calculator {  
          
        public int add(int a, int b);  
        public int division(int a, int b);  
      
    }  
    
    public class CalculatorImp implements Calculator {  
      
        @Override  
        public int add(int a, int b) {  
            System.out.println("add 方法执行了 ----> " + (a + b));  
            return (a + b);  
        }  
      
        @Override  
        public int division(int a, int b) {  
            System.out.println("division 方法执行了 ----> " + (a / b));  
            return (a / b);  
        }  
      
    }  
    
    import java.util.Arrays;  
    import java.util.List;  
    import org.aspectj.lang.JoinPoint;  
      
    public class LoggerAspect {  
          
        public void beforeAdvice(JoinPoint joinPoint) {  
            String methodName = joinPoint.getSignature().getName();   
            List<Object> args = Arrays.asList(joinPoint.getArgs());  
            System.out.println("Before 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
        }  
          
        public void afterAdvice(JoinPoint joinPoint) {  
            String methodName = joinPoint.getSignature().getName();   
            List<Object> args = Arrays.asList(joinPoint.getArgs());  
            System.out.println("After 后置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
        }  
          
        public void afterRunningAdvice(JoinPoint joinPoint, Object result) {  
            String methodName = joinPoint.getSignature().getName();   
            List<Object> args = Arrays.asList(joinPoint.getArgs());  
            System.out.println("AfterReturning 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);  
        }  
          
        public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {  
            String methodName = joinPoint.getSignature().getName();   
            System.out.println("AfterThrowing 异常通知 : 方法名 【 " + methodName + " 】and  exception is " + exception);  
        }  
      
    }  
    
    import java.util.Arrays;  
    import java.util.List;  
    import org.aspectj.lang.ProceedingJoinPoint;  
      
    public class AroundAspect {  
      
        public Object aroundAdvice(ProceedingJoinPoint joinPoint) {  
            Object result = null;  
            String methodName = joinPoint.getSignature().getName();   
            List<Object> args = Arrays.asList(joinPoint.getArgs());  
            try {  
                System.out.println("@Around 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
                result = joinPoint.proceed();  
                System.out.println("@Around 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);  
            } catch (Throwable e) {  
                e.printStackTrace();  
                System.out.println("@Around 异常通知 : 方法名 【 " + methodName + " 】and  exception is " + e);  
            }  
            System.out.println("@Around 后置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
              
            return result;  
        }  
          
    }  
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;  
      
    public class Main {  
          
        public static void main(String[] args) {  
              
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
            Calculator calculator = (Calculator) ctx.getBean("calculator");  
              
            calculator.add(11, 12);  
            calculator.division(21, 3); // 测试时,将被除数换成0,可以测试AfterReturning ,After 和 AfterThrowing  
              
            ctx.close();  
        }  
      
    }  
    
    Before 前置通知 : 方法名 【 add 】and args are [11, 12]  
    add 方法执行了 ----> 23  
    After 后置通知 : 方法名 【 add 】and args are [11, 12]  
    AfterReturning 返回通知 : 方法名 【 add 】and args are [11, 12] , result is 23  
    Before 前置通知 : 方法名 【 division 】and args are [21, 3]  
    division 方法执行了 ----> 7  
    After 后置通知 : 方法名 【 division 】and args are [21, 3]  
    AfterReturning 返回通知 : 方法名 【 division 】and args are [21, 3] , result is 7  
    

    到这里,基于xml文件的AOP编程也讲完了。4不4很简单。

  • 相关阅读:
    Overloaded的方法是否可以改变返回值的类型
    parseXXX的用法
    java的类型转换问题。int a = 123456;short b = (short)a;System.out.println(b);为什么结果是-7616?
    UVA 10405 Longest Common Subsequence(简单DP)
    POJ 1001 Exponentiation(大数处理)
    POJ 2318 TOYS(计算几何)(二分)
    POJ 1265 Area (计算几何)(Pick定理)
    POJ 3371 Flesch Reading Ease (模拟题)
    POJ 3687 Labeling Balls(拓扑序列)
    POJ 1094 Sorting It All Out(拓扑序列)
  • 原文地址:https://www.cnblogs.com/itdragon/p/7717951.html
Copyright © 2011-2022 走看看