探究Spring IOC控制反转 、DI 依赖注入
IoC 控制反转思想
可以用一句话来描述: 依赖即地狱 ------ IoC 控制反转思想是为了解决对象与对象之间、依赖地狱的问题
解释下这种依赖地狱, What is it ? 假设N个对象,Object_A 需调用 Object_B, Object_B又需调用Object_C, 而Object_C又必需调用Object_D ... Object_N 又必需调用Object_A, 一层层套娃式陷入了一个依赖地狱模式,形成这么一个依赖循环的闭合!
设计出这种牛掰的思想, 其目的是为了在开发中,对所有的对象,模块,进行高度化解耦,抽离出一个个单身的功能模块,把所有的对象与对象之间不再进行相互调用, 而是将它们注册到IOC容器, 即拿即用,每个模块只的职责只需负责自身对象中的成员变量函数方法等。Inversion of Control,将我们写好的对象交由IoC控制,是IoC控制调配Objects。 它是一种原理一种思想,协助我们完成控制程序执行流的反转. 譬如,在执行循环依赖闭合的开发时,因为没有初始化对象,编译是不会通过的 ! 交由IoC来完成这种闭合依赖的初始化对象。 而Dependency Injection依赖注入是解决Inversion of Control的设计模式,通过依赖注入, 减少了多个对象之间的耦合,因为它是由框架动态注入的。依赖流入有两种方式: Setter Injection 和 Constructor Injection.
概念态
概念形态的 bean ------ 指SpringMVC在类中所引用的注解,在绑定大量的xml文件来进行配置,通过class类中的注解来驱动开发。
譬如:Spring在xml文件或是class类中配置的
...
@Component
class Student {
...
}
@Bean
public Person person(){
...
}
定义态
定义形态的 bean ------ 指我们手动在xml文件中配置的一个个 bean
譬如, 自己手动去配置一个bean,需要通过配置它的class、id、scope、autoware、property ... 等来完成一个手动的配置, spring 会根据自己的资源配置信息在底层隐式的封装, 通过bean所定义的信息来完成BeanDefinition在IOC容器中的注册
而一个个的 bean 在spring底层是隐式封装,其过程解析:
第一步: 先完成xml的配置或类的注解映射
通过ClassPathXmlApplicationContext(xml) 、AnnotationConfigApplicationContext( )实例化 new ApplicationContext()
第二步: 读取XML 、配置类的信息
Spring 是通过 BeanDefinitionDocumentReader 解析配置文件中的资源配置信息 , 它作用于解析文件后, 为完成第三步 ClassPathBeanDefinitionScanner 成功后调用生成BeanDefinition的接口完成注册作铺垫。
public interface BeanDefinitionDocumentReader {
void registerBeanDefinitions(Document var1, XmlReaderContext var2) throws BeanDefinitionStoreException;
}
第三步: 扫描包下面的类,进行分析
Spring 利用 ClassPathBeanDefinitionScanner 扫描器分析,会对src/包下所有的类[ 符合条件的类 ] 逐个注册到 IOC 容器中,从成完成了BeanDefinitionRegistry 的注册
protected Set doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set beanDefinitions = new LinkedHashSet();
String[] var3 = basePackages;
int var4 = basePackages.length;
...
}
第四步: 将Bean注册到IOC容器中, 以 Map 存储
BeanDefinition是封装Bean的定义信息, 它也决定一个Bean是怎么生产的. [ beanDefinitionMap装载 ]
private final Map beanDefinitionMap = new ConcurrentHashMap(256);
spring底层用BeanDefinition中的beanClass来封装Bean, 如下方源码:
public class BeanDefinition extends NoOutputDefinition {
@XmlTransient
private Class> beanClass;
@XmlTransient
private Object bean;
...
}
纯净态
第一个环节是先实例化:
实例化后, 它就是一个纯净态的bean. 在实例化后它会生成bean的生命周期, Spring 底层实例化一个bean时用3种方式:
1 实例化方式一--------反射
@Component
public class Car{
@Autowired
private Tank tank;
private String name;
public void setTank(Tank tank) {
this.tank = tank;
}
}
上方的@Component注释会将Car类读取到AbstractBeanDefinition中的beanClass中去,通过这一属性反射。@Component和@ComponentScan有区别,@Component只是将属性反射,并未把方法注册到IOC容器 [ 多个类配置@Component, 利用@ComponentScan扫描包下的路径含有@Bean、@Component 的 annotations,都会将它们注册到工厂方法 ] 。
下方AbstractBeanDefinition源码中的beanClass
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
...
private volatile Object beanClass;
...
}
2 实例化方式二--------工厂方法 @bean
@bean是基于方法的
@ComponentScan(value = "com.cloud.car")
public class MainConfig{
@Bean
public void Car car() {
car.owner=("amy")
return car;
}
}
在xml中另一种工厂方法factory-method="" , 它也是工厂方法@bean注解的另一个体现方式
...
@ComponentScan作用于扫描已定义的包类的对象, 此处扫描@Component实例化的bean; 两种工厂方式: 类上配置@ComponentScan@bean注释和xml配置factory-method="",都是将定义的bean读到factoryMethodName这一属性上去。下方AbstractBeanDefinition源码中的factoryMethodName用于工厂方法读取工厂方法bean
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
private String factoryMethodName;
...
}
额外补充: 因为大部分开发者经常会混淆 @Component @Bean 注释驱动,会认为两个注释都是同一种用途的不同注释,其实他们大有不同!! 下方文献摘自技术WebSite。
In Spring, both annotations are quite different.
Difference between @Component and @Bean annotations
@Component used to auto-detect and auto-configure beans using classpath scanning. There’s an implicit one-to-one mapping between the annotated class and the bean (i.e. one bean per class). it will identify the classes annotated with @Component annotation (within the given package) and create the beans of such classes and register them in the ApplicationContext; @Component is a class level annotation and its purpose it to make the class as spring managed component and auto detectable bean for classpath scanning feature.
@Component is a class level annotation and its purpose it to make the class as spring managed component and auto detectable bean for classpath scanning feature.
if you want to know more about @Component and other stereo type annotations, it is recommended to look at this article.
@Bean is used to explicitly declare and register a bean (as a configuration bean) in Spring IOC container that is returned from a method. @Bean is a method level annotation and it is used within a class that is annotated with @Configuration.
Simply, @Bean annotation is used to register the bean returned by a method as a spring configuration bean in IOC Container. Another big difference is that @Component is a class level annotation where as @Bean is a method level annotation and ,by default, name of the method serves as the bean name.
To declare a bean, simply annotate a method with the @Bean annotation. When JavaConfig encounters such a method, it will execute that method and register the return value as a bean within a ApplicationContext. By default, the bean name will be the same as the method name.The following is a simple example of a @Bean method declaration.
3 实例化方式三 ------- 工厂类 FactoryBean
FactoryBean 是基于类的,以实现接口方法 getObject() 返回这个bean ,以下代码狸猫换太子, getObject() 是返回的是Tank对象
public class Car implement FactoryBean{
@Autowired
private Tank tank;
private String name;
public void setTank(Tank tank){this.tank = tank;}
public void getTank { return tank;}
public Car (String name){ this.name = name }
public String toString { ... }
...
@Override
public Object getObject() throws Exception{
return new Tank();
}
@Override
public Object getObject() throws Exception{
return null ;
}
}
public static void main(String[] args){
// 加载Spring上下文
AnnotionCofigApplicationContext context = new AnnotionCofigApplicationContext(MainConfig.class);
Tank tank= (Tank)context.getBean("tank");
system.out.println(car);
}
public static void main(String[] args){
// 加载Spring上下文, 通常是使用AnnotionCofigApplicationContext
AnnotionCofigApplicationContext context = new AnnotionCofigApplicationContext(MainConfig.class);
Car car= (car)context.getBean("car");
// BeanFactory的方式[需要额外手动配置,因为此方式和上方加载Spring上下文不同, 加载上下文是把类中的整个所有类中属性和实现方法全部扫描拿到bean ]
// BeanFactory占用内存更小, 这种方式适合于设备受限的内嵌应用
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.getBean("car") ; //会报错,需要额外配置其它属性
}
从上面我写的这段主运行程序中,区分BeanFactory 和 FactoryBean的区别:
beanFactory是一个子函数具体实现方法[它占的内存更小,需要手动去创建它,适合在资源受限的设置上内嵌开发 ],而FactoryBean是一个类 [它占的内存更大 ];
AnnotionCofigApplicationContext [它占的内存是最大的!在new AnnotionCofigApplicationContext ( XXX.class)是加载整个类,扫描整个 spring 上下文,整个类包含了各种方法 ] AnnotionCofigApplicationContext 包含BeanFactory的所有具体实现方法. 看下方的结构树图 :
AbstractApplicationContext中的getBean(), 拿到bean实现:
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean {
...
public Object getBean(String name) throws BeansException {
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(name);
}
public T getBean(String name, Class requiredType) throws BeansException {
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(name, requiredType);
}
public T getBean(Class requiredType) throws BeansException {
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(requiredType);
}
public Object getBean(String name, Object... args) throws BeansException {
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(name, args);
}
public T getBean(Class requiredType, Object... args) throws BeansException {
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(requiredType, args);
}
...
}
DefaultListableBeanFactory类中的getBean()实现:
[AnnotationConfigApplicationContext实现了此方法, 获取getBean()拿到bean ]
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
...
public T getBean(Class requiredType) throws BeansException {
return this.getBean(requiredType, (Object[])null);
}
public T getBean(Class requiredType, Object... args) throws BeansException {
NamedBeanHolder namedBean = this.resolveNamedBean(requiredType, args);
if (namedBean != null) {
return namedBean.getBeanInstance();
} else {
BeanFactory parent = this.getParentBeanFactory();
if (parent != null) {
return parent.getBean(requiredType, args);
} else {
throw new NoSuchBeanDefinitionException(requiredType);
}
}
...
}
AnnotationConfigApplicationContext类中super(beanFactory),它直接到上面的DefaultListableBeanFactory类从而获取getBean()拿到bean。
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
...
public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
...
}
第二个环节是@Autowired @Value属性注入:
利用三级缓存解决循环依赖 [ 另一篇文章单独写了关于此问题的解决 ]。
第三个环节是初始化生命周期的回调以及aware方法:
初始化生命周期的回调的三种方法: @PostConstruct & afterPropertiesSet() & init-method = " "
The container calls afterPropertiesSet() for the former and destroy() for the latter to let the bean perform certain actions upon initialization and destruction of your beans. @PostConstruct and @PreDestroy annotations are generally considered best practice for receiving lifecycle callbacks in a modern Spring application.
Spring 初始化Bean & 销毁化Bean [ 示范性三种方式 ]:
示范一:以afterPropertiesSet() + destroy() 方式
// In a managed bean, @PostConstruct is called after the regular Java object constructor.
// Why need to use @PostConstruct to initialize by bean, instead of the regular constructor itself?
// Because when the constructor is called, the bean is not yet initialized - i.e. no dependencies are injected. In the @PostConstruct method the bean is fully initialized and you can use the dependencies.
package com.amy.customer.services;
public class CustomerService implements InitializingBean, DisposableBean{
String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void afterPropertiesSet() throws Exception {
System.out.println("Init method after properties are set : " + message);
}
public void destroy() throws Exception {
System.out.println("Spring Container is destroy! Customer clean up");
}
}
Run it
package com.amy.common;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.amy.customer.services.CustomerService;
public class App{
public static void main( String[] args ){
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"Spring-Customer.xml"});
CustomerService cust = (CustomerService)context.getBean("customerService");
System.out.println(cust);
context.close();
}
}
// Output
Init method after properties are set : im property message
com.amy.customer.services.CustomerService@47393f
...
INFO: Destroying singletons in org.springframework.beans.factory.
support.DefaultListableBeanFactory@77158a:
defining beans [customerService]; root of factory hierarchy
Spring Container is destroy! Customer clean up
示范二: 以@ PostConstruct初始化注释 和 @ PreDestroy销毁化注释的方式
// Book.java
package com.concretepage;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class Book {
private String bookName;
public Book(String bookName) {
this.bookName = bookName;
}
@PostConstruct
public void springPostConstruct() {
System.out.println("---Do initialization task---");
if(bookName != null) {
System.out.println("bookName property initialized with the value:"+ bookName);
}
}
public String getBookName() {
return bookName;
}
@PreDestroy
public void springPreDestroy() {
System.out.println("---Release resources or perform destruction task---");
}
}
// AppConfig.java
package com.concretepage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public Book book(){
return new Book("Ramayana");
}
}
// Main Class
package com.concretepage;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class);
ctx.refresh();
Book book = ctx.getBean(Book.class);
System.out.println("Book name:" + book.getBookName());
ctx.close();
}
}
// Output
---Do initialization task---
bookName property initialized with the value:Ramayana
Book name:Ramayana
---Release resources or perform destruction task---
示范三: 以xml 映射的方式,通过绑定 init-method=" " destroy-method=" "对bean进行初始化回调和销毁化回调。
If you do not want to use the JSR-250 annotations but you still want to remove coupling, consider init-method and destroy-method bean definition metadata. In the case of XML-based configuration metadata.
若不用@注释的方式解除耦合性,可通过xml配置元数据的方式定义bean的初始化回调和摧毁化回调。
package com.amy.customer.services;
public class CustomerService{
String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void initIt() throws Exception {
System.out.println("Init method after properties are set : " + message);
}
public void cleanUp() throws Exception {
System.out.println("Spring Container is destroy! Customer clean up");
}
}
Run it
package com.amy.common;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mkyong.customer.services.CustomerService;
public class App {
public static void main( String[] args ) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"Spring-Customer.xml"});
CustomerService cust = (CustomerService)context.getBean("customerService");
System.out.println(cust);
context.close();
}
}
// Output
Init method after properties are set : i'm property message
com.mkyong.customer.services.CustomerService@47393f
...
INFO: Destroying singletons in org.springframework.beans.factory.
support.DefaultListableBeanFactory@77158a:
defining beans [customerService]; root of factory hierarchy
Spring Container is destroy! Customer clean up
Aware
The ApplicationContextAware Interface:
//The code overrides the setApplicationContext() method to lookup another bean with the id user using the injected ApplicationContext.
public class ApplicationContextAwareImpl implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
User user = (User) applicationContext.getBean("user");
System.out.println("User Id: " + user.getUserId() + " User Name :" + user.getName());
}
}
//The preceding code is of a bean that implements ApplicationContextAware.
The BeanFactoryAware Interface :
public class BeanFactoryAwareImpl implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println(beanFactory.getBean("user"));
}
}
The BeanNameAware Interface:
public class BeanFactoryAwareImpl implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println(beanFactory.getBean("user"));
}
}
The MessageSourceAware Interface:
// If you want to access i18n resources bundles for different locales into your java source code,
// then that java class must implement MessageSourceAware interface.
// String getMessage(String code, Object[] args, String default, Locale loc)
// String getMessage(String code, Object[] args, Locale loc)
// String getMessage(MessageSourceResolvable resolvable, Locale locale)
@Controller
public class EmployeeController implements MessageSourceAware{
private MessageSource messageSource;
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public void readLocaleSpecificMessage(){
String englishMessage = messageSource.getMessage("first.name", null, Locale.US);
System.out.println("First name label in English : " + englishMessage);
String chineseMessage = messageSource.getMessage("first.name", null, Locale.SIMPLIFIED_CHINESE);
System.out.println("First name label in Chinese : " + chineseMessage);
}
}
#messages_en_US.properties
first.name=FirstName in English
#messages_zh_CN.properties
first.name=FirstName in Chinese
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.howtodoinjava.demo.controller.EmployeeController;
public class TestSpringContext {
@SuppressWarnings("resource")
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext( new String[] { "/spring-servlet.xml" });
EmployeeController controller = (EmployeeController) context.getBean(EmployeeController.class);
controller.readLocaleSpecificMessage();
}
}
Output:
First name label in English : FirstName in English
First name label in Chinese : FirstName in Chinese
关于Aware接口就不一一罗列, 可以看下方的图片,Aware的子接口
...