模拟Spring IoC(三)Bean注解含参方法处理

本文深入探讨了处理Bean注解中含参方法的策略,重点在于如何检测依赖是否满足,避免循环依赖,以及通过构造MethodDefinition和利用Map管理参数类型与方法映射关系的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一篇文章中《模拟Spring IoC(二)Bean注解》解释了Bean注解的应用场景,但是没有处理有参方法的Bean注解,这边文章将主要描述Bean注解含参方法的处理过程。

分析:

含参方法处理的关键是:方法所依赖的参数是否满足?
如果满足,则,该方法是可以执行并得到一个Bean的。
如果最终都无法满足,那么必然形成了循环依赖,无法获取参数,则将循环依赖告知用户。
核心是:检测依赖是否满足。

这里可以先考虑构造MethodDefinition
MethodDefinition封装了方法反射执行所需要的对象:
1、对象;
2、方法本身;
3、参数对象集合。
这里最难解决的问题是:参数所需要的对象可能是随机满足的,即当你想要执行该方法时,该方法所需要的某个参数此时可能并没有存储在beanPool中。

处理方法:
将所有不满足依赖关系的方法中的参数形成一个Map(比如dependenceMethodPool ),此Map的键是参数类型,而值是MethodDefinition所形成的List(即含有该参数类型的所有方法组成的列表),此map用于管理参数类型与需要该参数类型的方法的映射关系。再准备一个参数不满足(暂时无法执行)的方法列表(列表1),参数满足要求的(即可执行的)方法列表(列表2)。
在这里插入图片描述

过程
1、遇到一个带参方法,先检测其所有参数;
1.1、若参数满足要求,则,暂不处理(可以将count–),这里的count是“参数个数”
1.2、若参数不能得到满足,则,将这个参数类型作为键,这个MethodDefinition作为值,存储到Map中;
1.3、当对所有参数都进行了检测,若存在未满足的参数,则,将MethodDefinition存储到列表1;
1.4、当所有参数都满足要求,则,将其存储到列表2;
2、每处理完一个Bean,都扫描Map,将依赖这个Bean的MethodDefinition的count–;若count为0了,说明参数都满足了,则,将其存储到列表2中。

实现

MethodDefinition:

public class MethodDefinition {
	private Method method;//方法本身
	private Object object;//执行方法的对象
	private int paraCount;//方法的参数个数
	
	public MethodDefinition() {
	}

	Method getMethod() {
		return method;
	}

	void setMethod(Method method) {
		this.method = method;
	}

	Object getObject() {
		return object;
	}

	void setObject(Object object) {
		this.object = object;
	}
	
	void setParaCount(int paraCount) {
		this.paraCount = paraCount;
	}

