# 环境与 profile

多个相同的 bean,但是其实现方式不同,在代码实际运行中,只会选择其中的一种。 @Profile 注解用于给 bean 标注,当该 profile 被激活时,才装配该 profile 对应的 bean。 @ActiveProfiles 注解用于标注被激活的 profile,可以设置多个。

# 在 Java 中配置 profile bean

基础对象的类:

People.java

package com.myapp;
public interface People {
    void speak();
}

Man.java

package com.myapp;
public class Man implements People {
    @Override
    public void speak() {
        System.out.println("i am man");
    }
}

Woman.java

package com.myapp;
public class Woman implements People {
    @Override
    public void speak() {
        System.out.println("i am woman");
    }
}

LittlePeople.java

package com.myapp;
public interface LittlePeople {
    void speak();
}

Boy.java

package com.myapp;
public class Boy implements LittlePeople {
    @Override
    public void speak() {
        System.out.println("i am a boy");
    }
}

Girl.java

package com.myapp;
public class Girl implements LittlePeople {
    @Override
    public void speak() {
        System.out.println("i am a girl");
    }
}

配置的类:

ManConfig.java

package com.myapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
@Profile("man")
public class ManConfig {
    @Bean
    public People createMan() {
        return new Man();
    }
    @Bean
    public LittlePeople createBoy() {
        return new Boy();
    }
}

WomanConfig.java

package com.myapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
@Profile("woman")
public class WomanConfig {
    @Bean
    public People createWoman() {
        return new Woman();
    }
    @Bean
    public LittlePeople createGirl() {
        return new Girl();
    }
}

测试类:

PeopleConfigTest.java

package profiles;
import com.myapp.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ManConfig.class, WomanConfig.class})
@ActiveProfiles("man")
public class PeopleConfigTest {
    @Autowired
    private People people;
    @Autowired
    private LittlePeople littlePeople;
    @Test
    public void test() {
        people.speak();
        littlePeople.speak();
    }
}

运行结果:

i am man
i am a boy

# 在 XML 中配置 profile bean

配置文件:

profile.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <beans profile="man">
        <bean class="com.myapp.Man"></bean>
        <bean class="com.myapp.Boy"></bean>
    </beans>
    <beans profile="woman">
        <bean class="com.myapp.Woman"></bean>
        <bean class="com.myapp.Girl"></bean>
    </beans>
</beans>

测试类:

PeopleConfigTest.java

package profiles;
import com.myapp.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:profile.xml")
@ActiveProfiles("man")
public class PeopleConfigTest {
    @Autowired
    private People people;
    @Autowired
    private LittlePeople littlePeople;
    @Test
    public void test() {
        people.speak();
        littlePeople.speak();
    }
}

运行结果:

i am man
i am a boy

# 条件化的 bean

@Conditional 注解当某个条件存在时才会创建 bean。

基础对象类:

Animal.java

package com.myapp;
public class Animal {
    public void play() {
        System.out.println("It's fun");
    }
}

条件类:

MagicExistsCondition.java

package com.myapp;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class MagicExistsCondition implements Condition {
    
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        return environment.containsProperty("magic");
    }
}

MagicExistsCondition 继承接口 Condition ,当 matches 方法返回 true 时,创建 bean。

配置类:

AnimalConfig.java

package com.myapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
    @Bean
    @Conditional(MagicExistsCondition.class)
    public Animal createAnimal() {
        return new Animal();
    }
}

测试类:

AnimalConfigTest.java

package bean;
import com.myapp.Animal;
import com.myapp.AnimalConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AnimalConfig.class)
public class AnimalConfigTest {
    @Autowired
    private Animal animal;
    @Test
    public void test() {
        animal.play();
    }
}

运行报错, Animal 没有被装配进来。将 matches 方法的返回值设置为 true。

package com.myapp;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class MagicExistsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return true;
    }
}

重新运行测试类,运行结果:

It's fun

# 处理自动装配的歧义性

# 标示首选的 bean

当多个相同的 bean 存在时,spring 并不知道要装配哪一个。 @Primary 设置首先被装配的 bean,当 spring 装备 bean 时,会选择有该注释的 bean。

基础对象类:

People.java
Man.java
Woman.java
LittlePeople.java
Boy.java
Girl.java

配置类:

ManConfig.java

