Spring-IOC
系列文章目录
摘要
本文简要介绍IOC概念,并会将IOC与Spring关系,还会简要分析其实现原理。
1 IOC
1.1 IOC基本概念
IOC,全名Inversion of Control
,翻译过来就是控制反转。也就是说,依赖的对象本来是用到时由使用者自己创建,但在IOC中将控制权交给第三方管理,来创建所需对象。
1.2 IOC好处
- 减少耦合
- 接口相关
- 模块热插拔,只需更改配置文件就可替换实现子类。
1.3 DI
2004年,Martin Fowler对IOC的“哪些方面的控制被反转了呢?”这个问题提出的结论是:“获得依赖对象的过程被反转”。
控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更贴合的名字依赖注入(Dependency Injection)
,简称为DI,即运行时,主动对象依赖IOC容器动态地将所需对象注入,提供使用。
也就是说,依赖注入(DI)和控制反转(IOC)只是从不同的角度来描述同一件事情:通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。
0x02 Spring-IOC简介
简单来说,Spring 启动时读取类似applicationContext.xml
这样的bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。具体可以点击这里。
Spring 的 IoC 容器在完成这些底层工作的基础上,还提供了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。
3 Spring核心接口
3.1 BeanFactory
3.1.1 基本概念
-
BeanFactory
是Spring框架最核心的接口,它提供了高级IoC的配置机制 -
BeanFactory接口面向Spring本身,而非使用者。
-
BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例目标Bean
-
若需要对bean定义后处理器,必须在代码中通过手工调用spring方法进行注册
-
BeanFactory最重要的方法就是定义了
getBean
-
上图中其他重要概念如下:
- BeanDefinitionRegistry:bean在Spring内用
BeanDefinition
定义,包含了其配置信息。注册时使用BeanDefinitionRegistry
的方法向IOC容器注册。 - ListableBeanFactory:定义了访问Bean基本信息的一些方法
- HierarchicalBeanFactory:父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器; 通过 HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。Spring 使用父子容器实现了很多功能,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务层和持久层的 Bean 则看不到展现层的 Bean。
- ConfigurableBeanFactory:增强了 IoC 容器的可定制性:它定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法;
- AutowireCapableBeanFactory:定义了将容器中的 Bean 按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法;
- SingletonBeanRegistry:单例Bean相关方法,如
registerSingleton
,getSingleton
等
- BeanDefinitionRegistry:bean在Spring内用
-
初始化
new XmlBeanFactory(new Resource())
示例:ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource res = resolver.getResource("classpath:com/chengc/test/spring/bean.xml"); // 通过 Resource 装载 Spring 配置信息并启动 IoC 容器 BeanFactory bf = new XmlBeanFactory(res); System.out.println("init BeanFactory"); Car car = bf.getBean("car",Car.class); System.out.println("car bean is ready for use!");
以上方式中
XmlBeanFactory
是最简陋的BeanFactory实现,现已被标注为@Deprecated
。他通过解析Spring xml配置,加载到Resource。注意,通过
BeanFactory
启动时,Bean的初始化会发生在对该Bean的第一次调用时。而且BeanFactory还会缓存单例模式的Bean,非首次调用
getBean
就会直接从IOC缓存获取。注意,在初始化 BeanFactory 时,必须为其提供一种日志框架,比如使用Log4J, 即在类路径下提供 Log4J 配置文件,这样启动 Spring 容器才不会报错。
也可以这么使用:
// 创建IoC配置文件的抽象资源(包含BenaDefinition定义信息) ClassPathResource res = new ClassPathResource("com/chengc/test/spring/bean.xml"); // 创建BeanFactory DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // 创建BeanDefinition Reader XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); // 读取配置信息,完成bean的载入和注册 reader.loadBeanDefinitions(resource);
3.1.2 源码解读
BeanFactory源码如下:
public interface BeanFactory {
// 用来获取FactoryBean
// 比如myJndiObject是FactoryBean,
// 则&myJndiObject会返回factory类而不是工厂返回的对象实例
String FACTORY_BEAN_PREFIX = "&";
// 获取该名称在IOC容器中的Bean实例
Object getBean(String name) throws BeansException;
// 获取该名称和指定类型在IOC容器中的Bean实例
Object getBean(String name, Class requiredType) throws BeansException;
// 检查指定名称Bean是否存在于IOC容器
boolean containsBean(String name);
// 检查指定名称Bean在IOC容器内是否单例模式
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// 返回指定名称Bean实例的Class
Class getType(String name) throws NoSuchBeanDefinitionException;
// 得到Bean的别名。如果根据别名来检索,那么其原名和其他别名也会一同返回
String[] getAliases(String name);
}
这里我们也看看最简单的XmlBeanFactory
:
public class XmlBeanFactory extends DefaultListableBeanFactory {
// 就是用了他来读取xml配置中的bean定义,并注册到IOC容器(XmlReaderContext)
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
// 使用指定beans资源创建XmlBeanFactory
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
// 这里就在加载资源文件中的bean了
this.reader.loadBeanDefinitions(resource);
}
}
3.2 FactoryBean
与BeanFactory注意区别。
在向Spring注册Bean的时候,如果该类实现了FactoryBean
接口,那么当获取Bean的时候是调用该Bean的getObject()
方法返回的对象。
3.3 ApplicationContext
3.3.1 基本概念
- 面向使用Spring框架的开发者,一般我们都直接使用
ApplicationContext
文件中定义的Bean
而不是BeanFactory
。 - 虽然
ApplicationContext
继承自BeanFactory
, 但他与BeanFactory不同,ApplicationContext
在初始化应用上下文时就实例化所有的单例的Bean,所以启动时会较慢一些。不过,可以通过<bean id="person" calss="com.chengc.test.spring.Person" lazy-init="true" />
(或者使用注解@Lazy(true)
)将Bean的加载方式设为懒加载,那么他将不会在ApplicationContext启动时被实例化,而是在首次调用getBean
时实例化。 - 若需要对bean定义后处理器,其会利用反射机制自动识别出配置文件中的Processor,并自动注册到应用上下文中。
- 实现了
ApplicationListener
事件监听接口的 Bean 可以接收到容器事件 , 并对事件进行响应处理 。 在ApplicationContext
抽象实现类AbstractApplicationContext
中,我们可以发现存在一个ApplicationEventMulticaster
,它负责保存所有监听器,以便在容器产生上下文事件时通知这些事件监听者。 - 上图中重要接口和接口:
- ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。
- MessageSource:为应用提供 i18n 国际化消息访问的功能;
- ResourcePatternResolver : 所有 ApplicationContext 实现类都实现了类似于- PathMatchingResourcePatternResolver 的功能,可以通过带前缀的 Ant 风格的资源文件路径装载 Spring 的配置文件。
- LifeCycle:该接口是 Spring 2.0 加入的,该接口提供了 start()和 stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现, ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接口的 Bean,以达到管理和控制 JMX、任务调度等目的。
- ConfigurableApplicationContext 继承于 ApplicationContext,它主要新增加了
refresh()
和close()
方法,让 ApplicationContext 具有启动、刷新和关闭应用上下文的能力:- 在应用上下文关闭的情况下调用 refresh()即可启动应用上下文;
- 在已经启动的状态下,调用 refresh()则清除缓存并重新装载配置信息,
- 而调用close()则可关闭应用上下文。
- 这些接口方法为容器的控制管理带来了便利,但作为开发者,我们并不需要过多关心这些方法。
3.3.2 重要实现类
// bean定义文件位于classpath下
new ClassPathXmlApplicationContext("classpath:bean.xml")
// bean定义文件位于文件目录
new FileSystemXmlApplicationContext("file:beans.xml")
// Bean注解方式
new AnnotationConfigApplicationContext(ConfigTest.class)
3.3.3 AnnotationConfigApplicationContext实例
// 使用@Configguration注解是告诉spring这个类是一个配置类,相当于我们的xml文件
@Configuration
// @ComponentScan则是指定需要spring来扫描的包,相当于xml中的context:component-scan属性。
@ComponentScan("demos.spring.initMethodTest.demo1")
public class ConfigTest {
/**
* @Bean用于声明本方法产生一个Bean对象
* 注意:本方法只会被Spring只会调用一次,并会将该Bean交给Spring管理。
*
* 默认bean的名称就是其方法名。但是也可以指定名称。
*/
@Bean(name="test1", initMethod = "init", destroyMethod = "destroy")
Test1 getTest1() {
return new Test1();
}
public static void main(String[] args) {
AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(ConfigTest.class);
Test1 test11 = (Test1)configApplicationContext.getBean("test1");
test11.sayHello();
configApplicationContext.close();
}
}
// 要注册的Bean类
public class Test1 {
public void init() {
System.out.println("this is init method1");
}
public Test1() {
super();
System.out.println("构造函数1");
}
public void sayHello(){
System.out.println(this + " Test1 Hello");
}
public void destroy() {
System.out.println("this is destroy method1");
}
}
3.3.4 ApplicationContext初始化详解
3.3.4.1 初始化触发
当我们执行以下代码:当我们执行以下代码:
ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("applicationContext.xml");
就会触发整个Spring applicationContext初始化过程,我们先看一个重要方法AbstractApplicationContext.refresh
,他里面包含了各个初始化流程:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
// 这里面就是包括了context配置读取、bean解析为BeanDefinition、注册
// 还有parentBeanFactory的注册,这很关键
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 对BeanFactory准备工作,使之可用
// 比如会将内部用的单例对象放入单例缓存,如StandardEnvironment
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 这一步会加载bean的class到JVM
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
// 实例化bean时,会利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)
// 对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// 检查listener类并注册到Spring
registerListeners();
// 前面会初始化一些对象
// 这一步会初始化剩余的非懒初始化的单例对象,
// 比如包括我们例子中的 test4,initOrDestroyTest
// 我们后面会分析
finishBeanFactoryInitialization(beanFactory);
// 最后一步:发布refresh事件
// 继承了ApplicationListener<ContextRefreshedEvent>
// 的onApplicationEvent(ContextRefreshedEvent event)方法会触发
// 比如DispatcherServlet就利用这一事件来执行initStrategies方法
// 进行一些基础Bean的初始化
finishRefresh();
}
catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
3.3.4.2 将Spring配置文件定位为Resource
来源于上面AbstractApplicationContext.refresh方法内的obtainFreshBeanFactory()
。
ApplicationContext的所有实现类都实现了ResourceLoader
接口,因此可以直接调用getResource
方法获取Resource。需要注意,不同的ApplicationContext实现类使用getResource
方法获取的资源类型不同。
代码位于AbstractBeanDefinitionReader.loadBeanDefinitions
:
// 这里的resources在我们例子里就是`applicationContext.xml`
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
3.3.4.3 将以Resource定位好的资源载入到BeanDefinition
来源于上面AbstractApplicationContext.refresh方法内的obtainFreshBeanFactory()
。
BeanDefinition被用来描述那些具有属性值、构造函数参数值以及关于具体方法实现的更多信息的Bean实例。
BeanDefinition的根据定位到的Resource资源对象中的Bean生成,他们在Spring的 IoC容器内被表示为BeanDefinition。IoC容器对Bean的管理和DI等都是通过操作BeanDefinition来进行的。
下面看看ClassPathXmlApplicationContext
载入BeanDefinition
的方法loadBeanDefinitions
的源码,会调用父类AbstractXmlApplicationContext
的loadBeanDefinitions
方法:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 为beanFactory创建读取BeanDefinition的reader
// 这里的beanFactory是`DefaultListableBeanFactory`实例
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 为beanDefinitionReader配置上下文参数参数
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 初始化beanDefinitionReader
initBeanDefinitionReader(beanDefinitionReader);
// 真正开始读取beanDefinition
loadBeanDefinitions(beanDefinitionReader);
}
继续看loadBeanDefinitions(beanDefinitionReader)
:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 获取所有已定位到的resource资源位置(用户定义)
Resource[] configResources = getConfigResources();
if (configResources != null) {
//载入resources
reader.loadBeanDefinitions(configResources);
}
// 获取所有本地配置文件的位置,比如applicationContext.xml
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);//载入resources
}
}
跟着代码走会走到XmlBeanDefinitionReader.loadBeanDefinitions
方法,部分核心代码如下:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
// 将applicationContext.xml置为读入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 读取该bean文件数据流
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
继续看DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions
方法核心代码:
// 这里的root就是xml文件的root节点,可以遍历xml文件内容
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
// 会处理默认配置
this.delegate = createDelegate(this.readerContext, root, parent);
// 处理xml文件前需要做的,当前类实现为空
// 可自定义子类来重写该方法,加入如权限控制等自定义功能
preProcessXml(root);
// 解析BeanDefinitions
parseBeanDefinitions(root, this.delegate);
// 处理xml文件后需要做的,当前类实现为空
postProcessXml(root);
}
再看其parseBeanDefinitions
方法:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
// 默认命名空间
// 遍历xml文件节点
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 处理默认命名空间的`bean`,`alias`,`beans`,`import`标签
// 如 http://www.springframework.org/schema/beans
// 这个分支会处理定义在xml文件中如
// <bean id="initOrDestroyTest" class="demos.spring.initMethodTest.demo3.Test3" init-method="init" destroy-method="destroy">
// 将处理此类bean,转为BeanDefinition和注册到IOC
parseDefaultElement(ele, delegate);
}
else {
// 处理其他命名空间如http://www.springframework.org/schema/context
// 如<context:component-scan base-package="demos.spring.initMethodTest.demo3"/>
// 调试代码发现,这个分支会处理scan即处理注解的类
// 主要是转为BeanDefinition和注册到IOC
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
3.3.4.4 将BeanDefiniton
注册到容器中
来源于上面AbstractApplicationContext.refresh方法内的obtainFreshBeanFactory()
。
不管是在xml文件注册<bean>
还是对类使用注解@Component
,都会使用方法DefaultListableBeanFactory.registerBeanDefinition
来注册已封装为BeanDefinition
的Beans到Spring IOC容器。核心代码如下
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
synchronized (this.beanDefinitionMap) {
// beanName放入beanDefinitionNames
this.beanDefinitionNames.add(beanName);
// 以beanName为key,
// 包装好的beanDefinition为value放入beanDefinitionMap
this.beanDefinitionMap.put(beanName, beanDefinition);
}
}
注册后beanDefinitionMap是一个ConcurrentHashMap,key为beanName
,value为beanDefinition
如图:
3.3.4.5 Bean的加载
来源于上面AbstractApplicationContext.refresh方法内的invokeBeanFactoryPostProcessors()
。
会调用DefaultListableBeanFactory.doGetBeanNamesForType
方法,遍历DefaultListableBeanFactory.beanDefinitionNames
,每个都调用AbstractBeanDefinition.resolveBeanClass
进行代码加载:
public Class<?> resolveBeanClass(ClassLoader classLoader) throws ClassNotFoundException {
// 例子className为:demos.spring.initMethodTest.demo3.Test3
String className = getBeanClassName();
if (className == null) {
return null;
}
// 这里就是从缓存获取或是新加载Class
Class<?> resolvedClass = ClassUtils.forName(className, classLoader);
this.beanClass = resolvedClass;
return resolvedClass;
}
我们必须看看ClassUtils.forName
:
public static Class<?> forName(String name, ClassLoader classLoader) throws ClassNotFoundException, LinkageError {
Class<?> clazz = resolvePrimitiveClassName(name);
if (clazz == null) {
// 我们例子类clazz为null
// 先从静态的Class缓存获取Class
// 这个cache只装了通用Class
// 如java.lang.Object等
clazz = commonClassCache.get(name);
}
if (clazz != null) {
// 如果已有,就直接返回该Class
return clazz;
}
// 处理几种不同风格名称的数组,就是截取前面一段类名称,然后加载类
if (name.endsWith(ARRAY_SUFFIX)) {
String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
Class<?> elementClass = forName(elementClassName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
...
ClassLoader clToUse = classLoader;
if (clToUse == null) {
// clToUse为空就获取默认ClassLoader
clToUse = getDefaultClassLoader();
}
try {
// clToUse不为空直接用classLoader.loadClass装载目标类
// 否则用当前类的ClassLoader来加载目标类
return (clToUse != null ? clToUse.loadClass(name) : Class.forName(name));
}
catch (ClassNotFoundException ex) {
...
throw ex;
}
}
3.3.4.6 Bean的实例化
即AbstractApplicationContext.finishBeanFactoryInitialization
前面我们提到过,他是Spring上下文初始化的倒数第二步,会初始化剩余的单例对象,并放入Spring容器内的单例缓存中,以便后续使用。在实例化过程中,使用BeanWrapper
对Bean进行封装,BeanWrapper提供了很多以Java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作;最后利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean。
在该类的finishBeanFactoryInitialization
方法中,有这样的代码:
// 提前初始化单例对象
beanFactory.preInstantiateSingletons();
跟下去,对beanDefinitionMap
进行同步锁后遍历了beanDefinitionNames
,随后调用会走到AbstractBeanFactory.doGetBean
方法,走到下面代码分支(这里是我们例子中获取自定义bean):
Object bean;
Object sharedInstance = getSingleton(beanName);
//有一段getBean逻辑,后面再讲
...
if (!typeCheckOnly) {
// 如果不是只能有type来匹配bean
// 标注该bean被创建
markBeanAsCreated(beanName);
}
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// 如果有该Bean有@DependsOn注解,就先初始化依赖的那些Bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dependsOnBean : dependsOn) {
getBean(dependsOnBean);
registerDependentBean(dependsOnBean, beanName);
}
}
if (mbd.isSingleton()) {
// 创建单例bean实例
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
// 创建bean时会调用该方法
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
// 得到实例化好的bean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// 创建多例bean实例
// isPrototype的,每次都会创建新实例
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
// 创建新的bean实例
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
// 按FactoryBean和非FactoryBean决定返回的真实对象实例
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
// 处理比如request或session类的bean
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,...)
}
}
return (T) bean;
继续看看上面这个getSingleton
(在其父类DefaultSingletonBeanRegistry
)方法核心代码在做啥:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
// 获取singletonObjects同步锁
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 首次肯定为null
// 这一步很关键,初始化bean实例
singletonObject = singletonFactory.getObject();
// 这里就将初始化好的bean实例添加到单例缓存singletonObjects了
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
还需要看看singletonFactory.getObject()
用到的AbstractAutowireCapableBeanFactory.createBean
方法内容:
protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// 确认该bean的Class已经被加载、连接了
resolveBeanClass(mbd, beanName);
// Prepare method overrides.
try {
mbd.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new ...
}
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbd);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new ...
}
Object beanInstance = doCreateBean(beanName, mbd, args);
// 返回已经处理好的、实例化好的bean实例
return beanInstance;
}
上面的doCreateBean方法
最终会调用SimpleInstantiationStrategy.instantiate
获得目标bean Class默认构造方法,然后使用BeanUtils.instantiateClass
构建bean实例并返回。
需要注意的是,单例对象创建会放入缓存,prototype对象则是每次都创建新的。
3.3.4.7 单例Bean的内存缓存
我们看看前面提到的DefaultSingletonBeanRegistry.addSingleton
方法:
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 这就是最核心的单例对象缓存
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
可以看到,其实Spring IOC容器内存放单例对象的缓存是一个名为singletonObjects
的ConcurrentHashMap
,位于org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
3.3.4.8 getBean
客户端获取Bean时,如果是单例对象,直接从Spring IOC容器内的单例对象缓存(singletonObjects
)中获取;否则创建一个新的对象返回。
如果是prototype类型,就每次创建新的对象实例返回。
我们现在对以下代码进行解读:
Test3 t3 = (Test3)context1.getBean("initOrDestroyTest");
会触发AbstractApplicationContext.getBean
public Object getBean(String name) throws BeansException {
// 这里的BeanFactory就是DefaultListableBeanFactory实例
return getBeanFactory().getBean(name);
}
然后会走到AbstractBeanFactory.doGetBean
,核心代码如下:
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// 会优先从`DefaultSingletonBeanRegistry.singletonObjects`缓存中获取单例对象
// 在前面提到的第6步`Bean的实例化`后放入singletonObjects
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 如果是非FactoryBean直接返回,否则返回该FactoryBean.getObject
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 否则尝试用父context的BeanFactory查找Bean实例
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (args != null) {
// 委托给父BeanFactory.getBean
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// 委托给父BeanFactory.getBean,无参数版本
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
// 创建bean实例的代码,前面已经分析过
...
return (T) bean;
}
3.3.5 ApplicationContext Bean初始化小结
- 如果用户为Bean配置了
lazy-init
属性,则会在容器在解析注册Bean定义时进行预实例化,触发依赖注入。 - 默认情况下,Bean是饿汉式初始化的。具体来说,Bean是在
ApplicationContext
初始化的时候就被读取(定位Resource
-> 包装为BeanDefinition
)、注册(BeanDefinition
形式注册到IOC)、加载(遍历BeanDefinitions
逐一loadClass
)、实例化(获取默认的无参Constructor.newInstance()
),放在单例缓存(singletonObjects
)中。 - 在Spring中,如果Bean定义的单态模式(Singleton),则容器在创建之前先从缓存(
singletonObjects
)中查找,以确保整个容器中只存在一个实例对象。 - 如果Bean定义的是原型模式(Prototype),则容器每次都会创建一个新的实例对象。除此之外,Bean定义还可以扩展为指定其生命周期范围。例如
scope
设为Request
和Session
。 - Request
对于每个http请求,Spring容器会创建一个 scope为Request
的bean 的新实例。即这种scope的bean是http请求之间相互隔离的。 当该次请求处理完毕,对应的bean实例也就销毁(被回收)了。 - Session
Http Session级别,每次创建新的,互相隔离。
3.3.6 属性值注入Bean对象
- 对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。
- 对于非集合类型的属性,大量使用了JDK的反射和内省机制,通过属性的
getter
方法(reader method)获取指定属性注入以前的值,同时调用属性的setter方法(writer method)为属性设置注入后的值。
3.4 WebApplicationContext
3.4.1 要点
WebApplicationContext
面向Web应用,可从Web根目录相对路径加载配置文件来进行初始化。- WebApplicationContext需要获得
ServletContext
实例引用,也就是说他必须要在拥有Web容器的前提下才能完成启动工作。WebApplicationContext对象将作为属性被放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。
3.4.2 配置方式
Spring分别提供了用于启动WebApplicationContext
的Servlet和Listener,在web.xml
中配置
org.springframework.web.context.ContextLoaderServlet
org.springframework.web.context.ContextLoaderListener
3.4.3 ServletContextListener
该类是ContextLoaderListener
的父接口。实现该接口的类会在Web应用启动和结束的时候分别调用其中定义的两个重要方法一次:
public interface ServletContextListener extends EventListener {
// 当Web应用的初始化进程正在执行。
// 注意,会在该Web应用的所有filter和servlet被初始化前,
// 就执行所有ServletContextListener实现类的该方法进行初始化通知
public void contextInitialized ( ServletContextEvent sce );
// 当ServletContext准备关闭时调用本方法进行通知。
// 注意,会在该Web应用的所有filter和servlet被销毁(destroy())后,
// 才执行所有ServletContextListener实现类的该方法进行初始化通知
public void contextDestroyed ( ServletContextEvent sce );
}
- 也就是说,我们可以利用
contextInitialized
方法调用早于所有servlet和filter初始化这一特点,进行最早的一些初始化行为 - 也可以利用
contextDestroyed
方法调用晚于所有servlet和filter销毁这一特点,进行最后的一些如资源释放等收尾动作
3.4.4 ContextLoaderListener初始化方式
ContextLoaderListener
继承自ServletContextListener,被用来初始化WebApplicationContext,部分源码如下:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
// 在web.xml中配置了该ContextLoaderListener,Web应用启动时会调用该方法
// initWebApplicationContext创建WebApplicationContext
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
}
继续看看contextLoader.initWebApplicationContext
核心代码:
// 创建了WebApplicationContext
this.context = createWebApplicationContext(servletContext);
// 将WebApplicationContext作为属性放入servletContext
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
可以看到新创建的WebApplicationContext被放入了servletContext,key为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"
我们也可以自己继承ContextLoaderListener
,并配置到web.xml,在初始化WebApplicationContext之前加上自己的一些逻辑。
public class ContextLoaderListenerTest extends ContextLoaderListener {
private static Logger LOG = LoggerFactory
.getLogger(ContextLoaderListenerTest.class);
/**
* Parent ContextLoaderListener will Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
LOG.info("*************ContextLoaderListenerTest*************");
ServletContext servletContext = servletContextEvent.getServletContext();
LOG.info("contextConfigLocation"+servletContext.getInitParameter("contextConfigLocation"));
LOG.info("key:"+servletContext.getInitParameter("username"));
super.contextInitialized(servletContextEvent);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
LOG.info("ContextLoaderListenerTest ContextDestroyed");
}
}
3.4.5 WebApplicationContext启动小结
- 启动Web应用,Web容器读取
web.xml
配置文件,执行startContext
方法初始化ServletContext
,他在Web应用内全局共享。可通过其getInitParameter
方法获取web.xml中配置的context-param
如contextConfigLocation
等 web.xml
中的ContextLoaderListener
被触发contextInitialized
事件,初始化WebApplicationContext
- 创建出一个WebApplicationContext的实现类
XmlWebApplicationContext
(默认,可通过context-param
以<param-name>
为contextClass
指定),他即是Spring IOC容器。 - 上一步创建的XmlWebApplicationContext同时继承了
ConfigurableWebApplicationContext
,会把<context-param>
中的contextConfigLocation
指定的如classpath:spring/applicationContext.xml
的资源位置引入。同时,该context含有ServletContext的引用。 - 创建完XmlWebApplicationContext后,会对其父类调用一次
AbstractApplicationContext.refresh
方法,对目标位置的bean进行装载、初始化等工作,与前面提到的ApplicationContext初始化流程相同。这个过程会比较慢。(注意,这里有一点不同,loadBeanDefinitions
的时候是调用的XmlWebApplicationContext
内的相关方法) - 将WebApplicationContext放入servletContext
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
这里就把初始化好的WebApplicationContext放入了servletContext。 - 到这里,ApplicationContext初始化完毕。这个阶段就已经将applicationContext.xml中的定义的bean或定义的scan范围中的注解引入的bean装载并初始化,加入
IOC
单例对象缓存了。 - 注意,这个阶段初始化bean一般不包括类似
web-demo/src/main/webapp/WEB-INF/spring-web-servlet.xml
中定义的@Controller
、interceptor
、VelocityConfigurer
、VelocityLayoutViewResolver
、CommonsMultipartResolver
等。他们的初始化一般是在FrameworkServlet.initWebApplicationContext
方法中创建了该servlet独有XmlWebApplicationContext
之后,调用refresh
方法后进行。
3.4.6 WebApplicationContext需要使用日志功能
- 用户可以将Log4j配置文件放到类路径 WEB-INF/classes下,这时Log4j引擎即可顺利启动
- 用户可以在web.xml中通过Log4jConfigServlet和Log4jConfigListener指定Log4j配置文件位置,但要注意启动顺序(load-on-startup)
3.5 Servlet-context.xml
-
在3.4.5小结讲述了ApplicationContext初始化步骤,随后Spring容器开始执行
Servlet
初始化。 -
比如我们在web.xml中通常都会定义的SpringMVC用的
DispatcherServlet
。他是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet在初始化的时候会建立自己的IoC上下文容器,来装载SpringMVC相关的Bean,这个servlet自己持有的上下文默认实现类也是XmlWebApplicationContext。
-
在DispatcherServlet建立自己的IoC context时,会利用
WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE
从ServletContext中获取Root Context
(即XmlWebApplicationContext)作为自己context的parent context
。 -
此后,DispatcherServlet开始初始化自己的context(
initStrategies
方法,大概的工作就是初始化处理器映射、视图解析等)。 -
初始化完毕后,Spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换)的属性为Key,也将其存到ServletContext中,以便后续使用。
-
这样每个servlet就持有自己的context,即拥有自己独立的bean空间。同时各个servlet共享相同的bean,即根XmlWebApplicationContext中定义的那些bean。
具体分析请参见Spring-SpringMVC-原理
3.6 RootContext和DispatcherServletContext关系
3.6.1 概念
-
Root XmlWebApplicationContext
最先创建,一般会初始化非Controller
的类,类似以下applicationContext.xml配置:<context:component-scan base-package="com.chengc.demos.web.demo1.*"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
-
Servlet XmlWebApplicationContext
会初始化一系列bean,一般包括类似web-demo/src/main/webapp/WEB-INF/spring-web-servlet.xml中定义的@Controller
、interceptor
、VelocityConfigurer
、VelocityLayoutViewResolver
、CommonsMultipartResolver等
。 -
Servlet XmlWebApplicationContext将Root XmlWebApplicationContext作为他的ParentContext,在getBean的时候会先尝试自己获取,如果获取不到就委托给ParentContext的BeanFactory获取;反之不行。这也就说明了为什么Controller内能引用
@Service
,@Repository
等,但反之不行的道理。
3.6.2 问题
- 不少开发者不知道Spring mvc里分有两个WebApplicationContext,导致各种重复构造bean,各种bean无法注入的问题。
- 有一些bean,比如全局的aop处理的类,如果先root WebApplicationContext里初始化了,那么child WebApplicationContext里的初始化的bean就没有处理到;如果在child WebApplicationContext里初始化,在root WebApplicationContext里的类就没有办法注入了。
- 区分哪些bean放在root/child很麻烦,不小心容易搞错,而且费心思。
所以可以将所有bean都放在 root WebApplicationContext中加载。
3.6.3 Springboot的玩法
Springboot默认情况下只有一个WebApplicationContext
,即org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext
。
3.7 Resource和ResourceLoader
org.springframework.core.io.Resource
接口是从实际类型的底层资源(例如 classpath , filesystem ,或者是 URL 网络资源, servletContext 等资源)中抽象出来的资源描述符,对具体资源解耦。org.springframework.core.io.ResourceLoader
使用了策略模式,被用来加载Resource。可通过传入Resource的信息,自动选择适合的底层Resource实现类,为生产对Resource的引用提供了极大的便利。
3.8 ApplicationListener
前面提到过的AbstractApplicationContext.refresh
的最后一步发布refresh事件,会使得继承了ApplicationListener<ContextRefreshedEvent>
的onApplicationEvent(ContextRefreshedEvent event)
方法触发。
比如DispatcherServlet就利用这一事件来执行initStrategies
方法,进行一些基础Bean的初始化。具体代码如下:
4 Bean的常用配置
如果需要注册到Spring的构造函数带参数,可以通过类似以下方式配置:
<bean id="target" class="com.chengc.test.spring.Person">
<!-- nickname为参数名称,value为值 -->
<constructor-arg name="nickname" value="jerry"></constructor-arg>
</bean>
5 Spring使用
5.1 Java加载ApplicationContext.xml的方式
参考Java类中加载Spring中的ApplicationContext.xml文件的方式
5.2 Java项目中获取ApplicationContext的方法
- spring项目中获取ApplicationContext对象,然后手动获取bean
- 手动获取spring的ApplicationContext和bean对象
- 获取Spring的ApplicationContext的几种方式
5.3 import标签
会将import标签指向的多个applicationContext.xml文件和本applicationContext.xml文件合并,最终合并为一个ApplicationContext,所以这些文件内定义的JavaBean可互相引用。
6 Spring-IOC缺点
- 性能损耗
- 引入Spring框架较重
7 IOC其他方案
7.1 Goole-Guice
本节内容转自 https://www.cnblogs.com/whitewolf/p/4185908.html
Guice
由Google大牛Bob lee
开发,是一款绝对轻量级的java IOC容器框架。
feature如下:
- 速度快,号称比spring快100倍。
- 无外部配置(如需要使用外部可以可以选用Guice的扩展包)
- 完全基于·annotation·特性,支持重构,代码静态检查
- 简单,快速,基本没有学习成本
- Guice和spring各有所长,Guice更适合与嵌入式或者高性能但项目简单方案,如OSGI容器,spring更适合大型项目组织