1. 概念
- IoC = inversion of control 控制反转 ,指的是把对象创建和管理的控制权交给框架、容器来管理。
- DI=dependency injection 依赖注入,是把对象依赖的其他对象通过不同方法传递给该对象的设计模式,通过依赖注入模式,避免对象因依赖关系产生的高耦合
- IoC容器 是通过使用管理容器来实现 IoC、DI,第一步是注册对象间的依赖关系到container中,第二步是通过给出的对象依赖关系,由容器来管理和创建所有对象,通过依赖注入模式,实现控制反转,实现对象的自动化管理。
2. IoC容器
- 管理对象依赖关系
- 直接编码写入
- xml配置文件
- 元数据(注解)
- 负责对象的创建和管理
3. BeanFactory
BeanFactory 默认是lazy-load,而applicationContext则默认是全部init完成的。
BeanFactory接口的主要方法:
public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
containsBean
isSingleton
isPrototype
isTypeMatch
isTypeMatch
getType
getAliases
}
- 对象注册和依赖关系的表达方式-configuration metadata
- 直接编码
- 外部配置(configuration.xml、properties文件)通过xmlBeanDefinationReader、PropertiesBeanDefinationReader读取器来读取配置文件,把对应的配置读取到BeanDefination中,然后再把BeanDefination添加进BeanDefinationRegistry,同时Registry和BeanFactory是一体两面的,最终也就得到了可用的IOC容器。
- 注解方式
classpath-scaning 类路径扫描,也就是通过扫描器来对指定的路劲进行加载扫描,把对应注解的类进行指定加载从而实现注解功能。
context:componant-scan/ 扫描器功能
@autowired、@resource 一个是bytype、一个是byname的加载模式
BeanDefinationRegistry 负责实现bean的注册管理工作,而BeanFactory则是负责从容器中对bean的获取访问。
Registry如何管理bean的注册?通过管理BeanDefination,beanDefination负责保存bean和bean的依赖关系的描述组成的bean对象的必要信息,当BeanFactory向容器请求一个对象的时候,就是通过Registry来查找到对应的BeanDefination,通过BeanDefination来构建出需要的对象实例。
xml配置
- beans
- 属性
default-lazy-init 默认 false
default-autowire 默认no, no、byname、bytype、constructor等
default-dependency-check 默认no
default-init-method和default-destroy-method 初始化方法和销毁方法 - 子标签
description:描述信息
import 导入其他的配置文件
alias 可以给某些bean进行别名定义
bean 对bean的定义
- 属性
- bean的scope 作用域
singleton
prototype
request、session和global session
工厂方法FactoryBean
Public interface FactoryBean<T>{
T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
FactoryBean接口在ioc容器中使用时,ioc容器会自动调用FactoryBean的getObject方法获取到对应的Bean对象,这样就可以通过FactoryBean实现复杂的组装和装配过程。这是IOC容器的内部机制!
容器启动的过程
- 通过BeanDefinationReader加载configuration metadata
- 把BeanDefination 保存到BeanDefinationRegistry中
- Registry加载到BeanFactory中(一般BeanFactory同时会实现BeanDefinationRegistry接口)
- Bean实例化, 在请求容器获取Bean时,会检查Bean是否存在,若不存在则实例化并保存后返回给出使用,若已经存在则直接返回(此流程是单例scope的情况)
- 容器的实例化干涉机制:BeanFactoryPostProcessor
org.springframework.beans.factory.config.PropertyPlaceholderConfigurer,进行properties中的占位符信息替换 ${}
org.springframework.beans.factory.config.PropertyOverrideConfigurer,进行properties信息覆盖
Bean的创建过程
可以在org.springframework.beans.factory.support.AbstractBeanFactory类的代
码中查看到getBean()方法的完整实现逻辑,可以在其子类org.springframework.beans.
factory.support.AbstractAutowireCapableBeanFactory的代码中一窥createBean()方
法的全貌。
在完成容器的初始化过程后,此时容器中已经拥有了所有的BeanDefinition对象,但并未进行bean的实例化,BeanFactory通过显示的getBean来实例化,ApplicationContext则是在初始化完成后内部调用getBean进行隐式初始化创建(也即自动创建)
applicationcontext 默认在启动后实例化所有的Bean:
参考:org.springframework.context.support.AbstractApplicationContext.refresh();
- BeanWrapper
org.springframework.beans.factory.support.InstantiationStrategy
CglibSubclassingInstantiationStrategy
BeanWrapper是对bean实例的一个包装器类,该类可以通过其实现的接口方便的给bean进行属性赋值操作,从而实现bean的实例化->属性赋值->init等动作。
Object provider =Class.forName("package.name.FXNewsProvider").newInstance();
Object listener=Class.forName("package.name.DowJonesNewsListener").newInstance();
Object persister=Class.forName("package.name.DowJonesNewsPersister").newInstance();
Class providerClazz = provider.getClass();
Field listenerField = providerClazz.getField("newsListener");
listenerField.set(provider, listener);
Field persisterField = providerClazz.getField("newsListener");
persisterField.set(provider, persister);
assertSame(listener, listenerField.get(provider));
assertSame(persister, persisterField.get(provider));
- aware接口
在通过beanwraper实例化出bean并完成了属性赋值之后,spring容器会检查bean是否实现了各种aware接口,并把这些aware的类注入到bean中 - BeanPostProcessor
类似于BeanFactoryPostProcessor,用在bean的实例化后,返回调用前,进行后置的拦截处理 - InitializingBean和init-Method
在postprocessor处理之后,会调用设置的这两个方法进行初始化操作 - distroy-method
在使用完bean之后,进行bean的回收和distroy
4. ApplicationContext
统一资源、资源加载
org.springframework.core.io.Resource
org.springframework.core.io.ResourceLoader
Resource作为spring中的统一的资源抽象和访问接口,用来表示spring使用的所有的资源。
ResourceLoader 是资源查找定位策略的统一抽象。
public interface ResourceLoader {
/** Pseudo URL prefix for loading from the class path: "classpath:" */
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
/**
* Return a Resource handle for the specified resource location.
* <p>The handle should always be a reusable resource descriptor,
* allowing for multiple {@link Resource#getInputStream()} calls.
* <p><ul>
* <li>Must support fully qualified URLs, e.g. "file:C:/test.dat".
* <li>Must support classpath pseudo-URLs, e.g. "classpath:test.dat".
* <li>Should support relative file paths, e.g. "WEB-INF/test.dat".
* (This will be implementation-specific, typically provided by an
* ApplicationContext implementation.)
* </ul>
* <p>Note that a Resource handle does not imply an existing resource;
* you need to invoke {@link Resource#exists} to check for existence.
* @param location the resource location
* @return a corresponding Resource handle (never {@code null})
* @see #CLASSPATH_URL_PREFIX
* @see Resource#exists()
* @see Resource#getInputStream()
*/
Resource getResource(String location);
/**
* Expose the ClassLoader used by this ResourceLoader.
* <p>Clients which need to access the ClassLoader directly can do so
* in a uniform manner with the ResourceLoader, rather than relying
* on the thread context ClassLoader.
* @return the ClassLoader (only {@code null} if even the system
* ClassLoader isn't accessible)
* @see org.springframework.util.ClassUtils#getDefaultClassLoader()
*/
ClassLoader getClassLoader();
}
从loader接口定义中可以知道spring中的常用的资源前缀符号和文件定位:
- file:C:/test.dat
- classpath:test.dat
- WEB-INF/test.dat
具体资源前缀修饰符在org.springframework.util.ResourceUtils
中定义
org.springframework.context.support.AbstractApplicationContext-refresh 是加载的入口,通过在该方法中的刷新加载applicationcontext,并且通过定义的一系列postprocess 来对applicationContext来进行处理!
- applicationcontext与resourceloader的关系
abstractapplicationcontext同时实现了resourceloader和resourcePatternResolver,也就是同时拥有getResource()和getResources()两个方法,在具体实现中,protected ResourcePatternResolver getResourcePatternResolver() { return new PathMatchingResourcePatternResolver(this); }
内部使用该方法构建并使用自身作为resourceloader传入,巧妙的利用自身实现双分派构建
- applicationcontext在resource方面的应用
- 作为resourceloader使用
applicationcontext本身就是一个resourceloader,因此如果要使用resourceloader,可以直接使用applicationcontext进行加载 - resourceloader的注入
也可以让bean实现applicationcontextaware或者resourceloaderaware接口,这样spring会自动注入到bean中一个resourceloader。 - resource类型的自动注入
applicationcontext 在启动时会使用一个org.springframework.beans.support.ResourceEditorRegistrar来注册Spring提供的针对Resource类型的PropertyEditor实现到容器中,这个PropertyEditor叫 做org.springframework.core.io.ResourceEditor。同时针对多个resource,会注册一个org.springframework.core.io.support.ResourceArrayPropertyEditor实现来进行array加载。我们通过CustomerEditorConfigurar来为applicationcontext配置加载的bean即可 - ApplicationContext中的Resource的资源路径定位
在ApplicationContext中,由于对ResourceLoader和ResourcePatternResolver的实现添加了Classpath:和classpath*:两个前缀协议,
而对于不同的ApplicationContext实现,也有不同的加载行为,classpathxmlApplicationContext默认会在classpath中加载资源,即使没有使用classpath前缀,而filesystemApplicationContext则会从文件系统加载,除非显示使用classpath,因此在使用时需要查看javadoc!
- ApplicationContext内的事件机制
通过java.util.EventListener
和java.util.EventObject
的继承和复用,在org。springframework.context
包中定义了ApplicationEvent、ApplicationListener、ApplicationEventPublisher接口,ApplicationContext就实现了一套事件发布机制,ApplicationContext本身继承并实现了ApplicationEvent,但具体工作委托给了其内部字段ApplicationEventMulticaster来进行处理。
在业务实现时,如果需要通过使用容器内的事件机制进行事件发布,一般可以使用这些方法
- ApplicationEventPublisherAware 直接注入一个Publisher来进行发布
- ApplicationContextAware,使用ApplicationContext作为publisher进行发布操作
IoC扩展知识
- @autowired
@autowired的自动注解,按照bytype的类型模式来进行依赖注入,可以使用在字段、方法、set方法和contructor上。其具体实现则是通过org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
这个Bean的PostProcessor来对Bean的实例化过程进行初始化拦截,从而实现注解解析功能。 - @Qualifier
@qualifier注解是按照byname 的方式进行模型注入,qualifier通过设置name字段,来告诉ApplicationContext为我们绑定哪个名字的bean - @Resource 和@PostConstructor、@PreDistroy
这时Jsr250中的3个注解,其中的@Resource类似于@Qualifier,通过byname进行注入,但是其实现是通过不同的postprocessor,org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
,因此注意区分一下
| 这两种分别属于不同的注解派系,虽然功能相似,但最好使用一个派系中的 - classpath-scan
<context:component-scan base-package=“org.spring21”/> 开启bean的自动扫描加载功能