第三章 高级装配

第三章 高级装配

标签(空格分隔): 未分类


[TOC]

环境与profile

配置profile bean

在开发过程中,不同开发环境可能会出现代码不同的情况,比方说:一般开发和测试会有两个不同的环境和数据库等等。Spring引入了Profile的功能,要使用Profile,你需要将不同的Bean整理到一个或者多个Profile中,将应用部署到每个环境的时候,需确定对应的Profile处于激活状态(active

在Java中,可以使用@Profile注解指定某个bean属于哪个profile

ProfileConfig.class

@Configuration
public class ProfileConfig {

    @Bean
    @Profile("dev") // 指定下面的bean属于dev profile
    public DemoBean devBean(){
        return new DemoBean("这是测试环境");
    }

    @Bean
    @Profile("prod") // 指定下面的bean属于prod profile
    public DemoBean prodBean(){
        return new DemoBean("这是生产环境");
    }

}

MainTest.java


public class MainTest {

    public static void main(String []args){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        context.getEnvironment().setActiveProfiles("dev"); // 激活某些profile状态
        context.register(ProfileConfig.class);
        context.refresh();

        DemoBean demoBean = context.getBean(DemoBean.class);

        System.out.println(demoBean.getContent());

        context.close();

    }
}

@Profile 注解不仅可以加到@Bean的下方,还可以加到类级别上(@Configuration):

@Configuration
@Profile(value="prod")
public class ProductionProfileConfig{
    // ...
}

表示仅有当@Profile中的环境激活时(也就是例子中的prod,dev...),才可以创建相应的bean

除了在Java文件中加上注解之外,我们还可以通过XML配置文件的方法来配置相应的profile:

spring-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
       profile="prod">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:main/example/sql/dev.sql"/>
        <jdbc:script location="classpath*:main/example/sql/prod.sql"/>
    </jdbc:embedded-database>

    <context:component-scan base-package="main.example"/>

</beans>

当prod的profile被激活时,spring-config.xml才会被用到。

我们还可以在一个XML文件中通过<beans>标签嵌套定义多个profile

重复使用 <beans> 元素来指定多个profile

spring-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <beans profile="qa">
        <bean>
            ...
        </bean>
    </beans>

    <beans profile="prod">
        <bean>
            ....
        </bean>
    </beans>

</beans>

激活profile

激活profile需要依赖两个独立的属性:

spring.profiles.active // 优先级要比default高
spring.profiles.default // 如果active没有设置,才会查找default中的值

系统会优先使用spring.profiles.active中所设置的profile

如果spring.profiles.activespring.profiles.default均没有设置的话,那就没有激活的profile,因此,只会创建那些没有定义在profile中的bean 。

有多种方式来设置这两个属性:

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

在web应用中的web.xml中设置默认的profile
web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="3.1">

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!-- 为上下文设置默认的profile -->
    <context-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Spring Configuration -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!-- 为servlet配置默认的profile -->
            <param-name>spring.profiles.default</param-name>
            <param-value>dev</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


</web-app>

使用profile进行测试

Spring提供了 @ActiveProfiles 注解,来指定运行测试时需要激活哪个profile

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-config.xml")
@ActiveProfiles("prod")
public class UserDaoTest {
    // ...
}

条件化的bean

假如我们希望某个bean只有当另一个特定的bean声明了之后才会创建,或者需要某个特定的环境变量才会创建,就需要用到 @Conditional 注解

它可以用在带有 @Bean 注解的方法上,如果给定的条件计算结果为true,就创建这个bean,否则就不创建。

例如,假设有一个MagicBean的类,当设置了magic环境属性的时候,Spring才会实例化这个类:

MagicBeanConfig.class

@Configuration
public class MagicBeanConfig {

    @Bean
    @Conditional(MagicExistsCondition.class)
    public MagicBean magicBean(){
        return new MagicBean();
    }

}

@Conditional 给定了一个class ,它指明了一个条件 —— 在本例中,也就是 MagicExistsCondition 类 。MagicExistsCondition需要实现Condition接口。

@Conditional 将会通过 Condition 接口进行对比:

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}

