Sping源码(九)—— Bean的初始化(非懒加载)— lookupMethod标签

news2025/9/17 13:02:05

序言

在继续深入Spring的对象创建流程之前,这篇文章先简单介绍一下lookupMethod标签的用法及作用。

准备的xml
自定义名为methodOverride.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"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<bean id="apple" class="org.springframework.methodOverrides.lookup.Apple"></bean>

	<bean id="banana" class="org.springframework.methodOverrides.lookup.Banana"></bean>

	<bean id="fruitPlate1" class="org.springframework.methodOverrides.lookup.FruitPlate">
		<lookup-method name="getFruit" bean="apple"/>
	</bean>

	<bean id="fruitPlate2" class="org.springframework.methodOverrides.lookup.FruitPlate">
		<lookup-method name="getFruit" bean="banana"/>
	</bean>
</beans>

Fruit类

public class Fruit {
    public Fruit() {
        System.out.println("I got Fruit");
    }
}

Apple类

public class Apple extends Fruit {
    public Apple() {
        System.out.println("I got a fresh apple");
    }
}

Banana类

public class Banana extends Fruit {
    public Banana() {
        System.out.println("I got a  fresh bananer");
    }
}

FruitPlate

public abstract class FruitPlate{
    // 抽象方法获取新鲜水果
    public abstract Fruit getFruit();
}

测试类

public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("methodOverride.xml");
		FruitPlate fruitPlate1 = (FruitPlate) ac.getBean("fruitPlate1");
		fruitPlate1.getFruit();

		FruitPlate fruitPlate2 = (FruitPlate) ac.getBean("fruitPlate2");
		fruitPlate2.getFruit();
	}

运行结果
我们来看看配置的<lookup-method>标签是如何进行的区分和覆盖。
在这里插入图片描述

lookup-method标签

我们在FruitPlate类中调用getFruit方法会返回Fruit类,而我们在lookup-method标签中配置调用getFruit方法会返回Apple类。
所以我们这次从FruitPlate对象的创建入手,看看整个流程中做了什么。

FruitPlate的创建
依然是 getBean ——》 doGetBean ——》 createBean 的整体流程的逻辑,我们直接看createBean

createBean

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		//删除无用代码
		
		RootBeanDefinition mbdToUse = mbd;
		
		mbdToUse.prepareMethodOverrides();

		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		return beanInstance;
	}

prepareMethodOverrides
如果bean对象配置了lookup-method、replace-method标签,说明要进行方法覆盖操作,设置标志位。

public void prepareMethodOverrides() throws BeanDefinitionValidationException {
		// Check that lookup methods exist and determine their overloaded status.
		//methodOverrides变量不为null,说明有方法覆盖
		if (hasMethodOverrides()) {
			//设置methodOverrides标记位
			getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride);
		}
	}
	protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
		int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
		if (count == 0) {
		}
		else if (count == 1) {
			// Mark override as not overloaded, to avoid the overhead of arg type checking.
			//标记overload为未重载,避免arg类型检查开销
			mo.setOverloaded(false);
		}
	}

doCreateBean

调用createBeanInstance方法取bean的策略实例。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		//去掉无用代码
		
		// Instantiate the bean.
		//这个beanWrapper是用来持有创建出来的bean对象的
		BeanWrapper instanceWrapper = null;

		//如果是单例对象,从factoryBeanInstanceCache缓存中移除该信息
		if (mbd.isSingleton()) {
			// 如果是单例对象,从factoryBean实例缓存中移除当前bean定义信息
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		// 没有就创建实例
		if (instanceWrapper == null) {
			// 根据执行bean使用对应的策略创建新的实例,如,工厂方法,构造函数主动注入、简单初始化
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		return exposedObject;
	}

createBeanInstance

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// 去除无用代码
		
		// 使用默认无参构造函数创建对象,如果没有无参构造且存在多个有参构造且没有@AutoWired注解构造,会报错
		return instantiateBean(beanName, mbd);
	}

