【Spring成神之路】老兄,来一杯Spring AOP源码吗?

文章目录

  • 一、引言
  • 二、Spring AOP的使用
  • 三、Spring AOP的组件
    • 3.1 Pointcut源码
    • 3.2 Advice源码
    • 3.3 Advisor源码
    • 3.4 Aspect源码
  • 四、Spring AOP源码刨析
    • 4.1 configureAutoProxyCreator源码解析
    • 4.2 parsePointcut源码解析
    • 4.3 parseAdvisor源码解析
    • 4.4 parseAspect源码解析
    • 4.5 小总结
    • 4.6 代理创建
  • 五、总结

一、引言

AOPSpring框架的重点之一,AOP全称为Aspect-Oriented Programming,意思为面向切面编程,这种编程方式是一种编程的范式。

AOP允许开发者将横向关注点(如日志记录、事务管理等操作)与业务逻辑分开来,从而提高代码的模块化和可维护性。

下面从浅入深彻底搞懂Spring AOP的底层原理!

注意,本篇文章中使用的Spring框架的版本是4.0.0.RELEASE,不同版本之间源码会有一点点不同,但是大体逻辑差不多。

推荐阅读者需要对动态代理以及Spring IOC源码有一定了解,如果不了解可以阅读以下文章

  1. 都2024年了,还有人不懂动态代理么?
  2. 【Spring成神之路】一次SpringIOC源码之旅,女朋友也成为了Spring大神!
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.0.0.RELEASE</version>
</dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>4.0.0.RELEASE</version>
</dependency>

二、Spring AOP的使用

UserService接口的定义

public interface UserService {String selectList();
}

UserService接口实现类

public class UserServiceImpl implements UserService {@Overridepublic String selectList() {System.out.println("小明, 小红, 小蓝");return "小明, 小红, 小蓝";}
}

AOP前置处理与后置处理

public class AOPUtil {private void before(JoinPoint joinPoint) {//获取方法签名Signature signature = joinPoint.getSignature();//获取参数信息Object[] args = joinPoint.getArgs();System.out.println("log---" + signature.getName() + "I am before");}private void after(JoinPoint joinPoint) {//获取方法签名Signature signature = joinPoint.getSignature();//获取参数信息Object[] args = joinPoint.getArgs();System.out.println("log---" + signature.getName() + "I am after");}
}

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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="messageService" class="spring.aop.UserServiceImpl"/><bean id="aopUtil" class="spring.aop.AOPUtil"/><aop:config><aop:aspect ref="aopUtil"><aop:pointcut id="myPoint"  expression="execution(* spring.aop.UserService.selectList(..))"/><aop:before method="before" pointcut-ref="myPoint"/><aop:after method="after" pointcut-ref="myPoint"/></aop:aspect></aop:config></beans>

测试类

public class AOPTest {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");System.out.println("context 启动成功");UserService userService = context.getBean(UserService.class);userService.selectList();}
}

image-20240707174827569

我们只需要编写好切面的逻辑即可,然后在xml配置文件配置后切面的方法的执行顺序即可实现在目标方法的前面或后面运行某个逻辑。

这当中到底发生了什么呢?这些切面方法是何时被调用呢?别急接着往下看!


三、Spring AOP的组件

在阅读Spring AOP源码之前,先来看看Spring AOP的几个组件,这里源码会采取点到为止的方式,并不深入讲解

