AOP,面向切面编程,通常用来封装与具体业务无关,却用被各业务模块统一调用的逻辑,比如日志管理、权限校验、事务管理等。
spring AOP基于动态代理,对类的方法进行增强。
使用示例
1、引入依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
2、启动类(主配置类)上添加@EnableAspectJAutoProxy注解,以支持处理AspectJ的@Aspect注解。
3、定义一个@Aspect注解的切面类,在切面类中添加@Pointcut注解的切点(规定对哪些方法织入通知/增强),以及切点对应的通知/增强(包括织入时机,前置、后置还是环绕等,以及具体逻辑)
package org.example.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.example.vo.ResultVo;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Slf4j
public class LogAspect {
/*
定义一个切点,常用的规则有:
- execution表达式,匹配方法,比如"execution(* org.example.controller.BookController.getBookById(..))",表示匹配
org.example.controller包下的BookController类的getBookById方法,最前面的*表示任意返回值类型,括号里的..表示任意参数类型。
- within表达式,匹配类,比如"within(org.example.controller.BookController)",表示匹配BookController类的所有方法。
- @annotation,匹配注解,比如"@annotation(org.example.annotation.TestPointcut)",表示匹配@TestPointcut自定义注解标注的方法。
需要注意的是,不要把被切入的目标方法声明为private,否则会报空指针异常。
*/
@Pointcut("execution(* org.example.controller.BookController.getBookById(..))")
public void pointCut() {
//default empty
}
@Before("pointCut()")
public void beforeGetBookById() {
log.info("beforeGetBookById");
}
@After("pointCut()")
public void afterGetBookById() {
log.info("afterGetBookById");
}
//环绕通知中,proceedingJoinPoint的proceed方法执行的就是我们的具体业务逻辑
@Around("pointCut()")
public ResultVo aroundGetBookById(ProceedingJoinPoint proceedingJoinPoint) {
log.info("aroundGetBookById start");
ResultVo resultVo;
try {
resultVo = (ResultVo) proceedingJoinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
log.info("aroundGetBookById end");
return resultVo;
}
}
现在启动服务,通过网络请求调用BookController#getBookById方法,可以看到输出如下:
@Around增强处理的逻辑包裹在@Before和@After逻辑的外层,最中间是业务代码的执行。
原理
先看下启动类上的@EnableAspectJAutoProxy注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies. The default is {@code false}.
*/
boolean proxyTargetClass() default false;
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
}
它包含了一个@Import(AspectJAutoProxyRegistrar.class)注解,同时还有两个属性,其中一个proxyTargetClass,指示是否创建基于子类(CGLIB)的代理,默认false,即使用JDK的代理机制。
再看下AspectJAutoProxyRegistrar.class:
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
实现ImportBeanDefinitionRegistrar接口,注册了一个org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator 类型的bean difinition。
再看下AnnotationAwareAspectJAutoProxyCreator(注解感知的AspectJ自动代理创造器),顾名思义,这个东西可以感知AspectJ的注解,并且自动创建代理对象,实现代码增强的功能。
通常一个bean要在spring容器启动过程中发挥作用,它本身需要是一个post processor,沿着它的父类往上找,果然实现了SmartInstantiationAwareBeanPostProcessor接口,并且重写了postProcessBeforeInstantiation、postProcessAfterInitialization方法。
AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInstantiation:
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
Object cacheKey = getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
//如果当前bean已经在待通知的bean列表中,返回
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
//判断是否需要跳过
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
// Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
//如果有自定义的TargetSource,创建自定义代理。
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}
这里面有个shouldSkip方法需要注意:
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
// TODO: Consider optimization by caching the list of the aspect names
//寻找类型为Advisor的bean,加入候选Advisors
List<Advisor> candidateAdvisors = findCandidateAdvisors();
for (Advisor advisor : candidateAdvisors) {
if (advisor instanceof AspectJPointcutAdvisor &&
((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
return true;
}
}
return super.shouldSkip(beanClass, beanName);
}
findCandidateAdvisors的逻辑如下:
@Override
protected List<Advisor> findCandidateAdvisors() {
// Add all the Spring advisors found according to superclass rules.
List<Advisor> advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
/*
buildAspectJAdvisors方法,找到@Aspect注解的切面类,获取切面类方法,解析@Before、@After等注解,获取切点表达式,然后构建方法对
应的通知器advisor
*/
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
获取到的通知器结构如下图:
此时切面类和通知器都已经解析并保存,但是通知器的切点还是"pointCut()",还没有解析到具体的目标类和方法上。
接下来看看postProcessAfterInitialization方法。
AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization:
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
//如果符合条件,就对这个bean进行包装(代理)
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
进入wrapIfNecessary方法:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
//找到与当前bean匹配的通知器和通知
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//创建代理对象并返回
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
沿着getAdvicesAndAdvisorsForBean方法一直找到AopUtils.canApply方法:
这里的pc是当前通知器里的切点,先调用pc.getClassFilter()方法,解析"pointCut()"这个切点具体对应的execution表达式,放进pc的成员变量中,然后再拿表达式去和目标类(当前初始化的bean类型)进行类型匹配。
这里如果类型不匹配则直接返回,如果类型匹配,再获取目标类中的方法,进行方法的匹配;如果最终匹配成功,则说明当前通知器与当前bean匹配。
沿着getClassFilter方法一直找到PointcutParser#parsePointcutExpression方法:
public PointcutExpression parsePointcutExpression(String expression, Class<?> inScope, PointcutParameter[] formalParameters)
throws UnsupportedPointcutPrimitiveException, IllegalArgumentException {
PointcutExpressionImpl pcExpr = null;
try {
//这里对我们定义的引用切点表达式"pointCut()"进行了解析,构建了一个ReferencePointcut对象。
Pointcut pc = resolvePointcutExpression(expression, inScope, formalParameters);
//这里具体化了"pointCut()",在切点所在类logAspect中扫描带有@Pointcut注解的方法,根据value属性(execution表达式)构建了一个
//KindedPointcut对象,重新赋值给pc
pc = concretizePointcutExpression(pc, inScope, formalParameters);
validateAgainstSupportedPrimitives(pc, expression); // again, because we have now followed any ref'd pcuts
//构建切点表达式对象,保存在顶层AspectJExpressionPointcut切点对象的成员变量中
pcExpr = new PointcutExpressionImpl(pc, expression, formalParameters, getWorld());
} catch (ParserException pEx) {
throw new IllegalArgumentException(buildUserMessageFromParserException(expression, pEx));
} catch (ReflectionWorld.ReflectionWorldException rwEx) {
throw new IllegalArgumentException(rwEx.getMessage());
}
return pcExpr;
}
解析完的切点对象中的pointcutExpression属性结构如下图:
回到wrapIfNecessary方法,找到匹配的通知器后,就是调用createProxy方法创建当前bean的代理对象。
沿着调用链往下找,在createAopProxy方法中,对代理方式进行了选择,示例中的BookController并非接口,所以走到了Cglib动态代理。
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
看一下最后生成的动态代理对象:
里面的一些自动注入的属性值都是null,是Cglib创建动态代理时使用默认的构造方法导致。