我是靠谱客的博主 生动小霸王,最近开发中收集的这篇文章主要介绍Spring IOC实现原理----------懒汉模式,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

本文主要关注Spring IOC部分

将需要实例化的类提前交给Spring IOC容器,并且将类关联的类也实例化并且赋值给关联的类(称为依赖注入)。懒汉模式呢就是当我们需要这个类的时候才会去实现依赖注入。

这次主要看的是IOC也就是控制反转。
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
(抄写百度百科,有关于Spring框架的IOC定义)

我们采用的是懒汉模式,就是先将扫描到的类先放进去,并不注入,等我开始取对象的时候再开始注入。

既然要保存那么我们就需要建立一个工厂来保存。我用的是注解的方式,当然如果你喜欢XML文件的方式也是可以的。
我们需要三个注解,@Component,@Autowire,@Bean。

  • @Component需要实例化的类
  • @Autowire写在成员的身上,需要实例化的类的关联类(八大基本类型就没必要了啊)
  • @Bean这个是写在方法上面的(这个有一些复杂稍后再解释,和jar包有关)

像这样

@Component
public class OneKlass {

	@Autowired
	private Complex complex;
	@Autowired
	private MecPoint point;

写好之后让我们来建一个工厂。(BeanFactory)工厂里面有一个map叫beanPool,它的键为类的名字(String类型的),值为BeanDefinition(一个类)。

BeanDefinition里就是这些东西,还有这些成员的get和set方法。

	private Class<?> klass;
	private Object object;
	private boolean inject;

klass是带有@Component注解的类,object是这个类的对象。inject是判断这个类是否被注入,就是说这个类里的带有@Autowire注解的成员是否被赋值(也可以说是否被实例化成一个对象)。

第一件事当然是进行包扫描了。将扫描到的带有@Component注解的类打扮一下,以这个类的名字为键,再构建一个BeanDefinition为值放入beanPool中就好了。

public class BeanFactory {
	private static final Map<String, BeanDefinition> beanPool;
	static {
		beanPool = new HashMap<String, BeanDefinition>();
	}
	
	public BeanFactory() {
	}
	
