本文承接上一节:Spring_总结_04_高级配置(一)之Profile
在上一节,我们了解到 Profile 为不同环境下使用不同的配置提供了支持,那么Profile到底是如何实现的呢?其实Profile正是通过条件注解来实现的。
条件注解的应用场景举例:
(1)希望一个或多个 bean 只有在应用的类路径下包含特定的库时才创建
(2)希望某个bean只有当另外某个特定bean也声明了之后才创建
(3)希望只有某个特定的环境变量设置之后,才会创建某个bean
上述场景能让我们联想到springboot的自动化配置、Profile以及配置文件等。
@Conditionnal 能根据特定条件来控制Bean的创建行为。
@Conditional注解可以用到带有@Bean注解的方法上,如果给定的条件计算结果为true,就会创建这个bean,否则,忽略这个bean。
可以用来进行一些自动化配置,如上述三个应用场景。
Condition接口如下,只有一个 matches 方法,用来判断是否满足条件。
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
Condition实现类 MagicExistsCondition
public class MagicExistsCondition implements Condition {
/**
* 1.判断是否满足条件
* @param context
* @param metadata
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
/**
* 检查环境中是否存在magic属性,若存在则满足条件
*/
return environment.containsProperty("magic");
}
}
如以下代码,在装配Bean的时候,使用@conditional注解,以便只有在满足条件地情况下才创建此bean
/**
* 条件化地创建Bean
* 若满足MagicExistsCondition的条件,则创建此Bean,否则忽略此bean.
* @return
*/
@Conditional(MagicExistsCondition.class)
@Bean
public MagicBean magicBean(){
return new MagicBean();
}
Condition接口如下,只有一个 matches 方法,用来判断是否满足条件。
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
在设置条件时,可用 ConditionContext 和 AnnotatedTypeMetadata 来进行条件地设置。
源码如下:
/**
* Context information for use by {@link Condition}s.
*
* @author Phillip Webb
* @author Juergen Hoeller
* @since 4.0
*/
public interface ConditionContext {
/**
* Return the {@link BeanDefinitionRegistry} that will hold the bean definition
* should the condition match.
* @throws IllegalStateException if no registry is available (which is unusual:
* only the case with a plain {@link ClassPathScanningCandidateComponentProvider})
*/
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 (or not downcastable to {@code ConfigurableListableBeanFactory}).
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
/**
* Return the {@link Environment} for which the current application is running.
*/
Environment getEnvironment();
/**
* Return the {@link ResourceLoader} currently being used.
*/
ResourceLoader getResourceLoader();
/**
* Return the {@link ClassLoader} that should be used to load additional classes
* (only {@code null} if even the system ClassLoader isn't accessible).
* @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
*/
@Nullable
ClassLoader getClassLoader();
}
作用如下:
1 | getRegistry | 利用返回的 BeanDefinitionRegistry 来检查bean定义 |
---|---|---|
2 | getBeanFactory | 利用返回的 ConfigurableListableBeanFactory来检查bean是否存在,甚至探查bean的属性 |
3 | getEnvironment | 利用返回的 Environment 检查环境变量是否存在以及它的值是什么 |
4 | getResourceLoader | 读取并探查返回的 ResourceLoader 所加载的资源 |
5 | getClassLoader | 借助返回的ClassLoader加载并检查类是否存在 |
源码如下:
/**
* Defines access to the annotations of a specific type ({@link AnnotationMetadata class}
* or {@link MethodMetadata method}), in a form that does not necessarily require the
* class-loading.
*
* @author Juergen Hoeller
* @author Mark Fisher
* @author Mark Pollack
* @author Chris Beams
* @author Phillip Webb
* @author Sam Brannen
* @since 4.0
* @see AnnotationMetadata
* @see MethodMetadata
*/
public interface AnnotatedTypeMetadata {
/**
* Determine whether the underlying element 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 annotationName the fully qualified class name of the annotation
* type to look for
* @return whether a matching annotation is defined
*/
boolean isAnnotated(String annotationName);
/**
* Retrieve the attributes of the annotation of the given type, if any (i.e. if
* defined on the underlying element, as direct annotation or meta-annotation),
* also taking attribute overrides on composed annotations into account.
* @param annotationName the fully qualified class name of 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.
*/
@Nullable
Map<String, Object> getAnnotationAttributes(String annotationName);
/**
* Retrieve the attributes of the annotation of the given type, if any (i.e. if
* defined on the underlying element, as direct annotation or meta-annotation),
* also taking attribute overrides on composed annotations into account.
* @param annotationName the fully qualified class name of 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.
*/
@Nullable
Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
/**
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
* defined on the underlying element, as direct annotation or meta-annotation).
* Note that this variant does <i>not</i> take attribute overrides into account.
* @param annotationName the fully qualified class name of 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)
*/
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
/**
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
* defined on the underlying element, as direct annotation or meta-annotation).
* Note that this variant does <i>not</i> take attribute overrides into account.
* @param annotationName the fully qualified class name of 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)
*/
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}
通过 AnnotatedTypeMetadata 可以检查 @Bean 注解的方法上还有什么其他注解。
@Profile注解是基于@Conditional 和 condition 实现的
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})
public @interface Profile {
String[] value();
}
可以看到,@Profile 注解本身也使用了 @Conditional 注解,而Condition实现类为ProfileCondition
/**
* {@link Condition} that matches based on the value of a {@link Profile @Profile}
* annotation.
*
* @author Chris Beams
* @author Phillip Webb
* @author Juergen Hoeller
* @since 4.0
*/
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
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;
}
}
可以看到, 此类主要是
(1)通过 AnnotatedTypeMetadata 获取到了 @Profile 的所有属性。
(2)然后检查value属性值,该属性包含了bean的profile名称
(3)根据 通过 ConditionContext 得到的Environment来检查 该profile是否处于激活状态【借助 acceptsProfiles方法】。