【Java框架】自主模拟实现Spring核心——循环依赖问题的解决


承接上文制作的工具( IoC容器),我们可以进行对循环依赖问题的讨论。

(一)循环注入

给三个类说明这个问题

import com.mec.mSpring.core.Autowired;
import com.mec.mSpring.core.Component;

@Component
public class A {

	@Autowired
	private B b;
	
	public A() {
	}

	public B getB() {
		return b;
	}

	public void setB(B b) {
		this.b = b;
	}

	@Override
	public String toString() {
		return "A类中的B成员:" + b;
	}
	
	
}

---

import com.mec.mSpring.core.Autowired;
import com.mec.mSpring.core.Component;

@Component
public class B {

	@Autowired
	private C c;
	
	public B() {
	}

	public C getC() {
		return c;
	}

	public void setC(C c) {
		this.c = c;
	}

	@Override
	public String toString() {
		return "B类中的C成员:" + c;
	}
	
	
}
---

import com.mec.mSpring.core.Autowired;
import com.mec.mSpring.core.Component;

@Component
public class C {

	@Autowired
	private A a;
	
	public C() {
	}

	public A getA() {
		return a;
	}

	public void setA(A a) {
		this.a = a;
	}

	@Override
	public String toString() {
		return "C类中的A成员:" + a;
	}
	
}

测试

import com.mec.mSpring.core.BeanFactory;

public class Demo4 {

	public static void main(String[] args) {

		BeanFactory.scanPackage("com.mec.mSpring.test");
		BeanFactory beanFactory = new BeanFactory();
		A result = beanFactory.getBean(A.class);
		System.out.println(result);
	}

}

/*输出结果
Exception in thread "main" java.lang.StackOverflowError
	at java.base/java.lang.Class.privateGetDeclaredFields(Unknown Source)
	at java.base/java.lang.Class.getDeclaredFields(Unknown Source)
	at com.mec.mSpring.core.BeanFactory.injectByField(BeanFactory.java:90)
	at com.mec.mSpring.core.BeanFactory.doInject(BeanFactory.java:85)
	at com.mec.mSpring.core.BeanFactory.getBean(BeanFactory.java:66)
	at com.mec.mSpring.core.BeanFactory.getBean(BeanFactory.java:75)
	at 
	...
*/

  从结果我们可以看见,栈溢出,该错误一般是由无穷递归造成的。仔细跟踪代码发现A需要注入的时候,去beanPool中找B,发现B也要注入,去beanPool中找C,发现C也需要注入,又去beanPool中找A,这样就循环注入了,所以栈溢出。
在这里插入图片描述
再接着跟踪代码,发现出递归代码出现的地方。

/*以前注入时的做法*/
		if (!beanDefinition.isInject()) {
			synchronized (beanPool) {	
				if (!beanDefinition.isInject()) {
					doInject(beanDefinition);
					beanDefinition.setInject(true);
				}
			}
		}

/*改过之后*/
		if (!beanDefinition.isInject()) {
			synchronized (beanPool) {	
				if (!beanDefinition.isInject()) {
					beanDefinition.setInject(true);
					doInject(beanDefinition);
				}
			}
		}

  是的,错误就在设置是否注入这里,如果设置在后面,则会一直卡在这里,因为它等它,它等它成了死循环的等待。我们的解决方法时,只要它没注入准备注入,那么就先把它设置为已经注入了再去做注入操作。

/*再次测试发现结果还是有错误*/
Exception in thread "main" java.lang.StackOverflowError
	at java.base/java.lang.StringUTF16.checkBoundsOffCount(Unknown Source)
	at java.base/java.lang.StringUTF16.inflate(Unknown Source)
	at java.base/java.lang.StringLatin1.inflate(Unknown Source)
	at java.base/java.lang.AbstractStringBuilder.inflate(Unknown Source)
	at java.base/java.lang.AbstractStringBuilder.putStringAt(Unknown Source)
	at java.base/java.lang.AbstractStringBuilder.append(Unknown Source)
	at java.base/java.lang.StringBuilder.append(Unknown Source)
	at java.base/java.lang.StringBuilder.<init>(Unknown Source)
	.....

原来是我们的toString方法出现了递归调用。

/*修改C类中的toString方法*/
@Override
public String toString() {
	return "C类中的a成员" + (a == null ? "为" : "不为") + "null";
}