package com.myapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class ManConfig {
    @Bean
    @Primary
    public People createMan() {
        return new Man();
    }
    @Bean
    public LittlePeople createBoy() {
        return new Boy();
    }
}

WomanConfig.java

package com.myapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
@Configuration
public class WomanConfig {
    @Bean
    public People createWoman() {
        return new Woman();
    }
    @Bean
    @Primary
    public LittlePeople createGirl() {
        return new Girl();
    }
}

测试类:

PeopleConfigTest.java

package profiles;
import com.myapp.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ManConfig.class, WomanConfig.class})
@ActiveProfiles("man")
public class PeopleConfigTest {
    @Autowired
    private People people;
    @Autowired
    private LittlePeople littlePeople;
    @Test
    public void test() {
        people.speak();
        littlePeople.speak();
    }
}

运行结果:

i am man
i am a girl

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <beans>
        <bean class="com.myapp.Man" primary="true"></bean>
        <bean class="com.myapp.Boy"></bean>
    </beans>
    <beans>
        <bean class="com.myapp.Woman"></bean>
        <bean class="com.myapp.Girl" primary="true"></bean>
    </beans>
</beans>

# 限定自动装配的 bean

@Qualifier 注解是限定符,指定想要注入进去的是哪个 bean。

基础对象类:

People.java
Man.java
Woman.java
LittlePeople.java
Boy.java
Girl.java

配置类:

ManConfig.java

package com.myapp;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ManConfig {
    @Bean
    @Qualifier("man")
    public People createMan() {
        return new Man();
    }
    @Bean
    public LittlePeople createBoy() {
        return new Boy();
    }
}

WomanConfig.java

package com.myapp;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WomanConfig {
    @Bean
    public People createWoman() {
        return new Woman();
    }
    @Bean
    @Qualifier("girl")
    public LittlePeople createGirl() {
        return new Girl();
    }
}

测试类:

package profiles;
import com.myapp.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ManConfig.class, WomanConfig.class})
public class PeopleConfigTest {
    @Autowired
    @Qualifier("man")
    private People people;
    @Autowired
    @Qualifier("girl")
    private LittlePeople littlePeople;
    @Test
    public void test() {
        people.speak();
        littlePeople.speak();
    }
}

运行结果:

i am man
i am a girl

在 XML 中的配置只需指定 bean 的 id 即可。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <beans>
        <bean class="com.myapp.Man" id="man"></bean>
        <bean class="com.myapp.Boy" id="boy"></bean>
    </beans>
    <beans>
        <bean class="com.myapp.Woman" id="woman"></bean>
        <bean class="com.myapp.Girl" id="girl"></bean>
    </beans>
</beans>

# bean 的作用域

Spring 的作用域包括:

  • 单例:整个应用中只会创建一个 bean 实例。
  • 原型:每次注入都会创建一个新的 bean 实例。
  • 会话:web 应用的每个会话创建一个 bean 实例。
  • 请求:web 应用的每个请求创建一个 bean 实例。

创建的 bean 默认是单例模式。

基础对象类:

Animal.java

package com.myapp;
public class Animal {
    private int i = 0;
    public void play() {
        i++;
        System.out.println(i);
    }
}

配置类:

AnimalConfig.java

package com.myapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
    @Bean
    public Animal createAnimal() {
        return new Animal();
    }
}

测试类:

package bean;
import com.myapp.Animal;
import com.myapp.AnimalConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AnimalConfig.class)
public class AnimalConfigTest {
    @Autowired
    private Animal animal1;
    @Autowired
    private Animal animal2;
    @Test
    public void test() {
        animal1.play();
        animal2.play();
    }
}

输出结果:

1
2

说明在两次注入 Animal 时,注入的是同一个 bean。

原型模式在标注 bean 的位置标记 Scope。

配置类:

AnimalConfig.java

package com.myapp;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class AnimalConfig {
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Animal createAnimal() {
        return new Animal();
    }
}

测试结果:

1
1

说明两次注入的 Animal 是不同的 bean。

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="animal" class="com.myapp.Animal" scope="prototype"></bean>
</beans>

会话和请求作用域使用 WebApplicationContext 中的 SCOPE_SESSIONSCOPE_REQUEST ,bean 以作用域代理的方式进行注入,代理可以是接口或者类,使用 proxyMode = ScopedProxyMode.INTERFACESproxyMode = ScopedProxyMode.TARGET_CLASS 指定为代理接口或代理类。

