SpringBoot conditional注解和自定义conditional注解使用

conditional注解是Springboot starter的基石,自动装配的时候会根据条件确定是否需要注入这个类。

含义:基于条件的注解。

作用:根据是否满足某个特定条件来决定是否创建某个特定的Bean。

意义:Springboot实现自动配置的关键基础能力。

@Conditional相关注解

@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 {
}

测试文件

扫描二维码关注公众号,回复: 9541990 查看本文章
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;
	}

发布了1602 篇原创文章 · 获赞 1208 · 访问量 1238万+

猜你喜欢

转载自blog.csdn.net/21aspnet/article/details/104345464