/*结果*/
A类中的B成员:B类中的C成员:C类中的a成员不为null

  问题成功解决。但是setInject(true)放前面是有风险的,如果注入过程inject()失败,这不就成谎报军情了吗?我们这就规定注入过程一定不能有错误。只要注入算法写的十分好,就不会出现上述循环注入问题,去看Spring源代码的注入过程如何巧妙。

  之所以不称它为循环依赖而叫循环注入,是因为依赖是放进beanPool的过程,在扫描的时候不会出现错误,它已经放在beanPool里面去了,依赖关系是成功的,而这个错误是出现在注入过程的,所以我认为他应该叫循环注入。

  再强调一遍,A中有B,B中有C,C中有A这不是循环依赖,这是循环注入!!!

(二)循环依赖

接下来这个例子才是真正的循环依赖。

/*没有Component注解的First类*/
import com.mec.mSpring.core.Autowired;

public class First {

	@Autowired
	private Second second;
	
	public First() {
	}

	public Second getSecond() {
		return second;
	}

	public void setSecond(Second second) {
		this.second = second;
	}

	@Override
	public String toString() {
		return "First类中的Second成员:" + second;
	}
	
}

/*没有Componenet注解的Second类*/
import com.mec.mSpring.core.Autowired;

public class Second {

	@Autowired
	private First first;
	
	public Second() {
	}

	public First getFirst() {
		return first;
	}

	public void setFirst(First first) {
		this.first = first;
	}

	@Override
	public String toString() {
		return "Second的First成员" + ((first == null) ? "为" : "不为") + "null";
	}
	
}

/*带有Configuration注解类里面加Bean注解方法*/
	@Bean
	public First getFirst(Second second) {
		First first = new First();
		first.setSecond(second);
		
		return first;
	}
	
	@Bean
	public Second getSecond(First first) {

		Second second = new Second();
		second.setFirst(first);
		
		return second;
		
	}

  很明显First和Second类都没有Componenet注解,也就意味着它们不会被扫描到,也就不会通过扫描的时候加在beanPool中,他俩是通过Bean注解通过方法得到的。getFirst()有first资源但没有second资源,getSecond()有two资源但没有one资源。两边都不松手,造成卡在这里了,类似于操作系统的死锁。

在这里插入图片描述
  关于循环依赖的问题,编程根被无法解决,只能依靠用户自己设置的细心,没有其他办法。但是,我们可以将循环依赖的的依赖关系给用户输出出来!给用户看,知道自己在哪出错,就可以去改。
  关于循环依赖问题显示的解决也需要先处理带参数的@Bean注解的方法。或者说,两者是应该同时需要考虑的。接下来就是最精彩的关于带参数的@Bean注解的方法的解决。

(三)带参数的@Bean注解的方法

思考过程

  上文我们提到了,Bean注解下的方法,被反射机制执行,方法所得到的结果放到IoC容器里。无参方法当然可以直接执行。关键是带参数的方法,仔细考虑,其参数一定也是IoC容器里的一个Bean。但这时候就有问题了,相关参数不确定当前有没有存在在IoC容器中,所以我们不能贸然执行这个方法。

  也就是说,扫描到这个方法了,但他参数还未满足(Ioc容器中),我们不可以贸然invoke这个方法。时机问题啊!

  对于有Bean注解且含有参数的方法的处理:这里的关键是,方法所依赖的参数是否满足?如果得到了满足,则该方法反射执行就能得到一个Bean。如果多个方法的参数形成了循环依赖关系,则,应该对这种循环依赖情况告知用户。其核心是:检测方法所依赖的参数是否得到满足,而这种检测何时进行,这需要考虑。问题不在于最终方法的执行。

  大题思路:所以我认为,扫描到带参方法,先把他的依赖关系存起来,存到容器里,不急执行,等到时机成熟,开始执行。最后容器为空说明依赖关系都满足了;不为空,说明还存在依赖关系,很有可能就是循环依赖。

  可以先考虑对这些要完成的带参数的方法进行讨论,根据面对对象思想,我们这个方法也就可以形成一个类MethodDefinition。其组成有:对象,方法本身,这些很容易想到。但是,再次强调,最需要解决的不是方法本身,也不是方法执行所需要的对象,而是方法执行时所需要的参数及参数的依赖关系。或者说这个参数是否满足了,如果满足了就可以进行最终的执行,不满足还要等一等。如何表示参数满足了呢?标志位(boolean)?解决方法应该是,参数个数!paraCount,其初始值就是方法参数个数,满足一个-1,减到0说明到了满足了就可以执行了。

  依赖关系的表示,经过多次考虑,思想挣扎。将所有不满足依赖关系的方法形成一个Map,键为参数类型,值为MethodDefinition形成的List。一旦这个参数经过某次包扫描得到了满足,就在这个Map里把他所依存关系进行删除(删除Map里一个键值对),删除之前由将MethodDefinition形成的List的所有MethodDefinition的count-1。

  还需要两个容器进一步表示依赖关系,一个不满足要求(count != 0)的MethodDefinition形成的list,一个满足要求(count = 0)的MethodDefinition形成的list(方便最终执行)。

