07.Spring Framework 之 AOP

1. Spring AOP 概念

详见文档 https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-introduction-defn

1. Aspect(切面)

Aspect: A modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented by using regular classes (the schema-based approach) or regular classes annotated with the @Aspect annotation (the @AspectJ style).

它是 Join pointAdvicePointcut 的载体

在 AspectJ 中,它是一个类;在 Spring XML 中,它是一段配置

2. Join point(连接点)

Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.

目标对象中的方法

3. Advice(通知)

Advice: Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.

要通知的具体内容和通知到方法的哪个位置

增强类型 xml 配置 注解 应用场景
前置增强(Before advice) aop:before @Before 权限控制、记录调用日志
后置增强(After returning advice) aop:after-returning @AfterReturning 统计分析结果数据
异常增强(After throwing advice) aop:throwing @AfterThrowing 通过日志记录方法异常信息
最终增强(After finally advice) aop:after @After 释放资源
环绕增强(Around advice) aop:around @Around 缓存、性能日志、权限、事务管理
4. Pointcut(切点)

Pointcut: A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.

它表示连接点的集合,切点只定位到某个方法上

5. Introduction(引介)

Introduction: Declaring additional methods or fields on behalf of a type. Spring AOP lets you introduce new interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to make a bean implement an IsModified interface, to simplify caching. (An introduction is known as an inter-type declaration in the AspectJ community.)

引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过 AOP 的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类

6. Target object(目标对象)

Target object: An object being advised by one or more aspects. Also referred to as the “advised object”. Since Spring AOP is implemented by using runtime proxies, this object is always a proxied object.

目标对象

7. AOP proxy(AOP 代理)

AOP proxy: An object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy.

在 Spring Framework 中,AOP 代理是 JDK 动态代理或 CGLIB 代理,它是代理后的对象

8. Weaving(编织)

Weaving: linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.

把代理逻辑加入到目标方法上的过程叫做织入

2. Spring 使用 AspectJ 实现 AOP

2.1 Spring AOP 支持的切入点表达式

详见文档 https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts

2.1.1 execution

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-execution 工程

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

这里问号表示当前项可以有也可以没有,其中各项的语义如下:

  • modifiers-pattern:方法的可见性,如 public,protected

  • ret-type-pattern:方法的返回值类型,如 int,void 等

  • declaring-type-pattern:方法所在类的全路径名,如 com.spring.Aspect

  • name-pattern:方法名类型,如 buisinessService()

  • param-pattern:方法的参数类型,如 java.lang.String

  • throws-pattern:方法抛出的异常类型,如 java.lang.Exception

表达式的最小粒度是方法,在 aop 中主要使用

@Aspect
@Component
@Order(1)
public class LoggingAspect {

    /**
     * 匹配 pers.masteryourself.tutorial.spring.framework.aop.service 包及其子包中的任意以 save 开头的方法
     * execution(* pers.masteryourself.dao.*.*(..)) 表示匹配 pers.masteryourself.dao 包下的任意接口和类的任意方法
     * execution(public * pers.masteryourself.dao.*.*(..)) 表示匹配 pers.masteryourself.dao 包下的任意接口和类的 public 方法
     * execution(public * pers.masteryourself.dao.*.*()) 表示匹配 pers.masteryourself.dao 包下的任意接口和类的 public 无方法参数的方法
     * execution(* pers.masteryourself.dao.*.*(java.lang.String, ..)) 表示匹配 pers.masteryourself.dao 包下的任意接口和类的第一个参数为 String 类型的方法
     * execution(* pers.masteryourself.dao.*.*(java.lang.String)) 表示匹配 pers.masteryourself.dao 包下的任意接口和类的只有一个参数,且参数为 String 类型的方法
     * execution(public * *(..)) 表示匹配任意的 public 方法
     * execution(* save*(..)) 表示匹配任意的以 save 开头的方法
     * execution(* pers.masteryourself.dao.IndexDao.*(..)) 表示匹配 pers.masteryourself.dao.IndexDao 接口中任意的方法
     * execution(* pers.masteryourself.dao..*.*(..)) 表示匹配 pers.masteryourself.dao 包及其子包中任意的方法
     */
    @Pointcut("execution(* pers.masteryourself.tutorial.spring.framework.aop.service..*.save*(..))")
    public void declareJointPointExpression() {
    }

    @Before("declareJointPointExpression()")
    public void before() {
        System.out.println("aop 生效");
    }

}
2.1.2 within

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-within 工程

表达式的最小粒度是类

withinexecution 相比,粒度更大,仅能实现到包和接口、类级别。而 execution 可以精确到方法的返回值,参数个数、修饰符、参数类型等

@Aspect
@Component
@Order(1)
public class LoggingAspect {

    /**
     * 匹配 pers.masteryourself.tutorial.spring.framework.aop.service 包及其子包中的任意方法
     * within(pers.masteryourself.tutorial.spring.framework.aop.service.*) 表示匹配 pers.masteryourself.tutorial.spring.framework.aop.service 包中的任意方法
     */
    @Pointcut("within(pers.masteryourself.tutorial.spring.framework.aop.service..*)")
    public void declareJointPointExpression() {
    }

    @Before("declareJointPointExpression()")
    public void before() {
        System.out.println("aop 生效");
    }

}
2.1.3 this

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-this 工程

