IOC功能模拟实现
什么是IOC?
把创建和管理bean的过程转移给了第三方,而这个第三方就是Spring IOC.
何为控制,控制的是什么?
是bean的创建、管理的权利,控制bean的整个生命周期。
什么是反转?
对象的创建这个权利交给了Spring容器,而不是自己去控制,就是反转。
何为依赖,依赖什么?
程序运行需要依赖外部的资源,提供程序内对象的所需要数据、资源。
什么是注入?
配置文件把资源从外部注入到内部,容器加载了外部的文件、对象、数据,然后把这些资源注入给程序内的对象,维护了程序内外对象之间的依赖关系。
为什么要用IOC这种思想,IOC能给我们带来什么好处?
解耦。
如图:
IOC实现原理:
- 自定义注解
- 扫描根路径,递归,通过反射获取所有的类,放入 AllSet 里
- 从 AllSet 里获取所有带有IOC注解的类,放到 IOCSet 里
- 利用反射(Class.getDeclaredConstructor().newInstance())给 IOCSet 里的每一个类实例化,然后放入类型为ConcurrentHashMap的 InstanceMap 里
- 接着从AllSet里遍历每一个类,判断里面有没有字段带注入注解(@Autowired),没有就跳过
- 有,则首先根据类型去去匹配 InstanceMap
- 如果匹配上,且注解上没有自定义类的名字,从 InstanceMap 取出对应类型的实例,利用反射注入到成员变量对应的实例上面
- 没匹配上,且注解上没有自定义类的名字,则去 InstanceMap 里找对应类的实现类或者子类,再利用反射注入
- 注解上自定义了类的名字,且实现类和子类仍没有匹配上,则遍历每一个类,并获得类的 SimpleName ,用 SimpleName与自定义类名比较,相同的话利用反射注入
Spring中 @Autowired注解与@Resource注解的区别?
@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false。如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。需要注意的是,@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。
SpringIOC到底省略了我们什么工作?
new 对象的过程。
Spring中的bean的作用域有哪些?
- singleton: 唯一bean实例,Spring中的bean默认都是单例的。
- prototype:每次从容器中取,如getBean都会创建一个新的bean实例
- request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTPrequest内有效。
- session:同一个HTTPSession公用一个bean实例。
Spring中的单例bean的线程安全问题?
当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
解决方法:
-
在bean中尽量减少可变的成员变量(private final 变量)
-
在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中。(怎么理解???):基于线程的变量,不会被其他线程获取到。ThreadLocal数据结构类似一个Map的ThreadLocalMap结构。用法
ThreadLocal<Integer> totalScore = new ThreadLocal<>(); totalScore.set(1); System.out.println(totalScore.get()); totalScore.set(2); totalScore.set(3); System.out.println(totalScore.get());
Spring中的bean生命周期?
- Bean容器找到配置文件中Spring Bean的定义。
- Bean容器利用Java Reflection API创建一个Bean的实例。
- 如何涉及到一些属性值, 利用set()方法设置一些属性值。
- 如果bean实现了beanNameAware 接口,调用setBeanName()方法,传入Bean的名字。
- 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
- 如果Bean实现了BeanFactoryAware接口,调用setBeanClassLoader()方法,传入ClassLoader 对象的实例。
- 与上面相似,如果实现了其他的 *.Aware接口,就调用相应的方法。
- 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法
- 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法
- 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
- 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法
- 当要销毁 Bean 的时候,如果 Bean 实现了
DisposableBean
接口,执行destroy()
方法。 - 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
英文版:
中文版:
如果注入的属性为null,你会从哪些方面去排查?
- 检查Spring配置,看看你的xml中有没有放置bean或者,需要注入的类上有没有加注解。
- 检查实例化的方式,看看你的对象是不是从容器中获取的
- @autowired不能放在一个静态属性上
- web容器启动顺序:Listener -> filter -> servlet,如果在filter里去使用controller类调用它里面service层的属性,此时因为还没到servlet层,没注入属性,就会空指针 (有待探讨)(怎么理解???)
你在项目开发中,IOC有遇到什么难点吗?如何解决的?
Spring循环依赖如何解决?
解决方案同一道LeetCode题:two sum 相似
先说思路:A先实例化放到缓存中,然后A填充字段属性,需要B,切B没有实例化,那么接着去实例化B,然后填充B的字段属性,发现B需要A,检查缓存,发现A存在然后取出A填充B,B填充结束后,把B的实例化对象再填充A。
阅读DefaultSingletonBeanRegistry类,你会发现里面有三个Map:
-
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
-
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
-
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
这三个Map就是解决循环依赖的本质:
- 准备实例化A,先生成一个工厂singletonFactoriesA,然后填充A的属性
- 发现需要填充B,B没有在缓存中,需要实例化,先生成一个工厂singletonFactoriesB,然后填充B的属性
- 发现需要填充A,检查缓存,发现存在工厂singletonFactoriesA,则先实例化一个早期的A实例,放入earlySingletonObjects Map中,并删除singletonFactoriesA
- 把早期的A实例填充到B上,B成功实例化,放入singletonObjectsMap中,并删除A早期实例
- 最后把A一直在等待方法回调结果依赖的B实例填充到A上,放入singletonObjectsMap中
- 由于map的key一直是beanname,所以当
循环依赖发生的场景只发生在singelton(默认的单实例)中
https://mp.weixin.qq.com/s/5mwkgJB7GyLdKDgzijyvXw
https://www.jianshu.com/p/a77e64250a9e
Cglib在代理中有什么限制?
委托类不能是final类
引用:
Spring常见问题
什么是Spring循环依赖?
排查IOC注入属性为空