	public void scanBeanByPackage(String packageName) {
		ParameterDependance pd = new ParameterDependance();
		OnReadyMethodBeanDefinition ormbd = new OnReadyMethodBeanDefinition();
		
		new PackageScanner() {
			@Override
			public void dealClass(Class<?> klass) {
				if(klass.isAnnotation()
						|| klass.isEnum()
						|| klass.isInterface()
						|| klass.isPrimitive()
						|| klass.isArray()
						|| !klass.isAnnotationPresent(Component.class)) {
					return;
				}
				Object object = null;
				try {
					object = klass.newInstance();
					
					//构建一个BeanDefinition
					BeanDefinition bd = new BeanDefinition();
					bd.setKlass(klass);
					bd.setObject(object);
					
					beanPool.put(klass.getName(), bd);
				} catch (Exception e) {
					e.printStackTrace();
				}
				
				//这里只收集bean,并不对bean做任何处理。
				collectBean(klass, object, ormbd);
			}
		}.packageScanner(packageName);
		
		//这里才对bean进行处理
		pd.dealDependance(ormbd);
		dealOnreadyBean(ormbd, pd);
	}

我们谈谈注入,当执行getBean方法的时候,就要开始注入了,这个时候已经是所有的扫描结果结束,该放在map里面的已经都在了。(不可能这么简单的)

a类getBean的时候,我们返回它的对象结果,但是如果这个类里有需要我们实例化的成员怎么办,所以就需要我们在得到这个类的对象的时候,同时也要对其需要注入的成员进行注入。这样返回的结果就是一个有灵魂的对象。

设想一种情况,两个类,a和b都有@Component注解,然后a里面有b,b里面有a。这样子去注入的话,如果不加以判断,就会产生循环依赖。因为注入的过程是一个递归啊。

准备注入,先判断inject,为false,先将其设置为true(表示注入了,但是这样做是很危险的,万一后面注入的代码没有正常运行,那就完了),然后去注入。在injectFieldToKlass这个方法里,我们可以得到一个类的所有成员,反射机制啊,我们要注入当然要找需要注入的,就是那些带有@Autowire注解的成员。得到这个成员以后,我们就开始调用getBean方法,看到没有,递归就形成了。但不同的是,这个成员在要注入的时候会判断自己注入过没有,没有就继续,注入过的话就直接返回。这就避免了循环依赖。

private void injectFieldToKlass(BeanDefinition bd) {
		Class<?> klass = bd.getKlass();
		Object obj = bd.getObject();
		Field[] fields = klass.getDeclaredFields();
				
		for(Field field : fields) {
			if(!field.isAnnotationPresent(Autowired.class)) {
				continue;
			}
			field.setAccessible(true);
			Object value = getBean(field.getType());
			try {
				field.set(obj, value);
			} catch (IllegalArgumentException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		bd.setInject(true);
	}
	
	/*
	 * getBean();执行的时候,我们就要开始注入了。
	 */
	@SuppressWarnings("unchecked")
	public <T> T getBean(String klassName) {
		BeanDefinition beanDefinition = getBeanDefination(klassName);
		
		if(beanDefinition == null) {
			throw new RuntimeException(klassName + "该类没有对应的Bean");
		}
		
		Object object = beanDefinition.getObject();
		//判断该类是否被注入,若没有,才进行注入。注入了就直接返回。被用来解决循环依赖
		if(!beanDefinition.isInject()) {
			beanDefinition.setInject(true);
			injectFieldToKlass(beanDefinition);
		}
		
		return (T) object;
	}
	
	public <T> T getBean(Class<?> klass) {
		return getBean(klass.getName());
	}
	
	public BeanDefinition getBeanDefination(Class<?> klass) {
		String klassName = klass.getName();
		return beanPool.get(klassName);
	}
	
	//getBeanDefination();方法只是得到beanDefinition,并不注入。
	public BeanDefinition getBeanDefination(String klassName) {
		return beanPool.get(klassName);
	}
	

还有最后一个问题,如果我们要给一个jar包自动生成对象,怎么办
总不可能去更改源代码吧,于是我们有一个办法,把生成jar包对象写成一个方法,然后反射调用这个方法,我们就可以得到他的对象了,再把这些对象也放到beanPool里,然后就算注入的时候成员是jar包形式的也不怕了。

别忘了注入一定是在所有的收集工作完成以后才开始。

假如MecPoint是jar包形式的,就像这样来实例化他的对象。

@Component
public class BeanJar {
	
	@Bean
	public MecPoint getPoint() {
		return new MecPoint();
	}

	@Bean
	public Complex getComplex(OneClass oc){
		return new Complex();
	}
	
}

OK,让我们来收集@Bean,有两种情况。一种是无参的,这种可以直接调用生成对象。还有一种是带参的,若是参数可以在工厂里找到,那便是极好的,可要是他的参数也是一个@Bean,那就要先放一放了。带参也有可能是多个参数。那怎么办呢?

/*
 * 带有bean注解的method
 * 方法中的参数个数不定,我们需要知道参数个数
 */
public class BeanMethodDefinition {
	private Class<?> klass;
	private Object object;
	private Method method;
	private int paraCount;

构建一个BeanMethodDefinition,类是放有@Bean注解方法的类,object就是这个类的对象,method就是带有@Bean注解的方法,paraCount就是参数的个数。

我们将参数与方法形成一种对应关系,存在这样一种情况,一个参数可能对应多个方法,于是就变成参数与方法列表形成对应关系,做一个map,以参数类型为键,方法列表为值。

我们在收集@Bean的时候,若是无参就放到一个List里面,泛型类型就是BeanMethodDefinition,这里面放的都是可以执行的方法,后期map里面有了可以执行的方法也放到list里面来。

/*
 * 这个类里放的都是已经准备好了的,可以直接调用产生对象的方法。
 * 他的功能,存放这些已经准备好的方法,可以再次加入准备好的方法,可以删除已经实现的方法。查看是否存在还有
 * 没有实现的方法。
 * 我们取的时候从第一个取,存的时候从末尾加。
 */
public class OnReadyMethodBeanDefinition {
	private List<BeanMethodDefinition> onReadyMethods;
	
	OnReadyMethodBeanDefinition() {
		onReadyMethods = new LinkedList<BeanMethodDefinition>();
	}
	
	void in(BeanMethodDefinition bmd) {
		onReadyMethods.add(bmd);
	}
	
	boolean hasNext() {
		return !onReadyMethods.isEmpty();
	}
	
	BeanMethodDefinition next() {
		return onReadyMethods.remove(0);
	}
	
}

有参就放到map里。放到paraDependance这个map中。

/*
 * 这个类里的map用来存放那些暂时还无法实现的方法,带有参数的,而参数还没有找到。
 */
public class ParameterDependance {
	private static final Map<Class<?>, List<BeanMethodDefinition>> paraDependance;
	static {
		paraDependance = new HashMap<Class<?>, List<BeanMethodDefinition>>();
	}
	
	//一个参数可能对应多个方法.参数与方法的对应。	将找到的带有bean注解的方法放入map
	//如果返回值是false,那么说明是无参,就可以放入onReady。反之,则放入本类的map中
	boolean putParameter(BeanMethodDefinition bmd) {
		Method method = bmd.getMethod();
		Parameter[] parameters = method.getParameters();
		
		if(parameters.length <= 0) {
			return false;
		}
		
		for(Parameter para : parameters) {
			Class<?> type = para.getType();
			if(!paraDependance.containsKey(type)) {
				paraDependance.put(type, new ArrayList<BeanMethodDefinition>());
			}
			List<BeanMethodDefinition> beanMethodList = paraDependance.get(type);
			beanMethodList.add(bmd);
		}
		
		return true;
	}
	
	//传递过来的参数,在map中遍历一遍,若是存在依赖关系,参数关系减一,并删除这种依赖关系
	/*
	 * 若是参数个数为零,那么就将这个方法放入onReady,若是List<BeanMethodDefinition>为零,那么就将map
	 * 中的这个键值对删掉。
	 * 每实现一个bean方法,得到该对象将其放入工厂中,并检查map中有没有可以放入onReady里的方法。
	 */
	void dealDependance(Class<?> klass, OnReadyMethodBeanDefinition orbd) {
		List<BeanMethodDefinition> beanMethodList = paraDependance.get(klass);
		if(!paraDependance.containsKey(klass)) {
			return;
		}
		if(beanMethodList == null) {
			return;
		}
		for(BeanMethodDefinition beanMethod : beanMethodList) {
			int count = beanMethod.decreaseParaCount();
			if(count == 0) {
				orbd.in(beanMethod);
			}
			beanMethodList.remove(beanMethod);
			if(beanMethodList.isEmpty()) {
				paraDependance.remove(klass);
			}
		}
	}
	
	/*
	 * 这样的做法工作量最小,先将工厂里的对象遍历一遍,将map里能解决的方法放入onReady里
	 *	然后再反射实现onReady里的方法
	 */
	void dealDependance(OnReadyMethodBeanDefinition orbd) {
		BeanFactory bft = new BeanFactory();
		
		for(Class<?> klass : paraDependance.keySet()) {
			if(bft.getBeanDefination(klass.getName()) != null) {
				dealDependance(klass, orbd);
			}
		}
	}
}

我们在得到所有的bean方法时,把他们分为两类,一类是无参的,一类是有参的。然后放在不同的地方,等到所有的bean方法也收集完成后。

先处理工厂里是否存在可以解决bean方法的参数类型,将paraDependance这个map中的参数类型遍历一遍,若是有对应关系,则方法列表里面的每一个方法参数个数减一,并删除这种对应关系。判断参数个数是否为零,为零,那么就将这个方法放入list里面。判断方法列表是否为空,若为空,说明这个参数对应的方法已经全部解决完了,那么就删除这种对应关系,从paraDependance这个map中。也就是上面的**void dealDependance(OnReadyMethodBeanDefinition orbd)**这个方法。

dealOnreadyBean方法:
然后就是list里面的bean方法了,每解决完一个bean方法,我们就调用上面的 void dealDependance(Class<?> klass, OnReadyMethodBeanDefinition orbd) 这个方法,将得到的对象和OnReadyMethodBeanDefinition传过去。

private void collectBean(Class<?> klass, Object object, 
			OnReadyMethodBeanDefinition ormbd) {
		ParameterDependance parameter = new ParameterDependance();
		
		Method[] methods = klass.getMethods();
		for(Method method : methods) {
			if(!method.isAnnotationPresent(Bean.class)) {
				continue;
			}
			BeanMethodDefinition bmd = new BeanMethodDefinition();
			bmd.setKlass(klass);
			bmd.setMethod(method);
			bmd.setObject(object);
			
			if(!parameter.putParameter(bmd)) {
				ormbd.in(bmd);
			}
		}
	}
	
	/*
	 * 如果参数是bean方法里面的,这个模式也可以解决。
	 */
	private Object[] getParas(Method method) {
		Parameter[] paras = method.getParameters();
		int paraCount = paras.length;
		Object[] values = null;
		if(paraCount <= 0) {
			return values;
		}
		int i = 0;
		values = new Object[paraCount];
		for(Parameter para : paras) {
			BeanDefinition bd = getBeanDefination(para.getType());
			if(bd != null) {
				values[i++] = bd.getObject();
			}
		}
		return values;
	}
	
	private void dealOnreadyBean(OnReadyMethodBeanDefinition ormbd, ParameterDependance pd) {
		while(ormbd.hasNext()) {
			BeanMethodDefinition bmd = ormbd.next();
			Method method = bmd.getMethod();
			Object obj = bmd.getObject();
			
			Object[] paras = getParas(method);
			try {
				Object object = method.invoke(obj, paras);
				Class<?> objectClass = object.getClass();
				BeanDefinition bd = new BeanDefinition();
				//因为是jar包形式的,没有办法对其进行注入,这还是一个问题。
				bd.setInject(true);
				bd.setKlass(objectClass);
				bd.setObject(object);
				beanPool.put(objectClass.getName(), bd);
				
				pd.dealDependance(objectClass, ormbd);
			} catch (IllegalAccessException | IllegalArgumentException 
					| InvocationTargetException e) {
				e.printStackTrace();
			}
		}
	}

感谢微易码教主的指导。

最后

以上就是生动小霸王为你收集整理的Spring IOC实现原理----------懒汉模式的全部内容,希望文章能够帮你解决Spring IOC实现原理----------懒汉模式所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(33)

评论列表共有 0 条评论

立即
投稿
返回
顶部