首页 > 其他分享 >Spring 冷知识:一个提前 AOP 的机会

Spring 冷知识:一个提前 AOP 的机会

时间:2023-11-05 20:35:40浏览次数:33  
标签:BeanPostProcessor beanName return 容器 Spring 知识 bean AOP beanClass


今天再来聊一个 Spring 中的冷门知识:Bean 的处理不走正常流程,而是提前进行 AOP。

本文算是前面文章(Spring Bean 名称暗藏玄机,这样取名就不会被代理)内容的一个补充,如果还没阅读前文,建议先阅读,这样有利于更好的理解本文。

1. Bean 创建流程

前面文章中,松哥和大家梳理了,在 Bean 创建的过程中,会先给 BeanPostProcessor 一个返回代理对象的机会:

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	//省略。。。
	try {
		// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
		Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
		if (bean != null) {
			return bean;
		}
	}
	catch (Throwable ex) {
		throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
				"BeanPostProcessor before instantiation of bean failed", ex);
	}
	try {
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		if (logger.isTraceEnabled()) {
			logger.trace("Finished creating instance of bean '" + beanName + "'");
		}
		return beanInstance;
	}
    //省略。。。
}

小伙伴们看,这里的 resolveBeforeInstantiation 方法就是给 BeanPostProcessor 一个返回代理对象的机会,在这个方法中,最终就会触发到 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法,而在 postProcessBeforeInstantiation 方法中,会先判断当前 bean 是否是 AOP 相关类等:

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
	Object cacheKey = getCacheKey(beanClass, beanName);
	if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
		if (this.advisedBeans.containsKey(cacheKey)) {
			return null;
		}
		if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return null;
		}
	}
	
	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;
}

前面 if 分支中的内容,松哥在前面的文章中已经和大家分析过了,这里就不再赘述。

这里主要来说说 getCustomTargetSource 中的逻辑。

先来说什么情况下会走到 getCustomTargetSource 方法:当前 Bean 不是代理对象,也不是 AOP 相关的类,就是一个普普通通的常规类,那么就会走到 getCustomTargetSource 方法这里来,这里失去查找到一个 TargetSource 对象,然后根据该对象创建当前 bean 的代理对象并返回,如果返回了代理对象,那么后续的 bean 创建流程就不执行了。

我们来看下这个方法的源码:

@Nullable
protected TargetSource getCustomTargetSource(Class<?> beanClass, String beanName) {
	// We can't create fancy target sources for directly registered singletons.
	if (this.customTargetSourceCreators != null &&
			this.beanFactory != null && this.beanFactory.containsBean(beanName)) {
		for (TargetSourceCreator tsc : this.customTargetSourceCreators) {
			TargetSource ts = tsc.getTargetSource(beanClass, beanName);
			if (ts != null) {
				return ts;
			}
		}
	}
	// No custom TargetSource found.
	return null;
}

可以看到,这里就是当前类 AbstractAutoProxyCreator 中有一个 customTargetSourceCreators 变量,现在就是遍历该变量,通过这个集合中保存的 TargetSourceCreator 来创建 TargetSource 对象。

TargetSourceCreator 是一个接口,这个接口只有一个抽象类 AbstractBeanFactoryBasedTargetSourceCreator,我们来看下 AbstractBeanFactoryBasedTargetSourceCreator 中的 getTargetSource 方法是怎么执行的:

@Override
@Nullable
public final TargetSource getTargetSource(Class<?> beanClass, String beanName) {
	AbstractBeanFactoryBasedTargetSource targetSource =
			createBeanFactoryBasedTargetSource(beanClass, beanName);
	if (targetSource == null) {
		return null;
	}

	DefaultListableBeanFactory internalBeanFactory = getInternalBeanFactoryForBean(beanName);
	// We need to override just this bean definition, as it may reference other beans
	// and we're happy to take the parent's definition for those.
	// Always use prototype scope if demanded.
	BeanDefinition bd = getConfigurableBeanFactory().getMergedBeanDefinition(beanName);
	GenericBeanDefinition bdCopy = new GenericBeanDefinition(bd);
	if (isPrototypeBased()) {
		bdCopy.setScope(BeanDefinition.SCOPE_PROTOTYPE);
	}
	internalBeanFactory.registerBeanDefinition(beanName, bdCopy);
	// Complete configuring the PrototypeTargetSource.
	targetSource.setTargetBeanName(beanName);
	targetSource.setBeanFactory(internalBeanFactory);
	return targetSource;
}

首先,TargetSource 对象是通过 createBeanFactoryBasedTargetSource 方法来创建的,这个方法是一个抽象方法,将来在子类中被实现。

接下来会调用 getInternalBeanFactoryForBean 方法创建一个新的内部容器 internalBeanFactory,本质上这个 internalBeanFactory 其实是一个子容器,现有的容器将作为这个子容器的父容器。

接下来就是获取到当前 beanName 所对应的 BeanDefinition,然后进行属性配置,并注册到内部容器中,最后返回 targetSource 对象。

