conditional注解是Springboot starter的基石,自动装配的时候会根据条件确定是否需要注入这个类。
含义:基于条件的注解。
作用:根据是否满足某个特定条件来决定是否创建某个特定的Bean。
意义:Springboot实现自动配置的关键基础能力。
@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean
@ConditionalOnClass:某个class位于类路径上才会实例化一个Bean
@ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean
@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean
@ConditionalOnMissingClass:某个class不位于类路径上才会实例化一个Bean
@ConditionalOnNotWebApplication:不是web应用
配置文件有这个配置
spring.data.mongodb.uri=mongodb://paopaoedu:paopaoedu@localhost:27017/paopaoedu
判断有这个配置才注入这个类
package com.paopaoedu.springboot.condition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
@Component
@ConditionalOnProperty("spring.data.mongodb.uri")
public class ConditionalTest {
}
测试文件
package com.paopaoedu.springboot;
import com.paopaoedu.springboot.condition.ConditionalTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SpringBootLearningApplication.class})
//不用类似new ClassPathXmlApplicationContext()的方式,从已有的spring上下文取得已实例化的bean。通过ApplicationContextAware接口进行实现。
public class SpringBootLearningApplicationTests implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
applicationContext = context;
}
@Test
public void testA() {
System.out.println("testA>>>"+applicationContext.getBean(ConditionalTest.class));
}
}
关于ApplicationContextAware使用理解参考https://www.jianshu.com/p/4c0723615a52
可以看出这个ConditionalTest被注入了:
如果你去掉这个配置测试用例就会报错找不到这个bean:
自定义conditional注解实现
自定义注解引入condition接口实现类
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(MyCondition.class)
public @interface MyConditionAnnotation {
String[] value() default {};
}
实现一个自定义注解
实现Condition接口重写matches方法,符合条件返回true
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String[] properties = (String[])metadata.getAnnotationAttributes("com.paopaoedu.springboot.condition.MyConditionAnnotation").get("value");
for (String property : properties) {
if (StringUtils.isEmpty(context.getEnvironment().getProperty(property))) {
return false;
}
}
return true;
}
}
引入conditional注解
@Component
@MyConditionAnnotation({"spring.redis.master.host", "spring.redis.follow.host"})
public class ConditionalTest2 {
}
配置文件
# Redis服务器地址
spring.redis.master.host=r-xxx1.redis.rds.aliyuncs.com
# Redis服务器连接端口
spring.redis.master.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.master.password=
# Redis服务器地址
spring.redis.follow.host=r-xxx2.redis.rds.aliyuncs.com
# Redis服务器连接端口
spring.redis.follow.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.follow.password=
修改测试用例
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SpringBootLearningApplication.class})
//不用类似new ClassPathXmlApplicationContext()的方式,从已有的spring上下文取得已实例化的bean。通过ApplicationContextAware接口进行实现。
public class SpringBootLearningApplicationTests implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
applicationContext = context;
}
@Test
public void testA() {
System.out.println("testA>>>"+applicationContext.getBean(ConditionalTest.class));
System.out.println("testA>>>"+applicationContext.getBean(ConditionalTest2.class));
}
}
输出
重要的调试断点ClassPathScanningCandidateComponentProvider.scanCandidateComponents()
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}