	//参数个数增加
	void add() {
		++this.paraCount;
	}
	//参数个数减少
	int sub() {
		return --this.paraCount;
	}

这是处理带参方法的入口,该段程序属于dealBean方法:
在这里插入图片描述

/*
 * 
 * 获取beanPool中没有的参数
 * 1.获取方法的参数类型,对参数类型进行去重操作再装入paraPool
 * 2.产生一个ArrayList,在BeanPool中查找是否存在该参数类型,
 * 3.若存在,则从paraPool中删掉
 * */
private static Map<Class<?>,Integer> getMethodPara(Object object,Method method) {
	Map<Class<?>, Integer> paraPool = new HashMap<>();//存储BeanPool中没有的参数
	
	Class<?>[] parameterTypes = method.getParameterTypes();
	for(int index = 0;index <parameterTypes.length;index++) {
		//HashMap中不存在相同的key值,put方法会自动调用equals方法和hashCode
		//去重;去掉类型重复的参数类型
		paraPool.put(parameterTypes[index], 0);//暂时只需要键,即参数类型,至于缺失参数的方法目前不put
	}
	//Map和List不能边遍历边删除,先将其存储到klassList
	List<Class<?>> klassList = new ArrayList<Class<?>>();
	for(Class<?> type:paraPool.keySet()) {
		//此处只需要判断bean是否存在,使用get()
		BeanDefinition beanDefinition = BeanPool.get(type.getName());
		if(beanDefinition != null) {
			klassList.add(type);
		}
	}
	for(Class<?>type:klassList) {
		paraPool.remove(type);
	}
	//避免边遍历边删除
	
	return paraPool;
}

注意:之所以不把参数存放在List列表而存放在Map中,是因为每次将参数放入List,都需要检查一遍前面有没有放入过该参数,如此一来时间复杂度会很高。而用Map来存放的话,是通过键值对的方式,一个键只能对应一个值,当有相同的键加入时Map时,会自动覆盖前面这个键对应的值,不需要再花费时间复杂度去检查有没有,也不存在键的重复,这个过程中,键所对应的的值并不重要,因为我们需要的只是不重复的参数类型。就算某个方法的参数有三个,其中两个参数是同一个类型的,虽然我们把参数在Map中加入了三次,但是实际上Map中存放的只有两种参数类型,这样避免了对相同参数类型的参数重复加入。

处理带参数的方法:先判断方法的所有参数是否满足(getMethodPara方法),为空则执行方法,不为空则将方法加入参数不满足列表

private static void dealMethodWithPara(Class<?> klass,Object object,Method method) {
		Map<Class<?>, Integer> paraTypeMap = getMethodPara(object, method);
		
		//若为空,即说明所有参数均以获取,可执行方法
		if(paraTypeMap.isEmpty()) {
			invokeMethodWithPara(klass,object, method);
			return;
		}
		
		//若不为空,说明该方法执行需要的参数不满足
		//将该方法的执行对象,方法本身和参数个数进行封装
		//加入到未执行方法列表。
		MethodDefinition methodDefinition = new MethodDefinition();
		methodDefinition.setObject(object);
		methodDefinition.setMethod(method);
		methodDefinition.setParaCount(paraTypeMap.size());
		
		MethodDependence.addUninvokeMethod(methodDefinition, paraTypeMap);
	}

当参数满足后,执行带参数的方法

static void invokeMethodWithPara(Class<?> klass,Object object,Method method) {
		Class<?>[] paraTypes = method.getParameterTypes();//获取方法所有参数的类型
		Object[] paraValues = new Object[paraTypes.length];//生成对应参数个数的实参列表
		
		for(int index=0;index<paraTypes.length;index++) {
			Class<?> paraklass = paraTypes[index];
			//这里的getBeanObject()是一定可以取到的,
			//在该方法外层已经判断过,所有需要的参数均存储在BeanPool
			BeanDefinition beanDefinition = getBeanObject(paraklass.getName());
			paraValues[index] = beanDefinition.getObject();
		}

		try {
			//执行该方法,把该方法执行之后产生的bean也才存储在BeanPool中
			Class<?> beanClass = method.getReturnType();
			Object bean = method.invoke(object, paraValues);
			BeanDefinition beanDefinition = new BeanDefinition();
			
			beanDefinition.setKlass(beanClass);
			beanDefinition.setObject(bean);
			//注入和单例保持默认
			
			BeanPool.put(beanClass.getName(), beanDefinition);
			MethodDependence.checkDependence(beanClass);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		
	}

类----MethodDependence,包含四个方法,分别是将方法加入参数不满足列表、检查依赖(即检查方法需要的参数是否满足)、执行参数满足列表中的方法、输出dependenceMethodPool中剩余的内容(即最终也无法满足依赖的参数和对应的方法)

public class MethodDependence {
	private static final List<MethodDefinition> uninvokeMethodList
			= new ArrayList<MethodDefinition>();//存储参数不满足的方法
	private static final List<MethodDefinition> invokeableMethodList
			= new LinkedList<MethodDefinition>();//存储满足参数,可执行的方法
	private static final Map<Class<?>, List<MethodDefinition>>
			dependenceMethodPool = new HashMap<Class<?>, List<MethodDefinition>>();//存储暂时不存在的参数类型

	MethodDependence() {
	}
	
	/*
	 * 当遇到参数不满足的方法时:
	 * 当发现方法的参数不满足时,需要把方法放到uninvokeMethodList,
	 * 并将不满足的参数类型放到dependenceMethodPool
	 * 		如果pool里不存在该参数类型,创建一个以该类型为key,value暂时为空的键值对
	 * 存在,根据该参数类型获取对应的方法列表,把该方法加入列表中
	 */
	static void addUninvokeMethod(MethodDefinition methodDefinition,
			Map<Class<?>, Integer> paraTypePool) {
		
		uninvokeMethodList.add(methodDefinition);
		
		for (Class<?> paraType : paraTypePool.keySet()) {
			//判断参数类型是否在Pool中,
			//如不包含,增加一个键值对
			if (!dependenceMethodPool.containsKey(paraType)) {
				List<MethodDefinition> methodList = new ArrayList<>();
				dependenceMethodPool.put(paraType, methodList);
			}
			//包含则从Pool获取methodDefination,加到methodList中
			List<MethodDefinition> methodList = dependenceMethodPool.get(paraType);
			methodList.add(methodDefinition);
		}
	}
	
	/*
	 * 检查依赖:
	 *		每次只要产生bean,即向beanPool中进行put,就要执行检查依赖操作
	 * 根据类型获取map中的mdList
	 * 遍历列表 ,判断列表中方法的参数个数是否为0
	 * 若为0,及说明该方法需要的所有参数均已满足,将其加入okMethodList
	 * (okMethodList为中间临时存储可执行方法的列表)
	 * 	若okMethodList不为空,将该列表中方法从未满足列表删除,加入可执行列表
	 */
	static void checkDependence(Class<?> beanClass) {
		List<MethodDefinition> mdList = dependenceMethodPool.get(beanClass);
		if (mdList == null) {
			return;
		}
		List<MethodDefinition> okMethodList = new ArrayList<>();
		for (MethodDefinition md : mdList) {
			//此处调用sub(),是因为之前的操作确定了beanPool中
			//新增了一个之前不存在的参数类型,现在找到了该参数类型,
			//那么该参数类型对应的方法的参数个数就需要减1
			if (md.sub() == 0) {
				okMethodList.add(md);
			}
		}
		//若okMethodList不为空,说明该方法可执行
		//将该方法从uninvokeMethodList删除,加入invokeableMethodList
		if (!okMethodList.isEmpty()) {
			for (MethodDefinition method : okMethodList) {
				uninvokeMethodList.remove(method);
				invokeableMethodList.add(method);
			}
		}	
		dependenceMethodPool.remove(beanClass);//删除pool中的键
	}
	
	//执行invokeableMethodList列表中方法
	static void invokeDependenceMethod() {
		//每次执行“可执行列表”中方法,每次执行一个,删除一个方法,直到列表中没有方法
		while (!invokeableMethodList.isEmpty()) {
			MethodDefinition methodDefinition = invokeableMethodList.get(0);
			Object object = methodDefinition.getObject();
			Class<?> klass = object.getClass();
			Method method = methodDefinition.getMethod();
			invokeableMethodList.remove(0);//只删除第一个方法
			
			//调用BeanFactory中的方法,执行参数满足的方法
			BeanFactory.invokeMethodWithPara(klass, object, method);
		}
	}
	
	//输出dependenceMethodPool中最后残留的方法和其参数类型
	static String getUndependence() {
		StringBuffer str = new StringBuffer();
		
		for (Class<?> dependenceClass : dependenceMethodPool.keySet()) {
			List<MethodDefinition> mdList = dependenceMethodPool.get(dependenceClass);
			for (MethodDefinition md : mdList) {
				str.append(md.getMethod())
				.append("  --> ").append(dependenceClass.getName())
				.append('\n');
			}
		}
		
		return str.toString();
	}	
}

若将所有可装入的Bean均装载完成,还是有方法的参数不能满足,如A m1(B),B m2(A),这两个方法的参数为彼此,这就导致两个方法最终都互相牵制,无法执行,这就是***循环依赖***。对于这样的情况,只能进行输出,由用户自行处理。

//输出循环依赖,由用户判断
	private void showCircleDependency() {
		System.out.println(MethodDependence.getUndependence());
	}

这就是对带参方法的整个处理过程。
下面进行一下测试

@Component
public class Config {
	public Config() {
	}
	
	@Bean
	public Two getTwo(One one) {//Two的参数类型为One
		Two two = new Two();
		two.setOne(one);
		return two;
	}
	
	@Bean
	public One getOne(Two two) {//One的参数类型为Two
		One one = new One();
		one.setTwo(two);
		return one;
	}
}
//测试
One o = beanFactory.getBean(One.class);

运行结果:输出无法执行的方法以及对应的参数类型在这里插入图片描述

到目前为止,还有一个注解Qualifier,下一篇博客将会解释该注解的作用以及用法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值