书成

再这样堕落下去就给我去死啊你这混蛋!!!

0%

IoC容器

Spring Boot IoC

IoC 是一种通过描述生成或者获取对象的技术,IoC 容器需要具备两个功能:

  • 通过描述管理 Bean,包括发布和获取 Bean
  • 通过描述完成 Bean 之间的依赖关系

IoC 容器简介

Spring IoC 容器是一个管理 Bean 的容器,容器的顶级接口是 BeanFactory。在 BeanFactory 中主要定义了多个 getBean 方法,可以通过名称或类型从 IoC 容器中获取 Bean,这对于依赖注入是十分重要的。

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
public interface BeanFactory {
// 前缀
String FACTORY_BEAN_PREFIX = "&";
// 多个获得bean的方法
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
// 获得 bean 的对象工厂
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
// 判断是否包含 bean
boolean containsBean(String name);
// 判断 bean 是否是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// 判断 bean 是否为原型
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
// 判断指定名称的bean是否和指定类型匹配
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
// 获得 bean 的类型
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
// 获得 bean 的别名
String[] getAliases(String name);
}

BeanFactory 只是定义了 IoC 容器最基础的功能,Spring 中提供了功能更丰富的容器接口 ApplicationContext。它的继承图如下:

在继承图中 ResourcePatternResolver 是资源模式解析接口;MessageSource 是消息国际化接口;EnvironmentCapable 是环境可配置接口;ApplicationEventPublisher 是应用事件发布接口;HierarchicalBeanFactory定义了 BeanFactory 之间的分层功能,能过得父级 BeanFactory;ListableBeanFactory能列出当前容器中的所有 Bean。

在 Spring Boot 中,主要是通过注解装配 Bean 到 IoC 容器中,因此主要使用的是基于注解的 IoC 容器:AnnotationConfigApplicationContext,这个容器也是继承自 ApplicationContext

装配自定义 Bean

在 Spring 中装配自定义的 Bean,有三种方式:自动扫描装配;JavaConfig 方式;xml方式,在 Spring Boot 中,很少使用 xml 方式。

JavaConfig 方式

通过 JavaConfig 的方式装配 Bean,主要依赖两个注解:@Configuration 以及 @Bean@Configuration 表示这是一个 Java 配置文件,Spring IoC 容器会根据它生成需要装配的 Bean,@Bean 表示将方法返回的对象装配到 IoC 容器中,其中的属性 name 可以定义 Bean 的名称,initMethod 定义初始化方法,destroyMethod 定义销毁方法。

新建一个 Spring Boot 项目,在其中新建一个类 People 如下:

1
2
3
4
5
6
public class People {
int id;
String name;
int age;
// 省略其它代码...
}

新建一个配置类:

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class PeopleConfig {

@Bean("people")
public People getPeople(){
People people = new People();
people.setAge(100);
people.setId(1);
people.setName("configPeople");
return people;
}
}

之后在单元测试中测试 Bean 是否已经被装配:

1
2
3
4
5
6
7
8
9
@SpringBootTest
class IocdemoApplicationTests {
@Autowired
People people;
@Test
void contextLoads() {
System.out.println(people);
}
}

自动扫描装配

自动扫描装配主要需要的注解是 @Component 以及 @ComponentScan@Component 注解标明哪个类被扫描进 Spring IoC 容器,@ComponentScan 注解标明采用什么策略去扫描装配 Bean。

修改 People 类如下:

1
2
3
4
5
6
7
8
9
10
@Component
public class People {
@Value("2")
int id;
@Value("componentPeople")
String name;
@Value("66")
int age;
// 省略其它代码...
}

将 PeopleConfig 中的 @Bean 注解去掉,之后重新运行单元测试查看 Bean 是否已经被装配,因为项目启动文件的注解 @SpringBootApplication 中已经包含了 @ComponentScan,所以不需要再加上这个注解。

依赖注入