设置给 @Conditional 的类可以是任意实现了Condition 接口的类型 ,实现 Condition接口需要实现matches方法。如果 matches() 方法返回 true , 那么就会创建带有 @Conditional 注解的bean,否则就不会创建这些 bean

MagicExistsCondition.class

public class MagicExistsCondition implements Condition {

    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        // 检查环境中是否存在magic的环境属性
        return environment.containsProperty("magic");
    }
}

MainTest.class —— 测试类

public class MainTest {
    public static void main(String []args){
        System.setProperty("magic","true");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MagicBeanConfig.class);
        // 打印 ture
        System.out.println(context.containsBean("magicBean"));
        context.close();
    }
}

Condition实现的考量要比本例中的更多,mathes()方法会得到ConditionContextAnnotatedTypeMetadata 对象用来做出决策 。

ConditionContext是一个接口,大致如下所示:

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

AnnotatedTypeMetadata能让我们检查带有 @Bean 注解的方法上还有什么其他的注解 , 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);
}

返回到这篇笔记刚开始的地方,我们看下 @Profile 是如何实现的:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
    String[] value();
}

@Profile 本身也是用了 @Conditional 注解 ,并引用ProfileCondition 作为 Condition 的实现 。

ProfileCondition 检测某个 bean profile 是否可用

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) {
            // 获取value属性
            for (Object value : attrs.get("value")) {
                // 检查value属性中的profile是否处于激活状态
                if (context.getEnvironment().acceptsProfiles((String[]) value)) {
                    return true;
                }
            }
            return false;
        }
        return true;
    }
}

自动处理装配的歧义性

仅有一个bean匹配所需的结果时 ,自动装配才是有效的 。如果不仅有一个bean能够匹配结果的话 ,这种歧义性会阻碍 Spring 自动装配属性 、构造器参数或者方法参数 。

在发生歧义时,可以选择bean中的某一个设为首选(Primary)的bean 。或者使用限定符(Quailfier)来帮助Spring将可选的bean的范围缩小到只有一个bean 。

标志首选的bean --- @Primary

甜点的例子

public interface Dessert {
    void play();
}

@Component
public class Cake implements Dessert {
    // ...
}

@Component
public class Cookie implements Dessert {
    // ...
}

@Component
public class IceCream implements Dessert {
    // ...
}

在本例中, Dessert 是一个接口 ,并且有三个类实现了这个接口 。因为这三个类均使用了 @Component 注解 , 在组件扫描时 ,能够发现他们并且将其创建为Spring上下文参数的bean 。

@Component
public class DessertMachine implements Machine {
    private Dessert dessert;
    @Autowired
    public void setDessert(Dessert dessert){
        this.dessert = dessert;
    }
    public void play(){
        dessert.play();
    }
}

当Spring试图装配 setDessert() 中的 Dessert 参数时 ,它并没有唯一、无歧义的可选值 。Spring会抛出相应的异常 。

在Spring中,可以使用 @Primary 来表达最喜欢的方案, @Primary@Component组合用在组件扫描的bean上,例如:

@Primary
@Component
public class IceCream implements Dessert {
    // ...
}

@Primary 也可以与 @Bean组合用在Java 配置的bean 声明中。

如果你使用XML配置bean的话,<bean>元素有一个primary属性来指定首选的bean 。

<bean id="iceCream" class="com.desserteater.IceCream" primary="true"/>

当时,如果你标识了两个或者更多的首选bean ,那么它就无法正常工作了。

限定自动装配的bean

@Qualifier 注解是使用限定符的主要方式 , 可以和 @Autowired 协同使用,在注入的时候指定想要注入哪一个bean 。

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

@Qualifier注解所设置的参数就是想要注入的bean的ID 。因为所有使用 @Component 注解的声明的类都会创建为bean ,并且bean的ID为首字母变为小写的类名,所以,@Qualifier("iceCream") 指向的是组件扫描时所创建的bean,并且这个bean是 IceCream 的实例 。

这里的问题是如果重构了IceCream类,bean的ID和默认的限定符就会变,这里就无法匹配setDessert()中指定的限定符 。自动装配会失败 。

@Qualifier可以自定义名称,做到与类名(或者说bean的ID)解耦 :

@Component
@Qualifier("cold")
public class IceCream implements Dessert {

