美文网首页AOPJava
Spring学习笔记 | 详解AOP及其配置

Spring学习笔记 | 详解AOP及其配置

作者: 一颗白菜_ | 来源:发表于2019-10-17 00:21 被阅读0次

    AOP简介

    AOP(面向切面编程)是一种新的方法论,是对传统OOP的补充。AOP的主要编程对象是切面,而切面模块化横切关注点
    在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样一来横切关注点就被模块化到特殊的对象(切面)里。

    AOP的好处:

    • 每个事务逻辑位于一个位置,代码不分散,便于维护和升级。
    • 业务模块更简洁,只包含核心业务代码。

    AOP术语

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

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

    Spring中启用AspectJ注解支持

    要在Spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库:aopalliance.jar、aspectj.weaver.jar和spring-aspects.jar。

    步骤:

    • 在配置文件中加入如下配置:
        <!-- 使AspjectJ注解起作用:自动为匹配的类型生成代理对象-->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    • 把横切关注点的代码抽象到切面的类中
      切面首先是一个IOC中的bean,即加入@Component注解,切面还需要加入@Aspect注解。
    • 在类中声明各种通知:声明方法后在方法前加入如下注解:
      @Before:前置通知,在方法执行之前执行
      @After:后置通知,在方法执行之后执行
      @AfterRunning:返回通知,在方法返回结果之后执行
      @AfterThrowing:异常通知,在方法抛出异常之后执行
      @Around:环绕通知,围绕着方法执行
      可以使用execution()来匹配各种方法,例如execution(public int com.spring.aop.impl.ArithmeticCalculator.*(int ,int ))。表示匹配com.spring.aop.impl.ArithmeticCalculator类中的所有方法。也可以使用通配符来匹配,例如execution(* com.spring.aop.impl.*.*(int ,int ))表示匹配任意返回值和任意修饰符的com.spring.aop.impl包下的任意类的方法。
    • 可以在通知方法中声明一个类型为JoinPoint的参数,然后就能访问链接细节,如方法名称和参数值。例如
    @Before("execution(public int com.spring.aop.impl.ArithmeticCalculator.*(int ,int ))")
        public void beforMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            List<Object> args = Arrays.asList(joinPoint.getArgs());
            System.out.println("The method "+methodName+" begins "+args);
        }
    

    例如我们定义一个计算接口,并且定义其实现类,里面写了一般的业务逻辑(相当于AOP中的目标):

    package com.spring.aop.impl;
    
    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);
    
    }
    
    
    package com.spring.aop.impl;
    
    import org.springframework.stereotype.Component;
    
    @Component("arithmeticCalculatorImpl")
    public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
        @Override
        public int add(int i, int j) {
            int res = i+j;
            return res;
        }
    
        @Override
        public int sub(int i, int j) {
            int res = i-j;
            return res;
        }
    
        @Override
        public int mul(int i, int j) {
            int res = i*j;
            return res;
        }
    
        @Override
        public int div(int i, int j) {
            int res = i/j;
            return res;
        }
    }
    
    

    然后我们想要实现一个日志功能(在方法执行前打印,方法执行后打印),将其作为横切关注点,建立LoggingAspect类(具体各种通知的实现看看此代码):

    package com.spring.aop.impl;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    import java.util.List;
    
    /***
     * 日志切面
     * 需要把这个类声明为一个切面:需要把该类放入IOC容器中,再声明为一个切面
     */
    //@Component表示将其放入IOC容器中,@Aspect表示其是一个切面
    @Component
    @Aspect
    public class LoggingAspect {
    
        /**
         * 在com.spring.aop.impl.ArithmeticCalculator接口的每一个实现类的每一个方法开始之前执行一段代码
         * 在目标方法开始之前执行
         * @param joinPoint
         */
        @Before("execution(public int com.spring.aop.impl.ArithmeticCalculator.*(int ,int ))")
        public void beforMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            List<Object> args = Arrays.asList(joinPoint.getArgs());
            System.out.println("The method "+methodName+" begins "+args);
        }
    
        /***
         * 后置通知:在目标方法执行后(无论发生异常),执行的通知
         * 在后置通知中还不能访问目标方法执行的结果
         * @param joinPoint
         */
        @After("execution(* com.spring.aop.impl.*.*(int ,int )))")
        public void afterMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method "+ methodName +" ends ");
        }
    
        /***
         * 返回通知:
         * 在方法正常结束后执行的代码
         * 返回通知是可以访问到方法的返回值的
         * @param joinPoint
         */
        @AfterReturning(value = "execution(public int com.spring.aop.impl.ArithmeticCalculator.*(..)))",returning = "result")
        public void afterReturning(JoinPoint joinPoint,Object result){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method "+ methodName +" ends with "+result);
        }
    
        /***
         * 异常通知:
         * 在方法发生异常后执行的代码
         * 在方法中传入一个Exception参数可以访问到方法出现的异常
         * 可以在方法中传入一个特定的异常时再执行通知代码(例如指定空指针异常才执行:方法参数的异常类型定义为NullPointerException)
         * @param joinPoint
         * @param ex
         */
        @AfterThrowing(value = "execution(public int com.spring.aop.impl.ArithmeticCalculator.*(..)))",throwing = "ex")
        public void afterThrowing(JoinPoint joinPoint,Exception ex){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method "+ methodName +" occurs excetion "+ex);
        }
    
    
        /***
         * 环绕通知:
         * 需要携带ProceedingJoinPoint类型的参数
         * 环绕通知类似于动态代理的全过程,ProceedingJoinPoint类型的参数可以决定是否执行目标方法
         * 且环绕通知必须有返回值,返回值即为模板方法的返回值
         */
        @Around("execution(public int com.spring.aop.impl.ArithmeticCalculator.*(..)))")
        public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
            //相当于整个动态代理的全过程:
    
            String methodName = proceedingJoinPoint.getSignature().getName();
            Object res = null;
            //执行目标方法
    
            try{
                //可在此定义前置通知.....
                System.out.println("The method "+methodName+" begins with"+ Arrays.asList(proceedingJoinPoint.getArgs()));
                res = proceedingJoinPoint.proceed();
                //可在此定义返回通知.......
            }catch (Throwable e){
                e.printStackTrace();
                //可在此定义异常通知......
            }
            //可在此定义后置通知.......
            return res;
        }
    
    
    }
    
    
    

    配置如下:

    <?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:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 配置自动扫描的包-->
        <context:component-scan base-package="com.spring.aop.impl"/>
    
        <!-- 使AspjectJ注解起作用:自动为匹配的类型生成代理对象-->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    </beans>
    

    主方法:

    package com.spring.aop.impl;
    
    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-aop.xml");
    
           ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculatorImpl");
    
            int res = arithmeticCalculator.add(3,6);
            System.out.println("result:"+res);
    
            int res1 = arithmeticCalculator.div(6,3);
            System.out.println("result:"+res1);
        }
    }
    
    
    指定切面的优先级

    在有多个切面的时候,可以使用@Order(num)来指定切面的优先级,num越小,优先级越高。例如:@Order(1)

    切点表达式

    可以使用@Pointcut来声明一个切入点表达式,然后在后面的通知需要用到该表达式的时候,直接使用方法名来引用当前切入点表达式即可。
    例如我们先声明一个切入点表达式:

    /**
         * 定义一个方法,用于声明切入点表达式,
         * 一般该方法中不需要填入其他的代码
         */
        @Pointcut("execution(public int com.spring.aop.impl.ArithmeticCalculator.*(..))")
        public void declareJoinPointExpression(){ }
    

    在下面使用到该表达式:

    @Before("declareJoinPointExpression()")
        public void beforMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            List<Object> args = Arrays.asList(joinPoint.getArgs());
            System.out.println("The method "+methodName+" begins "+args);
        }
    

    如果不在同一个包中,则还需加上包名。
    使用切入点表达式替换后的LoggingAspect类如下:

    package com.spring.aop.impl;
    
    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;
    import java.util.List;
    
    /***
     * 日志切面
     * 需要把这个类声明为一个切面:需要把该类放入IOC容器中,再声明为一个切面
     */
    //@Component表示将其放入IOC容器中,@Aspect表示其是一个切面
    @Component
    @Aspect
    @Order(1)
    public class LoggingAspect {
    
        /**
         * 定义一个方法,用于声明切入点表达式,
         * 一般该方法中不需要填入其他的代码
         */
        @Pointcut("execution(public int com.spring.aop.impl.ArithmeticCalculator.*(..))")
        public void declareJoinPointExpression(){ }
    
        /**
         * 在com.spring.aop.impl.ArithmeticCalculator接口的每一个实现类的每一个方法开始之前执行一段代码
         * 在目标方法开始之前执行
         * @param joinPoint
         */
        @Before("declareJoinPointExpression()")
        public void beforMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            List<Object> args = Arrays.asList(joinPoint.getArgs());
            System.out.println("The method "+methodName+" begins "+args);
        }
    
        /***
         * 后置通知:在目标方法执行后(无论发生异常),执行的通知
         * 在后置通知中还不能访问目标方法执行的结果
         * @param joinPoint
         */
        @After("declareJoinPointExpression()")
        public void afterMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method "+ methodName +" ends ");
        }
    
        /***
         * 返回通知:
         * 在方法正常结束后执行的代码
         * 返回通知是可以访问到方法的返回值的
         * @param joinPoint
         */
        @AfterReturning(value = "declareJoinPointExpression()",returning = "result")
        public void afterReturning(JoinPoint joinPoint,Object result){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method "+ methodName +" ends with "+result);
        }
    
        /***
         * 异常通知:
         * 在方法发生异常后执行的代码
         * 在方法中传入一个Exception参数可以访问到方法出现的异常
         * 可以在方法中传入一个特定的异常时再执行通知代码(例如指定空指针异常才执行:方法参数的异常类型定义为NullPointerException)
         * @param joinPoint
         * @param ex
         */
        @AfterThrowing(value = "declareJoinPointExpression()",throwing = "ex")
        public void afterThrowing(JoinPoint joinPoint,Exception ex){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method "+ methodName +" occurs excetion "+ex);
        }
    
    
        /***
         * 环绕通知:
         * 需要携带ProceedingJoinPoint类型的参数
         * 环绕通知类似于动态代理的全过程,ProceedingJoinPoint类型的参数可以决定是否执行目标方法
         * 且环绕通知必须有返回值,返回值即为模板方法的返回值
         */
        @Around("declareJoinPointExpression()")
        public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
            //相当于整个动态代理的全过程:
    
            String methodName = proceedingJoinPoint.getSignature().getName();
            Object res = null;
            //执行目标方法
    
            try{
                //可在此定义前置通知.....
                System.out.println("The method "+methodName+" begins with"+ Arrays.asList(proceedingJoinPoint.getArgs()));
                res = proceedingJoinPoint.proceed();
                //可在此定义返回通知.......
            }catch (Throwable e){
                e.printStackTrace();
                //可在此定义异常通知......
            }
            //可在此定义后置通知.......
            return res;
        }
    }
    
    

    基于配置文件的方式来配置AOP

    步骤:

    • 编写切面
    • 在配置文件中配置bean
    • 在配置文件中配置切面的bean
    • 在配置文件中配置aop

    例如我们上述的例子,将切面类去掉注解后如下:

    package com.spring.aop.impl;
    
    import org.aspectj.lang.JoinPoint;
    
    import java.util.Arrays;
    import java.util.List;
    
    /***
     * 日志切面
     * 需要把这个类声明为一个切面:需要把该类放入IOC容器中,再声明为一个切面
     */
    
    public class LoggingAspect {
    
    
        public void beforMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            List<Object> args = Arrays.asList(joinPoint.getArgs());
            System.out.println("The method "+methodName+" begins "+args);
        }
    
    
        public void afterMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method "+ methodName +" ends ");
        }
    
    
        public void afterReturning(JoinPoint joinPoint,Object result){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method "+ methodName +" ends with "+result);
        }
    
    
        public void afterThrowing(JoinPoint joinPoint,Exception ex){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("The method "+ methodName +" occurs excetion "+ex);
        }
    
    
    }
    
    

    我们再编写了另一个切面如下:

    package com.spring.aop.impl;
    
    import org.aspectj.lang.JoinPoint;
    
    public class VlidationAspect {
    
        public void validateArys(JoinPoint joinPoint){
            System.out.println("befort method ..... validate");
        }
    }
    
    

    我们在配置文件中配置如下:

    <?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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!-- 配置bean -->
        <bean id="arithmeticCalculator"
            class="com.spring.aop.impl.ArithmeticCalculatorImpl"></bean>
    
        <!-- 配置切面的bean -->
        <bean id="loggingAspect"
              class="com.spring.aop.impl.LoggingAspect"></bean>
    
        <bean id="validation"
              class="com.spring.aop.impl.VlidationAspect"></bean>
    
        <!-- 配置aop -->
        <aop:config>
    
            <!-- 配置切点表达式 -->
            <aop:pointcut id="pointcut" expression="execution(* com.spring.aop.impl.ArithmeticCalculator.*(..))"/>
            <!-- 配置切面及通知 -->
            <aop:aspect ref="loggingAspect" order="2">
                <!-- 配置前置通知-->
                <aop:before method="beforMethod" pointcut-ref="pointcut"/>
                <!-- 配置后置通知 -->
                <aop:after method="afterMethod" pointcut-ref="pointcut"/>
                <!-- 配置返回通知 -->
                <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
                <!-- 配置异常通知-->
                <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
            </aop:aspect>
    
            <aop:aspect ref="validation" order="1">
                <aop:before method="validateArys" pointcut-ref="pointcut"/>
            </aop:aspect>
    
        </aop:config>
    </beans>
    

    测试类:

    package com.spring.aop.impl;
    
    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.xml");
            ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
            int res = arithmeticCalculator.add(3,6);
            System.out.println("result:"+res);
    
            int res1 = arithmeticCalculator.div(6,1);
            System.out.println("result:"+res1);
        }
    }
    
    

    运行结果跟第一种方法一致。

    相关文章

      网友评论

        本文标题:Spring学习笔记 | 详解AOP及其配置

        本文链接:https://www.haomeiwen.com/subject/khaumctx.html