美文网首页
Spring AOP使用

Spring AOP使用

作者: 仿若尘土 | 来源:发表于2019-03-10 18:41 被阅读0次

    [TOC]

    1. 概念

    AOP(Aspect Oriented Programming,面向切面编程),可使各业务逻辑分离,降低耦合,提高复用,增加开发效率。
    应用于日志记录、性能统计、安全控制、权限管理、事务处理、异常处理和资源池管理等。

    2. 概念

    • 切面(aspect)
      关注点的模块化,本文的AopAspect,用<aop:aspect>配置
    • 连接点(Joinpoint)
      程序执行中的某个行为,<aop:pointcut id="minstrelPointCut" expression="execution(* com.cui.springShizhan.*.*(..))"/>中的expression定义了连接点
    • 通知(Advice)
      切面对于连接点产生的动作,分为5种:
      1. 前置通知(Before advice):在连接点之前执行,配置在<aop:before>中;
      2. 后置通知(After advice):在连接点之后执行,配置在<aop:after>中;
      3. 返回后通知(After return advice):程序正常完成后执行,不包括抛出异常,配置杂<after-returning>
      4. 环绕通知(Around advice):包围一个连接点的通知,配置在<aop:around>中;
      5. 抛出异常后通知(After throwing advice):抛出异常退出执行,配置在<aop:after-throwing>中。
    • 切入点(Pointcut)
      匹配连接点的断言,通知(Advice)和一个切入点关联。配置在<aop:pointcut>中,如<aop:pointcut id="savePoint" expression="execution(* com.cui.springShizhan.aop.*.save(..))"></aop:pointcut>
      详细介绍见 第3章
    • 目标对象(Target Object)
      被一个或多个切面通知的对象,实际操作的是代理对象
    • AOP代理(AOP Proxy)
      spring采用JDK动态代理和CGLIB代理,目标对象实现接口,则使用JDK动态代理;未实现接口,则使用CGLIB代理。

    3. 切入点

    3.1 匹配语法

    • “ * ”:匹配任何数量字符;
    • “ .. ”:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
    • “ + ”:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
      如:
    1. java.lang.String:匹配String类型;
    2. java.*.String:匹配java包下的任何“一级子包”下的String类型;如匹配java.lang.String,但不匹配java.lang.ss.String
      3.java..*:匹配java包及任何子包下的任何类型;如匹配java.lang.Stringjava.lang.annotation.Annotation
    3. java.lang.*ing:匹配任何java.lang包下的以ing结尾的类型;
    4. java.lang.Number+:匹配java.lang包下的任何Number的自类型;如匹配java.lang.Integer,也匹配java.math.BigInteger

    3.2 匹配逻辑

    可以使用且(&&)、或(||)、非(!)来组合切入点表达式。由于在XML中使用“&&”需要使用转义字符“&&”来代替之,所以很不方便,因此Spring ASP 提供了and、or、not来代替&&、||、!。

    3.3 切入点表达式

    • execution:匹配方法,支持通配符
      1. 所有公共方法
      execution(public * *(..))
      
      1. set开头的所有方法
      execution(* set*(..))
      
      1. AccountService接口中定义的所有方法
      execution(* com.xyz.service.AccountService.*(..))
      
      1. service包中定义的所有类的方法(不包含子包)
      execution(* com.xyz.service.*.*(..))
      
      1. service包中定义的所有类的方法(包含子包)
      execution(* com.xyz.service..*.*(..))
      
    • within:匹配类,支持通配符
      1. service包下的所有类(不包含子类)
      within(com.xyz.service.*)
      
      1. service包下的所有类(包含子类)
      within(com.xyz.service..*)
      
    • this:代理类类型匹配。不支持通配符
      1. 匹配所有代理类(AOP生成的代理类,包括动态代理和CGLIB代理)实现了AccountService接口的类
      this(com.xyz.service.AccountService)
      
    • target:目标类匹配。不支持通配符
      1. 匹配所有目标类实现了AccountService接口的类
      target(com.xyz.service.AccountService)
      
    • args:参数匹配和参数绑定,支持通配符
      1. 匹配一个参数,且为Serializable的方法
      args(java.io.Serializable)
      
      1. 匹配String的参数,并传递到before方法中
    @Before("execution(* com.cui.springShizhan.ch4.Test.test(String)) && args(testName)")
        public void before(String testName) {
            System.out.println("before");
            System.out.println("para:" + testName);
        }
    
    image.png
    • @target:目标类注解匹配,在接口中不注解不匹配。不支持通配符
      1. 目标类加了@Transactional注解
      @target(org.springframework.transaction.annotation.Transactional)
      
    • @within:申明类型注解匹配,如果声明为接口,则注解在接口生效;如果为实现类,则注解在实现类才会生效。不支持通配符。
      1. 声明类型加了@Transactional注解
      @within(org.springframework.transaction.annotation.Transactional)
      
    • @annotation:执行方法注解匹配。不支持通配符
      1. 当前执行方法加了@Transactional注解
      @annotation(org.springframework.transaction.annotation.Transactional)
      
    • @args:参数直接匹配
      1. 参数加了Classified注解
      @args(com.xyz.security.Classified)
      
    • bean:beanName匹配
      1. 只匹配beanNametradeServicebean
      bean(tradeService)
      
      1. 只匹配beanNameService结尾的bean
      bean(*Service)
      

    4. 实例

    • maven
            <!--spring的context上下文即IoC容器-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${spring.version}</version>
            </dependency>
    
            <!--spring aop依赖-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>${spring.version}</version>
            </dependency>
    
            <!--spring测试依赖-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>${spring.version}</version>
                <scope>test</scope>
            </dependency>
    
            <!--junit依赖-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
    
            <!--spring aop依赖AspectJ-->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.2</version>
            </dependency>
    

    注:比如引入aspectjweaver,否则会报错Spring AOP报错:java.lang.NoClassDefFoundError: org/aspectj/lang/JoinPoint

    • 目标对象(Target Object)
      接口定义:
    package com.cui.springShizhan.aop;
    
    /**
     * 接口定义
     */
    public interface IDateOperationService {
        int save();
    
        int query() throws Exception;
    }
    

    接口实现:

    package com.cui.springShizhan.aop;
    
    /**
     * 实现接口,使用JDK动态代理,否则使用CGLIB代理
     */
    public class DateOperationServiceImpl implements IDateOperationService {
        @Override
        public int save() {
            System.out.println("----保存到数据库----");
            return 1;
        }
    
        @Override
        public int query() throws Exception {
            System.out.println("----从数据库中得到查询结果----");
            try {
                int i = 1 / 0;
            } catch (Exception e) {
                throw new Exception();
            }
            return 9;
        }
    }
    
    • 切面定义:
    package com.cui.springShizhan.aop;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    
    /**
     * AOP 切面相关定义
     */
    public class AopAspect {
        /**
         * 前置通知:在连接点之前执行
         * @param joinPoint 连接点
         */
        public void before(JoinPoint joinPoint) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println("前置通知: " + methodName + "执行前");
        }
    
        /**
         * 后置通知:在连接点之后执行
         * @param joinPoint 连接点
         */
        public void after(JoinPoint joinPoint) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println("后置通知:" + methodName + "执行后");
        }
    
        /**
         * 返回后通知:在return后执行这段逻辑,异常退出不执行
         * @param joinPoint
         * @param result    返回结果
         */
        public void afterReturn(JoinPoint joinPoint, Object result) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println("返回后通知: " + methodName + "已正常return, result: " + result);
        }
    
        /**
         * 抛出异常后通知:抛出异常后执行这段逻辑
         * @param joinPoint
         * @param ex    异常类型
         */
        public void afterThrowing(JoinPoint joinPoint, Exception ex) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println("抛出异常后通知:method: " + methodName + "抛出异常,异常为: " + ex);
        }
    
        /**
         * 环绕通知:可实现前面4种通知
         * @param proceedingJoinPoint
         * @return
         */
        public Object around(ProceedingJoinPoint proceedingJoinPoint) {
            Object result = null;
            String methodName = proceedingJoinPoint.getSignature().getName();
            try {
                System.out.println("环绕通知 → 前置通知: " + methodName + "执行前");
                result = proceedingJoinPoint.proceed();
                System.out.println("环绕通知 → 返回后通知: " + methodName + "已正常return, result: " + result);
            } catch (Throwable throwable) {
                //异常通知
                System.out.println("环绕通知 → 抛出异常后通知:method: " + methodName + "抛出异常,异常为: " + throwable);
                throw new RuntimeException(throwable);
            }
    
            System.out.println("环绕通知 → 后置通知:" + methodName + "执行后");
            return result;
        }
    }
    
    • xml配置:
    <?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 http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean id="dateOperationService" class="com.cui.springShizhan.aop.DateOperationServiceImpl"/>
    
        <!--定义切面-->
        <bean id="aopAspect" class="com.cui.springShizhan.aop.AopAspect"/>
    
        <aop:config>
            <!--id:唯一标识符,ref:切面,order:执行顺序-->
            <aop:aspect id="aopTest" ref="aopAspect" order="1">
                <!--定义切点-->
                <aop:pointcut id="savePoint" expression="execution(* com.cui.springShizhan.aop.*.save(..))"></aop:pointcut>
                <aop:pointcut id="queryPoint" expression="execution(* com.cui.springShizhan.aop.*.query(..))"/>
    
                <aop:before method="before" pointcut-ref="savePoint"/>
                <aop:after method="after" pointcut-ref="savePoint"/>
                <aop:after-returning method="afterReturn" returning= "result" pointcut-ref="savePoint"/>
                <aop:around method="around" pointcut-ref="savePoint"/>
    
                <!--异常定义-->
                <aop:after-throwing method="afterThrowing" pointcut-ref="queryPoint" throwing="ex"/>
                <aop:around method="around" pointcut-ref="queryPoint"/>
            </aop:aspect>
        </aop:config>
    </beans>
    
    • 客户端:
    package com.cui.springShizhan.aop;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Client {
        public static void main(String[] args) throws Exception {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:springshizhan/applicationContext-aop.xml");
            IDateOperationService dateOperationService = (IDateOperationService) applicationContext.getBean("dateOperationService");
            System.out.println("-------------------save开始-----------------");
            dateOperationService.save();
            System.out.println("-------------------save结束-----------------\n\n");
    
            System.out.println("-------------------query开始-----------------");
            dateOperationService.query();
            System.out.println("-------------------query结束-----------------\n\n");
        }
    }
    
    • 输出结果:
    -------------------save开始-----------------
    前置通知: save执行前
    环绕通知 → 前置通知: save执行前
    ----保存到数据库----
    环绕通知 → 返回后通知: save已正常return, result: 1
    环绕通知 → 后置通知:save执行后
    返回后通知: save已正常return, result: 1
    后置通知:save执行后
    -------------------save结束-----------------
    
    
    -------------------query开始-----------------
    环绕通知 → 前置通知: query执行前
    ----从数据库中得到查询结果----
    环绕通知 → 抛出异常后通知:method: query抛出异常,异常为: java.lang.Exception
    抛出异常后通知:method: query抛出异常,异常为: java.lang.RuntimeException: java.lang.Exception
    Exception in thread "main" java.lang.RuntimeException: java.lang.Exception
        at com.cui.springShizhan.aop.AopAspect.around(AopAspect.java:63)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
        at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
        at com.sun.proxy.$Proxy4.query(Unknown Source)
        at com.cui.springShizhan.aop.Client.main(Client.java:15)
    Caused by: java.lang.Exception
        at com.cui.springShizhan.aop.DateSaveServiceImpl.query(DateSaveServiceImpl.java:19)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
        at com.cui.springShizhan.aop.AopAspect.around(AopAspect.java:58)
    

    5. 参考

    1. Spring Aop详尽教程
    2. Spring AOP--返回通知,异常通知和环绕通知
    3. spring doc
    4. springAOP中的target、this、within的区别

    相关文章

      网友评论

          本文标题:Spring AOP使用

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