我是靠谱客的博主 生动小霸王,这篇文章主要介绍Spring IOC实现原理----------懒汉模式,现在分享给大家,希望可以做个参考。

本文主要关注Spring IOC部分

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

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

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

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

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

像这样

复制代码
1
2
3
4
5
6
7
8
@Component public class OneKlass { @Autowired private Complex complex; @Autowired private MecPoint point;

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

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

复制代码
1
2
3
4
private Class<?> klass; private Object object; private boolean inject;

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

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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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方法,看到没有,递归就形成了。但不同的是,这个成员在要注入的时候会判断自己注入过没有,没有就继续,注入过的话就直接返回。这就避免了循环依赖。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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包形式的,就像这样来实例化他的对象。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component public class BeanJar { @Bean public MecPoint getPoint() { return new MecPoint(); } @Bean public Complex getComplex(OneClass oc){ return new Complex(); } }

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

复制代码
1
2
3
4
5
6
7
8
9
10
/* * 带有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里面来。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* * 这个类里放的都是已经准备好了的,可以直接调用产生对象的方法。 * 他的功能,存放这些已经准备好的方法,可以再次加入准备好的方法,可以删除已经实现的方法。查看是否存在还有 * 没有实现的方法。 * 我们取的时候从第一个取,存的时候从末尾加。 */ 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中。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/* * 这个类里的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传过去。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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内容请搜索靠谱客的其他文章。

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

评论列表共有 0 条评论

立即
投稿
返回
顶部