我们来看下这里的 getInternalBeanFactoryForBean 方法:

protected DefaultListableBeanFactory getInternalBeanFactoryForBean(String beanName) {
	synchronized (this.internalBeanFactories) {
		return this.internalBeanFactories.computeIfAbsent(beanName,
				name -> buildInternalBeanFactory(getConfigurableBeanFactory()));
	}
}

protected DefaultListableBeanFactory buildInternalBeanFactory(ConfigurableBeanFactory containingFactory) {
	// Set parent so that references (up container hierarchies) are correctly resolved.
	DefaultListableBeanFactory internalBeanFactory = new DefaultListableBeanFactory(containingFactory);
	// Required so that all BeanPostProcessors, Scopes, etc become available.
	internalBeanFactory.copyConfigurationFrom(containingFactory);
	// Filter out BeanPostProcessors that are part of the AOP infrastructure,
	// since those are only meant to apply to beans defined in the original factory.
	internalBeanFactory.getBeanPostProcessors().removeIf(beanPostProcessor ->
			beanPostProcessor instanceof AopInfrastructureBean);
	return internalBeanFactory;
}

这个其实就是正常的容器创建,倒也没啥好说的,但是有几个需要注意的点:

  1. 在调用 buildInternalBeanFactory 方法构建容器的时候,会先调用 getConfigurableBeanFactory 方法获取到当前容器作为父容器,如果当前容器不存在,那么就会抛出异常。这就意味着,当我们自己提供 TargetSourceCreator 实例的时候,一定要指定一个容器。
  2. 在创建了内部容器之后,会从内部容器中移除所有 AopInfrastructureBean 类型的 BeanPostProcessor,也就是内部容器将来创建出来的 bean,不再走 AopInfrastructureBean 类型后置处理器,因为这种类型的后置处理器主要是用来处理 AOP 的,现在,AOP 代理当场就生成了,就不再需要这些后置处理器了。

好了,这就是大致的 AOP 提前生成原理,接下来松哥写一个案例我们一起来看下。

2. 实践

首先,我们先来自定义一个 TargetSource:

public class UserServiceTargetSource extends AbstractBeanFactoryBasedTargetSource {
    @Override
    public Object getTarget() throws Exception {
        return getBeanFactory().getBean(getTargetBeanName());
    }

    @Override
    public boolean isStatic() {
        return true;
    }
}

关于 TargetSource 本身,松哥在之前的 Spring 源码视频中已经和大家介绍过很多了,这里我就不再啰嗦了。

接下来自定义 TargetSourceCreator:

public class CustomTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator {

    @Override
    protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource(Class<?> beanClass, String beanName) {
        if (getBeanFactory() instanceof ConfigurableListableBeanFactory) {
            if (beanClass.isAssignableFrom(UserService.class)) {
                return new UserServiceTargetSource();
            }
        }
        return null;
    }
}

如果要创建的 bean 是 UserService 的话,那么就给返回一个 UserServiceTargetSource 对象。

最后,也是最关键的一步,根据前面的分析,TargetSourceCreator 是存在于 AnnotationAwareAspectJAutoProxyCreator 这样一个 InstantiationAwareBeanPostProcessor 类型的后置处理器中的,因此,我们要想办法把自定义的 TargetSourceCreator 设置给 AnnotationAwareAspectJAutoProxyCreator,如下:

@Component
public class SetCustomTargetSourceCreator implements BeanPostProcessor, PriorityOrdered, BeanFactoryAware {

    private BeanFactory beanFactory;

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof AnnotationAwareAspectJAutoProxyCreator) {
            AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator = (AnnotationAwareAspectJAutoProxyCreator)bean;
            CustomTargetSourceCreator customTargetSourceCreator = new CustomTargetSourceCreator();
            customTargetSourceCreator.setBeanFactory(beanFactory);
            annotationAwareAspectJAutoProxyCreator.setCustomTargetSourceCreators(customTargetSourceCreator);
        }
        return bean;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

AnnotationAwareAspectJAutoProxyCreator 本身就是一个 BeanPostProcessor,我们现在要做的就是修改这个 BeanPostProcessor,BeanPostProcessor 是在 Spring 容器启动时候的 refresh 方法中去初始化的,整个的初始化过程松哥在之前的BeanPostProcessor 是在何时介入 Bean 创建的?一文中已经详细介绍过了。

BeanPostProcessor 初始化的时候,先初始化实现了 PriorityOrdered 接口的,再初始化实现了 Ordered 接口的,最后再去初始化那些没有实现任何排序接口的 BeanPostProcessor。

而我们这里 SetCustomTargetSourceCreator 一定要赶在 AnnotationAwareAspectJAutoProxyCreator 之前进行初始化,这样,当 AnnotationAwareAspectJAutoProxyCreator 进行初始化的时候,就会用到 SetCustomTargetSourceCreator 这样一个后置处理器,进而在该处理器中修改 AnnotationAwareAspectJAutoProxyCreator 的属性。