    public void play() {
        System.out.println("这是 IceCream");
    }
}

这样在装配bean的时候就需要引用cold限定符了 :

@Autowired
@Qualifier(value = "cold")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

当程序员自定义@Qualifier值时,最佳实践是为bean选择特征性或者描述性的术语。在IceCream中,我们将Qualifier自定义为cold,假设另外一个Dessert的实现类也是cold的话 :

@Component
@Qualifier(value = "cold")
public class Popsicle implements Dessert {
    public void play() {
        System.out.println("这是 水果冰棒");
    }
}

程序再次出现了歧义性的问题 。

Java8允许出现重复的注解,只要这个注解本身在定义的时候带有 @Repeatable 注解就可以。不过,Spring的@Qualifier注解并没有在定义时添加@Repeatable注解。所以,下面这种使用方法是错误的:

@Autowired
@Qualifier("cold")
@Qualifier("creamy")
public void setDessert(Dessert dessert){
  this.dessert = dessert;
}

那么,我们应该如何区分IceCreamPopsicle类呢 ?

创建自定义的限定符注解:

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
    // ...
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy {
    // ...
}

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Fruit {
    // ...
}

现在,我们可以重新看下 IceCream ,并为其添加以下注解:

@Component
@Cold
@Creamy
public class IceCream implements Dessert {
    // ...
}
// ...
@Component
@Cold
@Fruit
public class Popsicle implements Dessert {
    // ...
}

最终,在注入点,我们使用必要的限定符注解进行任意组合,从而将可选范围缩小到只有一个bean满足需求 。

@Autowired
@Cold
@Fruit
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

为了创建自定义的条件化注解,我们创建了一个新的注解并在这个注解上添加了@Conditional 。为了创建自定义的限定符注解,我们创建一个新的注解并在这个注解上加上 @Qualifier

自定义@Conditional注解示例

ConditionalOnMyProperties.class

@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnMyPropertiesCondition.class)
public @interface ConditionalOnMyProperties {

    String name();
}

OnMyPropertiesCondition.class

public class OnMyPropertiesCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 获取注解上的name属性
        Object propertiesName = metadata.getAnnotationAttributes(ConditionalOnMyProperties.class.getName()).get("name");
        if (propertiesName != null){
            // 检查环境中是否存在该属性的值
            boolean value = context.getEnvironment().containsProperty(propertiesName.toString());
            if(value){
                return true;
            }
        }
        return false;
    }
}

HelloWorld.class

public class HelloWorld {
    public void print() {
        System.out.println("hello world");
    }
}

ConditionClass.class

@Configuration
@ConditionalOnMyProperties(name = "message")
public class ConditionClass {
    @Bean
    public HelloWorld helloWorld(){
        return new HelloWorld();
    }
}
public static void main(String[] args) {
        System.setProperty("message","something");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionClass.class);
        try {
            context.getBean(HelloWorld.class).print();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

bean的作用域

在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。

Spring定义了多种作用域,可以基于这些作用域创建bean

  • 单例(Singleton):在整个应用中,只创建bean的一个实例。
  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
  • 会话(Session):在Web应用中,为每个会话创建一个bean实例。
  • 请求(Request):在Web应用中,为每个请求创建一个bean实例。

单例是默认的作用域,但对于易变(mutable)的类型来说,这种作用域并不合适,如果选择其他的作用域,要使用@Scope注解,他可以和@Component或者@Bean一起使用。

例如:

如果你使用组件扫描来发现bean,那么你可以在bean的类上使用 @Scope 注解,将其声明为原型bean 。

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class NotePad{
    // ...
}

如果你想在Java配置(显示配置)中将Notepad声明为原型bean,那么可以组合使用 @Scope@Bean 来指定所需要的作用域。

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad(){
    return new Notepad();
}

你还可以在XML中设置作用域:

<bean id="notepad" class="com.java.notepad" scope="prototype"/>

使用会话和请求作用域

在Web应用中,如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事情。就购物车bean来说,每一个用户都需要有不同的购物车,所以会话作用域是最为合适的,因为它与给定的用户关联性最大。

要指定会话作用域,我们可以使用@Scope注解:

@Component
@Scope(value= WebApplicationContext.SCOPE_SESSION,
      proxyMode = ScopedProxyMode.INTERFACES)
)
// 当ShoppingCart是一个接口时
public ShoppingCart cart(){
  // ...
}

