一文读懂Spring-AOP

前言

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 – 百度百科

AOP在Spring框架中用于

    提供声明式企业服务。最重要的此类服务是 声明式事务管理。

    让用户实现自定义方面,用 AOP 补充他们对 OOP 的使用。

实际工作使用场景

    鉴权、日志、缓存、内容传递、统一异常处理、分布式链路追踪、事务等。


AOP 概念

    Aspect(切面): 面向规则,具有相同规则的方法的集合体。

    Join point: 连接点, 程序执行过程中的一个点, 例如方法的执行或异常的处理,在 Spring AOP 中,一个连接点总是代表一个方法的执行。

    Pointcut: 切点,匹配连接点的谓词。Advice 与切点表达式相关联,并在与切点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。由切点表达式匹配的连接点的概念是 AOP 的核心,Spring 默认使用 AspectJ 切点表达式语言。

    Introduction:引入,Spring AOP 允许用户向任何建议的对象引入新的接口(和相应的实现)。是指在不更改源代码的情况,给一个现有类增加属性、方法,以及让现有类实现其它接口或指定其它父类等,从而改变类的静态结构。Spring AOP通过采代理加拦截器的方式来实现的,可以通过拦截器机制使一个实有类实现指定的接口

    Target object:目标对象,被一个或多个切面通知的对象。也称为“切面对象”。由于 Spring AOP 是使用运行时代理实现的,所以这个对象始终是一个被代理的对象。

    AOP proxy: 由 AOP 框架创建的对象,目的是实现切面契约(通知方法执行等等)。 在spring 框架中,AOP 代理是 JDK 动态代理或 CGLIB 代理。

    Weaving:将切面与其他应用程序类型或对象联系起来以创建切面对象。这可以在编译时(例如,使用 AspectJ 编译器)、加载时或运行时完成。Spring AOP 与其他纯 Java AOP 框架一样,在运行时执行编织

    Advice:切面在特定连接点执行的代码, 前置通知、后置通知’、环绕通知等。

AOP通知类型

    Before advice 前置通知,在连接点之前运行的通知、可以通过抛异常的方式阻止后续代码运行。

    After returning advice 业务代码成功返回后通知, 业务代码抛出异常则不执行。

    After throwing advice 如果方法通过抛出异常退出,则运行通知。

    After (finally) advice 不管连接点退出的方式(正常或异常返回)都将运行的通知。

    Around advice 环绕通知, 环绕通知可以在方法调用前后执行自定义行为。它还负责选择是继续连接点还是通过返回自己的返回值或抛出异常来缩短业务的方法执行。


样例

    1.@AspectJ @AspectJ 指的是一种将方面声明为带有注解的常规 Java 类的风格。

    1.1 启用@AspectJ

// 注解形式
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
// xml 形式
<aop:aspectj-autoproxy/>

    1.2 声明一个切面

// xml 形式
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of the aspect here -->
</bean>

// 注解
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {
    // 需要注意的是 一定要把这个类交给spring进行管理才行
}

    1.2 声明一个切入点

//使用@Pointcut注解时指定切入点表达式
@Pointcut("execution(* transfer(..))")
//使用一个返回值为void,方法体为空的方法来命名切入点,方法名即为切点名
private void anyOldTransfer() {}

// 组合形式 
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 

@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} 

// 都匹配的情况下 才执行
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 

    1.2.1 execution样例

//所有的公共方法
execution(public * *(..))

// set 开头的方法
execution(* set*(..))

// 某个类下所有的方法
execution(* com.xxx.service.XxxService.*(..))

// 当前包下所有的方法
execution(* com.xxx.service.*.*(..))

// 当前包及其子包
execution(* com.xxx.service..*.*(..))

// 当前包所有的类
within(com.xxx.service.*)

// 当前包及其子包所有的类
within(com.xxx.service..*)

// AccountService的任何实现类
this(com.xxx.service.AccountService)

// 目标对象是AccountServiceImpl的代理对象
target(com.spring.service.AccountServiceImpl)

// 任何单个参数的方法
args(java.io.Serializable)

// 目标对象需要有指定注解
@within(org.springframework.transaction.annotation.Transactional)

// 带有指定注解的方法
@annotation(org.springframework.transaction.annotation.Transactional)

// 这里是结合springbean 进行实现的, 意义为 spring bean 中名称:tradeService 的bean 进行代理
bean(tradeService)

// 通配符
bean(*Service)

    还记得ssm中事务的配置吗?

<aop:config>
    <aop:advisor
    <!-- 这里就是一个通用的切点 -->
        pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
        advice-ref="tx-advice"/>
</aop:config>

<!-- 指定切点 -->
<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

    1.3 通知

     前置通知


import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {
    
    // 这里可以是切点的方法,也可以直接写表达式  写方法的好处是在大型项目中更方便管理,以及复用
    @Before("com.xxx.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
    
    
    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck1() {
        // ...
    }
}

     后置通知

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // 方法正常返回通知
    }
    
    // 如果需要返回值 
    @AfterReturning(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }
    
}

     异常通知

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }
    
    // 拿到异常信息
    @AfterThrowing(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }
    
}

     finally通知

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }
    
    // 拿到异常信息
    @AfterThrowing(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }
    
}

     环绕通知

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    // JoinPoint
    // getArgs():返回方法参数。
    // getThis(): 返回代理对象。
    // getTarget(): 返回目标对象。
    // getSignature():返回所建议的方法的描述。
    // toString():打印所建议方法的有用描述。
    
    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed(); // 业务方法
        // stop stopwatch
        return retVal;
    }
    
}

    多个切面顺序控制

        @order() 注解 注解中的value方法值越小越先执行

        实现Ordered类 重写getOrder方法 返回int值越小越先执行


