一、环境与profile
略。

二、条件化的bean

       Spring 4引入了一个新的@Conditional注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean会被忽略。

       举例:假设有一个名为MagicBean的类,我们希望只有设置了magic环境属性的时候,Spring才会实例化这个类。如果环境中没有这个属性,那么MagicBean将会被忽略。

package com.springinaction.ch03.conditional;

public class MagicBean {}

使用@Conditional注解条件化地配置bean:

package com.springinaction.ch03.conditional;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

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

设置给@Conditional的类(例如MagicExistsCondition)可以是任意实现了Condition接口的类型(这个接口是Spring中的接口)。这个接口实现起来很简单直接,只需提供matches()方法的实现即可。如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。如果matches()方法返回false,将不会创建这些bean。

package com.springinaction.ch03.conditional;

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 context, AnnotatedTypeMetadata metadata) {  
		Environment env = context.getEnvironment();  
		return env.containsProperty("magic"); 
	} 
}

       它通过给定的ConditionContext对象进而得到Environment对象,并使用这个对象检查环境中是否存在名为magic的环境属性。只要属性存在即可满足要求。如果满足这个条件的话,matches()方法就会返回true。所带来的结果就是条件能够得到满足,所有@Conditional注解上引用MagicExistsCondition的bean都会被创建。matches()方法会得到ConditionContext和AnnotatedTypeMetadata对象用来做出决策。


测试:

package com.springinaction.ch03.conditional.test;

import static org.junit.Assert.*;

import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.springinaction.ch03.conditional.MagicConfig;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=MagicConfig.class)
public class MagicExistsTest {

	@BeforeClass
	public static void setProperty() {
		System.setProperty("magic", "true");
	}
	
	@Autowired
	private ApplicationContext context;
  
	/*
	 * This test will fail until you set a "magic" property.
	 * You can set this property as an environment variable, a JVM system property, by adding a @BeforeClass
	 * method and calling System.setProperty() or one of several other options.
	 */
	@Test 
	public void shouldNotBeNull() {   
		assertTrue(context.containsBean("magicBean"));
	}
  
}


ConditionContext是一个接口:

SpringInAction笔记(三)—— 高级装配

通过ConditionContext,我们可以做到如下几点:
1、借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
2、借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
3、借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
4、读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
5、借助getClassLoader()返回的ClassLoader加载并检查类是否存在。

三、处理自动装配的歧义性
        Dessert是一个接口,并且有三个类实现了这个接口,分别为Cake、Cookies和IceCream:
@Component
public class Cake implements Dessert {}

@Component
public class Cookies implements Dessert {}

@Component
public class IceCream implements Dessert {}

在自动化装配时,

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

因为这三个实现均使用了@Component注解,在组件扫描的时候,能够发现它们并将其创建为Spring应用上下文里面的bean。然后,当Spring试图自动装配setDessert()中的Dessert参数时,它并没有唯一、无歧义的可选值。之后会发生异常(NoUniqueBeanDefinitionException)。

        解决歧义性的方案有:


3.1 标记首选的bean
在Spring中,可以通过@Primary来表达最喜欢的方案。@Primary能够与@Component组合用在组件扫描的bean上:

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class IceCream implements Dessert {
}

@Primary与@Bean组合用在Java配置的bean声明中:

    @Bean
    @Primary
    public Dessert iceCream(){
        return new IceCream();
    }
用XML配置bean,<bean>元素有一个primary属性用来指定首选的bean:
SpringInAction笔记(三)—— 高级装配
不管采用哪种方式首选bean,都是告诉Spring在遇到歧义性的时候要首选的bean。
       如果你标示了两个或更多的首选bean,那么它就无法正常工作了。

3.2 限定自动装配的bean
       @Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。例如想要确保将IceCream注入到setDessert()之中:

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

为@Qualifier注解所设置的参数就是想要注入的bean的ID。setDessert()方法上所指定的限定符与要注入的bean的名称是紧耦合的。对类名称的任意改动都会导致限定符失败。       

        如果没有指定其他的限定符的话,所有的bean都会给定一个默认的限定符,这个限定符与bean的ID相同。


创建自定义的限定符       

可以为bean设置自己的限定符,而不是依赖于bean ID作为限定符。自定义限制符的名称:

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

