当前位置: 首页 > news >正文

【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代理,然后返回代理类并放进容器

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


相关文章:

【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 小总…...

轻松理解c++17的string_view

文章目录 轻松理解c17的string_view设计初衷常见用法构造 std::string_view常用操作作为函数参数 注意事项总结 轻松理解c17的string_view std::string_view 是 C17 引入的一个轻量级、不拥有&#xff08;non-owning&#xff09;的字符串视图类。它的设计初衷是提供一种高效、…...

【机器学习理论基础】回归模型定义和分类

定义 回归分析是研究自变量与因变量之间数量变化关系的一种分析方法&#xff0c;它主要是通过因变量 Y Y Y与影响它的自变量 X i X_i Xi​ 之间的回归模型&#xff0c;衡量自变量 X i X_i Xi​ 对因变量 Y Y Y 的影响能力的&#xff0c;进而可以用来预测因变量Y的发展趋势。…...

探讨4层代理和7层代理行为以及如何获取真实客户端IP

准备工作 实验环境 IP角色192.168.1.100客户端请求IP192.168.1.100python 启动的HTTP服务192.168.1.102nginx服务192.168.1.103haproxy 服务 HTTP服务 这是一个简单的HTTP服务&#xff0c;主要打印HTTP报文用于分析客户端IP #!/usr/bin/env python # coding: utf-8import …...

java算法day11

二叉树的递归遍历二叉树的非递归遍历写法层序遍历 递归怎么写&#xff1f; 按照三要素可以保证写出正确的递归算法&#xff1a; 1.确定递归函数的参数和返回值&#xff1a; 确定哪些参数是递归的过程中需要处理的&#xff0c;那么就在递归函数里加上这个参数&#xff0c; 并且…...

linux下安装cutecom串口助手;centos安装cutecom串口助手;rpm安装包安装cutecom串口助手

在支持apt-get的系统下安装 在终端命令行中输入&#xff1a; sudo apt-get install cutecom 安装好后输入 sudo cutecom 就可以了 关于如何使用&#xff0c;可以看这个https://www.cnblogs.com/xingboy/p/14388610.html 如果你的电脑不支持apt-get。 那我们就通过安装包…...

2024年信息系统项目管理师2批次上午客观题参考答案及解析(1)

1、关于收集需求管理过程及相关技术的描述&#xff0c;正确的是&#xff08;&#xff09; A&#xff0e;需求跟踪矩阵是把产品需求从其来源链接到能满足需求的可交付成果的一种表格 B&#xff0e;原型法是一种结构化的头脑风暴形式&#xff0c;通过投票排列最有用的创意 C&am…...

Xinstall揭秘:APP推广数据背后的真相,让你的营销更精准!

在这个移动互联网时代&#xff0c;APP如同雨后春笋般涌现&#xff0c;但如何在这片红海中脱颖而出&#xff0c;成为每一个开发者与运营者面临的共同难题。其中&#xff0c;APP推广统计作为衡量营销效果、优化推广策略的关键环节&#xff0c;更是不可忽视的一环。今天&#xff0…...

科研绘图系列:R语言小提琴图(Violin Plot)

介绍 小提琴图(Violin Plot)是一种结合了箱线图和密度图的图表,它能够展示数据的分布密度和分布形状。以下是对小提琴图的详细解释: 小提琴图能表达: 数据分布:小提琴图通过在箱线图的两侧绘制曲线来展示数据的分布密度,曲线的宽度表示数据点的密度。集中趋势:箱线图部…...

【Vite】修改构建后的 index.html 文件名

在 Vite 项目中&#xff0c;默认构建 index.html 。但有时候我们需要修改 index.html 为其他文件名&#xff0c;比如 index-{时间戳}.html 。 我们可以这样配置 vite.config.js&#xff1a; import { defineConfig } from vite; import type { PluginOption } from vite;// 自…...

解决IDEA每次新建项目都需要重新配置maven的问题

每次打开IDEA都要重新配置maven&#xff0c;这是因为在DEA中分为项目设置和全局设置&#xff0c;这个时候我们就需要去到全局中设置maven了。我用的是IntelliJ IDEA 2023.3.4 (Ultimate Edition)&#xff0c;以此为例。 第一步&#xff1a;打开一个空的IDEA&#xff0c;选择左…...

论文学习_Getafix: learning to fix bugs automatically

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

Xilinx FPGA:vivado关于真双端口的串口传输数据的实验