原理

    实现方式
       Spring AOP 默认为 AOP 代理使用标准的 JDK
动态代理。代理类必须有父类接口,且代理类的是实现父类接口,然后tager对象是默认实现类,所以强制向下转型则会报错。
        Spring AOP 也可以使用 CGLIB 代理。默认情况下,如果业务对象未实现接口,则使用 CGLIB。 使用CGLIB 实现的类是其子类。也可强制指定代理方式为CGLIB。如下

// 配置文件
spring.aop.proxy-target-class=true
// 注解形式
@EnableAspectJAutoProxy(proxyTargetClass = true)

// xml
<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>
// 或者
<aop:aspectj-autoproxy proxy-target-class="true"/>

    源码类

    切点基类

// org.springframework.aop.Pointcut

// 切入点根接口
public interface Pointcut {

    // 要通知的类
    ClassFilter getClassFilter();

    // 要通知的方法
    MethodMatcher getMethodMatcher();
    
    // TODO 将Pointcut接口分成两个方法是允许类和方法匹配部分以及细粒度组合操作
}
// org.springframework.aop.ClassFilter

// 类切入点
public interface ClassFilter {

    // 如果返回TRUE则匹配该类
    boolean matches(Class clazz);

    // 匹配所有类
    ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
// org.springframework.aop.MethodMatcher

// 方法切入点
public interface MethodMatcher {

    // 测试此切入点是否与目标类上的给定方法匹配 创建AOP代理时,进行匹配
    boolean matches(Method m, Class<?> targetClass);

    boolean isRuntime();

    // 如果两个参数matches匹配成功的时候,会调用isRuntime()方法,如果返回ture, 就进行这个方法进行匹配。应用场景: 比如需要统计用户登录次数时,那么登录传入的参数就是可以忽略的,则调用两个参数的matches()方法就已经足够了,但是若要在登陆时对用户账号执行特殊的操作(如赋予特殊的操作权限),就需要对参数进行一个类似于检验的操作,就需要调用三个参数的matches()进行匹配。 通俗来说,就是通过参数来确定是否需要进行代理
    boolean matches(Method m, Class<?> targetClass, Object... args);
}
Spring中常用的内置静态切入点

// 正则表达式切入点 使用 JDK 中的正则表达式
org.springframework.aop.support.JdkRegexpMethodPointcut

// 控制流程切入点 
org.springframework.aop.support.ControlFlowPointcut

// org.springframework.aop.support 包下的其它以Point结尾的类


// 如果想实现 自定义静态切入点 可以实现org.springframework.aop.support.StaticMethodMatcherPointcut 抽象类

    通知基类
在spring-aop中每个通知都是一个SpirngBean, 可以控制是否是单例对象

// org.aopalliance.intercept.MethodInterceptor

// 环绕通知基类
public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}

//  官网上的简单实现demo 
//    public class DebugInterceptor implements MethodInterceptor {
//
//        public Object invoke(MethodInvocation invocation) throws Throwable {
//            System.out.println("Before: invocation=[" + invocation + "]");
//            Object rval = invocation.proceed();
//            System.out.println("Invocation returned");
//            return rval;
//        }
//    }

// org.springframework.aop.MethodBeforeAdvice

// 前置通知
public interface MethodBeforeAdvice extends BeforeAdvice {

    // target 被代理的对象
    // m 反射的方法对象
    // args 参数
    void before(Method m, Object[] args, Object target) throws Throwable;
}
// org.springframework.aop.ThrowsAdvice

// 异常通知 标记接口 个人觉得这样的设计是为了处理不同的异常
public interface ThrowsAdvice extends AfterAdvice {

}

// 实现 标记不同的异常 然后根据匹配规则 进行调用
public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}


public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

// 不同的异常也可以组合处理, 适配器
public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}
// 后置通知

// org.springframework.aop.AfterReturningAdvice
public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}

// 引入 很特殊的一个功能 一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。

// 引入通知 只适用于类增强 而不是方法级别

// org.springframework.aop.IntroductionAdvisor
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

// org.springframework.aop.IntroductionInfo
public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}

// 想深入了解 可以参考官网 https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-api-advice-introduction
// Advisor 包含一个与切入点表达式相关联的的通知对象。

// org.springframework.aop.support.DefaultPointcutAdvisor

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {
    public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
    		this.pointcut = pointcut;
    		setAdvice(advice);
    }
}

// spring-aop  创建代理对象的工厂bean ProxyFactoryBean

// org.springframework.aop.framework.ProxyFactoryBean

public class ProxyFactoryBean extends ProxyCreatorSupport
		implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {

    // 部分属性说明
    @Nullable
	private String targetName; // 要代理的对象
	
	// 表示是否在生成代理对象时需要启用自动检测被代理对象实现的接口
    private boolean autodetectInterfaces = true;

    // CGLIB or JDK 代理
	private boolean autodetectInterfaces = true;

    // 这里有个细节 需要注意, 继承于 org.springframework.aop.framework.ProxyConfig 的属性,在代理对象为实现类,而不是接口的时候,此属性设置为true, 这个时候就会使用CGLIB 创建代理
    private boolean proxyTargetClass = false;

    // 要使用的Advisor、拦截器或其他建议名称的数组 这里就是控制先后调用顺序
    @Nullable
	private String[] interceptorNames;

}

授人以鱼不如授人以渔, 每个人都是天才。

关注公众号【Java菜鸟笔记】 领取海量技术书籍、面试资料。利用碎片时间学习

一文读懂Spring-AOP

参考:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop

上一篇:C# 关于AOP简单介绍


下一篇:AOP-底层原理(JDK动态代理实现)