  1. Pointcut:定义切面的匹配点,主要是类和方法。比如上面例子的spring.aop.UserService.selectList(..)方法就是匹配点。
  2. Advice:定义切面的行为,即在匹配点执行的操作。也就是上面例子的aop:beforeaop:after
  3. Advisor:将PointcutAdvice组合成一个对象,也就是一个完整的切面。
  4. Aspect:使用注解或XML配置方式定义的切面,通常包含了多个Advisor

3.1 Pointcut源码

Pointcut是一个接口,提供了getClassFilter方法和getMethodMatcher方法,用于获取当前匹配的类和方法

public interface Pointcut {/*** 返回一个ClassFilter对象, 用于确定哪些类被匹配,返回的对象不能为null*/ClassFilter getClassFilter();/*** 返回一个MethodMatcher对象,用于确定哪些方法被匹配*/MethodMatcher getMethodMatcher();/*** Canonical Pointcut instance that always matches.*/Pointcut TRUE = TruePointcut.INSTANCE;}

AspectJExpressionPointcutPointcut的实现,除了上面两个方法,还提供了matches方法,用于判断是否匹配。

public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {// 类的匹配public boolean matches(Class<?> targetClass) {}// 方法的匹配 public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {}
}

3.2 Advice源码

Advice接口没有需要实现的方法,它是用于定义切面的行为的,比如方法前切入、方法后切入、环绕切入等等。

public interface Advice {}
// 前置切入实现
public interface BeforeAdvice extends Advice {}// 后置切入实现
public interface AfterAdvice extends Advice {}

3.3 Advisor源码

Advisor接口定义了getAdvice方法和isPerInstance方法

public interface Advisor {/*** 返回与该Advisor关联的Advice*/Advice getAdvice();/*** 返回这个Advice是否与特定的实例关联。*/boolean isPerInstance();}

3.4 Aspect源码

Spring AOP并没有Aspect这个接口,但是Aspect通常指下面这个

<aop:aspect ref="aopUtil">
</aop:aspect>

四、Spring AOP源码刨析

阅读Spring AOP源码和阅读Spring IOC源码的步骤一样,都是从读取application.xml配置文件开始。

因为AOP的配置信息是写在XML配置文件的,所以肯定需要读取XML配置文件获取AOP相关的信息,那么我们就看看这其中都做了什么吧。

直接定位到org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions这个方法,不了解的可以先看看【Spring成神之路】一次SpringIOC源码之旅,女朋友也成为了Spring大神!

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {NodeList nl = root.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element) node;if (delegate.isDefaultNamespace(ele)) {// 如果是bean 这些就走这个逻辑parseDefaultElement(ele, delegate);}else {// AOP走这个逻辑delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}
}

既然知道parseCustomElement方法是处理aop标签的,那么我们就进去看看是怎么个事?

public BeanDefinition parseCustomElement(Element ele) {return parseCustomElement(ele, null);
}public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {// 获取当前的命名空间String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根据命名空间获取对应的处理器NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 用处理器就行解析return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

parseCustomElement方法干三件事:

  1. 获取元素的命名空间
  2. 根据命名空间获取对应的处理器
  3. 调用处理器的parse方法进行处理

继续DEBUG,定位到了org.springframework.beans.factory.xml.NamespaceHandlerSupport#parse方法

public BeanDefinition parse(Element element, ParserContext parserContext) {// 获取适合解析当前元素的BeanDefinitionParserBeanDefinitionParser parser = findParserForElement(element, parserContext);// 进行解析并返回一个BeanDefinitionreturn (parser != null ? parser.parse(element, parserContext) : null);
}private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {// 获取元素的本地名称, 比如aop:config,本地名称就是configString localName = parserContext.getDelegate().getLocalName(element);// 获取该本地名称的解析器BeanDefinitionParser parser = this.parsers.get(localName);if (parser == null) {parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);}return parser;
}
public BeanDefinition parse(Element element, ParserContext parserContext) {CompositeComponentDefinition compositeDef =new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));parserContext.pushContainingComponent(compositeDef);// 设置自动代理创建器,这是Spring AOP实现的核心configureAutoProxyCreator(parserContext, element);// 遍历当前标签下的标签不同的标签进行不同处理List<Element> childElts = DomUtils.getChildElements(element);for (Element elt: childElts) {String localName = parserContext.getDelegate().getLocalName(elt);if (POINTCUT.equals(localName)) {parsePointcut(elt, parserContext);}else if (ADVISOR.equals(localName)) {parseAdvisor(elt, parserContext);}else if (ASPECT.equals(localName)) {parseAspect(elt, parserContext);}}parserContext.popAndRegisterContainingComponent();return null;
}

4.1 configureAutoProxyCreator源码解析

configureAutoProxyCreator方法是Spring IOC的核心,主要作用是确保 AspectJ 自动代理创建器被正确注册到 Spring 容器中!

