Spring framework: IoC容器
IoC容器
简介
IoC(Inversion of Control,控制反转)容器的原则是使原本有相互依赖的各个部分解耦,实现松耦合地运作 在传统的组合中,上层的模块在内部定义某些对象的类型,控制权在上层的模块中,如果需要修改内部的业务逻辑则会违反开闭原则 而IoC的思想则是使上层的模块将控制权“反转”给一个第三方,例如开发者自己或者一个IoC容器,由外部来控制并决定向上层模块提供什么样的实现类IoC容器的常见的实现方法是DI(Dependency Injection,依赖注入),即上层模块所依赖的其它对象仅通过构造方法或工厂方法或setter方法暴露注入入口,容器在创建对象时控制注入对象的实现类当我们用到
IoC容器时,说明一个类的实例往往是全局单例的,只要明白这个点,就能理解为什么会发生注入冲突 但同样可以实现非单例的实例化例子:
1
2
3
4
5
6
7
8
9
10
11
12class Person {
private Eye e = new BlackEye(); // 在后续改动时违反开闭原则
}
class Person {
private Eye e;
public Person(Eye e) {
this.e = e;
}
}
Person = new Person(new BlackEye()); // 在外部注入而在
Spring框架中,这个“外部注入”不需要由开发者手动地注入,而是通过声明式注解由Spring内置的IoC容器自动注入org.springframework.beans与org.springframework.context是IoC容器的核心依赖:
1
2
3
4
5<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>7.0.0-M9</version>
</dependency>
元数据配置
上下文
Spring的IoC容器通过解析元数据来管理Bean元数据通常包括以下内容:
Bean的类型Bean的idBean的依赖注入Bean的作用域Bean的生命周期行为(以回调形式存在)Bean的懒加载、协作者等其它元数据
ApplicationContext:应用上下文接口的实例表示一个IoC容器,它会自动创建、配置、组装所有的Bean- 继承
BeanFactory接口:该接口提供一系列和Bean有关的方法T getBean(String name, Class<T> clazz):获取它管理的类型为T、名字为name的Bean - 继承
MessageSource接口:该接口提供国际化处理信息的能力String getMessage(String msg, Object[] args, Locale locale):根据locale以及可选的选项args解析msg
- 继承
创建一个
ApplicationContext实例: 创建ApplicationContext实例同时意味着许多配置,在不依靠外部框架(如Spring Boot和Tomcat)的情况下,ApplicationContext只能通过new创建Spring提供了两种构造ApplicationContext实例的方式,分别为基于XML的配置、基于Java的配置 基于XML的配置:实现类为ClassPathXmlApplicationContext,构造方法需要的参数是String...,即一系列xml配置文件的路径 基于Java的配置:实现类为AnnotationConfigApplicationContext,构造方法需要的参数是Class<?>...,即一系列由@Configuration注解的配置类基于
XML、基于注解、基于Java的关系- 三种方式都由
Spring控制实例的注入 XML是最早的Spring提供的配置方式,它是外部的、支持非侵入式地修改元数据配置Spring 2.0开始逐步地提供一系列注解来方便分散化、简洁化配置,提供了基于注解的配置,位于org.springframework.stereotype和org.springframework.beans.factory.annotations包中 在基于注解的配置里,开发者对实例的创建和注入的控制方式和XML形式类似,只是停留在声明Bean上,目的是简化配置代码书写并提供编译时的类型检查 例如stereotype包中的@Service、@Repository、@Component,它们的修饰表示声明这个类属于某种Bean,即配置是什么的元数据 例如beans.factory.annotations包中的@Autowired和@Qualifier,它们修饰表示指示如何创建Bean这种方式的配置需要在XML或Java中启用组件扫描来纳入管理Spring 3.0开始,提供org.springframework.context.annotations来替代整个XML形式的配置,并提供了新的ApplicationContext的实现类,例如@Configuration与@Bean尽管注入仍由Spring框架管理,但这种方式允许开发者十分灵活地决定如何创建一个实例,允许使用Java代码进行带有分支判断地创建实例- 第二种方式和另外两种方式并不是互斥的,第一种方式和第三种方式对应着两种不同的实现类,而第二种方式则是作为一种方便的声明式
API存在,适用于结构简单的Bean的声明 现代应用中常使用第二种与第三种方式混合的配置方案,但并不意味着XML配置完全无用,它的外部、非侵入性正是它的特点
- 三种方式都由
Bean的XML配置
<bean/>的id、name属性或<alias/>:它们都是可选项,默认由Spring生成一个唯一的idid是唯一标识,若自定义,则必须是唯一的,不允许使用特殊字符,通常使用驼峰命名法name是别名集合(字符串表示),不同别名用,或;隔开,允许重复、允许使用类似/的特殊字符- 若使用引用,则查找
id是否含有匹配项、然后查找name是否含有匹配项、然后查找类型是否匹配,若有二义性则报错 <alias name="" alias=""/>:在定义bean标签以外的地方定义它的别名
选择
bean的构造器:class属性:用于指定该bean所使用的构造器的来源,并不一定是class类型的- 若不提供任何构造方法,则自动根据提供的参数匹配合适的构造方法
- 使用静态工厂方法:指定
factory-method属性,会调用指定class属性这个类内部的factory-method静态方法 - 使用工厂类实例的工厂方法:不能指定
class属性,而是指定factory-bean属性,它是另一个类型为某个工厂类的bean primary布尔属性:标识该<bean/>对应的构造器为默认的创建方法
提供构造参数(依赖注入)来消除同名构造器的二义性:
- 基于构造器的注入:
<constructor-arg/>:作为<bean/>的子标签,表示传递给构造器的参数,为了消除二义性,它有若干属性,不必全部指定而只需要保证不存在歧义即可:name属性表示参数的名称index属性表示参数的索引(0-idx)type属性表示参数的类型value属性表示参数的字面值ref属性表示使用其它bean作为参数 - 基于
setter的注入:要求该bean对应的class含有指定属性的setter方法,则可使用<property/>传递用setter设置的参数 其属性与<constructor-arg/>一致 - 一般来说,
<constructor-arg/>会严格匹配构造器,因此会更安全(不会出现null)<property/>则是在构造后通过调用setter方法进行配置的方案,可能导致某些属性没被注意而为null前者若想达到可选参数的效果,也可使构造器参数为Optional<T>或使用@Nullable注解
- 基于构造器的注入:
启用组件扫描:
1
2
3<beans>
<context:component-scan base-package="包名" />
</beans>例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- primary标识首选 -->
<bean id="userBean" class="demo.beans.UserBean" primary="true">
<!-- 根据构造参数唯一确定构造器 -->
<constructor-arg index="0" type="int" value="10"/>
<constructor-arg index="1" name="otherBean" ref="otherBean"/>
<constructor-arg index="2" type="java.lang.String" value="str"/>
<!-- 后续的setter配置 -->
<property name="optionalArg" value="1"/>
</bean>
<!-- 不带任何参数表示选择默认构造方法, 但这是在primary之后的选择 -->
<bean id="userBean" class="demo.beans.UserBean">
</bean>
<!-- 在外部起别名 -->
<alias name="userBean" alias="uB"/>
Bean的Java配置
声明为具有某种功能的
bean(类级修饰):@Component(value=""):声明为一般的bean,value可选,表示该bean的名称,默认为类名的驼峰命名@Service:声明为服务类,@Component的一种@Repository:声明为持久化类,@Component的一种@Controller:声明为控制器类,@Component的一种- 需要用
@ComponentScan(basePackages="")启动扫描
依赖注入:
@Autowired(required=true):修饰方法或属性,表示自动注入,修饰方法时若修饰构造器或setter则对应构造器注入或setter注入、修饰一般方法则会使bean在初始化完成后调用该方法,(不推荐)修饰属性时表示直接注入 默认强制要求注入,可使required=false或使用Optional<T>或使用@Nullable表示可选 如果含有多个构造方法,基于注解的配置要求至少有一个@Autowired@Qualifier(value=""):修饰类或@Bean方法时给该bean起限定符,修饰属性或形参时表示指定使用value对应的bean注入该属性@Value(value=""):注入简单字面值,可以修饰形参@Primary:标识某个类时表示当某个接口或某个抽象类存在多个实现类时,选择该类作为首选的实现 标识某个@Bean方法时表示当存在多个返回同一类型的方法时首选该方法
基于
Java的配置:@Configuration:标识某个类为配置类bean,它也是一个@Component@Bean:标识某个方法为创建某个类型bean的方法,配合@Primary决定首选方法 其name属性表示这个bean的名字,它可以同@Qualifier的value属性一样被绑定然后用@Qualifier指定注入 因为一般name的命名有一定风格(通常是类名的驼峰命名),而如果需要用含特殊语义的命名,@Qualifier的value属性是一种选择
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41package demo.config;
public class BeansConfiguration {
// 默认用方法名作为@Bean的name
public User user() {
return new User();
}
public User user(int type) {
return new User(type);
}
}
package demo.service;
public class UserService {
private final UserRepository userRepo;
public UserService() {
}
public UserService(UserRepository userRepo, int type) {
this.userRepo = userRepo;
}
}
package demo;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(demo.config.BeansConfiguration);
}
}