文章目录
WHAT:什么是IOC(Inversion of Control)
IOC中文翻译叫控制反转,可以说是spring最核心的部分。是spring家族任意组件的基本。控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。
依赖注入
在软件工程中,依赖注入(dependency injection)的意思为,给予调用方它所需要的事物。 “依赖”是指可被方法调用的事物。依赖注入形式下,调用方不再直接指使用“依赖”,取而代之是“注入” 。“注入”是指将“依赖”传递给调用方的过程。在“注入”之后,调用方才会调用该“依赖”。传递依赖给调用方,而不是让调用方直接获得依赖,这个是该设计的根本需求。
注:编程语言层次下,“调用方”为对象和类,“依赖”为变量。在提供服务的角度下,“调用方”为客户端,“依赖”为服务。
该设计的目的是为了分离关注点,分离调用方和依赖,从而提高可读性以及代码重用性。
依赖注射是控制反转的最为常见的一种技术。
附上wiki中对ioc的介绍:https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC
WHY:为什么要用IOC
1.减少代码的维护成本
举个例子,设计行李箱,行李箱依赖箱体,箱体依赖底盘,底盘依赖轮子。这样的设计看起来没问题,但是可维护性很低。加入要改变轮子的尺寸,这样上层所有的零件都要重做。
用代码来展示一下设计行李箱:
这时需求来了,把轮子的大小改成动态的,就会变成这样:
非常的麻烦,项目上真的这样写,代码基本上可以说无法维护了。
如果使用依赖注入则会变成这样:
如果要改变轮子的大小,现在只需要改变轮子就可以了。现在是用过上层控制下层。代码会变这样:
这次要让轮子能改变大小,只需要这样:
2.利于协同开发和单元测试
假设这四个零件是四个开发小组,只要定义好了接口,可以同时进行开发,而不相互受到限制。
对于单元测试来讲,如果要写某个零件的单元测试,只需要mock出所需要的的类传入就可以了,而不用把所有零件都new一遍。
WHEN:什么时候用
HOW:怎么用
实现控制反转主要有两种方式:依赖注入和依赖查找。两者的区别在于,前者是被动的接收对象,在类A的实例创建过程中即创建了依赖的B对象,通过类型或名称来判断将不同的对象注入到不同的属性中,而后者是主动索取相应类型的对象,获得依赖对象的时间也可以在代码中自由控制。
依赖注入
依赖注入有如下实现方式:
基于接口
实现特定接口以供外部容器注入所依赖类型的对象。
基于构造器
基于 set 方法
实现特定属性的public set方法,来让外部容器调用传入所依赖类型的对象。
基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
基于注解
基于Java的注解功能,在私有变量前加“@Autowired”等注解,不需要显式的定义以上三种代码,便可以让外部容器传入对应的对象。该方案相当于定义了public的set方法,但是因为没有真正的set方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为set方法只想让容器访问来注入而并不希望其他依赖此类的对象访问)。
依赖查找
依赖查找更加主动,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态
WHERE:在哪可以用到
当我们想用到设计模式的依赖倒置原则时。依赖注入就好的实践。
Spring IOC组件注入
Spring本身是一个轻量级的框架,但是随着项目的不断扩大,如果仍然只使用配置文件形式开发,维护成本会变得越来越高。
这也引出了使用注解的方式进行配置,其中最常用的就是使用注解将组件注入到IOC容器。
使用@ComponentScan
使用包扫描的方式进行组件注入
1.首先新建一个maven工程,引入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.2</version>
<scope>test</scope>
</dependency>
</dependencies>
工程结构
2.在Config中编写代码
@ComponentScan("com.gcl")
public class Myconfig {
}
意为将com.gcl下的所有带@Component注解的组件进行注入
3.新建测试类
一直使用的都是这一个方法
public class MyTest {
@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Myconfig.class);
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
打印结果为
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myconfig
myController
前面是spring的组件,myconfig是配置类,myController加了@Controller注解
使用@Bean
编写配置类代码
@Configuration
public class Myconfig {
@Bean
public Person person(){
return new Person();
}
}
打印结果
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myconfig
person
person被注入进了ioc容器中
使用@Import
其中有三种方式
1.@Import
在配置类中编写代码
@Configuration
@Import(Person.class)
public class Myconfig {
}
打印结果
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myconfig
com.gcl.bean.Person
person类被注入
2.ImportSelector
编写一个类,实现ImportSelector接口,代码如下
public class MySelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.gcl.bean.Person"};
}
}
配置类
@Configuration
@Import(MySelector.class)
public class Myconfig {
}
测试类打印结果
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myconfig
com.gcl.bean.Person
person同样被注入
3.ImportBeanDefinitionRegistrar
写一个类实现ImportBeanDefinitionRegistrar ,在其中设置一个规则,如果red和green都注入了,再注入blue
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
boolean definition1 = beanDefinitionRegistry.containsBeanDefinition("com.gcl.bean.Red");
boolean definition2 = beanDefinitionRegistry.containsBeanDefinition("com.gcl.bean.Green");
if (definition1 && definition2){
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Blue.class);
beanDefinitionRegistry.registerBeanDefinition("blue",rootBeanDefinition);
}
}
}
配置类
@Configuration
@Import({MyImportBeanDefinitionRegistrar.class, Red.class, Green.class})
public class Myconfig {
}
打印结果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myconfig
com.gcl.bean.Red
com.gcl.bean.Green
blue
FactoryBean
创建一个类,实现FactoryBean接口,代码如下:
public class MyFactoryBean implements FactoryBean<Red> {
public Red getObject() throws Exception {
return new Red();
}
public Class<?> getObjectType() {
return Red.class;
}
public boolean isSingleton() {
return true;
}
}
修改配置类
@Configuration
public class Myconfig {
@Bean
public MyFactoryBean myFactoryBean(){
return new MyFactoryBean();
}
}
修改测试类
public class MyTest {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Myconfig.class);
@Test
public void test01() {
printBeans(applicationContext);
Object factoryBean1 = applicationContext.getBean("myFactoryBean");
Object factoryBean2 = applicationContext.getBean("myFactoryBean");
System.out.println(factoryBean1.getClass());
System.out.println(factoryBean1 == factoryBean2);
}
private void printBeans(AnnotationConfigApplicationContext applicationContext) {
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
测试结果
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myconfig
myFactoryBean
class com.gcl.bean.Red
true
工厂生成的是Red而不是它本身
如果想获取工厂本身
加一个前缀&
Object factoryBean3 = applicationContext.getBean("&myFactoryBean");
打印结果
com.gcl.config.MyFactoryBean@411f53a0