private void configureAutoProxyCreator(ParserContext parserContext, Element element) {AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
}public static void registerAspectJAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);registerComponentIfNecessary(beanDefinition, parserContext);
}

该方法的目的是往Spring IOC容器中注册一个AspectjAwareAdvisorAutoProxyCreator,其负责创建代理对象和实现切面编程。


4.2 parsePointcut源码解析

parsePointcut方法的主要作用是负责解析aop:pointcut标签

<aop:pointcut id="myPoint"  expression="execution(* spring.aop.UserService.selectList(..))"/>
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {// 获得String id = pointcutElement.getAttribute(ID);// 获得切点表达式String expression = pointcutElement.getAttribute(EXPRESSION);// 定义切点存储AbstractBeanDefinition pointcutDefinition = null;try {// 将当前解析的切点ID压入解析状态栈this.parseState.push(new PointcutEntry(id));// 根据切点表达式创建切点定义pointcutDefinition = createPointcutDefinition(expression);// 设置切点的来源pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));// 切点的Bean名称String pointcutBeanName = id;if (StringUtils.hasText(pointcutBeanName)) {// 如果设置了id, 将切点注册到spring ioc容器中parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);}else {// 否则也是注入进spring ioc容器中并设置别名pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);}// 注册一个PointcutComponentDefinition组件parserContext.registerComponent(new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));}finally {// 从栈弹出this.parseState.pop();}return pointcutDefinition;
}

4.3 parseAdvisor源码解析

parseAdvisor方法负责解析<advisor>标签

private void parseAdvisor(Element advisorElement, ParserContext parserContext) {AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);String id = advisorElement.getAttribute(ID);try {this.parseState.push(new AdvisorEntry(id));String advisorBeanName = id;// 注册进容器if (StringUtils.hasText(advisorBeanName)) {parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);}else {advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);}// 解析切点Object pointcut = parsePointcutProperty(advisorElement, parserContext);// 根据pointcut的类型,将其添加到advisorDef的属性值中,并注册相关的组件。if (pointcut instanceof BeanDefinition) {advisorDef.getPropertyValues().add(POINTCUT, pointcut);parserContext.registerComponent(new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));}else if (pointcut instanceof String) {advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));parserContext.registerComponent(new AdvisorComponentDefinition(advisorBeanName, advisorDef));}}finally {this.parseState.pop();}
}

4.4 parseAspect源码解析

parseAspect负责解析aspect标签

private void parseAspect(Element aspectElement, ParserContext parserContext) {// 获取ID值String aspectId = aspectElement.getAttribute(ID);// 获取ref属性String aspectName = aspectElement.getAttribute(REF);try {this.parseState.push(new AspectEntry(aspectId, aspectName));List<BeanDefinition> beanDefinitions = new ArrayList<>();List<BeanReference> beanReferences = new ArrayList<>();// 获得当前节点下的所有<declare-parents>子元素List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);for (int i = METHOD_INDEX; i < declareParents.size(); i++) {Element declareParentsElement = declareParents.get(i);// 解析节点并注册进IOC容器中beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));}// 获取当前节点的子节点NodeList nodeList = aspectElement.getChildNodes();boolean adviceFoundAlready = false;for (int i = 0; i < nodeList.getLength(); i++) {Node node = nodeList.item(i);// 是否是advice节点if (isAdviceNode(node, parserContext)) {// 是否是第一个advice节点if (!adviceFoundAlready) {adviceFoundAlready = true;if (!StringUtils.hasText(aspectName)) {parserContext.getReaderContext().error("<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",aspectElement, this.parseState.snapshot());return;}// 添加一个运行时bean引用到beanReferencesbeanReferences.add(new RuntimeBeanReference(aspectName));}// 如果节点是advice,则调用parseAdvice方法解析它,并创建一个顾问(advisor)的bean定义。AbstractBeanDefinition advisorDefinition = parseAdvice(aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);beanDefinitions.add(advisorDefinition);}}// 使用解析得到的信息创建一个切面组件定义AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);// 将切面组件定义推入解析上下文。parserContext.pushContainingComponent(aspectComponentDefinition);// 获取所有<pointcut>子元素。List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);for (Element pointcutElement : pointcuts) {// 解析切点parsePointcut(pointcutElement, parserContext);}// 从解析上下文中弹出并注册包含的组件。parserContext.popAndRegisterContainingComponent();}finally {this.parseState.pop();}
}

