第三章 高级装配

作者: 施瓦 | 来源:发表于2017-12-08 10:48 被阅读0次

    第三章 高级装配

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


    [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
    第三章 高级装配

    相关文章

      网友评论

        本文标题:第三章 高级装配

        本文链接:https://www.haomeiwen.com/subject/lqhjixtx.html