instantiateBean
忽略securityManagergetInstantiationStrategy中创建并返回了CglibSubclassingInstantiationStrategy对象。

	protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {
		try {
			Object beanInstance;
			if (System.getSecurityManager() != null) {
				beanInstance = AccessController.doPrivileged(
						(PrivilegedAction<Object>) () -> getInstantiationStrategy().instantiate(mbd, beanName, this),
						getAccessControlContext());
			}
			else {
				// 获取实例化策略并且进行实例化操作
				beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
			}
			// 包装成BeanWrapper
			BeanWrapper bw = new BeanWrapperImpl(beanInstance);
			initBeanWrapper(bw);
			return bw;
		}
	}

getInstantiationStrategy
方法创建了一个CglibSubclassingInstantiationStrategy类型对象。使用Cglib形式的动态代理创建对象。

private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

protected InstantiationStrategy getInstantiationStrategy() {
		return this.instantiationStrategy;
	}

而我们的CglibSubclassingInstantiationStrategy中就有对应的变量用来进行标记。

/**
	 * 如果没有override方法覆盖的话,那么索引位置为0
	 *
	 * Index in the CGLIB callback array for passthrough behavior,
	 * in which case the subclass won't override the original class.
	 */
	private static final int PASSTHROUGH = 0;

	/**
	 * 如果有lookup-method的覆盖,那么索引位置为1
	 *
	 * Index in the CGLIB callback array for a method that should
	 * be overridden to provide <em>method lookup</em>.
	 */
	private static final int LOOKUP_OVERRIDE = 1;

	/**
	 * Index in the CGLIB callback array for a method that should
	 * be overridden using generic <em>method replacer</em> functionality.
	 *
	 * 如果有replace-method的覆盖,那么索引位置为2
	 */
	private static final int METHOD_REPLACER = 2;

instantiate
因为我们配置了lookup-method标签,所以会有方法覆盖,此时hasMethodOverrides()方法会返回true,取反之后走else。

public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		// Don't override the class with CGLIB if no overrides.
		if (!bd.hasMethodOverrides()) {
			//去除无用代码
			return BeanUtils.instantiateClass(constructorToUse);
		}
		else {
			// Must generate CGLIB subclass.
			// 必须生成CGLIB子类
			return instantiateWithMethodInjection(bd, beanName, owner);
		}
	}

此时我们将要加载的bean就是xml文件中配置的FruitPlate methodOverrides变量存放的就是我们想要进行覆盖的getFruit()

在这里插入图片描述

instantiateWithMethodInjection
而因为我们创建了CglibSubclassingInstantiationStrategy类型对象,所以此时的instantiateWithMethodInjection方法的实现是CglibSubclassingInstantiationStrategy的方法。

protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
			@Nullable Constructor<?> ctor, Object... args) {

		// Must generate CGLIB subclass...
		// 必须生成一个cglib的子类
		return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
	}

封装beanDefinitionBeanFactoryCglibSubclassCreator对象中,类中包含3种方法的CALLBACK_TYPES

private static final Class<?>[] CALLBACK_TYPES = new Class<?>[]
				{NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class};

CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) {
			this.beanDefinition = beanDefinition;
			this.owner = owner;
		}

instantiate
此时,instance实例就是我们通过Cglib创建的FruitPlate对象,并设置callBacks属性值。

public Object instantiate(@Nullable Constructor<?> ctor, Object... args) {
			Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
			Object instance;
			if (ctor == null) {
				instance = BeanUtils.instantiateClass(subclass);
			}
			else {
				try {
					Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
					instance = enhancedSubclassConstructor.newInstance(args);
				}

			}

			Factory factory = (Factory) instance;
			factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
					new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
					new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
			return instance;
		}