总得来说,就是解析标签,将标签的内容包装成BeanDefinition并注册进IOC容器。

前面说到Advice就是一个切面的行为,而parseAdvice方法就是负责解析这个切面行为的

private AbstractBeanDefinition parseAdvice(String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {try {this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));// 解析advice节点中的 method 属性,将其创建成一个 beanDefinition 对象RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);methodDefinition.getPropertyValues().add("targetBeanName", aspectName);methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));methodDefinition.setSynthetic(true);// 关联aspectName(对应的就是案例中的aopUtil),包装为 SimpleBeanFactoryAwareAspectInstanceFactory 对象RootBeanDefinition aspectFactoryDef =new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);aspectFactoryDef.setSynthetic(true);// 解析切点并综合上面两个bean包装成AbstractAspectJAdviceAbstractBeanDefinition adviceDef = createAdviceDefinition(adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,beanDefinitions, beanReferences);// 最终包装成AspectJPointcutAdvisorRootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);advisorDefinition.setSource(parserContext.extractSource(adviceElement));advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);if (aspectElement.hasAttribute(ORDER_PROPERTY)) {advisorDefinition.getPropertyValues().add(ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));}// 注册advisorparserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);return advisorDefinition;}finally {this.parseState.pop();}
}

4.5 小总结

好吧,估计有点绕晕了。就parseAspect方法来说,主要的目的是想组装一个Advisor,但是得先把把advice初始化,这个时候就需要解析标签中的method等实例化出来methodDefinition

有了methodDefinition就知道这个方法是前置增还是后置增强。接着实例化Pointcut等这些,都是实例化完成后,再再按照下图的组件关系组装出一个完整的Advisor,这个AdvisorAOP的所有信息,并保存在了IOC容器中。

image-20240708215753514

既然切点、切面等信息以及包装好了,代理对象是怎么创建返回的呢?接着往下看。不过在此需要先加一点前置知识。


4.6 代理创建

IOC源码篇的时候,有讲解过org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization方法。

image-20240708221659137

org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons会实例化所有非延迟初始化的单例Bean

在这个方法中大致内容就是获取所有beanDefinitionNames,然后进行遍历,如果是属于FactoryBean则进行FactoryBean的处理,否则正常处理。

List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) {// ....省略}else {getBean(beanName);}}
}

因为AOP包装的Bean肯定不是FactoryBean,因此直接进入getBean方法

public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {// 转化别名String beanName = transformedBeanName(name);Object bean;// 提前检查单例缓存中是否有手动注册的单例对象,这和循环依赖有关联,以后会讲到Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {// 省略....}else {// 当对象都是单例的时候会尝试解决循环依赖的问题,但是原型模式下如果存在循环依赖的情况,那么直接抛出异常if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}// 省略若干行代码RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);String[] dependsOn = mbd.getDependsOn();// 是单例if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {// 创建单例return createBean(beanName, mbd, args);}catch (BeansException ex) {destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}}return (T) bean;
}

整体代码比较啰嗦,这里省略一些代码,大体内容如下:

  1. 转化bean的名字
  2. 是否有循环依赖,有的话解决循环依赖问题
  3. Bean属于单例,进行创建单例
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)try {Object beanInstance = doCreateBean(beanName, mbdToUse, args);if (logger.isTraceEnabled()) {logger.trace("Finished creating instance of bean '" + beanName + "'");}return beanInstance;}}protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){// 根据执行bean使用对应的策略创建新的实例,如,工厂方法,构造函数主动注入、简单初始化BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);// 对bean的属性进行填充,将各个属性值注入,其中,可能存在依赖于其他bean的属性,则会递归初始化依赖的beanpopulateBean(beanName, mbd, instanceWrapper);// 执行初始化逻辑exposedObject = initializeBean(beanName, exposedObject, mbd);
}

