spring ioc的模拟实现

ioc的模拟实现

Spring框架在Java中绝对是大名鼎鼎,ioc(反转控制)更是spring的核心。所谓临渊羡鱼不如退而结网,Spring框架的使用固然非常重要,但如果我们能更弄清楚spring框架的原理,使用自然不在话下。因此我做了一个ioc的模拟框架 ,希望能帮助到大家更深层的学习spring。

## 整体思路分析
1.构建容器
2.懒汉模式完成注入。

如何构建容器

1.加注解:
给需要存放在容器中的类加上@Component注解;给该类需要注入的成员加上@Autowired,表示在getBean的时候需要对这个成员进行自动的注入。如果我们想得到jar包中某个类的对象,当然无法给jar包中的类加注解,但是我们可以写一个方法,让它的返回值为jar包中的类,并给该方法加上@Bean注解,执行这个方法就能得到该类的对象。

这是普通类加注解的方式

@Component
public class ClassOne {
	@Autowired
	private Complex complex;
	private String str;

这是给jar包里面的类加注解的方式,当然假设Point类是jar包里的类啦,通过执行Bean注解的方法就能得到一个Point类的对象。

@Component
public class Configuration {

	public Configuration() {
	}
	
	@Bean
	public Point getPoint( PointOne c) {
		Point point = new Point();
		return point;
	}

2.包扫描收集带注解的类和方法。

通过包扫描收集所有带@Component注解的类,把它放在我们事先准备好的BeanPool里面,BeanPool是一个Map,其中键为类名,值是BeanDefinitation类的对象,而BeanDefinitation的三个成员为

	private Class<?> klass;
	private Object object;
	private boolean isinject//是否注入

其中通过包扫描,每次扫描到带@Component注解的类,就New一个BeanDefinitation对象,并设置它的成员为相关带注解的类的信息,如下是包扫描的相关代码

new PackageScanner() {
			@Override
			public void dealClass(Class<?> klass) {
				if(klass.isPrimitive()
						||klass.isAnnotation()
						||klass.isInterface()
						||klass.isEnum()
						||klass.isArray()
						||!klass.isAnnotationPresent(Component.class)){
						return;
					}
				
				BeanDefinitation bd = new BeanDefinitation();
				bd.setKalss(klass);
				try {
					bd.setObject(klass.newInstance());
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
				bd.setisInject(false);
				BeanPool.put(klass.getName(), bd);
				collectMethod(bd,alreadList);//收集相关带Bean注解的方法

			}
			
		}.packageScanner(packageName);
			dealBean(alreadList);//处理相关方法。
	}

collectMethod(bd,alreadList) 是 收集带Bean注解的方法,因为某些方法的参数可能需要在BeanPool里面获取,因此我们通过包扫描只收集相关方法,等到扫描结束形成了相关的BeanPool后统一执行

collectMethod(BeanDefinitation bd,AlreadMethod alreadList) 收集方法:

private void collectMethod(BeanDefinitation bd,AlreadMethod alreadList) {
		Method method[] = bd.getKalss().getDeclaredMethods();
		for(Method meth : method){
			if(!meth.isAnnotationPresent(Bean.class)){
				continue;
			}
			BeanMethodDefinitation bmd = new BeanMethodDefinitation();
			bmd.setKlass(bd.getKalss());
			bmd.setMethod(meth);
			bmd.setObj(bd.getObject());
			bmd.setParaCount();
			boolean hasDepen = bdpen.dealDependecheShip(bmd);//判断该方法是否还有依赖关系
			if(!hasDepen){
				alreadList.add(bmd);
			}
		}
	}

可以看出来,每扫描到一个带注解的类时,我们就得到这个类的所有带Bean注解的方法,并New一个BeanMethodDefinitation,与BeanDefinitation相似,用来保存执行该方法的相关信息。每一个方法的执行都需要有相关参数的依赖关系,通过调用bdpen.dealDependecheShip(bmd)( BeanDependence:专门用来处理方法参数之间依赖关系的类。bdpen为它的一个实例),如果判断依赖关系全部成立就把对应的BeanMethodDefinitation加入到alreadList 里面 (AlreadMethod 类的实例,用来处理可以直接执行的方法,alreadList.add(BeanMethodDefinitation),是把可执行的方法加到AlreadMethod中存储可执行方法的一个list里面) 如果不能直接执行就稍后处理。

## 如何建立依赖关系

一个类的实例可以作为参数和多个方法建立依赖关系,即很多方法的执行都需要这个参数,如下代码:

