这篇文章主要给大家介绍了关于对SpringIOC、DI的理解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
前言
你可能会有如下问题:
1、想看Spring源码,但是不知道应当如何入手去看,对整个Bean的流程没有概念,碰到相关问题也没有头绪如何下手
2、看过几遍源码,没办法彻底理解,没什么感觉,没过一阵子又忘了
本文将结合实际问题,由问题引出源码,并在解释时会尽量以图表的形式让你一步一步彻底理解Spring Bean的IOC、DI、生命周期、作用域等。
先看一个循环依赖问题
现象
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:
如何理解“依赖”呢,在Spring中有:
- 构造器循环依赖
- field属性注入循环依赖
直接上代码:
1.构造器循环依赖
@Service public class A { public A(B b) { } }
@Service public class B { public B(C c) { } }
@Service public class C { public C(A a) { } }
结果:项目启动失败,发现了一个cycle。
2.field属性注入循环依赖
@Service public class A1 { @Autowired private B1 b1; }
@Service public class B1 { @Autowired public C1 c1; }
@Service public class C1 { @Autowired public A1 a1; }
结果:项目启动成功
3.field属性注入循环依赖(prototype)
@Service @Scope("prototype") public class A1 { @Autowired private B1 b1; }
@Service @Scope("prototype") public class B1 { @Autowired public C1 c1; }
@Service @Scope("prototype") public class C1 { @Autowired public A1 a1; }
结果:项目启动失败,发现了一个cycle。
现象总结:同样对于循环依赖的场景,构造器注入和prototype类型的属性注入都会初始化Bean失败。因为@Service默认是单例的,所以单例的属性注入是可以成功的。
更多前端的相关知识储备,我们简单分析一下:因为Singleton是单例的,所以在项目启动时就会初始化,prototypeBean本质上只是它的一个Property,那么ApplicationContex中只存在一个SingletonBean和一个初始化SingletonBean时创建的一个prototype类型的PrototypeBean。
那么每次调用SingletonBean.doSomething()时,Spring会从ApplicationContex中获取SingletonBean,每次获取的SingletonBean是同一个,所以即便PrototypeBean是prototype的,但PrototypeBean仍然是同一个。每次打印出来的内存地址肯定是同一个。
那这个问题如何解决呢?
解决办法也很简单,这种情况我们不能通过注入的方式注入一个prototypeBean,只能在程序运行时手动调用getBean(“prototypeBean”)方法,我写了一个简单的工具类:
@Service public class SpringBeanUtils implements ApplicationContextAware { private static ApplicationContext appContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringBeanUtils.appContext=applicationContext; } public static ApplicationContext getAppContext() { return appContext; } public static Object getBean(String beanName) { checkApplicationContext(); return appContext.getBean(beanName); } private static void checkApplicationContext() { if (null == appContext) { throw new IllegalStateException("applicaitonContext未注入"); } } @SuppressWarnings("unchecked") public static T getBean(Class clazz) { checkApplicationContext(); Map map = appContext.getBeansOfType(clazz); return map.isEmpty() ? null : (T) map.values().iterator().next(); } }
对于这个ApplicationContextAware接口:
在某些特殊的情况下,Bean需要实现某个功能,但该功能必须借助于Spring容器才能实现,此时就必须让该Bean先获取Spring容器,然后借助于Spring容器实现该功能。为了让Bean获取它所在的Spring容器,可以让该Bean实现ApplicationContextAware接口。
感兴趣的读者自己可以试试。
总结:
回到循环依赖的问题,有的人可能会问singletonBeanFactory只是一个三级缓存,那么一级缓存和二级缓存有什么用呢?
其实大家只要理解整个流程就可以切入了,Spring在初始化Singleton的时候大致可以分几步,初始化――设值――销毁,循环依赖的场景下只有A――B――A这样的顺序,但在并发的场景下,每一步在执行时,都有可能调用getBean方法,而单例的Bean需要保证只有一个instance,那么Spring就是通过这些个缓存外加对象锁去解决这类问题,同时也可以省去不必要的重复操作。Spring的锁的粒度选取也是很吊的,这里暂时不深入研究了。
解决此类问题的关键是要对SpringIOC和DI的整个流程做到心中有数,看源码一般情况下不要求每一行代码都了解透彻,但是对于整个的流程和每个流程中在做什么事需要了然,这样实际遇到问题时才可以很快的切入进行分析解决。
希望这篇文章可以帮助你对Spring的IOC和DI的流程有一个更深刻的认识!
到此这篇关于理解SpringIOC、DI的文章就介绍到这了,更多相关理解SpringIOC、DI内容请搜索gaodaima搞代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持gaodaima搞代码网!
以上就是一篇文章彻底理解SpringIOC、DI的详细内容,更多请关注gaodaima搞代码网其它相关文章!