ok!终于定位到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)这个方法了,这个方法就是负责初始化Bean。

以上的内容属于IOC源码知识的补充,下面正式开始AOP代理创建的源码解析!!!

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {Object wrappedBean = bean;if (mbd == null || !mbd.isSynthetic()) {// 执行前置通知wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try {invokeInitMethods(beanName, wrappedBean, mbd);}catch (Throwable ex) {throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null),beanName, "Invocation of init method failed", ex);}if (mbd == null || !mbd.isSynthetic()) {// 执行后置通知wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}return wrappedBean;
}

前置通知和AOP没半毛钱关系,主要关注执行后置通知

为什么AOP与后置通知有关系?

还记不记得,前面说过会向容器注入AspectjAwareAdvisorAutoProxyCreator,这是类负责代理的创建

image-20240708223931696

是的,你没看错AspectjAwareAdvisorAutoProxyCreator是一个实现了BeanPostProcessor的类

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization方法的源码很简单,就是调用postProcessAfterInitialization返回一个bean,对当前的bean进行包装

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {Object current = processor.postProcessAfterInitialization(result, beanName);if (current == null) {return result;}result = current;}return result;
}

AspectjAwareAdvisorAutoProxyCreatorpostProcessAfterInitialization的实现是怎么样的?

其实AspectjAwareAdvisorAutoProxyCreator并没有该方法的实现,而是在其父类实现了org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {// 获取当前bean的key:如果beanName不为空,则以beanName为key,如果为FactoryBean类型,// 前面还会添加&符号,如果beanName为空,则以当前bean对应的class为keyObject cacheKey = getCacheKey(bean.getClass(), beanName);// 判断这个bean是否正在在被代理,如果正在被代理则不处理if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 进入需要被代理,进行代理处理return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 获取当前Bean的Advices和AdvisorsObject[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);// 对Bean的代理状态缓存if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 创建代理Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 缓存代理Bean的类型this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

着重关心一下createProxy,看看创建代理是怎么个事

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {// 给bean定义设置暴露属性if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}// 创建代理工厂ProxyFactory proxyFactory = new ProxyFactory();// 将当前实例的配置复制到proxyFactoryproxyFactory.copyFrom(this);// 决定对于给定的Bean是否应该试应targetClass而不是接口代理if (!proxyFactory.isProxyTargetClass()) {// 检查使用JDK代理还是cglib代理if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {// 添加代理接口evaluateProxyInterfaces(beanClass, proxyFactory);}}// 构建增强器Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);// 设置要代理的类proxyFactory.setTargetSource(targetSource);// 定制代理customizeProxyFactory(proxyFactory);// 控制代理工程被配置之后,是否还允许修改通知,默认值是falseproxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// 完成上面这些之后开始创建代理对象return proxyFactory.getProxy(getProxyClassLoader());
}

createAopProxy方法是根据代理目标选择JDK代理或者是Cglib代理,代码挺容易看懂的,就不解释了

public Object getProxy(@Nullable ClassLoader classLoader) {return createAopProxy().getProxy(classLoader);
}
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (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);}
}

至于getProxy方法,JDK代理和CGLib代码都有不同的实现,在都2024年了,还有人不懂动态代理么?有讲述过JDK代理和CGLIB代理的实现原理,具体请看这篇文章。


五、总结

经过上面这么一步步过来,这个Bean的代理对象就成功被内放进Spring IOC容器中,当我们下次从容器getBean的时候就可以获取到代理的对象了。

虽然源码很复杂,但是其实不需要记住每一步,只需要记住Spring AOP实现的大体步骤思路即可

  1. 读取并解析配置文件,将AOP的四大组件,解析封装成Bean,存放进IOC中
  2. IOC注入一个AspectjAwareAdvisorAutoProxyCreator,这是一个实现了BeanPostProcessor接口。这个非常重要,这个是Spring AOP的核心
  3. Spring IOC完成刷新之后,会进行单例Bean的懒加载,在懒加载的过程中会获取容器中的所有BeanPostProcessor实现类,然后调用其postProcessAfterInitialization方法
  4. AspectjAwareAdvisorAutoProxyCreator的实现会判断当前Bean需要被代理,然后根据目标Bean是否实现接口等条件判断使用JDK代理还是CGlib代理,然后返回代理类并放进容器

