Spring框架之Bean的高级配置

文章目录

  • Bean的高级装配
    • 环境与profile
    • 条件化的Bean
    • Bean的作用域
    • 运行时值注入

Bean的高级装配

环境与profile

项目开发需要多个环境,有开发环境,测试环境,生产环境等,在每一种环境中的配置可能不一样,比如:数据库的配置,开发环境需要配置开发的数据库dev,测试的时候需要使用测试环境中的数据库,而产品部分发布到现场那么就需要生产环境的正式数据库,所以我们可以创建多个配置类,或者配置文件,根据不同的环境使用对应的配置文件,那么怎么配置比较方便呢?

现在就要使用@Profile注解,此注解在spring3.2之后既可以用在类上,也可以用在方法上,如下:

@Configuration
public class DataSourceConfig {

    @Bean
    @Profile("dev")
    public DataSource embeddedDataSource(){
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:schema.sql")
                .addScript("classpath:test-data.sql")
                .build();
    }

    @Bean
    @Profile("prod")
    public DataSource jndiDataSource(){
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDs");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource)jndiObjectFactoryBean.getObject();
    }
}

既然我们配置了两个profile,那么具体使用哪一个怎么去激活呢?

Spring在确定哪个profile处于激活状态时, 需要依赖两个独立的属性: spring.profiles.active和spring.profiles.default。 如果设置了spring.profiles.active属性的话, 那么它的值就会用来确定哪个profile是激活的。 但如果没有设置spring.profiles.active属性的话, 那Spring将会查找spring.profiles.default的值。如果spring.profiles.active和spring.profiles.default均没有设置的话, 那就没有激活的profile, 因此只会创建那些没有定义在profile中的bean。有多种方式来设置这两个属性:

  • 作为DispatcherServlet的初始化参数;
  • 作为Web应用的上下文参数;
  • 作为JNDI条目;
  • 作为环境变量;
  • 作为JVM的系统属性;
  • 在集成测试类上, 使用@ActiveProfiles注解设置。

你尽可以选择spring.profiles.active和spring.profiles.default的最佳组合方式以满足需求 。比如在web应用中设置spring.profile.default的web.xml文件如下:

Spring框架之Bean的高级配置_第1张图片

条件化的Bean

Spring4引入新的 @Conditional 注解,它可以用到带有@Bean注解的方法上。如:

@Configuration
public class ConditionConfig {

    @Bean
    @Conditional(ConditionImpl.class)
    public Computer getBean(){
        return new Computer();
    }
}

其中ConditionImpl是接口Condition的实现类,接口Condition是Spring的接口,此接口源码如下:

@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);

}

只有一个方法matches,所以实现类的此方法返回为true时创建Computer的bean,若返回为false,则不创建bean。

我们发现matches方法有两个参数:ConditionContext context, AnnotatedTypeMetadata metadata

ConditionContext是个接口,有以下方法以及它们的作用:

  • 借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
  • 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
  • 借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
  • 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
  • 借助getClassLoader()返回的ClassLoader加载并检查类是否存在。

AnnotatedTypeMetadata也是个接口:

public interface AnnotatedTypeMetadata {
    boolean isAnnotated(String var1);

    @Nullable
    Map<String, Object> getAnnotationAttributes(String var1);

    @Nullable
    Map<String, Object> getAnnotationAttributes(String var1, boolean var2);

    @Nullable
    MultiValueMap<String, Object> getAllAnnotationAttributes(String var1);

    @Nullable
    MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
}

借助isAnnotated()方法, 我们能够判断带有@Bean注解的方法是不是还有其他特定的注解。 借助其他的那些方法, 我们能够检查@Bean注解的方法上其他注解的属性。

比如@Profile注解就是基于@Conditional和Condition实现的,其中ProfileCondition实现了Condition接口:

class ProfileCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获取profile的所有属性
		MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
		if (attrs != null) {
            //取profile的value属性
			for (Object value : attrs.get("value")) {
                //判断profile是否激活
				if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
					return true;
				}
			}
			return false;
		}
		return true;
	}

}

Bean的作用域

前边讲过了bean的作用域,以下是bean的几种作用域:

作用域 描述
单例singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值
原型prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()
请求request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
会话session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
全局global-session 一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境

Java代码配置bean的时候可以使用@Scope注解设置bean的作用域,如:

@Configuration
public class JavaConfig {

    @Bean(name = "iphonePc")
    @Scope("session")
    public IphoneComputer iphoneComputer(){
        return new IphoneComputer();
    }
}

当然在XML装配中标签中也有属性scope用于设置bean的作用域。

运行时值注入

我们项目在运行时可能需要读取一些配置文件中的值,避免硬编码。Spring提供了两种在运行时求值的方式:

  • 属性占位符(Property placeholder)
  • Spring表达式语言(SpEL)

我们只讲属性占位符,这也是总用到的。

@Properties注解可以读取配置文件,属性value为数组,所以设置多个配置文件的路径,如下创建PropertyBean的时候通过读取配置文件app.properties的配置项给属性赋值。

@Configuration
@PropertySource("classpath:/app.properties")
public class PropertyConfig {
    
    @Autowired
    private Environment environment;
    
    @Bean
    public PropertyBean getBean(){
        return new PropertyBean(
                environment.getProperty("appname"),
                environment.getProperty("port")
        );
    }
}

在这里使用了Environment类,该类继承PropertyResolver,其中一些重要方法需要学习:

  • String getProperty(String key)
  • String getProperty(String key, String defaultValue)
  • T getProperty(String key)
  • T getProperty(String key, String defaultValue)

这个四个方法是获取属性值得,即配置项。

还有几个方法:

  • boolean containsProperty(String key):判断是否包含某个配置项
  • String[] getActiveProfiles():返回激活profile名词的数组
  • String[] getDefaultProfiles():返回默认profile名词的数组
  • boolean acceptsProfiles(Profiles profiles):判断profile是否激活,在上面讲到@Profile的ProfileCondition实现了接口Condition并且通过ConditionContext获取到Environment,再判断profile是否激活

注解@Value属性占位符使我们常用到读取配置文件的方法,如:

    @Value("${appname}")
    String appname;
    
    @Value("${port}")
    String port;
    
    @Bean
    public PropertyBean getBeanByValue(){
        return new PropertyBean(
                environment.getProperty(appname),
                environment.getProperty(port)
        );
    }

你可能感兴趣的:(java语言)