spring基础 - AOP

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创建动态代理时使用默认的构造方法导致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值