美文网首页
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