cold限定符分配给了IceCream bean。因为它没有耦合类名,因此可以随意重构IceCream 的类名,而不必担心破坏自动装配。在注入的地方,只要引用cold限定符就可以了:

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

注意:当使用自定义的@Qualifier值时,最佳实践是为bean选择特征性或描述性的术语,而不是使用随意的名字。


使用自定义的限定符注解      

      如果多个bean都具备相同特性的话,这种做法也会出现重匹配的问题。(可以用多组描述区分)但是由于Java中不容许相同注解的出现,故需要自定义注解。  

       创建自定义的限定符注解,借助这样的注解来表达bean所希望限定的特性。这里所需要做的就是创建一个注解,它本身要使用@Qualifier注解来标注。这样我们将不再使用@Qualifier("cold"),而是使用自定义的@Cold注解。其用法和@Qualifier一样。

@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD,ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}
使用限定符注解:
@Component
@cold
public class IceCream implements Dessert {...}

四、bean的作用域
 Spring定义了多种作用域,可以基于这些作用域创建bean,包括:
  单例(Singleton):在整个应用中,只创建bean的一个实例。
  原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
     会话(Session):在Web应用中,为每个会话创建一个bean实例。
     请求(Rquest):在Web应用中,为每个请求创建一个bean实例。

单例是默认的作用域。


 4.1 原型

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

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//或为(@Scope("prototype"))
public class Notepad {
}

想在Java配置中将Notepad声明为原型bean,那么可以组合使用@Scope和@Bean来指定所需的作用域:

@Bean
@Scope("prototype")
public NodePad nodePad(){
    return new NodePad();
}
使用XML来配置bean的话,可以使用<bean>元素的scope属性来设置作用域:
SpringInAction笔记(三)—— 高级装配

不管使用哪种方式来声明作用域,每次注入或从Spring应用上下文中检索该属性的时候,都会创建新的实例。这样导致的结果是每次操作都能得到自己的Notepad实例。

4.2会话与请求域
       在典型的电子商务应用中,可能会有一个bean代表用户的购物车。如果购物车是单例的话,那么将会导致所有的用户都会向同一个购物车中添加商品。如果购物车是原型的话,那么在应用中某一个地方往购物车中添加商品,在应用中的另一个地方可能就不可用了,因为在这里注入的是另外一个原型作用域的购物车。
       就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。
	@Component
	@Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES)
	public ShoppingCart cart() {
	...	
	}

      将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart bean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean实际上相当于单例的。       

       而proxyMode属性被设置成了ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。例如将ShoppingCart bean注入到单例StoreService bean的Setter方法中

@Component
public class StoreService {

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

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

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

       proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。
  如果ShoppingCart是接口而不是类的话,这是可以的(也是最为理想的代理模式)。但如果ShoppingCart是一个具体的类的话,Spring就没有办法创建基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,我们必须要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。
SpringInAction笔记(三)—— 高级装配
 
请求作用域的bean应该也以作用域代理的方式进行注入。

4.3在xml中声明作用域及代理

  如果需要使用XML来声明会话或请求作用域的bean,那么就使用<bean>元素的scope属性;要设置代理模式,需要使用Spring aop命名空间的一个新元素:

<?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-4.3.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
     <bean id="cart" 
             class="com.myapp.ShoppingCart"
             scope="session">
       <aop:scoped-proxy/>        
     </bean>
     
     </beans>

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

<?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-4.3.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
     <bean id="cart" 
             class="com.myapp.ShoppingCart"
             scope="session">
       <aop:scoped-proxy proxy-target-class="false"/>        
     </bean>
     
     </beans>

五、运行时植入  
     当希望避免硬编码值,而是想让这些值在运行时再确定。Spring提供了两种在运行时求值的方式:
         属性占位符(Property placeholder)。
    Spring表达式语言(SpEL)。

5.1注入外部值

  在Spring中,处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性。

(1)BlankDisc.java

package com.springinaction.ch03.externals;


public class BlankDisc {

	private final String title;
	private final String artist;
  
	public BlankDisc(String title, String artist) { 
		this.title = title; 
		this.artist = artist; 
	}
  
	public String getTitle() { 
		return title;  
	}
	