	@Bean
	public Point getPoint(Complex c) {
		Point point = new Point(c);
		return point;
	}
	@Bean
	public PointOne getPointONe(Complex c, Point p1,Point p2,){
		return new PointOne();
	}
依赖参数方法
ComplexgetPoint[1], getPointONe[2]
PointgetPointONe [2]

中括号中为方法参数类型的个数。也是该方法依赖参数的个数
类型个数的获取还是有个小技巧的,代码如下:

void setParaCount() {
		int count = method.getParameterCount();
		if(count <= 0){
			return;
		}
		
		Map<Class<?>, Object> paraMap = new HashMap<Class<?>, Object>();
		for(Parameter para : method.getParameters()){
			paraMap.put(para.getType(), null);
		}
		this.paraCount = paraMap.size();
	}

我们先获取方法的所有的参数,把参数的类型作为键添加进去,如果键值相同就会进行覆盖,因此paraMap的大小就是方法参数类型的个数。

有了依赖参数和依赖这个参数的所有方法之后,我们就可以构建依赖关系的表了,代码入下:

boolean dealDependecheShip( BeanMethodDefinitation bmd){
		Method meth = bmd.getMethod();
		int count = bmd.getParaCount();
		if(count <= 0){
			return false;//无参方法直接返回false
		}
		
		List<BeanMethodDefinitation> methList = null;
		for (Parameter para : meth.getParameters()){//得到方法的所有参数
			if(!dependenceShip.containsKey(para.getType())){
			//判断这个参数是否已经作为键值与某些方法形成依赖关系,
			//如果没有,就申请一个methList用来存放依赖该参数的所有方法,
			//并把参数类型作为键,methList作为值加入到Map中去形成依赖对应
				methList = new ArrayList<BeanMethodDefinitation>();
				dependenceShip.put(para.getType(), methList);
			}
			methList = dependenceShip.get(para.getType());
			methList.add(bmd);//把这个方法加到对应参数依赖关系的List里面去
		}
		return true;//返回true,表示存在依赖关系	
	}

由此生成的dependenceShip形成了参数和方法对应关系的Map;

3.处理待执行方法
通过以上两步,我们收集了一些带有@Component注解的Bean和待执行的方法,接下来我们看看怎么处理这些待执行的方法,让我们的BeanPool丰富起来

每个方法执行之后都会对BeanPool进行拓展,依赖关系也会发生改变,因此每执行一个方法之前都需要检查一下依赖关系,需要把依赖关系列表中的存在的参数类型和BeanPool里面的类型进行比较,如果BeanPool里面已经有了 dependenceShip的参数类型,则所有方法对该参数的依赖关系都成立,每一个方法依赖的参数个数都会减少一个,此时如果该方法依赖的参数个数为0,说明该方法需要的所有参数都已经在BeanPool中存在了,就可以把它加入到readList里面去。下面是检测依赖关系的代码:

