根据环境变量装配Bean 我们将了怎么使用@Profile来装配对应的Bean,现在有时候,我们需要更细的条件来判断是否要装配某个Bean,比如在应用的类路径下有特定的库,某个环境变量是我们要的值等等,该怎么做呢?
@Conditional
Spring提供给了我们一个注解@Conditional来做到这个事情,我们举个例子,例子的逻辑是如果环境变量里有magic属性,我们才会创建MagicBean。
创建一个Bean的Class:
public class MagicBean {
}
创建一个Conditional类,用来控制这个Bean的创建:
public class MagicExistsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("magic");
}
}
创建ConfigJava:
@Configuration
public class MagicConfig {
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {
return new MagicBean();
}
}
写一个测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=MagicConfig.class)
public class MagicExistsTest {
@Autowired
private ApplicationContext context;
@Test
public void shouldNotBeNull() {
assertTrue(context.containsBean("magicBean"));
}
}
如果我们环境变量里有magic这个值,测试就会通过,如果没有,测试代码会抛出java.lang.AssertionError。
到底是怎么做到这个呢?通过上面的例子,我们看到@Conditional注解有个Class参数,这个参数要需要一个Condition接口类,这个接口有一个需要实现的方法:
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
ConditionContext
Condition接口的参数里需要的第一个参数是ConditionContext接口,它有以下几个方法:
/**
* Return the {@link BeanDefinitionRegistry} that will hold the bean definition
* should the condition match or {@code null} if the registry is not available.
* @return the registry or {@code null}
*/
BeanDefinitionRegistry getRegistry();
/**
* Return the {@link ConfigurableListableBeanFactory} that will hold the bean
* definition should the condition match or {@code null} if the bean factory
* is not available.
* @return the bean factory or {@code null}
*/
ConfigurableListableBeanFactory getBeanFactory();
/**
* Return the {@link Environment} for which the current application is running
* or {@code null} if no environment is available.
* @return the environment or {@code null}
*/
Environment getEnvironment();
/**
* Return the {@link ResourceLoader} currently being used or {@code null}
* if the resource loader cannot be obtained.
* @return a resource loader or {@code null}
*/
ResourceLoader getResourceLoader();
/**
* Return the {@link ClassLoader} that should be used to load additional
* classes or {@code null} if the default classloader should be used.
* @return the class loader or {@code null}
*/
ClassLoader getClassLoader();
通过它我们可以做到以下几点:
1. 借助getRegistry()方法返回的BeanDefinitionRegistry来检测Bean定义。
2. 借助getBeanFactory()方法返回的ConfigurableListableBeanFactory获取到某个Bean,来检查Bean是否存在,甚至可以检查Bean的属性等。
3. 借助getEnvironment()方法返回的Environment来检查环境变量以及它的值。
4. 借助方法getResourceLoader()返回的ResourceLoader检查当前Spring加载的资源。
5. 借助方法getClassLoader返回的ClassLoader加载并检查类是否存在。
AnnotatedTypeMetadata
借助接口AnnotatedTypeMetadata能让我们检查带有@Bean注解的方法上还有其他什么注解。
/**
* Determine whether the underlying type has an annotation or
* meta-annotation of the given type defined.
* <p>If this method returns {@code true}, then
* {@link #getAnnotationAttributes} will return a non-null Map.
* @param annotationType the annotation type to look for
* @return whether a matching annotation is defined
*/
boolean isAnnotated(String annotationType);
/**
* Retrieve the attributes of the annotation of the given type,
* if any (i.e. if defined on the underlying class, as direct
* annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @return a Map of attributes, with the attribute name as key (e.g. "value")
* and the defined attribute value as Map value. This return value will be
* {@code null} if no matching annotation is defined.
*/
Map<String, Object> getAnnotationAttributes(String annotationType);
/**
* Retrieve the attributes of the annotation of the given type,
* if any (i.e. if defined on the underlying class, as direct
* annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @param classValuesAsString whether to convert class references to String
* class names for exposure as values in the returned Map, instead of Class
* references which might potentially have to be loaded first
* @return a Map of attributes, with the attribute name as key (e.g. "value")
* and the defined attribute value as Map value. This return value will be
* {@code null} if no matching annotation is defined.
*/
Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);
/**
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
* defined on the underlying type ({@link AnnotationMetadata class} or
* {@link MethodMetadata method}), as direct annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @return a MultiMap of attributes, with the attribute name as key (e.g. "value")
* and a list of the defined attribute values as Map value. This return value will
* be {@code null} if no matching annotation is defined.
* @see #getAllAnnotationAttributes(String, boolean)
*/
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);
/**
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
* defined on the underlying type ({@link AnnotationMetadata class} or
* {@link MethodMetadata method}), as direct annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @param classValuesAsString whether to convert class references to String
* @return a MultiMap of attributes, with the attribute name as key (e.g. "value")
* and a list of the defined attribute values as Map value. This return value will
* be {@code null} if no matching annotation is defined.
* @see #getAllAnnotationAttributes(String)
*/
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);
通过isAnnotated()方法我们可以判断注解的方法是否还有其他注解,并且可以借助其他方法检查对应注解的是否有某些属性。
回到Profile
回到Profile,我们打开Profile的定义:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
Profile居然就是基于Conditional做的一个注解,并且引用了ProfileCondition作为@Conditional的判断类,我们打开这个类:
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
}
matches()方法的内部逻辑就是通过AnnotatedTypeMetadata获取到@Profile注解的value值,通过acceptsProfiles方法检测Profile的Value值是否被激活。