综上所述,需要准备三个容器来解决。

  • 参数类型为键,List<MethodDefinition>为值的Map
  • 不能满足依赖关系的MethodDefinition列表
  • 满足依赖关系的MethodDefinition列表

思考过程图片实例

  我知道上面一大段文字,你们肯定看晕了,但它确实就是我考虑这个问题真实的一步步的思路。为了更清晰明白的理解,用图片来描述吧。

假设有一次扫描带@Bean注解的方法扫描到这几个方法method1(arg1,arg2, arg3)method2(arg4,arg3)method3(arg1,arg3,arg4)。这几个参数都不满足,根据我们上面的讨论就会产生如下图示。

在这里插入图片描述

其中beanPool就是我们前面那个IoC容器,不满足列表是一个List<MethodDefinition>,放了不满足条件的方法;满足条件列表也是个List<MethodDefinition>,满足条件的放到这里来。最重要的是后面那个Map,其键为参数类型,值为因为缺少该参数的MethodDefinition形成的一个List。

经过一段时间arg3和arg4参数所在的包扫描到了,被加在beanPool中了,那么就要做以下事情。

arg3有了,删除参数依赖Map里arg3的依赖依赖关系(map的一个键值对),删除之前md1依赖关系从3变成2了(count),md2依赖关系从2变成1了。

arg4有了,删除pool里arg4的依赖依赖关系,删除之前md1依赖关系从2变成1了,md2依赖关系从1变成0了满足了,那就要移动,从不满足List移动到满足List。

在这里插入图片描述
就这样如此往复,参数依赖Map就会慢慢被清空,最后依赖关系Map里如果没有任何东西了,说明都刚刚好满足,如果里面还有东西就说明存在循环依赖了!

  接下里就阐述下引入这个工具的考量
  Q:为什么用一张表来保存参数和依赖参数的方法。
  A:是为了查找速度,没有这张表,你就要每次在不满足的List里面一个个找。该Map里面还有循环依赖的关系!给他的目的是为了降低时间复杂度,以前查方法再查找方法参数时间复杂度O(n^2),现在只需要O(n*logn)。存在意义是避免浪费时间。不满足的列表用LinkList,因为中间出去比较多。满足列表用ArrayList,最后一下顺序执行。

代码实现

  首先的类就是我们的MethodDefinition类,其实是描述我现在暂时不能执行的带参数的方法。想想它的组成,object调用对象,method方法本身,paraCount参数个数,Object[] 参数所需要执行的参数数组值。但经过深思熟虑,我们都知道参数是有要有顺序填充的,假如一个多参方法有满足的参数还有未满足的参数,那他首先填充就要一次遍历,当产生了为满足的参数,又需要再一次遍历,为了不加复杂性,我们不要Object[]参数数值,反正参数值最后都要存在beanPool中。根据paraCount去判断它该不该执行就好,当count为0,说明都满足了,无非就是从benaPool取数据再去填充。

MethodDefinition类

import java.lang.reflect.Method;

public class MethodDefinition {

	private Object object;
	private Method method;
	private int paraCount;
//	private Object[] paraValues;  经过考虑不需要
	
	MethodDefinition() { //工具内部用的,没想给外面用
	}

	Object getObject() {
		return object;
	}

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

	Method getMethod() {
		return method;
	}

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

	int getParaCount() {
		return paraCount;
	}

	void setParaCount(int paraCount) {
		this.paraCount = paraCount;
	}
		
	int subParaCount() {
		return --this.paraCount;
	}
	
}

有了这个类我们终于可以去处理带参数的方法了(初步)!

BeanFactory代码中

if (paraCount != 0) {
//带参数的Bean注解下的方法
	dealMethodWithPara(configObject, method);
} else {
	Object result = method.invoke(configObject, new Object[] {});
	BeanDefinition beanDefinition = new BeanDefinition(returnType, result);
	beanPool.put(returnType.getName(), beanDefinition);
}

private static void dealMethodWithPara(Object object, Method method) {
		
	}

此时,又出现问题了,假如我们遇到这样一个带@Bean注解的方法。

public Some getSome(T1 arg1, T2 arg2, T1 arg3, T1 arg4) { 
 }