AnnotationAwareAspectJAutoProxyCreator 类间接实现了 Ordered 接口,默认优先级是最低,但是在 Spring 容器启动时,在处理 BeanFactoryPostProcessor 时(具体是 ConfigurationClassPostProcessor),将其优先级设置为最高。

所以,我们如果想要让自定义的 SetCustomTargetSourceCreator 抢在 AnnotationAwareAspectJAutoProxyCreator 之前执行,那么就只能让 SetCustomTargetSourceCreator 去实现 PriorityOrdered 接口了,实现 PriorityOrdered 接口之后,重写 getOrder 方法,这个方法返回值是什么无所谓,反正都会在实现了 Ordered 接口的 BeanPostProcessor 之前执行。

最后,我们再在启动类上开启自动代理即可:

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class JavaConfig {
}

大功告成。

这样,当 Spring 容器创建一个 Bean 的时候,就会提前被 BeanPostProcessor 拦截,然后给出一个 TargetSource,进而据此创建代理对象,这样就不需要后续常规的 Bean 创建流程了。好啦,感兴趣的小伙伴可以自己去试一试哦~


标签:BeanPostProcessor,beanName,return,容器,Spring,知识,bean,AOP,beanClass
From: https://blog.51cto.com/u_9806927/8195038

相关文章

  • 如何让 Bean 深度感知 Spring 容器
    Spring有一个特点,就是创建出来的Bean对容器是无感的,一个Bean是怎么样被容器从一个Class整成一个Bean的,对于Bean本身来说是不知道的,当然也不需要知道,也就是Bean对容器的存在是无感的。但是有时候我们可能会遇到一些场景,这些场景让我们去感知容器的存在,松哥举几个例子:Sp......
  • Linux脚本:批量启动docker容器、批量启动springboot、批量启动Vuejs
    批量启动springboot#!/bin/bash#检查容器是否已经启动check_container(){sudodockerps|grep"$1">/dev/nullif[$?-ne0];thenecho"$1containerisnotrunning.Starting$1..."sudodockerstart"$1"elseecho......
  • 11、SpringMVC之文件下载和上传
    创建名为spring_mvc_file的新module,过程参考9.1节和9.5节11.1、文件下载11.1.1、创建图片目录并放置图片11.1.2、页面请求示例<ath:href="@{/test/down}">下载图片</a>11.1.3、控制器方法示例packageonline.liaojy.controller;importorg.springframework.http.Ht......
  • springboot第44集:Kafka集群和Lua脚本
    servers:Kafka服务器的地址。这是Kafka集群的地址,生产者将使用它来发送消息。retries:在消息发送失败时,生产者将尝试重新发送消息的次数。这个属性指定了重试次数。batchSize:指定了生产者在发送消息之前累积的消息大小(以字节为单位)。一次性发送多个消息可以提高性能。linger:指定了生......
  • Rockchip RK3399 - DRM encoder、connector基础知识
    3.1.3structdrm_connectorlinux内核使用structdrm_connector来表示一个连接器,用于连接编码器和显示设备。3.1.3structdrm_encoderlinux内核使用structdrm_encoder来表示一个编码器,用于连接CRT控制器和显示设备。3.2structdrm_devicelinux内核使用structdrm_device来......
  • 11月3日前端需要学习的知识、自闭合标签、meta标签、div标签
    目录前端需要学习的知识生成的网页类型静态网页动态网页网页的架构c/s架构b/s架构浏览器的特别用法第一种结合python来使用第二种将文件拖入浏览器里面(这就符合渲染了)重点HTML首先!DOCTYPEhtml其次就是html到/html还有就是head到/head的内部最后就是body到/body总结其它的标签......
  • Spring自定义数据校验并实现国际化功能
    通常,当我们需要验证用户输入时,SpringMVC提供标准的预定义验证器。我们会引入spring-boot-starter-validation依赖来实现数据校验功能。但是,当我们需要验证特定类型的输入时,我们就需要创建自己的自定义校验逻辑。这里我们取一个相对简单的校验手机号码的功能来实现。为了校验手......
  • springboot入门
    两年没写了。。连右下的小人都没了。得开始新一阶段的学习了。先从学习springboot及其前置内容开始学习。然后简单复习一下vue框架。idea在创建maven的springboot工程时自动下了个依赖,尽量选择版本低一点的。。适配java8不容易出问题。一旦出问题了多重建项目就会重新下springboo......
  • Python基础知识
    1.python单行注释用#例如:#helloworld2.多行注释用""""""来表示例如:"""helloworld"""3.print语句:(1)输出多个数据格式:print(内容一,内容二,内容三,......)(2)print语句默认输出后换行(3)print语句输出不换行的语法:print("内容",end='......
  • SpringBoot 基础知识
    ​#SpringBoot#​‍本文基于SpringBoot2.0最新稳定版2.7.6;目前SpringBoot3.0已经发布,后续会体验新版新特性。官网:SpringBoot‍SpringBoot程序的优点起步依赖(简化依赖配置)自动配置(简化常用工程相关配置)辅助功能(内置服务器,.....)快速上手SpringBoot工程联网......