所谓 IOC ,就是由 Spring IOC 容器来负责对象的生命周期和对象之间的关系
IoC
Inversion of Control,控制反转。是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(DependencyInjection,简称 DI),这也是 Spring 的实现方式。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
IoC原理
IoC 内部核心原理就是反射技术,当然这里面还涉及到 Bean 对象的初始化构建等步骤,这个在后面的生命周期中讲,这里我们需要了解 Java 中反射是如何做的就好。这里主要说明下主要的相关类和可能面试问题转向,具体的 API 实现需要自己去看:
类 | 说明 |
InvocationHandler | 通过这个接口定义横切的逻辑,然后通过反射机制调用目标类的方法,这样就能动态的把非业务逻辑和业务逻辑动态的拼接在一起 |
proxy | 提供创建动态代理类实例的静态方法,通常利用InvocationHandler创建代理实例,来间接调用代理的方法 |
都在 java.lang.reflect 包下。说到这个模块的时候,那么面试官可能会考察相关的知识,主要是考察你是否真的有去了解过反射的使用。**举两个例子: **
利用反射获取实例的私有属性值怎么做
这里其实就是里面的重要考察点就是反射对私有属性的处理。
/** * 通过反射获取私有的成员变量. */private Object getPrivateValue(Person person, String fieldName){ try { Field field = person.getClass().getDeclaredField(fieldName); // 主要就是这里,需要将属性的 accessible 设置为 true field.setAccessible(true); return field.get(person); } catch(Exception e) { e.printStackTrace(); } return null;}
如何通过反射构建对象实例?
使用默认构造函数(无参)创建的话:
Class.newInstance() Constroctor constroctor = clazz.getConstructor(String.class,Integer.class); Object obj = constroctor.newInstance("name", 18);
JDK 动态代理
必须实现 InvocationHandler 接口,然后通过 Proxy.newProxyInstance(ClassLoader
loader, Class<?>[] interfaces, InvocationHandler h) 获得动态代理对象。
CGLIB 动态代理
使用 CGLIB 动态代理,被代理类不需要强制实现接口。CGLIB 不能对声明为 final的方法进行代理,因为 CGLIB 原理是动态生成被代理类的子类。
OK,AOP 讲了。其实讲到这里,可能会有一个延伸的面试问题。我们知道,Spring事物也是 通 过 AOP 来 实 现的 , 我们使用的时候 一 般就是在方法上 加@Tranactional 注解,那么你有没有遇到过事物不生效的情况呢?这是为什么?这个问题我们在后面的面试题中会讲。
IoC 控制反转设计原理:
构造器注入
构造器注入,顾名思义就是被注入的对象通过在其构造方法中声明依赖对象的参数列表,让外部知道它需要哪些依赖对象。
YoungMan(BeautifulGirl beautifulGirl){
this.beautifulGirl = beautifulGirl;
}
构造器注入方式比较直观,对象构造完毕后就可以直接使用,这就好比你出生你家里就给你指定了你媳妇。
setter 方法注入
对于 JavaBean 对象而言,我们一般都是通过 getter 和 setter 方法来访问和设置对象的属性。所以,当前对象只需要为其所依赖的对象提供相对应的 setter 方法,就可以通过该方法将相应的依赖对象设置到被注入对象中。如下:
public class YoungMan {
private BeautifulGirl beautifulGirl;
public void setBeautifulGirl(BeautifulGirl beautifulGirl) {
this.beautifulGirl = beautifulGirl;
}
}
相比于构造器注入,setter 方式注入会显得比较宽松灵活些,它可以在任何时候进行注入(当然是在使用依赖对象之前),这就好比你可以先把自己想要的妹子想好了,然后再跟婚介公司打招呼,你可以要林志玲款式的,赵丽颖款式的,甚至凤姐哪款的,随意性较强。
接口方式注入
接口方式注入显得比较霸道,因为它需要被依赖的对象实现不必要的接口,带有侵入性。一般都不推荐这种方式
IOC容器工作机制:
1. 容器启动过程
web环境下Spring容器、SpringMVC容器启动过程:
- 首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
- 其次,在web.xml中会提供有contextLoaderListener(或ContextLoaderServlet)。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring容器以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
- 再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例(Spring MVC),这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文容器,用以持有spring mvc相关的bean,这个servlet自己持有的上下文默认实现类也是XmlWebApplicationContext。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文(即第2步中初始化的XmlWebApplicationContext作为自己的父容器)。有了这个parent上下文之后,再初始化自己持有的上下文(这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等)。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文定义的那些bean。
Bean加载过程
Spring的高明之处在于,它使用众多接口描绘出了所有装置的蓝图,构建好Spring的骨架,继而通过继承体系层层推演,不断丰富,最终让Spring成为有血有肉的完整的框架。所以查看Spring框架的源码时,有两条清晰可见的脉络:
1)接口层描述了容器的重要组件及组件间的协作关系;
2)继承体系逐步实现组件的各项功能。
接口层清晰地勾勒出Spring框架的高层功能,框架脉络呼之欲出。有了接口层抽象的描述后,不但Spring自己可以提供具体的实现,任何第三方组织也可以提供不同实现, 可以说Spring完善的接口层使框架的扩展性得到了很好的保证。纵向继承体系的逐步扩展,分步骤地实现框架的功能,这种实现方案保证了框架功能不会堆积在某些类的身上,造成过重的代码逻辑负载,框架的复杂度被完美地分解开了。
Spring组件按其所承担的角色可以划分为两类:
- 1)物料组件:Resource、BeanDefinition、PropertyEditor以及最终的Bean等,它们是加工流程中被加工、被消费的组件,就像流水线上被加工的物料;
- BeanDefinition:Spring通过BeanDefinition将配置文件中的<bean>配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中。Spring容器的后续操作直接从BeanDefinitionRegistry中读取配置信息。
- 2)加工设备组件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy以及BeanWrapper等组件像是流水线上不同环节的加工设备,对物料组件进行加工处理。
- InstantiationStrategy:负责实例化Bean操作,相当于Java语言中new的功能,并不会参与Bean属性的配置工作。属性填充工作留待BeanWrapper完成
- BeanWrapper:继承了PropertyAccessor和PropertyEditorRegistry接口,BeanWrapperImpl内部封装了两类组件:(1)被封装的目标Bean(2)一套用于设置Bean属性的属性编辑器;具有三重身份:(1)Bean包裹器(2)属性访问器 (3)属性编辑器注册表。PropertyAccessor:定义了各种访问Bean属性的方法。PropertyEditorRegistry:属性编辑器的注册表
该图描述了Spring容器从加载配置文件到创建出一个完整Bean的作业流程:
IOC主要组件:
Resource体系
Resource,对资源的抽象,它的每一个实现类都代表了一种资源的访问策略,如ClasspathResource 、 URLResource ,FileSystemResource 等。有了资源,就应该有资源加载,Spring 利用 ResourceLoader 来进行统一资源加载。
BeanFactory 体系
BeanFactory 是一个非常纯粹的 bean 容器,它是 IOC 必备的数据结构,其中 BeanDefinition 是她的基本结构,它内部维护着一个 BeanDefinition map ,并可根据 BeanDefinition 的描述进行 bean 的创建和管理。
BeanFacoty 有三个直接子类 ListableBeanFactory
、HierarchicalBeanFactory
和 AutowireCapableBeanFactory
,DefaultListableBeanFactory
为最终默认实现,它实现了所有接口。
Beandefinition 体系
BeanDefinition 用来描述 Spring 中的 Bean 对象。
BeandefinitionReader体系
BeanDefinitionReader 的作用是读取 Spring 的配置文件的内容,并将其转换成 Ioc 容器内部的数据结构:BeanDefinition。
ApplicationContext体系
这个就是大名鼎鼎的 Spring 容器,它叫做应用上下文,与我们应用息息相关,她继承 BeanFactory,所以它是 BeanFactory 的扩展升级版,如果BeanFactory 是屌丝的话,那么 ApplicationContext 则是名副其实的高富帅。由于 ApplicationContext 的结构就决定了它与 BeanFactory 的不同,其主要区别有:
- 继承 MessageSource,提供国际化的标准访问策略。
- 继承 ApplicationEventPublisher ,提供强大的事件机制。
- 扩展 ResourceLoader,可以用来加载多个 Resource,可以灵活访问不同的资源。
- 对 Web 应用的支持。
总结
- Spring IOC容器主要有继承体系底层的BeanFactory、高层的ApplicationContext和WebApplicationContext
- Bean有自己的生命周期
- 容器启动原理:Spring应用的IOC容器通过tomcat的Servlet或Listener监听启动加载;Spring MVC的容器由DispatchServlet作为入口加载;Spring容器是Spring MVC容器的父容器
- 容器加载Bean原理:
- BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个<bean>解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;
- 容器扫描BeanDefinitionRegistry中的BeanDefinition;调用InstantiationStrategy进行Bean实例化的工作;使用BeanWrapper完成Bean属性的设置工作;
- 单例Bean缓存池:Spring 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例 Bean 的缓存器,它是一个用 HashMap 实现的缓存器,单实例的 Bean 以 beanName 为键保存在这个HashMap 中。
来源:CSDN
作者:wandy0211
链接:https://blog.csdn.net/wjandy0211/article/details/103765102