某一个方法参数类型可能重复,那么问题来了,该被当成几个参数呢?当然是只有两个T1和T2。问题是如何得到是两个呢?循环过一遍它的参数,肯定是四次,如果再循环中检测是否有出现过的类型,算法复杂度就很大了。怎么处理这个问题呢?

处理方法:用Map,因为put不会加键值重复的,是覆盖。键是类型,后面Object(没有任何作用,因为键是重要的)。这一步叫参数类型去重

得到了这一个里面有明确参数类型的paraPool,接下来我需要证实哪一个能在beanPool中取得到,哪一个还不行。能从beanPool中取得就从paraPool中删除,不能取得就留在paraPool中。

但是!!!这里牵扯一个大问题!List和Map不可以一边遍历一遍删除!!!会报异常ConcurrentModificationException,其原因是因为modCount和exceptedModCount不一致,就会抛出此异常。这个是为了安全的考虑。以后看源代码时候可以了解到。

遇到的问题一:List和Map不可以一边遍历一遍删除

验证

public class Test {

	public static void main(String[] args) {

		List<String> list = new ArrayList<>();
		list.add("a");
		list.add("b");
		list.add("c");
		list.add("d");
		list.add("e");
		
		for (int i = 0; i < list.size(); i++) {
			list.remove(i);
		}
		System.out.println(list.size());
		for (int i = 0; i < list.size(); i++) {
			System.out.println(list.get(i));
		}
	}

}


/*输出结果
2
b
d
*/

图示解释
在这里插入图片描述
典型的舟已行,不可复刻。因此我们要记得:List和Map一定不可以简单的一边遍历一遍删除

原因是这样的:List和Map是一个动态容器,它会随着你的增删改变它的结构:如List的size属性,Map也是一样,删除一个,它的keyset结构可能发生变化,红黑树为了平衡去调整,取下一个元素就不准确了。所以当然不可以一边遍历一遍删除。

解决方法是先遍历一遍,标记要删除的,后面再删除。

我需要去验证哪个参数是可以取得的,哪一个参数是不可以取得的。可以取得的,把他从para删掉,取不到就在paraPool保留。

BeanFactory代码中

	private static void dealMethodWithPara(Object object, Method method) {
		Map<Class<?>, Object> paraTypeMap = getMethodPara(method);
		
		if (paraTypeMap.isEmpty()) {	
			//这里说明带参方法参数都满足了,那就赶紧去执行,别再往下了
			invokeMethodWithPara(object, method);
			return;
		}
		MethodDefinition methodDefinition = new MethodDefinition();
		methodDefinition.setObject(object);
		methodDefinition.setMethod(method);
		methodDefinition.setParaCount(paraTypeMap.size());
		//System.out.println("不是empty,说明要执行这里" + method);阶段性测试Demo5,发现是正确的
		//TODO 对于不能到现在不能执行的方法,要放到我们的工具中!
	}