此时要返回的instance就是通过Cglib创建的FruitPlate对象,并且callback属性中也设置了LookupOverrideMethodInterceptor用来进行方法的拦截。
在这里插入图片描述

createEnhancedSubclass
Cglib规定写法,创建Enhancer对象并设置callBack属性。

private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {
			//cglib规定用法,对原始class进行增强,并设置callback
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(beanDefinition.getBeanClass());
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			if (this.owner instanceof ConfigurableBeanFactory) {
				ClassLoader cl = ((ConfigurableBeanFactory) this.owner).getBeanClassLoader();
				enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl));
			}
			//过滤,自定义逻辑来指定调用的callback下标
			enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(beanDefinition));
			//只是产生class就直接指定callback类型,跟上面指定的callbackFilter对应
			enhancer.setCallbackTypes(CALLBACK_TYPES);
			return enhancer.createClass();
		}
	}

创建好了FruitPlate对象后,main方法中再次调用fruitPlate1.getFruit();方法。

public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("methodOverride.xml");
		FruitPlate fruitPlate1 = (FruitPlate) ac.getBean("fruitPlate1");
		fruitPlate1.getFruit();

		FruitPlate fruitPlate2 = (FruitPlate) ac.getBean("fruitPlate2");
		fruitPlate2.getFruit();
	}

通过索引LOOKUP_OVERRIDE LookupOverrideMethodInterceptor<lookup-method>标签中配置的方法进行拦截。
获取 lo 所对应的 BeanName,调用getBean()进行Bean实例的创建,从而调用Apple类中的getFruit()方法,从而实现覆盖

public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
			// Cast is safe, as CallbackFilter filters are used selectively.
			LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
			Object[] argsToUse = (args.length > 0 ? args : null);  // if no-arg, don't insist on args at all
			if (StringUtils.hasText(lo.getBeanName())) {
				return (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) :
						this.owner.getBean(lo.getBeanName()));
			}
			else {
				return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) :
						this.owner.getBean(method.getReturnType()));
			}
		}

然而,lookup-method的作用和效果远非如此,我们此时稍稍改动一下配置文件。将Apple的scope改为prototype。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="apple" class="org.springframework.methodOverrides.lookup.Apple" scope="prototype"></bean>
	
	<bean id="fruitPlate1" class="org.springframework.methodOverrides.lookup.FruitPlate">
		<lookup-method name="getFruit" bean="apple"/>
	</bean>

</beans>
public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("methodOverride.xml");

		FruitPlate fruitPlate1 = (FruitPlate) ac.getBean("fruitPlate1");
		Apple a1 =(Apple) fruitPlate1.getFruit();
		FruitPlate fruitPlate2 = (FruitPlate) ac.getBean("fruitPlate1");
		Apple a2 = (Apple) fruitPlate2.getFruit();
		System.out.println(a1 == a2);
	}

修改完配置文件和测试类后,此时的 a1 != a2,会输出 false。

原因在于,当lookup-method标签生效调用getFruit()进行拦截时走getBean()方法创建对象时,因为此时Apple的scope = prototype,所以singletonObjects缓存中不会缓存创建好的实例,每次都会重新创建,所以每次获取的都是新的对象。

所以lookup-method最重要的作用是利用了索引(LOOKUP_OVERRIDE)和拦截器(LookupOverrideMethodInterceptor)解决了单例引用原型的问题。

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

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

相关文章

旅行者1号有什么秘密?飞行240多亿公里,为什么没发生碰撞?

旅行者1号有什么秘密&#xff1f;飞行240多亿公里&#xff0c;为什么没发生碰撞&#xff1f; 自古以来&#xff0c;人类就对浩瀚无垠的宇宙充满了好奇与向往。从最初的仰望星空&#xff0c;到如今的深空探测&#xff0c;人类探测宇宙的历史发展可谓是一部波澜壮阔的史诗。 在…

【Shopee】计算虾皮订单的各项支出和订单收入计算方法

