[toc]
概述
众所周知,SpringBoot最腻害的地方就是容器,开发人员的日常工作就是编写bean,并由框架扫描存到容器里面,当程序跑起来的时候,各种bean协同工作完成了软件功能。
那么容器是什么呢?
从概念层面来讲,容器是一个池子;从物理层面来讲,容器是一个内存块。
SpringBoot中默认是以单例形式装载bean的,所以大多数
情况下,我们创建的bean对象在程序启动的时候都会被装载到org.springframework.beans.factory.support.DefaultSingletonBeanRegistry-singletonObjects
中,这是一个ConcurrentHashMap
一方面我们需要关注容器中的bean能够提供哪些功能,这是程序工作的细粒度单元,是提供软件功能的基石;另外一方面我们也需要关注bean的装配,处理好它们的依赖关系才能让它们协同工作,共同完成造物主(码农
)安排的任务。
本文总结了在SpringBoot中常用的bean装配方法:
- profile
- conditional
ConditionalOn
Profile
profile 顾名思义,就是环境相关的装配条件。常见的如静态资源的存储,开发环境我们期望存储到硬盘,生产环境可能会存到MinIO中,那么此时可以通过profile根据环境的不同装配不同的文件存储处理bean到容器中,消费者无需关心当前什么环境,直接从容器中获取文件存储处理bean并使用即可。
如下示例代码:
import com.ramble.springbootzgnetsdk.service.DiskResourceServiceImpl;
import com.ramble.springbootzgnetsdk.service.MinIoResourceServiceImpl;
import com.ramble.springbootzgnetsdk.service.ResourceService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
* Project springboot-zgnetsdk
* Package com.ramble.springbootzgnetsdk.config
* Class ResourceServiceConfig
* date 2024/1/26 10:49
* author cml
* Email liangchen_beijing@163.com
* Description
*/
@Configuration
public class ResourceServiceConfig {
@Profile("dev")
@Bean
public ResourceService initDiskResourceService() {
return new DiskResourceServiceImpl();
}
@Profile("prod")
@Bean
public ResourceService initMinIoResourceService() {
return new MinIoResourceServiceImpl();
}
}
当前所属环境通过配置文件中的 spring.profiles.active
配置项约束
- @Profile("dev"):当active的值为dev的时候,此注解注释的方法才会生效,结合@bean注解,方法的返回对象将被注入到容器中。
@Profile("!dev"):也可使用
!
来表示取反的操作,即不是dev的环境此注解注释的方法才生效Conditional
Conditional 位于
org.springframework.context.annotation
中,常常会结合Condition这个接口来完成条件装配,具体来说,Condition的match方法负责编写装配条件,返回true则表示允许装载,否则就不会装载。
假设我们有这样一个需求,程序需要和海康网络设备SDK做集成,那么我们可以在配置文件中通过一个配置项来做开关,hikvision.enable
,若此开关打开则装配海康网络设备SDK到容器中,方便其它开发人员使用,否则就不装配。
示例代码如下:
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Objects;
/**
* Project springboot-hcnetsdk
* Package com.ramble.springboothcnetsdk.condition
* Class HikvisionSdkInitCondition
*
* @author
* Email
* Description 海康sdk初始化条件装配
* @date 2024/1/10 13:19
*/
public class HikvisionSdkInitCondition implements Condition {
/**
* 装配规则,根据配置文件中hikvision.enable的值,为true或者1则装配
*
* @param context the condition context
* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return 返回true,允许初始化;否则不允许
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String property = context.getEnvironment().getProperty("hikvision.enable");
if (Objects.nonNull(property)) {
return property.contains("true") || property.contains("1");
} else {
return false;
}
}
}
HikvisionSdkInitCondition ,首先定义一个条件类,此类继承Condition,通过重写matches方法来处理装配条件。
- context:通过context对象的getEnvironment获取配置文件中的hikvision.enable
配置项- 若hikvision.enable值为true或者1,表示允许初始化,即允许装配到容器中
import com.ramble.springboothcnetsdk.condition.HikvisionSdkInitCondition;
import com.ramble.springboothcnetsdk.support.HikvisionSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* Project springboot-hcnetsdk
* Package com.ramble.springboothcnetsdk.config
* Class SdkInitConfig
*
* @author
* Email liangchen_beijing@163.com
* Description
* @date 2024/1/10 13:27
*/
@Configuration
public class SdkInitConfig {
/**
* 初始化海康sdk。
* 若满足Conditional注解,则向容器中注入 HikvisionSupport
* @return
*/
@Conditional(HikvisionSdkInitCondition.class)
@Bean
HikvisionSupport initHikvisionSdk() {
return new HikvisionSupport();
}
}
SdkInitConfig,定义一个sdk初始化配置类,通过此类将sdk装入容器中。
- @Configuration:添加此注解,让容器可以扫描到此配置类
- @Conditional(HikvisionSdkInitCondition.class):Conditional注解需要一个装配条件,当条件允许的时候就执行此方法,而条件具体逻辑已经在HikvisionSdkInitCondition中编写了
- @Bean:将方法返回值注入容器
@Autowired(required = false)
private HikvisionSupport hikvisionSupport;
在消费的地方注入的时候,必须添加 required = false
,否则编译无法通过。
ConditionalOn
ConditionalOn是一个总称,其中包含了很多具体的注解,常用的如下:
- @ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
- @ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
- @ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
- @ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
- @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
- @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
- @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
- @ConditionalOnExpression:基于SpEL表达式的条件判断。
- @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
- @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
- @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
- @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。
ConditionalOnProperty
ConditionalOnProperty位于org.springframework.boot.autoconfigure.condition
中,表示当配置文件中存在某配置项,并且该项值为具体某值的时候才装配bean。
还是以程序需要和第三方网络设备SDK做集成的需求举例说明。
示例代码如下:
import com.ramble.springbootzgnetsdk.support.ZgSupport;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
* Project springboot-zgnetsdk
* Package com.ramble.springbootzgnetsdk.config
* Class SdkInitConfig
* date 2024/1/24 10:17
* author
* Email liangchen_beijing@163.com
* Description
*/
@Configuration
@ConditionalOnProperty(value = "sdk.enable", havingValue = "true")
public class SdkInitConfig {
@Bean
@ConditionalOnMissingBean
ZgSupport initZgSdk() {
return new ZgSupport();
}
}
- @Configuration:添加此注解,让容器可以扫描到此配置类
- @ConditionalOnProperty(value = "sdk.enable", havingValue = "true"):当配置文件中存在
sdk.enable
配置项,并且配置项值为true
的时候,才会执行此配置类 - @Bean:将方法返回值注入容器
- @ConditionalOnMissingBean:确保此bean不会重复注入