在之前单元测试代码中的注解 @Autowired 注解是一个依赖注入的注解,Spring Boot 还支持另一个注解 @Resource,两个注解的区别如下:

  • @Resource 是属于 J2EE 的,而 @Autowired 是 Spring 提供的。
  • @Autowired 首先按照类型注入,如果对应类型的 Bean 不唯一,再根据属性名称与 Bean 的名称匹配,且默认不允许 Bean 不存在,可以使用 @Autowired(required = false) 允许 Bean 不存在。
  • @Resource 默认按照名称注入,也支持按照类型注入。

当某一个类型的 Bean 有多个时,有两个注解可以消除 @Autowired 的歧义,它们分别是 @Primary@Qualifier

在定义 Bean 时加入 @Primary 注解,IoC 容器发现有多个同类型 Bean 时,会优先使用该注解的 Bean 注入。@Qualifier 注解与 @Autowired 注解结合使用,可以通过类型与名称寻找 Bean 注入。

另外,如果一个类的属性使用了 @Autowired 依赖注入,这个类也要使用 @Component 交给 IoC 容器管理依赖注入才会生效

Bean 生命周期

Spring 初始化 Bean

Spring 初始化 Bean 的流程如下所示:

Spring 默认在发布 Bean 定义后立刻完成实例化和依赖注入,如果需要延迟初始化,可以在注入时添加注解 @Lazy 或者在 @ComponentScan 注解中全局配置 lazyInit 为 true

Bean作用域

Bean 的作用域可以在配置 Bean 时使用注解 @Scope 定义作用域。

作用域类型 使用范围 作用域描述
singleton 所有 Spring 应用 默认值
prototype 所有 Spring 应用 每当从 IoC 容器中取出 Bean,创建一个新的 Bean
session 所有 Spring Web 应用 HTTP 会话
application 所有 Spring Web 应用 Web 工程生命周期
request 所有 Spring Web 应用 Web 工程单次请求
globalSession 所有 Spring Web 应用 全局 HTTP Session 中,不常用

Bean 生命周期

Spring 完成依赖注入后,还会按照如下流程完成 Bean 的生命周期:

也就是说,在依赖注入之后,Bean的生命周期还包含以下流程

1.如果当前 Bean 实现了 BeanNameAware 接口,就调用它的 setBeanName方法。
2.如果当前 Bean 实现了 BeanFactoryAware 接口,就调用它的 setBeanFactory方法。
3.如果当前 Bean 实现了 ApplicationContextAware 接口,并且当前 IoC 容器是 ApplicationContext 的子类,就调用它的 setApplicationContext方法。
4.如果当前 IoC 容器中有实现了 BeanPostProcessor 接口的 Bean (不一定是当前 Bean),就调用它的 postProcessBeforeInitialization 方法,对全体 Bean 生效。
5.如果当前 Bean 有自定义的初始化方法,就调用它。(@PostConstruct 标注的方法或者 @Bean 注解中 initMethod 指定的方法)
6.如果当前 Bean 实现了 InitializingBean 接口,就调用它的 afterPropertiesSet方法。
7.如果当前 IoC 容器中有实现了 BeanPostProcessor 接口的 Bean (不一定是当前 Bean),就调用它的 postProcessAfterInitialization 方法,对全体 Bean 生效。
8.Bean 的正常使用。
9.当 Bean 需要销毁时,如果有自定义的销毁方法,就调用它。(@PreDestroy 标注的方法或者 @Bean 注解中 destroyMethod 指定的方法)
10.当 Bean 需要销毁时,如果实现了 DisposableBean 接口,就调用它的 destroy 方法。

下面通过一个例子来测试 Bean 的生命周期,对之前的 People 类加入生命周期的定义:

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
41
42
43
44
45
46
@Component
public class People implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
@Value("2")
int id;
@Value("componentPeople")
String name;
@Value("66")
int age;