	private void checkDependence(AlreadMethod almethod){
		Set<String> keys = BeanPool.keySet();//得到BeanPool的所有键值
		for(String key : keys){
			bdpen.isDependence(key, almethod);//遍历键值,检测该值是否在依赖		关系的列表中也存在
		}
	}
 void isDependence(String key, AlreadMethod almenth) {
		List<BeanMethodDefinitation> bmdlist;
		try {
			bmdlist = dependenceShip.get(Class.forName(key));//根据传过来的BeanPool的键值,寻找dependenceShip中对应参数的list
			if(bmdlist == null){
				return;//参数没有对应的依赖关系
			}
			for(BeanMethodDefinitation bmd : bmdlist){
				if(bmd.decrease()<= 0){//每一个方法依赖的参数个数都会减1,
					almenth.add(bmd);//如果该方法依赖的参数个数为0,说明该方法需要的所有参数都已经在BeanPool中存在了,就把它加入到可执行方法列表。
				}
			}
			dependenceShip.remove(Class.forName(key));//所有方法对该参数的依赖关系都成立,dependenceShip删除这个键。
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

举例说明:

@Bean
	public Point getPoint(Complex c) {
		Point point = new Point(c);
		return point;
	}
	@Bean
	public PointOne getPointONe(Complex c, Point p1,Point p2,){
		return new PointOne();
	}

如果我们要执行带注解的方法,把BeanPool中的键依次传递过来,在dependenceShip中对比有没有Point,和Complex参数,若Complex即在BeanPool里又在dependenceShip里,则Complex对应的依赖关系就会全部成立,
依赖关系就会变成

依赖参数方法
删除ComplexgetPoint[1 - 1], getPointONe[2 -1]
PointgetPointONe [2-1]

此时getPoint依赖的参数全部成立,可以列为可执行方法。

检查完依赖关系我们就可以执行方法了:

private <T> void dealBean(AlreadMethod readyMethod) {
		Object paraValue[];
		checkDependence(readyMethod);
		while(readyMethod.hasNext()){//判断可执行方法的列表是否还有
			BeanMethodDefinitation bmd = readyMethod.out();//取出第一个方法
			Parameter parameter[] = bmd.getMethod().getParameters();
			int count = parameter.length;
			if(count<=0){
				paraValue = new Object[]{};
			}else{
				
				paraValue = new Object[count];
				for(int i = 0; i < count; i++){
						paraValue[i] = BeanPool.get(parameter[i].getType().getName()).getObject
						//在BeanPool中获取参数,构造方法的参数数组
						//我们在执行方法时候没有对的它参数成员进行注入,通过直接getObject浅注入
						//这样做存在着一些不合理的地方,为简化期间,先进性这样的操作。
					
				}
			}
			
			Object result = bmd.invokeMeth(paraValue);//反射机制指向该方法
			BeanDefinitation bd = new BeanDefinitation();//用方法的返回值构造一个Bean
			bd.setKalss(result.getClass());
			bd.setObject(result);
			bd.setisInject(true);
			BeanPool.put(result.getClass().getName(), bd);//把Bean加入到BeanPool中
			checkDependence(readyMethod);//再次检查依赖关系是否变化
		}
	}

到此我们就完成了容器的构造工作!!!!!

懒汉模式注入

通过上面的操作,我们已经完成了容器的构建,接下来我们可以从容器中得到Bean了。下面是getBean方法:

public  <T> T getBean(String className){
		if(bdpen.hasNext()){//检查方法中的循环依赖
			System.out.println("存在循坏依赖");
			System.out.println(bdpen.dependenceShip);
		}
		BeanDefinitation bd = BeanPool.get(className);
		if(bd == null){
			throw new HasNoBeanException("没有找到" + className + "的Bean");
		}
	
		if(!bd.isIsinject()){//注入参数
			bd.setisInject(true);
			injectBean(bd);
		}
		
		return (T) bd.getObject();
	
	}

在得到Bean之前,我们先要判断所有的依赖关系是否都处理完成了,如果依赖关系的列表不为空,说明存在循环依赖,比如下面的情况:

	@Bean
	public Point getPoint(PointOne pointOne) {
		Point point = new Point();
		return point;
	}
	@Bean
	public PointOne getPointONe(Point point){
		return new PointOne();
	}

getPoint方法的执行需要PointOne,作为参数,getPointONe的执行需要Point作为参数,两个方法形成了循环依赖,无法执行,在第一次getBean的时候抛出异常,并输出无法正常指向的依赖关系,提示使用者。

循环依赖抛出异常:
在这里插入图片描述
如果能够正常执行就开始给这个Bean注入参数,一下是注入参数的代码:

private  void injectBean(BeanDefinitation bd){
		Field[] fields = bd.getKalss().getDeclaredFields();
		//得到Bean的所有成员
		for(Field field : fields){
			field.setAccessible(true);
			if(!field.isAnnotationPresent(Autowired.class)){
				continue;
			}
		//遍历它的所有成员得到带Autowired注解的方法
			String valueName = field.getType().getName();
			Object obj = getBean(valueName);
			//得到该类成员的Bean,递归调用完成对参数成员的注入
			if(obj == null){
				 throw new HasNoBeanException("没有找到" + valueName + "的Bean");
			}
			try {
				field.set(bd.getObject(), obj);
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}
## 下面举例说明一下有关getBean部分的递归调用。
@Component
public class ClassOne {
	@Autowired
	private Complex complex;
	private String str;
	
@Component
public class Complex {
	private double real;
	private double vir;
	@Autowired
	private ClassTwo calssTwo;
@Component
public class ClassTwo {
	private int number;

以上三个类,如果我们想通过getBean的方式得到ClassOne 的对象,就必须对它带@Autowired的成员进行注入,再对ClassOne的成员complex进行注入的过程中,发现complex中还有需要注入的成员classTwo,先完成对classTwo的注入,然后再注入Complex,最后完成对ClassOne的注入,由此就形成了getBean方法和inject方法的递归调用。

如何处理参数之间的循环依赖

@Component
public class ClassOne {
	@Autowired
	private Complex complex;
	private String str;
@Component
public class Complex {
	private double real;
	private double vir;
	@Autowired
	private ClassOne classOne;

如果要通过getBean得到ClassOne的对象,与习惯不同,我们先把该对象标记为注入,再对其进行注入这样就能避免循环递归。

	if(!bd.isIsinject()){//注入参数
			bd.setisInject(true);
			injectBean(bd);
		}

最后测试类是这个样子的:

public class Test {

	public static void main(String[] args) {
		BeanFactory2 bf = new BeanFactory2();
		bf.scanPackage("com.mec.spring.imitate.someclass");
		System.out.println(bf.getBean(Point.class));
		
	}

}

以上就是我对spring ioc 的模拟实现,虽然做得比较粗糙,还有很多不完善的地方,但是对IOC的总体思路是对的,以后也会一遍学习一遍继续完善,希望这篇文章能过帮助到大家学习spring框架。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值