执行当前对象(代理后的对象)是给定类型的任何方法

JDK 代理时,指向接口和代理类 Proxy,cglib 代理时,指向接口和子类

@Aspect
@Component
@Order(1)
public class LoggingAspect {

    /**
     * this 表示代理对象的匹配类型是否是 {@link PersonServiceImpl}
     * 如果是 JDK 动态代理将不会为方法代理成功,因为在 JDK 中,代理对象会实现 {@link PersonService} 继承 {@link Proxy}接口,所以不是 {@link PersonServiceImpl} 类型
     * 有两种解决方案:
     * 1. 可以通过 {@link EnableAspectJAutoProxy} 的 proxyTargetClass = true 属性强制使用 cglib 进行代理
     * 2. this(pers.masteryourself.tutorial.spring.framework.aop.service.impl.PersonServiceImpl) 换成 this(pers.masteryourself.tutorial.spring.framework.aop.service.PersonService)
     */
    @Pointcut("this(pers.masteryourself.tutorial.spring.framework.aop.service.impl.PersonServiceImpl)")
    public void declareJointPointExpressionByJDK() {
    }

    /**
     * 如果是 cglib 动态代理会生效,在 cglib 中,代理对象会继承 {@link UserServiceImpl}
     */
    @Pointcut("this(pers.masteryourself.tutorial.spring.framework.aop.service.impl.UserServiceImpl)")
    public void declareJointPointExpressionByCglib() {
    }

    @Before("declareJointPointExpressionByJDK() || declareJointPointExpressionByCglib()")
    public void before() {
        System.out.println("aop 生效");
    }

}
2.1.4 target

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-target 工程

执行目标对象(被代理的对象)是给定类型的任何方法

@Aspect
@Component
@Order(1)
public class LoggingAspect {

    /**
     * target 表示目标对象的匹配类型是否是 {@link PersonServiceImpl},目标对象类型永远不会发生变化
     * 因为要代理的对象就是 {@link PersonServiceImpl},所以肯定是
     */
    @Pointcut("target(pers.masteryourself.tutorial.spring.framework.aop.service.PersonService)")
    public void declareJointPointExpression() {
    }

    @Before("declareJointPointExpression()")
    public void before() {
        System.out.println("aop 生效");
    }

}
2.1.5 args

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-args 工程

执行目标参数具有指定类型实例的任何方法

@Aspect
@Component
@Order(1)
public class LoggingAspect {

    /**
     * args 表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关
     * 第一个入参类型必须是 {@link pers.masteryourself.tutorial.spring.framework.aop.bean.User},之后的入参类型无规定
     */
    @Pointcut("args(pers.masteryourself.tutorial.spring.framework.aop.bean.User,..)")
    public void declareJointPointExpression() {
    }

    @Before("declareJointPointExpression()")
    public void before() {
        System.out.println("aop 生效");
    }

}
2.1.6 @target

执行目标对象具有指定类型注解的任何方法

2.1.7 @args

执行目标参数具有指定类型注解的任何方法

2.1.8 @within

执行目标对象具有指定类型注解的任何方法

2.1.9 @annotation

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-annotation 工程

执行目标方法具有指定类型注解的任何方法

@Aspect
@Component
@Order(1)
public class LoggingAspect {

    /**
     * 作用于标注了 {@link MasterYourself} 注解的方法
     */
    @Pointcut("@annotation(pers.masteryourself.tutorial.spring.framework.aop.config.MasterYourself)")
    public void declareJointPointExpression() {
    }

    @Before("declareJointPointExpression()")
    public void before() {
        System.out.println("aop 生效");
    }

}

2.2 Introductions

详见文档 https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-introductions

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-introductions 工程

@Aspect
@Component
@Order(1)
public class LoggingAspect {

    /**
     * 让 pers.masteryourself.tutorial.spring.framework.aop.service 下面的类都实现 {@link BaseService}接口,默认使用的 {@link BaseServiceImpl}作为实现
     */
    @DeclareParents(value = "pers.masteryourself.tutorial.spring.framework.aop.service.*", defaultImpl = BaseServiceImpl.class)
    public static BaseService baseService;

}

3. Spring 使用 XML 实现 AOP

详见文档 https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-schema

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-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="loggingAspect" class="pers.masteryourself.tutorial.spring.framework.aop.config.LoggingAspect"/>

    <bean id="personService" class="pers.masteryourself.tutorial.spring.framework.aop.service.PersonService"/>

    <aop:config>
        <aop:aspect ref="loggingAspect">
            <aop:pointcut id="loggingPointcut"
                          expression="execution(* pers.masteryourself.tutorial.spring.framework.aop.service..*.save*(..))"/>
            <aop:before method="beforeMethod" pointcut-ref="loggingPointcut"/>
            <aop:after method="afterMethod" pointcut-ref="loggingPointcut"/>
            <aop:after-returning method="afterReturning" pointcut-ref="loggingPointcut" returning="result"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="loggingPointcut" throwing="e"/>
            <!--<aop:around method="aroundMethod" pointcut-ref="loggingPointcut"/>-->
        </aop:aspect>
    </aop:config>

</beans>
发布了37 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/masteryourself/article/details/100640484
今日推荐