@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode = ScopedProxyMode.TARGET_CLASS)
public Animal createAnimal() {
    return new Animal();
}

在 XML 配置中,使用 <aop:scoped-proxy> 告诉 Spring 创建一个作用域代理,默认是类代理,将 proxy-target-class 属性设置为 false 生成接口代理。

# 运行时值注入

之前注入 CompactDisc 时,在运行之前就已经在代码中设置了 bean 的属性的值,这属于硬编码。实际运用中,需要灵活的指定 bean 的属性的值。

<bean id="compactDisc" class="soundsystem.SgtPeppers">
    <constructor-arg value="title"></constructor-arg>
    <constructor-arg value="artist"></constructor-arg>
    <constructor-arg>
        <list>
            <value>s1</value>
            <value>s2</value>
            <value>s3</value>
            <value>s4</value>
        </list>
    </constructor-arg>
</bean>

Spring 提供了两种运行时值注入的方式:

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

# 注入外部的值

基础对象类:

Animal.java

package com.myapp;
public class Animal {
    private String name;
    public Animal(String name) {
        this.name = name;
    }
    public void play() {
        System.out.println(name);
    }
}

值配置:

animal.properties

name=Peppa

配置类:

AnimalConfig.java

package com.myapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
@Configuration
@PropertySource("classpath:animal.properties")
public class AnimalConfig {
    @Autowired
    Environment environment;
    @Bean
    public Animal createAnimal() {
        return new Animal(environment.getProperty("name"));
    }
}

测试类:

AnimalConfigTest.java

package bean;
import com.myapp.Animal;
import com.myapp.AnimalConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AnimalConfig.class)
public class AnimalConfigTest {
    @Autowired
    private Animal animal;
    @Test
    public void test() {
        animal.play();
    }
}

测试结果:

Peppa

# 深入学习 Spring 的 Environment

  • getProperty()
  1. String getProperty(String key)
  2. String getProperty(String key, String defaultValue)
  3. T getProperty(String key, Class<T> type)
  4. T getProperty(String key, Class<T> type, T defaultValue)

第一种直接返回属性对应的值;第二种当没有设置属性的值时,使用传入的默认值,返回第二个参数的值;第三种可以设置返回值的类型,比如 “3” 可以使 String,也可以是 Integer;第四种当没有设置属性的值时,返回默认值。

  • getRequiredProperty()

属性必须被定义,否则会报 IllegalStateException。

  • containsProperty()

判断某个属性是否存在,返回 Boolean 值。

  • getPropertyAsClass()

将属性解析为类。

  • getActiveProfiles()

返回激活 profile 名称的数组

  • getDefaultProfiles()

返回默认 profile 名称的数组

  • acceptsProfiles(String ... profiles)

如果 environment 支持给定的 profile,则返回 true。

# 解析属性占位符

基础对象类:

Animal.java

package com.myapp;
public class Animal {
    private String name;
    public Animal(String name) {
        this.name = name;
    }
    public void play() {
        System.out.println(name);
    }
}

配置文件:

bean.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:context="http://www.springframework.org/schema/context" xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder location="classpath:animal.properties"></context:property-placeholder>
    <bean id="animal" class="com.myapp.Animal" c:_0="${name}"></bean>
</beans>

测试类:

package bean;
import com.myapp.Animal;
import com.myapp.AnimalConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean.xml")
public class AnimalConfigTest {
    @Autowired
    private Animal animal;
    @Test
    public void test() {
        animal.play();
    }
}

测试结果:

Peppa

尝试使用 Java 配置:

package com.myapp;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
@Configuration
public class AnimalConfig {
    @Bean
    public Animal createAnimal(@Value("${name}") String name) {
        return new Animal(name);
    }
    @Bean
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        ClassPathResource classPathResource = new ClassPathResource("classpath:animal.properties");
        propertySourcesPlaceholderConfigurer.setLocations(classPathResource);
        return propertySourcesPlaceholderConfigurer;
    }
}

运行报错,无法加载 Spring 环境,原因未知。

# 使用 Spring 表达式语言进行装配

SpELl 表达式好像很牛逼的样子。