一、实验内容 用一个真双端RAM&#xff0c;端口A和端口B同时向RAM里写入数据0-99&#xff0c;A端口读出单数并存入单端口RAM1中&#xff0c;B端口读出双数并存入但端口RAM2中&#xff0c;当检测到按键1到来时将RAM1中的单数读出显示到PC端&#xff0c;当检测到按键2到来时&…...

RedisTemplate 中序列化方式辨析

在Spring Data Redis中&#xff0c;RedisTemplate 是操作Redis的核心类&#xff0c;它提供了丰富的API来与Redis进行交互。由于Redis是一个键值存储系统&#xff0c;它存储的是字节序列&#xff0c;因此在使用RedisTemplate时&#xff0c;需要指定键&#xff08;Key&#xff09…...

数据结构与算法基础篇--二分查找

必要前提&#xff1a;有序数组 算法简述&#xff1a;通过不断取中间值和目标target值进行比较&#xff08;中间值&#xff1a;mid (left right) / 2&#xff09; 如果目标值等于中间位置的值&#xff0c;则找到目标&#xff0c;返回中间位置如果目标值小于中间位置的值&…...

python xlsx 导出表格超链接

该Python脚本用于从Excel文件中的第一列提取所有超链接并保存到一个文本文件中。首先&#xff0c;脚本导入必要的库并定义输入和输出文件的路径。然后&#xff0c;它确保输出文件的目录存在。接着&#xff0c;脚本加载Excel文件并选择活动工作表。通过遍历第一列的所有单元格&a…...

Data Guard高级玩法:failover备库后,通过闪回恢复DG备库

作者介绍&#xff1a;老苏&#xff0c;10余年DBA工作运维经验&#xff0c;擅长Oracle、MySQL、PG、Mongodb数据库运维&#xff08;如安装迁移&#xff0c;性能优化、故障应急处理等&#xff09; 公众号&#xff1a;老苏畅谈运维 欢迎关注本人公众号&#xff0c;更多精彩与您分享…...

【Unity2D 2022:NPC】制作任务系统

一、接受任务 1. 编辑NPC对话脚本&#xff1a; &#xff08;1&#xff09;创建静态布尔变量用来判断ruby是否接受到任务 public class NPCDialog : MonoBehaviour {// 创建全局变量用来判断ruby是否接到任务public static bool receiveTask false; } &#xff08;2&#xff…...

【C++深度学习】多态(概念虚函数抽象类)

✨ 疏影横斜水清浅&#xff0c;暗香浮动月黄昏 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;C学习 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &…...

Ubuntu 安装CGAL

一、什么是CGAL CGAL&#xff08;Computational Geometry Algorithms Library&#xff09;是一个广泛使用的开源库&#xff0c;主要用于计算几何算法的实现。该库提供了一系列高效、可靠和易于使用的几何算法和数据结构&#xff0c;适用于各种应用领域。以下是 CGAL 的主要功能…...

RK3568平台开发系列讲解(网络篇)netfilter框架

🚀返回专栏总目录 文章目录 一、Netfilter 介绍二、netfilter 简单案例三、防火墙功能一、Netfilter 介绍 Linux内核自2.4版本开始引入了Netfilter框架,这是一项重要的网络功能增强。Netfilter框架由Linux内核防火墙和网络维护者 Rusty Russell 所提出和实现。这个作者还基于…...

检测音视频文件的声压

FFmpeg使用 ebur128 滤镜检测声压&#xff0c;EBU R128 是欧洲广播联盟&#xff08;European Broadcasting Union&#xff0c;简称 EBU&#xff09;推荐的音频响度测量和归一化标准。 ffmpeg -i input_video.mp4 -filter_complex ebur128peaktrue -f null --f null -&#xff…...

计算机网络-HTTP常见面试题

目录 1. HTTP是什么&#xff1f;2. HTTP常见的状态码&#xff1f;3. HTTP 常见的字段有哪些&#xff1f;4. GET和POST有什么区别&#xff1a;5. GET 和POST方法都是安全和幂等的吗&#xff1f;6. HTTP缓存技术7. HTTP/1.1相比HTTP/1.0提高了什么性能&#xff1f;8. HTTP/2做了什…...

LNMP搭建Discuz和Wordpress

1、LNMP L:linux操作系统 N&#xff1a;nginx展示前端页面web服务 M&#xff1a;mysql数据库&#xff0c;保存用户和密码&#xff0c;以及论坛相关的内容 P&#xff1a;php动态请求转发的中间件 数据库的作用&#xff1a; 登录时验证用户名和密码 创建用户和密码 发布和…...

java中的构造器