//这个方法是为了获取参数方法没有办法从beanpool获取参数的集合
	private static Map<Class<?>, Object> getMethodPara(Method method) {
		Map<Class<?>, Object> paraPool = new HashMap<>();
		Class<?>[] paraTypes = method.getParameterTypes();
		//去重,去掉类型重复的参数类型
		for (int i = 0; i < paraTypes.length; i++) {
			paraPool.put(paraTypes[i], null);  //值是次要的,我要的是键
		}
		List<Class<?>> klassList = new ArrayList<>();
		for (Class<?> type : paraPool.keySet()) {
			BeanDefinition beanDefinition = beanPool.get(type.getName()); 
			//这里不能用getBean,就去注入了,大大降低程序执行效率,我根本是不需要那个bean的,
			//只是看他存不存在
			if (beanDefinition != null) {
				klassList.add(type);
			}
		}
		for (Class<?> type : klassList) {	//删除pool的东西,没有对pool进行遍历
			paraPool.remove(type);
		}
		return paraPool;
	}

	static void invokeMethodWithPara(Object object, Method method) {
		Class<?>[] paraTypes = method.getParameterTypes();
		Object[] paraValues = new Object[paraTypes.length];
		
		for (int i = 0; i < paraTypes.length; i++) {
			Class<?> paraKlass = paraTypes[i];
			BeanDefinition beanDefinition = beanPool.get(paraKlass.getName());
			//依然不用getBean,不需要注入,因为注入的对象可能是缺失的,如果注入失败,下面就全错
			paraValues[i] = beanDefinition.getObject();
		}
		try {
			Object bean = method.invoke(object, paraValues);
			Class<?> returnKlass = method.getReturnType();
			BeanDefinition newBeanDefinition = new BeanDefinition(returnKlass, bean);
			beanPool.put(returnKlass.getName(), newBeanDefinition);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

需要注意的是,注意这里用的是beanPool.get()而不是getBean()方法,因为后者还要设计一大堆注入过程,我现在只想要个简简单单。用 getBean方法就会提前注入,我要的不是最后的Object,我现在只是看他存不存在,如果提前注入会大大降低程序执行效率。

做到这里,思路顺序已经完成了前半截部分,先别急往下,记得进行阶段性验证!

public class Demo5 {

	public static void main(String[] args) {

		BeanFactory.scanPackage("com.mec.mSpring.test");
		BeanFactory beanFactory = new BeanFactory();
		Four four = beanFactory.getBean(Four.class);
		System.out.println(four);
	}

}

import com.mec.mSpring.core.Autowired;

public class Four {

	@Autowired
	private Complex complex;
	
	public Four() {
	}

	public Complex getComplex() {
		return complex;
	}

	public void setComplex(Complex complex) {
		this.complex = complex;
	}
	
	@Override
	public String toString() {
		return "Four类中的成员:" + complex;
	}
}

@Configuration注解下的类中加
	@Bean
	public Four getFour(Complex complex) {
		Four four = new Four();
		four.setComplex(complex);
		return four;
	}

/*
其结果有可能是
1.bean[com.mec.mSpring.test.Four]没有定义
null
2.Four类中的成员:Complex [real=9.8, vir=2.0]
*/

发现上述测试竟然有两种结果!确实,是应该有两种结果,测试证明我们写到现在完全是1正确的!两种结果的原因是:结果一说明Complex类还没扫描到,先扫描到了getFour方法。结果二说明Complex类对象先存在在IoC中,然后扫描到了getFour方法。也充分验证了给IoC容器加对象的无序性,指不定哪个先哪个后。

辅助工具的产生(1Map2List)

//TODO 对于不能到现在不能执行的方法,要放到我们的工具中!

终于要到我们创造工具的时候了。这个工具经过我们前面的思考过程总结为一个Map和两个List组成。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class MethodDependence {
	
	private static final List<MethodDefinition> uninvokeMethodList
		= new ArrayList<>();
	private static final List<MethodDefinition> invokeableMethodList
		= new LinkedList<>();
	private static final Map<Class<?>, List<MethodDefinition>> dependenceMethodPool
 		= new HashMap<>();
	
	
	MethodDependence() {
	}
	
}

想想我们这工具具体操作有哪些呢?首先就是遇到了参数不满足的方法。

	/**
	 * 遇到了参数不满足的方法,需要进行对uninvokeMethodList和dependenceMethodPool进行填充
	 * @param methodDefinition 参数不满足的方法
	 * @param paraTypePool 不满足参数的类型
	 */
	static void addUninvokeMethod(MethodDefinition methodDefinition, 
			Map<Class<?>, Object> paraTypePool) {
		uninvokeMethodList.add(methodDefinition);
		
		for (Class<?> klass : paraTypePool.keySet()) {
			if (!dependenceMethodPool.containsKey(klass)) {
				List<MethodDefinition> methodDefinitionList = new ArrayList<>();
				dependenceMethodPool.put(klass, methodDefinitionList);
			}
			List<MethodDefinition> methodList = dependenceMethodPool.get(klass);
			methodList.add(methodDefinition);
		}
	}

然后就是如果IoC容器多了新的bean就要检查循环依赖关系,进行相关移动操作。

	/**
	 * 如果beanPool中加入一个新bean,就要checkDenpendence,从依赖池中获取到methodList
	 * 把methodList里的所有methodDefinition的count都-1,如果有count = 0,说明有的方法可以
	 * 执行了,生成一个okMethodList记载这些可执行的方法。在判断okMethodList是否为空,为空
	 * 则去做收尾工作,不为空,移动相关方法,再做收尾工作。
	 * 收尾工作:将满足的依赖关系从dependenceMethodPool中删除。拿键删除,方便GC回收。
	 * @param beanKlass beanPool中新加的bean的类型
	 */
	static void checkDenpendence(Class<?> beanKlass) {
		List<MethodDefinition> methodList = dependenceMethodPool.get(beanKlass);
		if (methodList == null) {	//如果是null,说明没有以这个参数类型存在的依赖关系
			return;
		}
		List<MethodDefinition> okMethodList = new ArrayList<>();
		for (MethodDefinition md : methodList) {
			if (md.subParaCount() == 0) {
				okMethodList.add(md);
			}
		}
		if (!okMethodList.isEmpty()) {
			for (MethodDefinition method : okMethodList) {
				uninvokeMethodList.remove(method);
				invokeableMethodList.add(method);
			}
		}
		dependenceMethodPool.remove(beanKlass);
	}

然后就是,扫描包完成后,要把放在可以执行列表的方法执行一下。

	/**
	 * 要执行可以执行的Method
	 */
	static void invokeDependenceMethod() {
		while(!invokeableMethodList.isEmpty()) {
			MethodDefinition methodDefinition = invokeableMethodList.get(0);
			Object object = methodDefinition.getObject();
			Method method = methodDefinition.getMethod();
			invokeableMethodList.remove(0);
			BeanFactory.invokeMethodWithPara(object, method);
		}
	}

最后就是,我们的循环依赖关系就存在在参数依赖Map中,将它整理好输出出去。

	/**
	 * 显示在dependenceMethodPool存在的循环依赖问题
	 * @return
	 */
	static String getCyclicDependence() {
		StringBuffer str = new StringBuffer();
		
		for (Class<?> dependenceClass: dependenceMethodPool.keySet()) {
			List<MethodDefinition> methodList = dependenceMethodPool.get(dependenceClass);
			for (MethodDefinition md : methodList) {
				str.append(md.getMethod())
					.append(" ---> ")
					.append(dependenceClass.getName()).append("\n");
			}
		}
		return str.toString();
	}

我们这个工具的所有成员和围绕成员的操作就提供好了。
工具完整代码。

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class MethodDependence {
	
	private static final List<MethodDefinition> uninvokeMethodList
		= new ArrayList<>();
	private static final List<MethodDefinition> invokeableMethodList
		= new LinkedList<>();
	private static final Map<Class<?>, List<MethodDefinition>> dependenceMethodPool
 		= new HashMap<>();
	
	MethodDependence() {
	}
	
	/**
	 * 遇到了参数不满足的方法,需要进行对uninvokeMethodList和dependenceMethodPool进行填充
	 * @param methodDefinition 参数不满足的方法
	 * @param paraTypePool 不满足参数的类型
	 */
	static void addUninvokeMethod(MethodDefinition methodDefinition, 
			Map<Class<?>, Object> paraTypePool) {
		uninvokeMethodList.add(methodDefinition);
		
		for (Class<?> klass : paraTypePool.keySet()) {
			if (!dependenceMethodPool.containsKey(klass)) {
				List<MethodDefinition> methodDefinitionList = new ArrayList<>();
				dependenceMethodPool.put(klass, methodDefinitionList);
			}
			List<MethodDefinition> methodList = dependenceMethodPool.get(klass);
			methodList.add(methodDefinition);
		}
	}
	
	/**
	 * 如果beanPool中加入一个新bean,就要checkDenpendence,从依赖池中获取到methodList
	 * 把methodList里的所有methodDefinition的count都-1,如果有count = 0,说明有的方法可以
	 * 执行了,生成一个okMethodList记载这些可执行的方法。在判断okMethodList是否为空,为空
	 * 则去做收尾工作,不为空,移动相关方法,再做收尾工作。
	 * 收尾工作:将满足的依赖关系从dependenceMethodPool中删除。拿键删除,方便GC回收。
	 * @param beanKlass beanPool中新加的bean的类型
	 */
	static void checkDenpendence(Class<?> beanKlass) {
		List<MethodDefinition> methodList = dependenceMethodPool.get(beanKlass);
		if (methodList == null) {	//如果是null,说明没有以这个参数类型存在的依赖关系
			return;
		}
		List<MethodDefinition> okMethodList = new ArrayList<>();
		for (MethodDefinition md : methodList) {
			if (md.subParaCount() == 0) {
				okMethodList.add(md);
			}
		}
		if (!okMethodList.isEmpty()) {
			for (MethodDefinition method : okMethodList) {
				uninvokeMethodList.remove(method);
				invokeableMethodList.add(method);
			}
		}
		dependenceMethodPool.remove(beanKlass);
	}
	
	/**
	 * 要执行可以执行的Method
	 */
	static void invokeDependenceMethod() {
		while(!invokeableMethodList.isEmpty()) {
			MethodDefinition methodDefinition = invokeableMethodList.get(0);
			Object object = methodDefinition.getObject();
			Method method = methodDefinition.getMethod();
			invokeableMethodList.remove(0);
			BeanFactory.invokeMethodWithPara(object, method);
		}
	}
	
	/**
	 * 显示在dependenceMethodPool存在的循环依赖问题
	 * @return
	 */
	static String getCyclicDependence() {
		StringBuffer str = new StringBuffer();
		
		for (Class<?> dependenceClass: dependenceMethodPool.keySet()) {
			List<MethodDefinition> methodList = dependenceMethodPool.get(dependenceClass);
			for (MethodDefinition md : methodList) {
				str.append(md.getMethod())
					.append(" ---> ")
					.append(dependenceClass.getName()).append("\n");
			}
		}
		return str.toString();
	}
	
}

BeanFactory应用这个工具

对于所有有往beanPool中添加新bean的时候,我们要检查依赖循环MethodDependence.checkDenpendence

对于扫描完一个包,我们要去把存在在可以执行的方法的List里的方法都执行一下。MethodDependence.invokeDependenceMethod()

对于得不到相关bean时我们要把存在在工具中的依赖关系输出出去供用户自己查看是否出现了依赖循环。

BeanFactory完整

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.mec.util.PackageScanner;

public class BeanFactory {

	private static final Map<String, BeanDefinition> beanPool = new HashMap<>();//IoC容器
	
	public BeanFactory() { 
	}
	
	public static void scanPackage(String packageName) {
		new PackageScanner() {
			
			@Override
			public void dealClass(Class<?> klass) {
				if (klass.isPrimitive() || klass.isArray() || klass.isInterface()
						|| klass == String.class || klass.isEnum() || klass.isAnnotation()
						|| (!klass.isAnnotationPresent(Component.class) && !klass.isAnnotationPresent(Configuration.class))) {
				return;
				}
				try {
					if (klass.isAnnotationPresent(Configuration.class)) {
						dealBean(klass);
					} else {
						Object object = klass.getConstructor(new Class[] {}).newInstance(new Object[] {});
						BeanDefinition beanDefinition = new BeanDefinition(klass, object);
						beanPool.put(klass.getName(), beanDefinition);
						MethodDependence.checkDenpendence(klass);
					}
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				} catch (SecurityException e) {
					e.printStackTrace();
				}
			}
		}.scanPackage(packageName);
		MethodDependence.invokeDependenceMethod();
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getBean(String beanId) {
		BeanDefinition beanDefinition = beanPool.get(beanId);
		if (beanDefinition == null) {
			System.out.println("bean["	+ beanId + "]没有定义");
			System.out.println(MethodDependence.getCyclicDependence());
			return null;
		}
		

		if (!beanDefinition.isInject()) {
			synchronized (beanPool) {	
				if (!beanDefinition.isInject()) {
					beanDefinition.setInject(true);
					doInject(beanDefinition);
				}
			}
		}
		return (T) beanDefinition.getObject();
	}
	
	public <T> T getBean(Class<?> klass) {
		return getBean(klass.getName());
	}
		
	public void addBean(String nickName, BeanDefinition beanDefinition) { //不想用类名称当作键,可以用别名
		beanPool.put(nickName, beanDefinition);
	}
	
	private void doInject(BeanDefinition beanDefinition) {
	 	Class<?> klass = beanDefinition.getKlass();
		Object object = beanDefinition.getObject();
		injectByField(klass, object);
		injectBySetMethod(klass, object);
	}
	
	private void injectByField(Class<?> klass, Object object) {
		Field[] fields = klass.getDeclaredFields();
		for (Field field : fields) {
			if (!field.isAnnotationPresent(Autowired.class)) {
				continue;
			}
			Class<?> fieldClass = field.getType();
			Object value = getBean(fieldClass);
			field.setAccessible(true);
			try {
				field.set(object, value);
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void injectBySetMethod(Class<?> klass, Object object) {
		Method[] methods = klass.getDeclaredMethods();
		for (Method method : methods) {
			if (!method.isAnnotationPresent(Autowired.class)
					|| !method.getName().startsWith("set")
					|| method.getParameterCount() != 1
					|| method.getModifiers() != Modifier.PUBLIC) {
				continue;
			}
			Class<?> paraClass = method.getParameterTypes()[0];
			Object value = getBean(paraClass);
			try {
				method.invoke(object, new Object[] {value});
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		}
	}
	
	private static void dealBean(Class<?> klass) {
		try {
			Object configObject = klass.getConstructor(new Class[] {}).newInstance(new Object[] {});
			Method[] methods = klass.getMethods();
			for (Method method :methods) {
				if (!method.isAnnotationPresent(Bean.class)) {
					continue;
				}
				int paraCount = method.getParameterCount();
				Class<?> returnType = method.getReturnType();
				if (paraCount != 0) {
					//带参数的Bean注解下的方法
					dealMethodWithPara(configObject, method);
				} else {
					Object result = method.invoke(configObject, new Object[] {});
					BeanDefinition beanDefinition = new BeanDefinition(returnType, result);
					beanPool.put(returnType.getName(), beanDefinition);
					MethodDependence.checkDenpendence(returnType);
				}
			}
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}
	}
	
	private static void dealMethodWithPara(Object object, Method method) {
		Map<Class<?>, Object> paraTypeMap = getMethodPara(method);
		
		if (paraTypeMap.isEmpty()) {	
			//这里说明带参方法参数都满足了,那就赶紧去执行,别再往下了
			invokeMethodWithPara(object, method);
			return;
		}
		MethodDefinition methodDefinition = new MethodDefinition();
		methodDefinition.setObject(object);
		methodDefinition.setMethod(method);
		methodDefinition.setParaCount(paraTypeMap.size());
		//System.out.println("不是empty,说明要执行这里" + method);阶段性测试Demo5,发现是正确的
		//对于不能到现在不能执行的方法,要放到我们的工具中!
		MethodDependence.addUninvokeMethod(methodDefinition, paraTypeMap);
	}
	
	//这个方法是为了获取参数方法没有办法从beanpool获取参数的集合
	private static Map<Class<?>, Object> getMethodPara(Method method) {
		Map<Class<?>, Object> paraPool = new HashMap<>();
		Class<?>[] paraTypes = method.getParameterTypes();
		//去重,去掉类型重复的参数类型
		for (int i = 0; i < paraTypes.length; i++) {
			paraPool.put(paraTypes[i], null);  //值是次要的,我要的是键
		}
		List<Class<?>> klassList = new ArrayList<>();
		for (Class<?> type : paraPool.keySet()) {
			BeanDefinition beanDefinition = beanPool.get(type.getName()); 
			//这里不能用getBean,就去注入了,大大降低程序执行效率,我根本是不需要那个bean的,
			//只是看他存不存在
			if (beanDefinition != null) {
				klassList.add(type);
			}
		}
		for (Class<?> type : klassList) {	//删除pool的东西,没有对pool进行遍历
			paraPool.remove(type);
		}
		return paraPool;
	}
	
	static void invokeMethodWithPara(Object object, Method method) {
		Class<?>[] paraTypes = method.getParameterTypes();
		Object[] paraValues = new Object[paraTypes.length];
		
		for (int i = 0; i < paraTypes.length; i++) {
			Class<?> paraKlass = paraTypes[i];
			BeanDefinition beanDefinition = beanPool.get(paraKlass.getName());
			//依然不用getBean,不需要注入,因为注入的对象可能是缺失的,如果注入失败,下面就全错
			paraValues[i] = beanDefinition.getObject();
		}
		try {
			Object bean = method.invoke(object, paraValues);
			Class<?> returnKlass = method.getReturnType();
			BeanDefinition newBeanDefinition = new BeanDefinition(returnKlass, bean);
			beanPool.put(returnKlass.getName(), newBeanDefinition);
			MethodDependence.checkDenpendence(returnKlass);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}
	
}

测试检测到循环依赖没,以前的First和Second例子

import com.mec.mSpring.core.BeanFactory;

public class Demo6 {

	public static void main(String[] args) {

		BeanFactory.scanPackage("com.mec.mSpring.test");
		BeanFactory beanFactory = new BeanFactory();
		First first= beanFactory.getBean(First.class);
		Second second = beanFactory.getBean(Second.class);
	}

}
/*输出结果
bean[com.mec.mSpring.test.First]没有定义
public com.mec.mSpring.test.Second com.mec.mSpring.test.Config.getSecond(com.mec.mSpring.test.First) ---> com.mec.mSpring.test.First
public com.mec.mSpring.test.First com.mec.mSpring.test.Config.getFirst(com.mec.mSpring.test.Second) ---> com.mec.mSpring.test.Second

bean[com.mec.mSpring.test.Second]没有定义
public com.mec.mSpring.test.Second com.mec.mSpring.test.Config.getSecond(com.mec.mSpring.test.First) ---> com.mec.mSpring.test.First
public com.mec.mSpring.test.First com.mec.mSpring.test.Config.getFirst(com.mec.mSpring.test.Second) ---> com.mec.mSpring.test.Second


*/

可以看到,循环依赖的关系显示出来了。

至此,我们模拟Spring——IoC容器部分就结束了。感谢您的阅读,希望对您有所帮助。有什么地方不足希望指出。有什么地方不理解可以私信或者评论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值