• Spring--AOP


    AspectJ:Java 社区里最完整最流行的 AOP 框架.

    在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP

    1).加入jar包

    Spring 中启用 AspectJ 注解支持
    org.aspectjweaver-1.8.9.RELEASE.jar
    spring-aop-4.0.0.RELEASE.jar
    spring-aspects-4.0.0.RELEASE.jar

    commons-logging-1.1.1.jar
    spring-beans-4.0.0.RELEASE.jar
    spring-context-4.0.0.RELEASE.jar
    spring-core-4.0.0.RELEASE.jar
    spring-expression-4.0.0.RELEASE.jar

    2).在配置文件中加入aop的命名空间

    xmlns:aop="http://www.springframework.org/schema/aop"

    3).基于注解的方式

    ①.在配置文件中加入如下配置

    <aop:aspectj-autoproxy/>

    当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.

    ②.创建切面类
    切面类要被IOC容器管理,加入@Component注解;切面还需要加入@Aspect注解

    用 AspectJ 注解声明切面,要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.

    ③.在切面类中声明各种通知

    通知是标注有某种注解的简单的 Java 方法.
    AspectJ 支持 5 种类型的通知注解:

    @Before: 前置通知, 在方法执行之前执行
    @After: 后置通知, 在方法执行之后执行 
    @AfterRunning: 返回通知, 在方法返回结果之后执行
    @AfterThrowing: 异常通知, 在方法抛出异常之后
    @Around: 环绕通知, 围绕着方法执行

    i.前置通知:
    在方法执行之前执行的通知
    前置通知使用 @Before 注解, 并将切入点表达式的值作为注解值

    ii.后置通知
    后置通知在目标方法执行后(无论是否发生异常),执行的通知

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

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

    iiiii.环绕通知

    环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.

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

    ④.Demo

    package yang.mybatis.test.AOP;
    
    public interface Calculator {
    
        int add(int i, int j);
        int sub(int i, int j);
        
        int mul(int i, int j);
        int div(int i, int j);
        
    }

     目标对象(Target)

    package yang.mybatis.test.AOP;
    
    import org.springframework.stereotype.Component;
    
    @Component("calculator")
    public class CalculatorImpl implements Calculator {
    
        @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;
        }
    
    }

     切面(Aspect)

    package yang.mybatis.test.AOP;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    
    /**
     * 1. 加入 jar 包
     * org.aspectj.weaver-1.6.8.RELEASE.jar
     * spring-aspects-4.0.0.RELEASE.jar
     * 
     * 2. 在 Spring 的配置文件中加入 aop 的命名空间。 
     * 
     * 3. 基于注解的方式来使用 AOP
     * 3.1 在配置文件中配置自动扫描的包: <context:component-scan base-package="..."></context:component-scan>
     * 3.2 加入使 AspjectJ 注解起作用的配置: <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
     * 为匹配的类自动生成动态代理对象. 
     * 
     * 4. 编写切面类: 
     * 4.1 一个一般的 Java 类
     * 4.2 在其中添加要额外实现的功能. 
     *
     * 5. 配置切面
     * 5.1 切面必须是 IOC 中的 bean: 实际添加了 @Component 注解
     * 5.2 声明是一个切面: 添加 @Aspect
     * 5.3 声明通知: 即额外加入功能对应的方法. 
     * 5.3.1 前置通知: @Before("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))")
     * @Before 表示在目标方法执行之前执行 @Before 标记的方法的方法体. 
     * @Before 里面的是切入点表达式: 
     * 
     * 6. 在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数. 
     * 
     * 7. @After 表示后置通知: 在方法执行之后执行的代码. 
     */
    
    //通过添加 @Aspect 注解声明一个 bean 是一个切面!
    @Aspect
    @Component
    public class LoggingAspect {
        //利用方法签名编写 AspectJ 切入点表达式
        @Before("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))")
        //可以在通知方法中声明一个类型为 JoinPoint 的参数. 然后就能访问链接细节. 如方法名称和参数值.
        public void beforeMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            Object [] args = joinPoint.getArgs();
            
            System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
        }
        //在后置通知中,因为方法可能会出异常,所以不能访问目标方法的执行的结果
        @After("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))")
        public void afterMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method " + methodName + " ends");
        }
        //返回通知,无异常时执行,返回通知是可以访问到方法的返回值的
        @AfterReturning(value = "execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int)))",
        returning = "result")
        public void afterReturnMethod(JoinPoint joinPoint,Object result){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method " + methodName + " ends with "+result);
        }
        //在目标方法出现异常时会执行的代码
        //可以访问到异常对象;且可以指定在出现特定异常时执行通知代码
        @AfterThrowing(value = "execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int)))",
                throwing = "ex")
        public void afterThrowingMethod(JoinPoint joinPoint, Exception ex){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method " + methodName + " occurs exception with "+ex);
    
        }
    
        /**
         * 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
         * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
         * 且环绕通知必须有返回值, 返回值即为目标方法的返回值
         */
    /*    @Around("execution(public int public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))")
        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 + " ends with " + result);
            } catch (Throwable e) {
                //异常通知
                System.out.println("The method " + methodName + " occurs exception:" + e);
                throw new RuntimeException(e);
            }
            //后置通知
            System.out.println("The method " + methodName + " ends");
    
            return result;
        }*/
    
    }
          ApplicationContext applicationContext = 

              new ClassPathXmlApplicationContext("applicationContext.xml"); Calculator calculator = (Calculator) applicationContext.getBean("calculator"); System.out.println(calculator.getClass().getName()); int result = calculator.add(11, 12); System.out.println("result:" + result); result = calculator.div(21,1); System.out.println("result:" + result);

     4).指定切面的优先级

    在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的.
    切面的优先级可以通过利用 @Order 注解指定.值越小, 优先级越高.

    @Order(0)    
    @Aspect
    @Component
    public class LoggingAspect {
    }
    @Order(1)    
    @Aspect
    @Component
    public class ValidateAspect {
    }
    同一个连接点上LoggingAspect 切面先于ValidateAspect切面执行 

    5).切入点表达式和重用切入点

    典型的切入点表达式时根据方法的签名来匹配各种方法:
    execution * com.nchu.spring.Calculator.*(..):
    // 匹配 Calculator 中声明的所有方法,第一个 * 代表任意修饰符及任意返回值.
    第二个 * 代表任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 可以省略包名.
    execution public * ArithmeticCalculator.*(..): // 匹配 Calculator 接口的所有公有方法. execution public double ArithmeticCalculator.*(..): // 匹配 Calculator 中返回 double 类型数值的方法 execution public double ArithmeticCalculator.*(double, ..): //匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数 execution public double ArithmeticCalculator.*(double, double): //匹配参数类型为 double, double 类型的方法.

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

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

    改造后的切面

    package yang.mybatis.test.AOP;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    //通过添加 @Aspect 注解声明一个 bean 是一个切面!
    @Order(0)
    @Aspect
    @Component
    public class LoggingAspect {
        /**
         * 该方法用于声明切入点表达式,一般的该方法中不需要添加其他代码
         */
        @Pointcut("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))")
        public void pointcut(){}
    
        /**
         * 利用方法签名编写 AspectJ 切入点表达式
         * @param joinPoint
         */
        @Before("pointcut()")
        //可以在通知方法中声明一个类型为 JoinPoint 的参数. 然后就能访问链接细节. 如方法名称和参数值.
        public void beforeMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            Object [] args = joinPoint.getArgs();
            
            System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
        }
    
        /**
         * 在后置通知中,因为方法可能会出异常,所以不能访问目标方法的执行的结果
         * @param joinPoint
         */
        @After("pointcut()")
        public void afterMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method " + methodName + " ends");
        }
    
        /**
         * 返回通知,无异常时执行,返回通知是可以访问到方法的返回值的
         * @param joinPoint
         * @param result
         */
    
        @AfterReturning(value = "pointcut()",
        returning = "result")
        public void afterReturnMethod(JoinPoint joinPoint,Object result){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method " + methodName + " ends with "+result);
        }
    
        /**
         * 在目标方法出现异常时会执行的代码
         *可以访问到异常对象;且可以指定在出现特定异常时执行通知代码
         * @param joinPoint
         * @param ex
         */
    
        @AfterThrowing(value = "pointcut()",
                throwing = "ex")
        public void afterThrowingMethod(JoinPoint joinPoint, Exception ex){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method " + methodName + " occurs exception with "+ex);
    
        }
    
        /**
         * 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
         * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
         * 且环绕通知必须有返回值, 返回值即为目标方法的返回值
         */
    /*    @Around("pointcut()")
        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 + " ends with " + result);
            } catch (Throwable e) {
                //异常通知
                System.out.println("The method " + methodName + " occurs exception:" + e);
                throw new RuntimeException(e);
            }
            //后置通知
            System.out.println("The method " + methodName + " ends");
    
            return result;
        }*/
    
    }

     6、基于XML的配置

    将上文中的两个类中的配置移除,在applicationContext.xml文件中加入以下配置

    <bean id="calculator" class="yang.mybatis.test.AOP.XML.CalculatorImpl"></bean>
        <bean id="loggingAspect" class="yang.mybatis.test.AOP.XML.LoggingAspect"></bean>
        <!--AOP配置-->
        <aop:config>
            <!-- 配置切点表达式 -->
            <aop:pointcut id="pointcut" expression="execution(public int yang.mybatis.test.AOP.XML.CalculatorImpl.*(..))"></aop:pointcut>
            <!-- 配置切面及通知 -->
            <aop:aspect ref="loggingAspect" order="0">
                <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
                <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
                <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
                <aop:after-returning method="afterReturnMethod" pointcut-ref="pointcut" returning="result"></aop:after-returning>
                <!--<aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>-->
            </aop:aspect>
            <aop:aspect ref="vlidationAspect" order="1">
                <aop:before method="validateArgs" pointcut-ref="pointcut"/>
            </aop:aspect>
        </aop:config>
  • 相关阅读:
    dpkg install command
    Notes: GitHub, CA errors and old curl's
    幫倒忙的IE8 JSON.stringify()
    Connect High quality middleware for node.js
    MVC3, jQuery and JSON.stringify
    Scraping the Dynamic Web with PhantomJS: Using jQuery and Node: James Morrin: 9781449321864: Amazon.com: Books
    FAQ’S Ultimate Web Scraping
    How Yottaa Brings Site Speed,
    Node入门 » 一本全面的Node.js教程
    用Meteor框架快速搭建高质量互联网应用
  • 原文地址:https://www.cnblogs.com/realshijing/p/7966322.html
Copyright © 2020-2023  润新知