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 = "&" ; 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 ; <T> ObjectProvider<T> getBeanProvider (Class<T> requiredType) ; <T> ObjectProvider<T> getBeanProvider (ResolvableType requiredType) ; boolean containsBean (String name) ; boolean isSingleton (String name) throws NoSuchBeanDefinitionException ; boolean isPrototype (String name) throws NoSuchBeanDefinitionException ; boolean isTypeMatch (String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException ; boolean isTypeMatch (String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException ; @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException; 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 { @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) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class .getName ()) ; if (attrs != null ) { Iterator var4 = ((List)attrs.get("value" )).iterator(); Object value; do { if (!var4.hasNext()) { return false ; } value = var4.next(); } while (!context.getEnvironment().acceptsProfiles(Profiles.of((String[])((String[])value)))); return true ; } else { return true ; } } }
Spring 还提供了几个方便使用的的条件装配注解:
1 2 3 4 @ConditionalOnBean @ConditionalOnMissingBean @ConditionalOnClass @ConditionalOnMissingClass
循环依赖 循环依赖就是指多个 bean 之间循环注入,例如:A 依赖 B, B 依赖 A。发生循环依赖时代码设计问题较大,最好能重构代码。
如果是构造器参数导致的循环依赖,有两种解决方法:1.可以通过延迟加载解决,在其中一个构造器参数上使用 @Lazy
注解即可。2.将构造器依赖注入改为 setter 注入。
以上解决方法是针对单例模式的 Bean ,因为单例模式下,所有正在创建还未设置属性的 Bean 都放入了缓存中,通过这个缓存解决了循环依赖的问题,而在原型模式下,Spring 不进行缓存,所以循环依赖问题依旧无法解决。