虾皮订单成交截图 基础条件&#xff1a; 商品金额&#xff1a;11.92 [4x2.98] 商品原价&#xff1a;7.5 商品折后价&#xff1a;2.98 商品数量&#xff1a;4 优惠券与回扣&#xff1a; 店铺优惠券&#xff08;减10%&#xff09;&#xff1a;1.2 [11.92x10% 四舍五入了] 订单实…

基于软件在环的飞控机建模仿真

安全关键系统&#xff08;Safety-Critical System&#xff0c;SCS&#xff09;是指由于某些行为或组合行为能够引发整体系统失效&#xff0c;继而导致财物损失、人员受伤等严重影响的系统&#xff0c;诸多安全关键领域如航空航天、核电系统、医疗设备、交通运输等领域的系统都属…

redis 笔记2之哨兵

文章目录 一、哨兵1.1 简介1.2 实操1.2.1 sentinel.conf1.2.2 问题1.2.3 哨兵执行流程和选举原理1.2.4 使用建议 一、哨兵 1.1 简介 上篇说了复制&#xff0c;有个缺点就是主机宕机之后&#xff0c;从机只会原地待命&#xff0c;并不能升级为主机&#xff0c;这就不能保证对外…

Redis之线程IO模型

引言 Redis是个单线程程序&#xff01;这点必须铭记。除了Redis之外&#xff0c;Node.js也是单线程&#xff0c;Nginx也是单线程&#xff0c;但是他们都是服务器高性能的典范。 Redis单线程为什么能够这么快&#xff01; 因为他所有的数据都在内存中&#xff0c;所有的运算都…

❤ npm运行打包报错归纳

❤ 前端运行打包报错归纳 &#xff08;安装依赖&#xff09;Cannot read property ‘pickAlgorithm’ of null" npm uninstall //删除项目下的node_modules文件夹 npm cache clear --force //清除缓存后 npm install //重新安装 备用安装方式 npm install with --for…

英格索兰IC12D3A1AWS-A控制器过热维修

在现代工业生产中&#xff0c;拧紧控制器作为一种自动控制工具&#xff0c;被广泛应用于汽车、航空、电子等领域。然而&#xff0c;在使用过程中&#xff0c;可能会出现IngsollRang拧紧控制器过热故障&#xff0c;影响生产效率和产品质量。 【拧紧设备维修】【英格索兰IngsollR…

初始化三板斧 - centos7

1、关闭防火墙、关闭SELinux ① 立即关闭防火墙 systemctl stop firewalld ② 设置开机关闭防火墙 systemctl disable firewalld ③ 立即关闭SELinxu setenforce 0 ④ 设置开机关闭SELinux 将SELINUXenforcing 修改替换为 SELINUXdisabled vim /etc/selinux/config se…

M41T11M6F串行实时时钟-国产兼容RS4C411

RS4C411是一款低功耗串行实时时钟&#xff08;RTC&#xff09;&#xff0c;具有56字节的NVRAM。内置32.768 kHz振荡器&#xff08;外部晶体控制&#xff09;和RAM的前8字节用于时钟/日历功能&#xff0c;并以二进制编码十进制&#xff08;BCD&#xff09;格式配置。地址和数据通…

汽车金属管检测新方法,分度盘高速视觉检测机检测效果如何?

汽车金属管是指在汽车制造和维修中广泛使用的金属管道&#xff0c;用于传输流体、气体或其他介质。汽车金属管在汽车中扮演着重要的角色&#xff0c;用于传输液体&#xff08;如燃油、冷却液、润滑油&#xff09;、气体&#xff08;如空气、排气&#xff09;、制动系统、液压系…

利用three-csg-ts对做物体交互式挖洞

默认物体均为居中&#xff0c;如果指定位置没有发生偏移&#xff0c;可能是因为在执行布尔操作之前没有正确设置变换。确保在进行布尔运算之前应用所有必要的变换。以下是经过修正的完整代码示例&#xff0c;它会确保圆柱正确旋转并与盒子进行 CSG 操作。 安装依赖 首先&…