@Override
public void setBeanName(String name) {
System.out.println(">>>>>>>>>>调用setBeanName");
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println(">>>>>>>>>>调用setBeanFactory");
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println(">>>>>>>>>>调用setApplicationContext");
}

@PostConstruct
public void myInit(){
System.out.println(">>>>>>>>>>调用自定义初始化方法");
}

@Override
public void afterPropertiesSet() throws Exception {
System.out.println(">>>>>>>>>>调用afterPropertiesSet");
}

@PreDestroy
public void myDestroy(){
System.out.println(">>>>>>>>>>调用自定义销毁方法");
}

@Override
public void destroy() throws Exception {
System.out.println(">>>>>>>>>>调用destroy方法");
}

// 省略其它代码...
}

新建一个 MyBeanPostProcessor 类实现 BeanPostProcessor 接口测试它的全局性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("调用BeanPostProcessor的预初始化方法");
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("调用BeanPostProcessor的后初始化方法");
return bean;
}
}

运行单元测试后,从控制台输出就可以观察 Bean 的生命周期。

条件装配

有时候一些客观因素会造成一些 Bean 无法初始化,为了处理这样的行为,Spring 提供了 @Conditional 实现条件装配,这个注解需要配合 Condition 接口来实现条件装配的功能。

新建一个 Book 类,配置为只有配置文件中有它的信息时才装配它:

1
2
3
4
5
6
7
8
9
@Component
@Conditional(BookConditional.class)
@ConfigurationProperties(prefix = "book")
public class Book {

String name;
int id;
// 省略其它代码...
}

新建对应的实现了 Condition 接口的 BookConditional 类:

1
2
3
4
5
6
7
8
9
10
11
public class BookConditional implements Condition {

// 方法返回 true 则装配 Bean,否则不装配
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
// 获得环境配置
Environment environment = conditionContext.getEnvironment();
// 判断环境配置中是否有相应的配置
return environment.containsProperty("book.name") && environment.containsProperty("book.id");
}
}

之后可以通过单元测试分别观察在 application.properties 中不配置与配置相关信息的结果。

Profile

@Profile 注解能够指定 Bean 在哪一个开发环境下装配,它的本质也是通过条件装配实现的。源码如下:

1
2
3
4
5
6
7
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})
public @interface Profile {
String[] value();
}

ProfileCondition 源码如下:

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
class ProfileCondition implements Condition {
ProfileCondition() {
}

public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获得所有指定的 profile
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
Iterator var4 = ((List)attrs.get("value")).iterator();

Object value;
do {
// 没有匹配的 profile,返回 false
if (!var4.hasNext()) {
return false;
}

value = var4.next();
// 如果当前环境是指定的某一个 profile 则退出循环返回 true
} while(!context.getEnvironment().acceptsProfiles(Profiles.of((String[])((String[])value))));

return true;
} else {
// 指定的profile为空则在任何情况下都能装配
return true;
}
}
}

Spring 还提供了几个方便使用的的条件装配注解:

1
2
3
4
@ConditionalOnBean         // 当给定的在bean存在时,则实例化当前Bean
@ConditionalOnMissingBean // 当给定的在bean不存在时,则实例化当前Bean
@ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
@ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean

循环依赖

循环依赖就是指多个 bean 之间循环注入,例如:A 依赖 B, B 依赖 A。发生循环依赖时代码设计问题较大,最好能重构代码。

如果是构造器参数导致的循环依赖,有两种解决方法:1.可以通过延迟加载解决,在其中一个构造器参数上使用 @Lazy 注解即可。2.将构造器依赖注入改为 setter 注入。

以上解决方法是针对单例模式的 Bean,因为单例模式下,所有正在创建还未设置属性的 Bean 都放入了缓存中,通过这个缓存解决了循环依赖的问题,而在原型模式下,Spring 不进行缓存,所以循环依赖问题依旧无法解决。