在这里,我们将value设置为了WebApplicationContext中的SCOPE_SESSION常量,这表明Spring为web应用中的每个会话创建一个实例。

要注意的是,@Scope还有一个proxyMode属性,它被设置为了ScopedProxyMode.INTERFACES。这个属性解决了将会话或者请求作用域的bean注入到单例bean中所遇到的问题 :

假设我们需要将ShoppingCart的bean注入到单例StoreServicebean的setter方法中:

@Component
public class StoreService{

  @Autowired
  public void setShoppingCart(ShoppingCart shoppingCart){
    this.shoppingCart = shoppingCart;
  }

}

因为StoreService是一个单例的bean,会在Spring应用上下文加载的时候创建。当它创建的时候,Spring会试图将ShoppingCartbean注入到setShoppingCart()方法中。但是ShoppingCartbean 是会话作用域的,此时并不存在。直到某个用户进入了系统,创建了会话之后,才会出现ShoppingCart的实例。

另外,系统中将会出现多个ShoppingCart实例,我们并不希望将特定的ShoppingCart注入到StoreService中,我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart恰恰是当前会话所对应的那个。

Spring并不会将实际的ShoppingCartbean注入到StoreService中,Spring会注入一个到ShoppingCartbean的代理:

[图片上传失败...(image-19b5ab-1533698521996)]

这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用购物车方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCartbean 。

如配置所示,proxyMode被设置成为了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。

如果ShoppingCart是一个接口而不是一个类,这是可以的。但是如果ShoppingCart是一个具体的类,Spring就没有办法创建基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体的类的话,我们必须要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS

请求作用域和会话作用域一样,也需要以作用域的方式进行注入。

在XML中使用作用域代理

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="shoppingCart" class="demo.test.ShoppingCart" scope="session">
        <!-- shoppingCart是接口的时候 -->
        <aop:scoped-proxy proxy-target-class="false"/>
    </bean>

</beans>

<bean>中的scope属性能够设置bean的作用域,<aop:scoped-proxy>是和@Scope注解中的proxyMode属性功能相同的Spring XML配置元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。但是,我们可以将proxy-target-class属性设置为false,进而告诉它生成基于接口的代理。

运行时值注入

在讨论依赖注入的时候,我们通常所讨论的是将一个bean引用注入到另一个bean的属性或者构造器参数中。它通常来讲是将一个对象与另一个对象进行关联。

然而还有另一个方面是将一个值注入到bean的属性或者构造器中。

例如:我们将专辑的名字装配到BlanckDiscbean 的构造器或者title属性中:

BlankDisc.class

public class BlankDisc implements CompactDisc {

    private String title;
    private String artist;

    public BlankDisc(String title, String artist) {
        this.title = title;
        this.artist = artist;
    }

    @Override
    public void play() {
        System.out.print("Playing " + title + " by " + artist);
    }
}

使用XML装配:

<bean id="blankDisc" class="soundsystem.compactdisc.BlankDisc">
        <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
        <constructor-arg value="The Beatles"/>
</bean>

我们还可能使用Java的方式进行装配:

@Bean
public CompactDisc sgtPeppers(){
  return new BlankDisc("Sgt. Pepper's Lonely Hearts Club Band","The Beatles");
}

但是,以上两种方式都是将值以固定编码的形式注入到bean中的。Spring提供了两种方式让这些值在运行时注入:

  • 属性占位符
  • Spring表达式语言

注入外部的值

在Spring中,获取外部值最简单的方式是声明数据源并通过Spring的Enviroment来获取属性值 :

@Configuraion
@PropertyResource(value="classpath:/app.properties")
public class ExpressiveConfig{
    @Autowired
    private Enviroment enviroment;
    @Autowired
    private BlankDisc disc(){
        return new BlankDisc(enviroment.getProperty("disc.title"),
                            enviroment.getProperty("disc.artist")
        );
    }
}

app.properties