Java 中的构造器&#xff08;也称为构造方法&#xff09;是一种特殊的方法&#xff0c;用于初始化对象的状态。在创建 Java 类的实例时&#xff0c;构造器会被自动调用。 构造器的定义&#xff1a; 构造器的名称必须与类名完全相同。构造器没有返回值类型&#xff0c;甚至不包括…...

机器学习筑基篇,​Ubuntu 24.04 快速安装 PyCharm IDE 工具,无需激活!

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] Ubuntu 24.04 快速安装 PyCharm IDE 工具 描述&#xff1a;虽然在之前我们安装了VScode&#xff0c;但是其对于使用Python来写大型项目以及各类配置还是比较复杂的&#xff0c;所以这里我们还是推…...

从0开始基于transformer进行股价预测(pytorch版本)

目录 数据阶段两个问题开始利用我们的代码进行切分 backbone网络训练效果 感觉还行&#xff0c;没有调参数。源码比较长&#xff0c;如果需要我后续会发&#xff08;因为太长了&#xff01;&#xff01;&#xff09; 数据阶段 &#xff01;&#xff01;&#xff01;注意&#…...

【多GPU训练方法】

一、数据并行 这是最常用的方法。整个模型复制到每个GPU上。训练数据被均匀分割&#xff0c;每个GPU处理一部分数据。所有GPU上的梯度被收集并求平均。通常使用NCCL&#xff08;NVIDIA Collective Communications Library&#xff09;等通信库实现。参数更新 使用同步后的梯度…...

2024年PMP考试备考经验分享

PMP是项目管理领域最重要的认证之一,本身是IT行业比较流行的证书&#xff0c;近几年在临床试验领域也渐渐流行起来&#xff0c;是我周围临床项PM几乎人手一个的证书。 考试时间&#xff1a;PMP认证考试形式为180道选择题&#xff0c;考试时间为3小时50分。 考试计划&#xff…...

MT3046 愤怒的象棚

思路&#xff1a; a[]存愤怒值&#xff1b;b[i]存以i结尾的&#xff0c;窗口里的最大值&#xff1b;c[i]存以i结尾的&#xff0c;窗口里面包含✳的最大值。 &#xff08;✳为新大象的位置&#xff09; 例&#xff1a;1 2 3 4 ✳ 5 6 7 8 9 则ans的计算公式b3b4c4c5c6b7b8b9…...

网站空间租用费用/广告投放公司

6月5日下午&#xff0c;中国国际广播电台(以下简称“国广”)俄东中心和成都电视台经济资讯频道联合摄制的纪录片《针传》在成都举行首映。这是一部讲述中国传统文化的纪录片&#xff0c;该片介绍了中国针灸悠久的历史、发展和传承。成都针灸医师邹群和她的徒弟们也来到首映仪式…...

网站后台选项卡效果/上海推广seo

2019独角兽企业重金招聘Python工程师标准>>> 一. 什么是Native Method 简单地讲&#xff0c;一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法&#xff1a;该方法的实现由非java语言实现&#xff0c;比如C。这个特征并…...

seo站内站怎么做/今日广州新闻头条

1.卸载文件系统fuser -km/home/ap/scratch1 先使用这条命令强制杀掉正在使用此文件系统的进程umount /home/ap/scratch12.修复fsck -fy /home/ap/scratch13.挂载修复文件系统mount /home/ap/scratch1说明&#xff1a;这个是因为出现了大量orphan filenode&#xff0c;一般来说l…...

怎么在濮阳网站做宣传/百度小说风云榜2022

第四章 计算机网络练习题1、Internet网采用的通信协议是( B )协议。A、x.25 B、TCP/IP C、SMIP D、IPX/SPX2、国际标准化组织的英文缩写是( B )。(A) OSI (B) ISO (C) ANSI (D) IEEE3、WWW采用( A )技术组织和管理浏览或信息检索的系统。A、超文本和超媒体 B、快速查询 C、动画…...

乌镇网站建设投标书/百度推广如何计费

什么是Dynamic Island Dynamic Island 是一项功能,可调整 iPhone 14 Pro 和 iPhone 14 Pro Max 上药丸形凹口的大小。调整大小让黑色区域包含更多信息、交互式信息,您可以点击或长按以访问不同的功能。 脚步 我们需要创建一个带有 Widget Extension 的项目。你可以参考之前的…...

淘宝客怎么做自己的网站/seo教程最新

九、SpEL SpEL是Spring表达式语言(Spring Expression Language)的简称&#xff0c;是一个支持运行查询和操作对象图的强大的表达式语言。 SpEL的语法类似EL&#xff0c;SpEL使用#{}作为界定符&#xff0c;所有在大括号内的字符都将被认为是SpEL。SpEL为bean的属性进行动态赋值提…...