总体核心就这四点,但里面的细节还有很多很多,这篇文章就先写这吧。明天还要上班呢~


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/43333.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑缺少dll文件是怎么回事?教你5种有效的解决方法

当您的计算机显示DLL文件已经遗失时&#xff0c;您应如何应对呢&#xff1f;实际上&#xff0c;针对此类DLL文件的处置过程相对来说较为简易。今日&#xff0c;我们在此为大家详细介绍此领域的相关知识&#xff0c;让大家轻松解决电脑中因丢失DLL文件而产生的问题。 一、关于DL…

案例开发-日程管理-第一期

九 案例开发-日程管理-第一期 共7期 9.1 登录页及校验 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><style>.ht{text-align: center;color: cadetblue;font-family: 幼…

MVC 生成验证码

在mvc 出现之前 生成验证码思路 在一个html页面上&#xff0c;生成一个验证码&#xff0c;在把这个页面嵌入到需要验证码的页面中。 JS生成验证码 <script type"text/javascript">jQuery(function ($) {/**生成一个随机数**/function randomNum(min, max) {…

RedHat Linux8 修改root管理员账户密码命令

RedHat Linux8 修改root管理员账户密码命令&#xff1a; sudo passwd root RedHat重置root管理员密码&#xff1a; 1. 查看Linux系统版本信息 cat /etc/redhat-release2. 重置密码 2.1 进入内核编辑界面 重启Linux系统并出现引导界面&#xff0c;按下键盘上的e键进入内…

Prometheus+Grafana主机运行数据

目录 介绍 安装Node Exporter 配置Prometheus 验证配置 导入仪表盘 介绍 Prometheus是一款开源的监控和警报工具&#xff0c;而Node Exporter是Prometheus的一个官方插件&#xff0c;用于采集主机上的各种系统和硬件指标。 安装Node Exporter 下载最新版本的Node Export…

FLinkCDC引起的生产事故(二)

背景&#xff1a; 最近在做实时数据的抽取工作&#xff0c;利用FLinkCDC实时抽取目标库Oracle的数据到Doris中&#xff0c;但是在抽取的过程中&#xff0c;会导致目标库的生产库数据库非常卡顿&#xff0c;为了避免对生产环境的数据库造成影响&#xff0c;对生产环境的数据库利…

qcom 平台efuse机器抓取dump log的方法

引言&#xff1a; qcom 平台机器&#xff0c;一旦efuse后机器将无法抓取dump log qcom 原文&#xff1a; efuse机器抓取dump log的方法如下&#xff1a; 一、修改配置文件&#xff1a; 把kamorta_debugpolicy.xml 在配置了debugpolicy&#xff08;加入串号和打开开关&#x…

怎么检查SSL证书是否有效?

SSL证书的有效性对于保护网站数据安全和用户隐私至关重要。然而&#xff0c;有时可能会出现证书过期、无效或被吊销的情况。为了确保网站的安全性&#xff0c;对SSL证书的有效性进行检查至关重要。本文将介绍几种常用的方法来检查SSL证书的有效性&#xff0c;帮助大家有效评估和…

基于Make的c工程No compilation commands found报错

由于安装gcc时只安装了build-essential&#xff0c;没有将其添加到环境变量中&#xff0c;因此打开Make工程时&#xff0c;CLion会产生如下错误&#xff1a; 要解决这个问题&#xff0c;一个方法是将GCC添加到环境变量中&#xff0c;但是这个方法需要修改至少两个配置文件&…

数据结构——约瑟夫环C语言链表实现

约瑟夫环问题由古罗马史学家约瑟夫&#xff08;Josephus&#xff09;提出&#xff0c;他参加并记录了公元66—70年犹太人反抗罗马的起义。在城市沦陷之后&#xff0c;他和40名死硬的将士在附近的一个洞穴中避难。起义者表示“宁为玉碎不为瓦全”&#xff0c;约瑟夫则想“留得青…

dledger原理源码分析(四)-日志

简介 dledger是openmessaging的一个组件&#xff0c; raft算法实现&#xff0c;用于分布式日志&#xff0c;本系列分析dledger如何实现raft概念&#xff0c;以及dledger在rocketmq的应用 本系列使用dledger v0.40 本文分析dledger的日志&#xff0c;包括写入&#xff0c;复制…

网站SEO百度搜索排名—通过关键字提升网站流量

添加网站关键字 <meta name"keywords" content"系统通过搜索到的关键字XXXXXXXXX"> <meta name"description" content"网站的介绍内容XXXXXXXXXXXXXXXXX"> <title>平台名称XXXXXXX</title> 在 百度站点管理 …

STM32串口通讯(RS232、RS485、TTL)详解

前言 STM32串口&#xff08;Serial Communication Interface&#xff09;是STM32微控制器中用于串行通信的接口&#xff0c;通常指的是USART&#xff08;通用同步异步收发器&#xff09;或UART&#xff08;通用异步收发传输器&#xff09;。这些接口允许STM32微控制器与其他设…

zabbix 学习笔记

文章目录 Zabbix 安装Ubuntu 18.04.1 server 安装Zabbix 4.0Centos7 安装Zabbix3.4Centos7 安装zabbix4.2Centos7.1908安装zabbix 基于ngixDebian11安装zabbix6.0LTS 基于PostgreSQL和NGINXAlmaLinux9.2使用国内清华源在线安装zabbix6.0.18LTS 基于MySQL和NGINXUbunut22.04使用…

图文讲解IDEA如何导入JDBC驱动包

前言 学习JDBC编程,势必要学会如何导入驱动包,这里笔者用图文的方式来介绍 视频版本在这里 50秒教你怎么导入驱动包然后进行JDBC编程的学习_哔哩哔哩_bilibili 忘记录音频了,大伙凑合着看 下载驱动包 https://mvnrepository.com/artifact/mysql/mysql-connector-java 去中…

GB28181设备如何添加

简介 此篇描述视频网关&#xff08;中间件&#xff09;接入大华、海康、ONVIF设备&#xff08;NVR、摄像头&#xff09;、GB28181设备步骤和流程。 阅读本文档之前建议先阅览视频网关&#xff08;中间件&#xff09;用户使用手册。 接入方式和说明 视频网关&#xff08;中间…

论文学习_Getafix: learning to fix bugs automatically

1. 引言 研究背景:现代生产代码库极其复杂并且不断更新。静态分析器可以帮助开发人员发现代码中的潜在问题(在本文的其余部分中称为错误),这对于在这些大型代码库中保持高代码质量是必要的。虽然通过静态分析尽早发现错误是有帮助的,但修复这些错误的问题在实践中仍然主要…

jmeter-beanshell学习7-props获取全局变量和设置全局变量

继续写点不痛不痒的小东西。第一篇写了vars设置变量&#xff0c;但是vars只能作用在同一个线程组。跨线程组情况比较少&#xff0c;要是用到跨线程组&#xff0c;有个pros&#xff0c;用法和vars一样。 在setup线程组设置变量a&#xff0c;执行的时候&#xff0c;jmeter会先执行…

第二证券:转股溢价率是什么意思?高好还是低好?

转股溢价率是指可转债在商场上的交易价格相对于其转股价值的溢价份额&#xff0c;能够用来衡量投资者为取得将债券转换为股票权力而付出的额定金额&#xff0c;是可转债的重要指标。 转股溢价率的核算公式为&#xff1a;溢价率&#xff1d;&#xff08;转债价格-转股价值&…

MySql性能调优01-[数据结构和索引]

数据结构和索引 什么是索引索引的种类常见索引数据结构和区别二叉树 红黑树 什么是索引 索引的种类 在Mysql中索引是在存储引擎层实现的&#xff0c;而不是在服务层实现的 按数据结构分&#xff1a;Btree索引、Hash索引、Full-text索引按存储结构分&#xff1a;聚簇索引、非聚…