disc.title = 寻宝游戏
disc.artist = vae
public interface CompactDisc {
    void play();
}
// ...
public class BlankDisc implements CompactDisc{
    private String title;
    private String artist;

    public BlankDisc(String title , String artist) {
        this.title = title;
        this.artist = artist;
    }

    public void play() {
        System.out.println("title : " + title + " artist : " + artist);
    }
} 

Enviroment类中还有其他一系列的方法,其中包括getProperty()方法的重载以及其他方法 :

public interface PropertyResolver {
    // 检查某个属性是否存在
    boolean containsProperty(String var1);

    String getProperty(String var1);
    // 当指定属性var1不存在时候,返回默认值var2
    String getProperty(String var1, String var2);

    @Nullable
    // 返回非字符串类型T
    <T> T getProperty(String var1, Class<T> var2);
    // 当指定属性var1不存在时候,返回指定类型T var3
    <T> T getProperty(String var1, Class<T> var2, T var3);
    // 当指定属性不存在时候报错
    String getRequiredProperty(String var1) throws IllegalStateException;

    <T> T getRequiredProperty(String var1, Class<T> var2) throws IllegalStateException;

    String resolvePlaceholders(String var1);

    String resolveRequiredPlaceholders(String var1) throws IllegalArgumentException;
}

除了属性相关功能之外,Enviroment对象还提供了一些方法来检查哪些profile处于激活状态 :

public interface Environment extends PropertyResolver {
    // 获取到profile为active的数组
    String[] getActiveProfiles();
    // 获取到profile为default的数组
    String[] getDefaultProfiles();
    // 如果environment支持给定的profile 就返回true
    boolean acceptsProfiles(String... var1);
}

解析属性占位符

使用XML方式

<bean id="blankDisc" class="com.springdemo.soundsystem.BlankDisc" c:_0="${disc.title}" c:_1="${disc.artist}"/>
<context:property-placeholder location="classpath:/app.properties"/>

使用Java配置方式

@Configuration
@ComponentScan(basePackages = "com.springdemo")
@PropertySource(value = "classpath:/app.properties")
public class ExpressiveConfig {
    @Bean
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
        return new PropertySourcesPlaceholderConfigurer();
    }
}
@Component
public class BlankDisc implements CompactDisc {
    // ...  
    public BlankDisc(
            @Value("${disc.title}") String title ,
            @Value("${disc.artist}") String artist) {
        this.title = title;
        this.artist = artist;
    }
}

总结

在这章中我们主要学习了以下内容 :

  • @Profile注解 : 主要用来区别不同开发或者运行环境问题,例如是mysql还是oracle数据库等等
  • 激活profile的几种方法 : 作为WEB应用上下文、作为JNDI条目、作为环境变量、作为JVM系统属性、在集成测试中使用@ActiveProfiles注解设置
  • @Conditional注解 :@Profile的“升级版”,设置条件化的Bean,@Conditional注解中的类必须要实现Conditional接口中的matches()方法
  • @Primary注解 : 标示首选的bean(不建议使用)
  • @Qualifier注解 : 消除装配时的歧义性 。也可以通过@Qualifier自定义限定符注解 。
  • bean的四种作用域 :单例、原型 、会话 、请求
  • @Scope注解修改bean的作用域 :@Scope(ConfigurationBeanFactory.SCOPE_PROTOTYPE),如果是会话/请求作用域的话,除了设置@Scope(value = WebApplicationContext.SCOPE_SESSION)之外,还需要设置proxyMode=ScopedProxyMode.INTERFACE或者proxyMode=ScopedProxyMode.TARGET_CLASS
第三章 高级装配
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 3.1 环境与profile 3.1.1 配置profile bean 要使用profile,你首先要将所有不同的...
    如一诺然阅读 356评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,729评论 6 342
  • 李良凯知道自己生病了,浑身止不住的颤抖,说不出是因为冷,还是因为内心的恐惧。 他一回到家,全身瞬间无力,瘫倒在玄关...
    何哞哞阅读 412评论 0 1
  • 乡下就是农村,就是和城市不一样的地方。 半夜起来上厕所,鸡叫的声音代替各路车的鸣笛,夜里整个村庄一片宁静,很少有车...
    一缕一丝阅读 259评论 0 1