	public String getArtist() {  
		return artist;
	}
  
}


(2)EnvironmentConfig.java

package com.springinaction.ch03.externals;

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;

import com.springinaction.ch03.externals.BlankDisc;

@Configuration
@PropertySource("classpath:/com/springinaction/ch03/externals/app.properties")
public class EnvironmentConfig {

	@Autowired
	Environment env;
    
	@Bean 
	public BlankDisc blankDisc() {  
		return new BlankDisc( 
				env.getProperty("disc.title"),   
				env.getProperty("disc.artist"));
	
	}
  
}

(3)app.properties

disc.title=Sgt. Peppers Lonely Hearts Club Band
disc.artist=The Beatles


测试:

package com.springinaction.ch03.externals.test;

import static org.junit.Assert.*;

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;

import com.springinaction.ch03.externals.BlankDisc;
import com.springinaction.ch03.externals.EnvironmentConfig;

public class EnvironmentInjectionTest {
 
	@RunWith(SpringJUnit4ClassRunner.class) 
	@ContextConfiguration(classes=EnvironmentConfig.class) 
	public static class InjectFromProperties {
     
		@Autowired 
		private BlankDisc blankDisc;
      
		@Test 
		public void assertBlankDiscProperties() { 
			assertEquals("The Beatles", blankDisc.getArtist());      
			assertEquals("Sgt. Peppers Lonely Hearts Club Band", blankDisc.getTitle());  
		}  
	}

}

 占位符

  Spring一直支持将属性定义到外部的属性的文件中,并使用占位符值将其插入到Spring bean中。在Spring装配中,占位符的形式为使用“${... }”包装的属性名称。

(1) BlankDisc.java

package com.springinaction.ch03.placeholder;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class BlankDisc {
  
	private String title;  
	private String artist;

	public BlankDisc(@Value("${disc.title}") String title, @Value("${disc.artist}") String artist) {   
		this.title = title;  
		this.artist = artist;
	}
  
	public String getTitle() {   
		return title; 
	}
    
	public String getArtist() {   
		return artist;
	}
  
}

或XML版本:

    <bean id="blankDisc" class="com.springinaction.ch03.placeholder.BlankDisc"
          c:title="${disc.title}"
          c:artist="${disc.artist}">
    </bean>
注意:不能使用“c:_title”,只能使用"c:title"或"c:_0","c:"命名空间后面直接跟构造参数名称或者下标

(2) PlaceholderConfig.java

package com.springinaction.ch03.placeholder;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

@Configuration
@ComponentScan("com.springinaction.ch03.placeholder")
@PropertySource("classpath:/com/springinaction/ch03/placeholder/app.properties")
public class PlaceholderConfig {

	/*
	 * 3.1开始官方文档推荐优先使用PropertySourcesPlaceholderConfigurer取代PropertyPlaceholderConfigurer
	 */
	@Bean
	public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer ();
	}
	
}

       为了使用占位符,必须配置一个PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。从Spring3.1开始,推荐使用PropertySourcesPlaceholderConfigurer,因为它能够基于Spring Environment及其属性源来解析占位符。

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:c="http://www.springframework.org/schema/c"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	   http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
	   http://www.springframework.org/schema/context
	   http://www.springframework.org/schema/context/spring-context-4.3.xsd">
	
    <bean id="blankDisc" class="com.springinaction.ch03.placeholder.BlankDisc"
          c:_0="${disc.title}"
          c:_1="${disc.artist}">
    </bean>
    <context:property-placeholder location="classpath:/com/springinaction/ch03/placeholder/app.properties"/>
    
</beans>

测试:
package com.springinaction.ch03.placeholder.test;

import static org.junit.Assert.*;

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;

import com.springinaction.ch03.placeholder.BlankDisc;
import com.springinaction.ch03.placeholder.PlaceholderConfig;

public class EnvironmentInjectionTest {

	@RunWith(SpringJUnit4ClassRunner.class) 
	@ContextConfiguration(classes=PlaceholderConfig.class) 
	public static class InjectFromProperties {
    
		@Autowired
		private BlankDisc blankDisc;
     
		@Test  
		public void assertBlankDiscProperties() {   
			assertEquals("The Beatles", blankDisc.getArtist());   
			assertEquals("Sgt. Peppers Lonely Hearts Club Band", blankDisc.getTitle());   
		}   
	}

}


相关文章: