Spring IOC
(Inversion of Control,控制反转
)是Spring的一个核心思想,其包括两层含义:html
控制
:控制是指当前对象对内部依赖对象的控制权,好比建立依赖对象。反转
:这种控制权不禁当前对象来管理,由第三方(好比容器)来管理。(反转也能够理解为获取依赖对象的方式从主动变成了被动)在Spring中,控制反转
也能够简单地理解为将对象的建立和管理交由IOC容器来完成。java
IOC将依赖对象的控制权转移到外部,但当前对象仍是须要依赖对象的,这时候就须要使用依赖注入
将所须要的依赖对象从外部注入进来。spring
控制反转是一种思想,而依赖注入是一种实现方式。segmentfault
为何须要使用Spring IOC?从IOC思想来看,主要有两大优势:数组
从Spring IOC的实际实现来看,还有以下好处:bash
没有IOC的状况下,使用依赖的对象须要手动new一个对象出来,根据构造器是否须要参数,能够分为有参对象和无参对象。而Spring只须要在xml文件中集中配置一次,或者使用注解就能够实现依赖注入,不须要手动new对象出来。app
接下来考虑几种须要修改具体对象实现的状况下代码重构的成本,来理解Spring IOC如何实现松耦合。工具
好比须要将类名从Message修改成News,那么此时能够借助开发工具的重构功能实现代码重构。
重构难度:1星。post
好比须要将对象类型名从Student修改成Teacher,那么此时能够借助查找替换功能实现代码重构。 无重写方法状况下,重构难度:2星; 有重写方法状况下,重构难度:3星;单元测试
好比须要在建立Student对象时,增长年龄参数,此时借助查找功能也须要一个个手动修改代码,增长参数。
重构难度:5星。
若是使用Spring IOC,即使对难度最大的第三种状况,也只须要在xml文件中修改下注入的参数,或者在对应对象中增长一个属性,并使用注解自动注入便可。
重构难度:1星。
IOC容器
是Spring提供的一个工厂
,用于管理全部的bean、以及bean之间的关系。
Java反射机制
,经过反射机制获取类的全部信息(属性、类名等);Spring中主要提供两类IOC容器,分别是BeanFactory
和ApplicationContext
。
二者间继承关系以下图,可见ApplicationContext间接继承自BeanFactory。
BeanFactory是Spring提供的最基础的IOC容器,用于帮助完成bean的注册和bean之间关系的绑定。
特色:
延加载
策略。当容器中bean被访问时,才进行bean的初始化和依赖注入工做。BeanFactory最基础的实现类是DefaultListableBeanFactory
。
旧版本中BeanFactory最多见实现类是XmlBeanFactory(现已被废弃),新版中使用XmlBeanDefinitionReader
+ DefaultListableBeanFactory
替代XmlBeanFactory
。
出于方便,可直接创建了一个SpringBoot工程。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
</beans>
复制代码
这里使用@Data注解省略了getter和setter方法。
@Data
public class User {
private Integer age;
private Nation nation;
public User() {
}
public User(Integer age) {
this.age = age;
}
public User(Nation nation) {
this.nation = nation;
}
}
复制代码
<bean id="user" class="com.example.springdemo.xml.model.User">
<constructor-arg name="age" type="java.lang.Integer" value="26"></constructor-arg>
</bean>
复制代码
public static void main(String[] args) {
// 1. XmlBeanFactory方式,已废弃
// XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
// 2. XmlBeanDefinitionReader + DefaultListableBeanFactory 替代XmlBeanFactory
Resource resource = new ClassPathResource("application.xml");
BeanFactory factory = new DefaultListableBeanFactory();
BeanDefinitionReader bdr = new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory);
bdr.loadBeanDefinitions(resource);
// 根据bean名从容器中获取bean
User user1 = (User) factory.getBean("user");
System.out.println(user1.getAge());
}
复制代码
ApplicationContext
是在BeanFactory的基础上构建的,是相对比较高级的容器实现,除了拥有 BeanFactory的全部支持,ApplicationContext还提供了其余高级特性,好比:
ApplicationContext常见实习类有:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext和XmlWebApplicationContext。
仍然使用BeanFactory示例中的配置文件,同时使用ClassPathXmlApplicationContext
来读取xml文件。
// 3. ApplicationContext拥有比BeanFactory更高级的特性
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
User user3 = (User) applicationContext.getBean("user");
System.out.println(user3.getAge());
复制代码
依赖注入方式
主要包括:
构造器注入
属性注入
(setter方法注入)其中经常使用的两种方式是构造器注入
和属性注入
。虽然官方文档建议能用构造器注入就用构造器注入,由于这样可使得依赖关系明确,而且若是缺乏依赖的话在初始化阶段就能够发现问题。但属性注入
更加灵活
,而且构造器注入
方式没法解决循环依赖问题,因此通常使用属性注入
,强制依赖
建议使用构造器方式
注入。
基于构造器的依赖注入是指,在bean的构造器中指明依赖的bean,并在初始化时完成注入。
User类见2.2.1.2中,存在以下构造器:
public User(Integer age) {
this.age = age;
}
复制代码
<!-- 构造器方式注入 -->
<bean id="user" class="com.example.springdemo.xml.model.User">
<constructor-arg name="age" type="java.lang.Integer" value="26"></constructor-arg>
</bean>
复制代码
基于属性的依赖注入是指,容器经过无参构造器初始化bean,再经过调用setter方法来完成注入。
User类如上,有属性age(经过@Data注解省略了getter/setter方法):
private Integer age;
复制代码
<!-- 属性注入 -->
<bean id="user2" class="com.example.springdemo.xml.model.User">
<property name="age" value="27"></property>
</bean>
复制代码
// user使用了构造器注入
User user1 = (User) factory.getBean("user");
System.out.println("DI by constructor:" + user1.getAge());
// user2使用了属性注入
User user2 = (User) factory.getBean("user2");
System.out.println("DI by setter:" + user2.getAge());
复制代码
输出:
在3.1小节中,User bean注入了int型的age属性,一样能够注入其它由IOC容器管理的bean。
User类还存在以下构造器,表示User bean依赖Nation bean。
public User(Nation nation) {
this.nation = nation;
}
复制代码
Nation类:
public class Nation {
public String name;
public Nation(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
复制代码
注册Nation bean,同时注册User bean,并使用构造器方式对User bean注入Nation bean。
<bean id="nation" class="com.example.springdemo.xml.model.Nation">
<constructor-arg name="name" type="java.lang.String" value="China"></constructor-arg>
</bean>
<!-- 手动注入依赖bean -->
<bean id="user3" class="com.example.springdemo.xml.model.User">
<constructor-arg name="nation" type="com.example.springdemo.xml.model.Nation" ref="nation"></constructor-arg>
</bean>
复制代码
// 注入IOC容器管理的bean,user注入时须要nation。
// 这时候也能明显体现Spring Ioc解耦的优势,若是不使用依赖注入,将对象的建立交由bean来作的话,代码以下。
// 若是须要修改传入参数,项目中全部地方都须要修改
Nation nation = new Nation("China");
User user4 = new User(nation);
System.out.println("Name of nation in user3 by code: " + user4.getNation().getName());
// 而使用依赖注入的话,只须要在xml中一个地方集中配置管理,在配置文件中注入取值或者内部bean,而不须要每一个建立对象的地方都要修改
User user5 = (User) factory.getBean("user3");
System.out.println("Name of nation in user3 by IOC: " + user5.getNation().getName());
复制代码
输出:
在实际项目中,通常对于Dao、Service都须要单例,不会建立过多的bean,那么不少时候这些bean并不存在name、type的冲突,这时候是否是能够根据特定的规则,来简化bean的装配。
Spring IOC容器还提供了自动装配
,能够根据bean的type、name进行自动装配,而不须要显示地声明bean间依赖关系。
<!-- 自动装配bean,这里是byType方式。自动装配时会将知足条件的bean注入到构造器和setter方法中 -->
<!-- 自动装配缺点:(1)不适用构造器重写的状况;(2)不能装配基本类型和字符串 -->
<bean id="user4" class="com.example.springdemo.xml.model.User" autowire="byType">
<constructor-arg name="age" type="java.lang.Integer" value="28"></constructor-arg>
</bean>
复制代码
// 自动装配bean
User user6 = (User) factory.getBean("user4");
System.out.println("自动装配:" + user6.getAge());
System.out.println("自动装配:" + user6.getNation().getName());
复制代码
输出:
Spring IOC容器读取依赖关系方式
有:
xml配置文件
方式注解
方式相比较而言,xml配置文件
方式拥有集中配置
的优势,而注解
方式则拥有简化配置
的优势。
Spring IOC容器能够经过加载xml配置文件,来读取依赖关系。
因为xml配置文件拥有繁杂、不易配置的缺点,Spring IOC容器还同时支持注解
来简化配置。
使用注解来读取依赖关系的步骤以下:
在bean类上使用注解@Component声明bean,使用注解@Autowired来声明依赖关系:
@Component
@Data
public class User {
private Integer age;
@Autowired
private Nation nation;
}
复制代码
建立ApplicationContext类型的IOC容器(BeanFactory实测不支持),并设置要扫描注解的包路径。
// 能够传入要扫描的包路径动态数组,也能够传入Class动态数组
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.example.springdemo.annation");
User user1 = (User) applicationContext.getBean("user");
System.out.println(user1.getNation().getName());
复制代码
Spring IOC容器其实还支持xml配置文件和注解混用的方式来读取bean间依赖关系。
@Autowired
private Nation nation;
复制代码
使用构造器方式注入age属性,但没有注入nation字段
<!-- 注解与xml混用方式,这里并无注入nation,也没有使用自动装配 -->
<bean id="user5" class="com.example.springdemo.xml.model.User">
<constructor-arg name="age" type="java.lang.Integer" value="30"></constructor-arg>
</bean>
复制代码
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
// xml与注解混用,经过@Autowired注入nation bean(实际测试BeanFactory不支持注解,须要使用ApplicationContext)
User user7 = (User) applicationContext.getBean("user5");
System.out.println(user7.getAge());
System.out.println(user7.getNation().getName());
复制代码
从输出能够发现age和nation属性都被注入了。
在3.3.2中介绍了注解的简单使用,因为注解已经成为如今主流的使用方式,接下来详细介绍些经常使用注解知识。
@Bean
、@Component
等注解用于将对象注册为bean,交由Spring容器管理。
@Component
注解在类
上使用,将类的定义与bean的声明绑定,@Bean则将二者分离。通常@Component更适合声明service、dao等单例对象,而@Bean更适合用于在不一样场景下建立不一样对象的状况。@Bean
注解注册同一类的多个bean,该注解使用在方法上。@Componet
、@Configuration
注解声明的类里面使用声明里面使用;@Bean
// @Primary表示默认bean,当有多个相同类型的bean时,可使用@Primary来表示默认bean,其它地方注入依赖时就不须要都指明具体bean名。
@Primary
public Nation nation1() {
return new Nation("China");
}
@Bean
public Nation nation2() {
return new Nation("England");
}
// 使用name属性为bean命名
@Bean(name = "nation3")
public Nation nation() {
return new Nation("America");
}
复制代码
当有多个相同类型的bean时,可使用@Primary
来表示默认bean,其它地方注入默认bean时就不须要指明具体bean名。
可使用@Value
注解来注入外部属性,而@ConfigurationProperties
则可用来注入一整个对象的全部属性,或者说批量注入外部属性。
@Configuration
@PropertySource("classpath:application.properties")
public class JavaConfiguration {}
复制代码
@Bean
// 使用@Value来注入外部属性,须要使用@PropertySource引入资源文件。还可使用@ConfigurationProperties来批量注入外部属性
public Nation nation4(@Value("${nation4.name:#{null}}") String nation4name) {
return new Nation(nation4name);
}
复制代码
@Autowired
和@Required
用来注入依赖的bean。
@Autowired
注解能够在字段
上使用,也能够在setter方法
上使用,同时能够在构造器
上使用。@Autowired
默认采用了自动装配
,且优先根据类型
自动装配,同类型则优先注入primary bean,而后再依据bean名(与属性同名)进行注入。@Autowired
还能够结合@Qualifier
来指定要注入的bean名。例如:@Qualifier("nation2")。@Required
注解也能够用来注入依赖,但已经被废弃。
Spring同时支持JSR-250的注解,一样可使用@Resource
来注入依赖(字段或者setter方法上),并使用name字段来指定bean名。例如:@Resource(name = "nation2")。
Spring3.0还支持JSR-330注解,可使用@Inject
来注入依赖。
@Configuration
注解代表该类是一个Java配置类,用于声明bean,在该类中可使用@Bean注解来声明bean。通常状况下会将同一功能或用途的bean在一个配置累中声明。
Spring IOC中Bean有多种不一样做用域,主要有:
接下来主要介绍下singleton和prototype做用域。
singleton
模式(单例模式)是默认模式,该模式的bean有如下特色:
(1)注册bean 注册一个bean名为nation1的单例bean。
@Bean
public Nation nation1() {
return new Nation("China");
}
复制代码
(2)从容器获取bean 两次从容器中获取同一个bean,是同一对象地址相同。
// 建立的nation1和nation2对象是相同bean
Nation nation1 = (Nation) applicationContext.getBean("nation1");
Nation nation2 = (Nation) applicationContext.getBean("nation1");
System.out.println("nation1 == nation2: " + (nation1 == nation2));
复制代码
输出:
prototype
模式(原型模式、多例模式)的bean有如下特色:
(1)注册bean 注册一个名为nation7的多例bean。
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Nation nation7() {
return new Nation();
}
复制代码
(2)从容器获取bean 两次从容器获取同一个bean,是不一样的对象,地址不一样。
// nation3和nation4是不一样bean。
// 此处须要注意,若是bean类上使用了lombok注解,不要被输出所迷惑,由于lombok重写了toString(),使得二者看起来像是一个对象,实际应该比较对象地址
Nation nation3 = (Nation) applicationContext.getBean("nation7");
Nation nation4 = (Nation) applicationContext.getBean("nation7");
System.out.println("nation3 == nation4: " + (nation3 == nation4));
System.out.println("nation3:" + nation3);
System.out.println("nation4:" + nation4);
复制代码
输出:
未完待续。。。。。
Spring IOC能够说是Spring全家桶的基石,AOP等都依赖IOC的实现,要理解学习Spring应该先掌握Spring IOC的知识。
本文先是简单介绍了Spring IOC的思想和一些基本概念,而后针对Spring IOC容器实现依赖注入相关的知识点,而且分别给出了xml形式和注解形式依赖注入代码实现。
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html
https://juejin.im/post/5bf51d4c5188256d9832b0d3
https://segmentfault.com/a/1190000013700859
https://segmentfault.com/a/1190000014979704
https://juejin.im/post/5b399eb1e51d4553156c0525
复制代码