企业怎样管控员工外发文件,公司文件外发管控的方法

在数字化办公日益普及的今天&#xff0c;企业信息的安全管理成为了维护企业竞争力的关键一环。 员工在日常工作中外发文件的行为&#xff0c;如果不加以适当管控&#xff0c;很可能导致敏感信息泄露&#xff0c;影响企业运营乃至声誉。 因此&#xff0c;建立一套有效的文件外…

element 表格el-table的 :cell-style用法-表格固定行文字高亮

el-table的 :cell-style用法 实现表格固定行文字高亮效果 <el-tableref"table"borderstripe:data"list":height"height"highlight-current-row:cell-style"cellStyle"><el-table-columnprop"code"label"规则…

代码解读 | Hybrid Transformers for Music Source Separation[06]

一、背景 0、Hybrid Transformer 论文解读 1、代码复现|Demucs Music Source Separation_demucs架构原理-CSDN博客 2、Hybrid Transformer 各个模块对应的代码具体在工程的哪个地方 3、Hybrid Transformer 各个模块的底层到底是个啥&#xff08;初步感受&#xff09;&#xff1…

Doris连接超时问题排查记录

文章目录 一、现象描述二、问题排查1、分析驱动包2、分析Mysql客户端&#xff08;问题解决&#xff09; 一、现象描述 先上官网部署地址&#xff0c;按照官网上一步步进行部署 https://doris.apache.org/zh-CN/docs/get-starting/quick-start 基本到最后都挺顺利的&#xff0c…

STM32学习笔记(四)--TIM定时器中断详解

&#xff08;1&#xff09;配置步骤1.配置RCC外设时钟2.配置时基单元的时钟3.配置初始化时基单元4.使能更新中断5.配置NVIC 选择一个合适的优先级6.启动定时器 其中涉及外设有 RCC内部时钟&#xff08;EIR外部时钟 ITR其他定时器 TIx捕获通道&#xff09;、TIM、NVIC 高级定时器…

大模型赛道有前景吗?普通人该如何入门大模型?(附AI大模型资源)

大模型赛道有前景吗&#xff1f; 这个问题&#xff0c;是个热门话题&#xff0c;但不是个好问题。 因为&#xff0c;它基于不同的提问人、提问意图&#xff0c;会有不同的答案。 对于一个职业发展初期的新人&#xff0c;提问的意图可能是&#xff1a;我要不要转行去大模型赛…

【VS】尚未配置为Web项目XXXX指定的本地IIS URL HTTP://localhost

报错原因&#xff1a; 我们在Web项目的属性配置中勾选了“使用本地IIS Web服务器”&#xff1b; 本来嘛&#xff0c;这也没啥&#xff0c;问题是当我们的电脑IP改变时&#xff0c;将会导致程序找不到原来的IP地址了&#xff0c;那么当然会报错啦。 解决办法&#xff1a; 其实…

南方cass专业测绘软件下载,南方cass功能强大的cad辅助测绘软件获取!

在测绘领域&#xff0c;南方CASS测绘软件无疑是一颗璀璨的明星&#xff0c;被誉为“全能选手”。这款软件在功能方面表现出了令人赞叹的多样性和专业性&#xff0c;为测绘工作提供了极大的便利。 ​ 首先&#xff0c;南方CASS测绘软件具备强大的数据兼容性&#xff0c;支持多种…

Blender:渲染输出

渲染输出界面 渲染设置界面&#xff1a; 输出设置界面&#xff1a; 输出文件格式 【文档】 视频导出格式&#xff1a; AVI JPEG 使用JPEG压缩的AVI。有损&#xff0c;能得到更小的文件&#xff0c;但大小无法与编解码器的压缩算法得到的文件相比。JPEG 压缩也是数字摄像机使用…