配置文件的混用
- 使用
@ImportResource
在Java Config 类中导入XML配置文件 - 在XML中引入Java Config
@Import 注解
- 使用@Import来导入整合Java Config
@Configuration
public class Config {
@Bean
public OneBean oneBean() {
return new OneBean();
}
}
@Configuration
@Import(Config.class)
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
OneBean oneBean = context.getBean("oneBean", OneBean.class);
System.out.println(oneBean);
}
}
- 根据ImportSelector接口selectImports方法的返回值来导入相关配置类
//自定义的注解,用来自动配置OneBean对象
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(OneBeanImportSelector.class)
public @interface EnableOneBean {
boolean value() default true;
}
//OneBean对象的Java Config
@Configuration
public class Config {
@Bean
public OneBean oneBean() {
return new OneBean();
}
}
//主配置文件,注意,主配置文件中并没有导入OneBean对象的Java Config
@EnableOneBean
@Configuration
public class Demo {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
OneBean oneBean = context.getBean("oneBean", OneBean.class);
System.out.println(oneBean);
}
}
//是否需要配置OneBean对象的逻辑
public class OneBeanImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//AnnotationMetadata对象可以获取注解上的信息,可以根据注解上的信息来判断是否需动态注入bean
Map attributes = importingClassMetadata.getAnnotationAttributes(EnableOneBean.class.getName());
if ((boolean) attributes.get("value")) {
return new String[]{Config.class.getName()};
}
return null;
}
}
- 使用ImportBeanDefinitionRegistrar接口动态注册bean
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(OneBeanImportBeanDefinitionRegistrar.class)
public @interface EnableOneBean {
boolean value() default true;
}
public class OneBeanImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//AnnotationMetadata 对象可以获取 注解上的信息,可以根据注解上的信息来判断是否需动态注入bean
Map attributes = importingClassMetadata.getAnnotationAttributes(EnableOneBean.class.getName());
if ((boolean) attributes.get("value")) {
//BeanDefinitionRegistry 对象可以用来 动态的往Spring 容器中添加Bean
//使用 BeanDefinitionBuilder 来构建 BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(OneBean.class);
builder.setScope(BeanDefinition.SCOPE_SINGLETON);
// builder.addPropertyValue("name", "xxx");
registry.registerBeanDefinition("oneBean", builder.getBeanDefinition());
}
}
}
@Configuration
@EnableOneBean
public class Config {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
OneBean oneBean = context.getBean("oneBean", OneBean.class);
}
}
占位符的支持
- XML
简单分析一下原理:
我们这里引入了命名空间,Spring会有相应的类根据去解析我们引入的命名空间,代码如下
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
····
}
}
也就是说,Spring解析到我们在XML中使用了property-placeholder
,就会自动为我们创建一个PropertyPlaceholderBeanDefinitionParser
对象。该类的代码如下
class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser {
private static final String SYSTEM_PROPERTIES_MODE_ATTRIBUTE = "system-properties-mode";
private static final String SYSTEM_PROPERTIES_MODE_DEFAULT = "ENVIRONMENT";
@Override
protected Class> getBeanClass(Element element) {
// As of Spring 3.1, the default value of system-properties-mode has changed from
// 'FALLBACK' to 'ENVIRONMENT'. This latter value indicates that resolution of
// placeholders against system properties is a function of the Environment and
// its current set of PropertySources.
if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
return PropertySourcesPlaceholderConfigurer.class;
}
// The user has explicitly specified a value for system-properties-mode: revert to
// PropertyPlaceholderConfigurer to ensure backward compatibility with 3.0 and earlier.
return PropertyPlaceholderConfigurer.class;
}
···
从代码的注释中,我们可以知道,在Spring3.1之后,Spring使用PropertySourcesPlaceholderConfigurer
类来支持Spring的参数占位符填充。所以,XML的另一种配置的方式为:
- Java Config
@Configuration
@PropertySource("db.properties") // 使用该注解来引入外部资源文件
public class Config {
/**
* 使用PropertySourcesPlaceholderConfigurer 来解析占位符
*
* 注意: 要确保该Bean在使用占位符之前就已经被初始化了,使用static修饰,保存该Bean初始化的优先级
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurerP() {
return new PropertySourcesPlaceholderConfigurer();
}
@Value("${db.username}") // 可以通过 @Value 来引入占位符
private String username;
@Value("${db.password}")
private String password;
@Value("${db.url}")
private String url;
}
profile
注意: Spring 并不是在构建的时候去根据环境选择是否要创建Bean,而是在等到运行时期在确定,所以能够适用于所有的环境,没必要重新构建项目。
配置profile
- XML
- Java Config
@Profile
注解用于指定某个Bean属于哪一个Profile,该注解也可以直接在配置类上使用,表明该配置类中的Bean都属于某一个Profile。
@Configuration
public class Config {
@Profile("dev")
@Bean
public OneBean devOneBean() {
return new OneBean("devBean");
}
@Profile("test")
@Bean
public OneBean testOneBean() {
return new OneBean("testBean");
}
}
激活Profile
- 如果设置了
spring.profiles.active
属性,那么就更他的值来确定激活哪个profile。 - 如果没有设置,就会根据
spring.profiles.default
属性的值来激活profile。 - 如果前面两者都没有确定的话,那就只会构建没有profile的Bean。
- 有多种方式来设置这两个属性的值:
- JVM的启动参数
- 作为DispatcherServlet的初始化参数
- 作为Web应用的上下文
- 作为环境变量
- 在测试类中,使用@ActiveProfile
Spring的环境Environment接口
Environment 接口主要就是两个作用:
- 获取环境中的属性值
- 获取profile的激活状态
@Conditional注解和Condition接口
-
@Conditional
可以根据满足某一特定的条件来创建一个特别的bean - 可以通过实现
Condition
接口的matches
方法来构造判断条件
public class ExistOneBean implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 如果Spring 的环境中存在 OneBean 对象, 就返回true
return context.getBeanFactory().getBeansOfType(OneBean.class).size() > 0;
}
}
@Configuration
public class Config {
@Bean
public OneBean oneBean() {
return new OneBean();
}
@Bean
@Conditional(ExistOneBean.class) // 如果当前环境中存在OneBean,才实例化该Bean
public LocalDateTime dateTime() {
return LocalDateTime.now();
}
}
@Profile的实现原理
在Spring4中,@Profile也使用@Conditional注解来实现。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap 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;
}
}