Spring实战 第三章高级装配

《Spring 实战》 读书笔记

第三章 高级装配

3.1环境与profile

由于开发环境和生产环境的不同,必须要有一种方法来配置DataSource,使其在每种环境下都会选择最为合适的配置。

其中一种方式就是在单独的配置类(或XML文件)中配置每个bean,然后在构建阶段(可能会使用Maven的profile)确定要将哪一个配置编译到可部署的应用中。这种方式的问题在于要为每种环境重新构建应用。

3.1.1配置profile bean

1.在Java中配置profile

Spring为环境相关的bean所提供的解决方案其实与构建时的方案没有太大的差别。当然,在这个过程中需要根据环境决定该创建哪个bean和不创建哪个bean。不过Spring并不是在构建的时候做出这样的决策,而是等到运行时再来确定。这样的结果就是同一个部署单元(可能会是WAR文件)能够适用于所有的环境,没有必要进行重新构建。

在3.1版本中,Spring引入了bean profile的功能。要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。

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

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig{
    @Bean
    public DataSource dataSource(){
        ...
    }
}

@Profile注解应用在了类级别上。它会告诉Spring这个配置类中的bean只有在dev profile激活
时才会创建。如果dev profile没有激活的话,那么带有@Bean注解的方法都会被忽略掉。

在Spring3.1中,只能在类级别上使用@Profile注解。不过,从Spring3.2开始,也可以在方法级别上使用@Profile注解,与@Bean注解一同使用。这样的话,就能将这两个bean的声明放到同一个配置类之中。

@Configuration
public class DataSourceConfig{
    
    @Bean(destroyMethod="shutdowm")
    @Profile("dev")
    public DataSource embeddedDataSource(){
        ...
    }

    @Bean
    @Profile("prod")
    public DataSource jndiDataSource(){
        ...
    }

}

这里有个问题需要注意,尽管每个DataSource bean都被声明在一个profile中,并且只有当规定的
profile激活时,相应的bean才会被创建,但是可能会有其他的bean并没有声明在一个给定的
profile范围内。没有指定profile的bean始终都会被创建,与激活哪个profile没有关系。

2.在XML中配置profile

1.可以通过<beans>元素的profile属性,在XML中配置profile bean;
2.可以在根<beans>元素中嵌套定义<beans>元素,而不是为每个环境都创建一个profile XML文件。

除了所有的bean定义到了同一个XML文件之中,这种配置方式与定义在单独的XML文件中的实际效果是一样的。

3.1.2激活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。

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

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

使用profile进行测试

Spring提供了@ActiveProfiles注解,可以使用它来指定运行测试时要激活哪个profile。

@RunWith(SpringJUnit4ClassRunner.calss)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest{
    ...
}

3.2条件化的bean

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

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

可以看到,@Conditional中给定了一个Class,它指明了条件——MagicExistsCondition。
@Conditional将会通过Condition接口进行条件对比:

public interface Condition{
    boolean matches(ConditionContext ctxt,
                    AnnotatedTypeMetadata metadata);
}

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

在本例中,我们需要创建Condition的实现并根据环境中是否存在magic属性来做出决策。

public class MagicExistsCondition implements Condition{
    public boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata){
        Environment env = context.getEnvirronment();
        return env.containsProperty("magic");
    }
}

MagicExistsCondition中只是使用了ConditionContext得到的Environment,但Condition
实现的考量因素可能会比这更多。matches()方法会得到ConditionContext和AnnotatedType
Metadata对象用来做出决策。
ConditionContext是一个接口,大致如下所示:
public interface ConditionContext{
    BeanDefinitionRegistry getRegistry();
    ConfigurableListableBeanFactory getBeanFactory();
    Environment getEnvironment();
    ResourceLoader getResourceLoader();
    ClassLoader getClassLoader();
}

通过ConditionContext,可以做到如下几点:

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

AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他的注解,AnnotatedTypeMetadata也是一个接口:

public interface AnnotatedTyoeMetadata{
    boolean isAnnotated(String annotationType);
    Map<String,Object> getAnnotationAttributes(String annotationType);
    Map<String,Object> getAnnotationAttributer(
                    String annotationType,boolean classValuesAsString);
    MultiValueMao<String,Object> getAllAnnotationAttributes(
                    String annotationType);
    MultiValueMap<String,Object> getAllAnnotationAttributes(
                    String annotationType,boolean classValuesAsString);
}

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

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

3.3处理自动装配的歧义性

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

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

3.3.1标示首选的bean

在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装配时的歧义性。在Spring中,可以通过@Primary来表达最喜欢的方案。@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中。

1.将@Component注解的IceCream bean声明为首选的bean:
    @Component
    @Primary
    public class IceCream implements Dessert{
        ...
    }

2.通过Java配置显式地声明IceCream:
    @Bean
    @Primary
    public Dessert iceCream(){
        return new IceCream();
    }

使用XML配置bean:元素有一个primary属性用来指定首选的bean:

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

如果标示了两个或者更多的首选bean,那么久产生了新的歧义性,久解决歧义性问题而言,限定符是一种更为强大的机制。

3.3.2限定自动配置的bean

Spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限制条件。

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

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

为@Qualifier注解所设置的参数就是想要注入的bean的ID。所有使用@Component注解声明的类都
会创建为bean,并且bean的ID为首字母变为小写的类名。更准确地讲,@Qualifier("iceCream")
所引用的bean要具有String类型的“iceCream”作为限定符。如果没有指定其他的限定符的话,所有
的bean都会给定一个默认的限定符,这个限定符与bean的ID相同。

创建自定义的限定符

可以为bean设置自己的限定符,而不是依赖于将bean ID作为限定符。在这里所需要做的就是在
bean声明上添加@Qualifier注解。例如,它可以与@Component组合使用:

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

在这种情况下,cold限定符分配给了IceCream bean。因为它没有耦合类名,因此可以随意重构Ice
Cream的类名,而不必担心会破坏自动装配。在注入的地方,只要引用cold限定符就可以了:
@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

当通过Java配置显式定义bean的时候,@Qualifier也可以与@Bean注解一起使用:
@Bean
@Qualifier("cold")
public Dessert iceCream(){
    return new IceCream();
}

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

使用自定义的限定符注解

当两个类都存在@Qualifier("cold")时,有存在歧义性。
再添加一个@Qualifier注解:
@Component
@Qualifier("cold")
@Qualifier("creamy")
public class IceCream implements Dessert{...}

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

在注入点中,可以将范围缩小到IceCream:
@Autowired
@Qualifier("cold")
@Qualifier("creamy")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

这里存在一个问题:Java不允许在同一个条目上重复出现相同类型的多个注解。Java8允许出现重复
的注解,只要这个注解本身在定义的时候带有@Repeatable注解就可以。不过,Spring的@Qualifi
er注解并没有在定义时添加@Repeatable注解。

可以创建自定义的限定符注解,借助这样的注解来表达bean所希望限定的特性。这里所需要做的就是
创建一个注解,它本身要使用@Qualifier注解来标注。这样我们将不再使用@Qualifier("cold")
,而是使用自定义的@Cold注解,该注解定义如下:
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,
         ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold{ }

通过在定义时添加@Qualifier注解,它们就具有了@Qualifier注解的特性。它们本身实际上就成为了限定符注解。

为了创建自定义的条件化注解,我们创建一个新的注解并在这个注解上添加了@Conditional。为了创建自定义的限定符注解,我们创建一个新的注解并在这个注解上添加了@Qualifier。这种技术可以用到很多的Spring注解中,从而能够将它们组合在一起形成特定目标的自定义注解。

3.4bean的作用域

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

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

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

单例是默认的作用域,如果选择其他的作用域,要使用@Scope注解,它可以与@Component或者@Bean一起使用。

例如,如果使用组件扫描来发现和声明bean,那么可以在bean的类上使用@Scope注解,将其声明为原
型bean:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad{...}

这里,使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了原型作用域。当然也可
以使用@Scope("prototype"),但是使用SCOPE_PROTOTYPE常量更加安全并且不易出错。

在Java配置中将Notepad声明为原型bean,可以组合使用@Scope和@Bean来指定所需的作用域:
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad(){
    return new Notepad();
}

使用XML来配置bean的话,可以使用<bean>元素的scope属性来设置作用域:
<bean id="notepad"
        class="com.myapp.Notepad"
        scope="prototype" />

3.4.1使用会话和请求作用域

在Web应用中,如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事情。例如,在典型的电子商务应用中,可能会有一个bean代表用户的购物车。如果购物车是单例的话,那么将会导致所有的用户都会向同一个购物车中添加商品。另一方面,如果购物车是原型作用域的,那么在应用中某一个地方往购物车中添加商品,在应用的另外一个地方可能就不可用了,因为在这里注入的是另外一个原型作用域的购物车。

就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大,要指定会话作用域,我们可以使用@Scope注解,它的使用方式与指定原型作用域是相同的:

@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
       proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){...}

这里,将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值时session)。
这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart
bean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean实际上相    
当于单例的。
要注意的是,@Scope同时还有一个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是会话作用域的,此时并不存在。知道某个用户进入系统,创建了会话之后,才会出现Shopping
Cart实例。
另外,系统中将会有多个ShoppingCart实例,每个用户一个。我们并不想让Spring注入某个固定的
ShoppingCart实例到StoreService中。我们希望的是当StoreService处理购物车功能时,它所
使用的ShoppingCart实例恰巧是当前会话所对应的的那一个。
Spring并不会将实际的ShoppingCart bean注入到StoreService中,Sring会注入一个到
ShoppingCart bean的代理,如下图所示。这个代理会暴露于ShoppingCart相同的方法,所以
StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,
代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。

[外链图片转存失败(img-Gcjzn8j0-1567469263172)(C:\Users\li.ba\Pictures\Saved Pictures\作用域代理能够延迟注入请求和会话作用域的bean.png)]

  proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并    将调用委托给实现bean。
  如果ShoppingCart是接口而不是类的话,这是可以的(也是最为理想的代理模式)。但如果ShoppingCart是一个具体的类的话,Spring就没有办法创建基于接口的代理了。此时,它必须要将proxyMode属性设置为ScopedProxyMode.TARGET.CLASS,以此来表明要以生成目标类扩展的方式创建代理。

注意:请求作用域的bean也会面临相同的装配问题。因此,请求作用域的bean应该也以作用域代理的方式进行注入。

3.4.2在XML中声明作用域代理

如果需要使用XML来声明会话或者请求作用域的bean,那么就不能使用@Scope注解机器proxyMode属性了。元素的scope属性能够设置bean的作用域。要设置代理模式,需要使用Spring aop命名空间的一个新元素:

<bean id="cart"
		class="com.myapp.ShoppingCart"
		scope="session">
	<aop:scope-proxy />
</bean>

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

3.5运行时值注入

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

但是bean装配的另外一个方面指的是将一个值注入到bean的属性或者构造器参数中。

@Bean
public CompactDisc sgtPeppers(){
	return new BlankDisc("Sgt.Pepper's Lonely Hearts Club Band","The Beatles");
}
或者使用XML:
<bean id="sgtPeppers"
		class="soundsystem.BlankDisc"
		c:_title="Sgt.Pepper's Lonely Hearts Club Band"
		c:_artist="The Beatles" />
上面代码分别用java和XML实现的相同的需求:为BlankDisc bean设置title和artist,但它在实现的时候是将值硬编码在配置类中的。有时候硬编码是可以的,但有的时候,我们可能会希望避免硬编码值,而是想让这些值在运行时再确定。为了实现这些功能,Spring提供了两种在运行时求值的方式:
1.属性占位符(Property placeholder)
2.Spring表达式语言(SpEL)

3.5.1注入外部的值

在Spring中,处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性。如下代码展现了一个基本的Spring配置类,它使用外部的属性来装配BlankDisc bean。

@Configuration
//声明属性源
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig{
	@Autowired
	Environment env;
	@Bean
	public BlankDisc disc(){
		retnrn new BlanKDisc(env.getProperty("disc.title"),
							 env.getProperty("disc.artist"));
	}
}
在本例中,@PropertySource引用了类路径中一个名为app.properties的文件。它大致会如下所示:
disc.title=Sgt.Peppers Lonely Hearts Club Band
disc.artist=The Beatles
这个属性文件会加载到Spring的Environment中,稍后可以从这里检索属性。同时,在disc()方法中,会创建一个新的BlankDisc,它的构造器参数是从属性文件中获取的,而这是通过调用getProperty()实现的。

深入学习Spring的Environment:

​ Environment的getProperty()方法并不是获取属性的唯一方法,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)

1.返回String类型的值;
2.返回String类型的值,在指定属性不存在的时候,会使用一个默认值;
3和4.剩下的两种getProperty()方法与前面两种非常类似,但是他们不会将所有值都视为String类型。例如,假设想要获取的值所代表的含义是连接池中所维持的连接数量。如果我们从属性文件中得到的是一个String类型的值,那么在使用之前还需要将其转换为Integer类型。但是,如果使用重载形式的getProperty()的话,就能非常便利地解决这个问题:
int connectionCount = env.getProperty("db.connection.count",Integer.class,30);

Environment还提供了几个与属性相关的方法,如果在使用getProperty()方法的时候没有指定默认值,并且这个属性没有定义的话,获取到的值时null。如果希望这个属性必须要定义,可以使用getRequiredProperty()方法:

@Bean
public BlankDisc disc(){
	return new BlankDisc(
		env.getRequiredProperty("disc.title"),
		env.getRequiredProperty("disc.artist"));
}
在这里,如果disc.title和disc.artist属性没有定义的话,将会抛出IllegalStateException异常。

如果想检查一下属性是否存在的话,可以调用Environment的containsProperty()方法:

boolean titleExists = env.containProperty("disc.title");

如果想将属性解析为类的话,可以使用getPropertyAsClass()方法:

Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class",CompactDisc.class);

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

1.String[] getActiveProfiles():返回激活profile名称的数组;
2.String[] getDefaultProfiles():返回默认profile名称的数组;
3.boolean acceptsProfiles(String... profiles):如果environment支持给定profile的话,就返回true。

通常来讲,我们并不会频繁使用Environment相关的方法,但是知道有这些方法还是有好处的。

解析属性占位符:

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

作为样例,我们可以在XML中按照如下的方式解析BlankDisc构造器参数:

<bean id="sgtPeppers"
		class="soundsystem.BlankDisc"
		c:_title="${disc.title}"
		c:_artist="${disc.artist}" />

如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话,可以使用@Value注解,它的使用方式与@Autowired注解非常相似:

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

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

如下的@Bean方法在Java中配置了PropertySourcesPlaceholderConfigurer:

@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfugurer(){
	renturn new PropertySourcesPlaceholderConfigurer();
}
  如果你想使用XML配置的话,Spring context命名空间中<context:property-placeholder>元素将会为你生成PropertySourcesPlaceholderConfigurer bean。

解析外部属性能够将值的处理推迟到运行时,但是它的关注点在于根据名称解析来自于Spring Environment和属性源的属性。而Spring表达式语言提供了一种更通用的方式在运行时计算所要注入的值。

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

Spring3引入了Spring表达式语言(Spring Expression Language,SpEL),它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。

​ SpEL拥有很多特性,包括:

1.使用bean的ID来引用bean;
2.调用方法和访问对象的属性;
3.对值进行算术、关系和逻辑运算;
4.正则表达式匹配;
5.集合操作。

​ SpEL表达式要放到“#{…}”之中,这与属性占位符有些类似,属性占位符需要放到“${…}”之中。

最简单的SpEL表达式:
#{1}
除去“#{...}”标记之后,剩下的就是SpEL表达式体了,也就是一个数字常量。

#{T(System).currentTimeMillis()}
它的最终结果是计算表达式的那一刻当前时间的毫秒数。T()表达式会将java.lang.System视为Java中对应的类型,因此可以调用static修饰的currentTimeMillis()方法。

SpEL表达式也可以引用其他的bean或其他bean的属性。例如,如下的表达式会计算得到ID为sgtPeppers的bean的artist属性:
#{sgtPeppers.artist}

还可以通过systemProperties对象引用系统属性:
#{systemProperties]['disc.title']}

​ 如果通过组件扫描创建bean的话,在注入属性和构造器参数时,可以使用@Value注解,这与之前看到的属性占位符非常类似。不过,在这里我们所使用的不是占位符表达式,而是SpEL表达式:

public BlankDisc(
		@Value("#{systemProperties['disc.title']}") String title,
		@Value("#{systemProperties['disc.artist']}") String artist){
	this.title = title;
    this.artist = artist;
}

​ 在XML配置中,可以将SpEL表达式传入或的value属性中,或者将其作为p-命名空间或c-命名空间条目的值:

<bean id="sgtPeppers"
		class="soundsystem.BlankDisc"
		c:_title="#{systemProperties['disc.title']}"
		c:_artist="#{systemProperties['disc.artist']}" />

表示字面值:

浮点值:
#{3.14159}
科学计数法:
#{9.87E4}
String类型字面值:
#{'Hello'}
Boolean类型的值:
#{false}

引用bean、属性和方法:

SpEL所能做的另外一件基础的事情就是通过ID引用其他的bean.
将一个bean装配到另一个bean中:
#{sgtPeppers}

在一个表达式中引用属性:
#{sgtPeppers.artist}

引用bean的方法:
#{artistSelector.selectArtist()}

引用bean的方法的方法:
#{artistSelector.selectArtist().toUpperCase()}
如果selectArtist()返回值是null的话,会出现NullPointerException,我们可以使用类型安全的运算符:
#{artistSelector.selectArtist()?.toUpperCase()}
与之前只是使用点号(.)来访问toUpperCase()方法不同,现在我们使用了"?."运算符。这个运算符能够在访问它右边的内容之前,确保它所对应的元素不是null。所以,如果selectArtist()的返回值不是null的话,那么SpEL将不会调用toUpperCase()方法。表达式的返回值会是null。

在表达式中使用类型:

如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关键的运算符。例如,为了在SpEL中表达Java的Math类,需要按照如下的方式使用T()运算符:
T(java.lang.Math)
这里所示的T()运算符的结果会是一个Class对象,代表了java.lang.Math。如果需要的话,甚至可以将其装配到一个Class类型的bean属性中。但是T()运算符的真正价值在于它能够访问目标类型的静态方法和常量。
例如:
将PI值装配到bean属性中:
T(java.lang.Math).PI
调用T()运算符所得到的类型的静态方法:
T(java.lang.Math).random()

SpEL运算符:

例如
#{2 *T(java.lang.Math).PI * circle.radius}

当使用String类型的值时,“+”运算符执行的是连接操作,与在Java中是一样的:
#{disc.title + ' by ' + disc.artist}

SpEL同时还提供了比较运算符,用来在表达式中对值进行对比。比较运算符有两种形式:符号形式和文本形式。在大多数情况下,符号运算符与对应的文本运算符作用是相同的,使用哪一种形式均可以。
#{counter.total == 100}
#{counter.total eq 100}

三元运算符的一个常见场景就是检查null值,并用一个默认值来替代null。
#{disc.title ?: 'Rattle and Hum'}
判断disc.title的值是不是null,如果是null的话,那么表达式的计算结果就会是“Rattle and Hum”。这种表达式通常称为Elvis运算符。

计算正则表达式:

当处理文本时,有时检查文本是否匹配某种模式是非常有用的。SpEL通过matches运算符支持表达式中的模式匹配。matches运算符对String类型的文本(作为左边参数)应用正则表达式(作为右边参数)。matches的运算结果会返回一个Boolean类型的值:如果与正则表达式相匹配,则返回true;否则返回false。
判断一个字符串是否包含有效的邮件地址:
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}

计算集合:

引用列表中的一个元素:
#{jukebox.songs[4].title}

[]运算符用来从集合或数组中按照索引获取元素,实际上,它还可以从String中获取一个字符:
#{'This is a test'[3]}
这个表达式引用了String中的第四个(基于零开始)字符,也就是“s”。

SpEL还提供了查询运算符(.?[]),它会用来对集合进行过滤,得到集合中的一个子集。例如,希望得到jukebox中artist属性为Aerosmith的所有歌曲:
#{jukebox.songs.?[artist eq 'Aerosmith']}

SpEL还提供了另外两个查询运算符:“.^[]”和“.$[]”,它们分别用来在集合中查询第一个匹配项和最后一个匹配项。例如,它会查找列表中第一个artist属性为Aerosmith的歌曲:
#{jukebox.songs.^[artist eq 'Aerosmith']}

SpEL还提供了投影运算符(.![]),它会从集合的每个成员中选择特定的属性放到另外一个集合中。例如:将title属性投影到一个新的String类型的集合中:
#{jukebox.songs.![title]}

实际上,投影操作可以与其他任意的SpEL运算符一起使用。比如,我们可以使用如下的表达式获得Aerosmith所有歌曲的名称列表:
#{jukebox.songs.?[artist eq 'Aerosmith'].![title]}

3.6小结

​ Spring profile解决了Spring bean要跨各种部署环境的通用问题。在运行时,通过将环境相关的bean与当前激活的profile进行匹配,Spring能够让相同的部署单元跨多种环境运行,而不需要进行重新构建。

​ Profile bean是在运行时条件化创建bean的一种方式,但是Spring4提供了一种更为通用的方式,通过这种方式能够声明某些bean的创建与否要依赖于给定条件的输出结果。结合使用@Conditional注解和Spring Condition接口的实现,能够为开发人员提供一种强大和灵活的机制,实现条件或地创建bean。

​ 两种解决自动装配歧义性的方法:首选bean以及限定符。借助限定符将范围缩小到只有一个符合条件的bean。

​ Spring能够让bean以单例、原型、请求作用域或者会话作用域的方式来创建。在声明请求作用域或会话作用域的bean的时候,我们还学习了如何创建作用域代理,它分为基于类的代理和基于接口的代理的两种方式。

​ Spring表达式语言,能够在运行时计算要注入到bean属性中的值。

e属性投影到一个新的String类型的集合中:
#{jukebox.songs.![title]}

实际上,投影操作可以与其他任意的SpEL运算符一起使用。比如,我们可以使用如下的表达式获得Aerosmith所有歌曲的名称列表:
#{jukebox.songs.?[artist eq ‘Aerosmith’].![title]}


> 3.6小结

​	Spring profile解决了Spring bean要跨各种部署环境的通用问题。在运行时,通过将环境相关的bean与当前激活的profile进行匹配,Spring能够让相同的部署单元跨多种环境运行,而不需要进行重新构建。

​	Profile bean是在运行时条件化创建bean的一种方式,但是Spring4提供了一种更为通用的方式,通过这种方式能够声明某些bean的创建与否要依赖于给定条件的输出结果。结合使用@Conditional注解和Spring Condition接口的实现,能够为开发人员提供一种强大和灵活的机制,实现条件或地创建bean。

​	两种解决自动装配歧义性的方法:首选bean以及限定符。借助限定符将范围缩小到只有一个符合条件的bean。

​	Spring能够让bean以单例、原型、请求作用域或者会话作用域的方式来创建。在声明请求作用域或会话作用域的bean的时候,我们还学习了如何创建作用域代理,它分为基于类的代理和基于接口的代理的两种方式。

​	Spring表达式语言,能够在运行时计算要注入到bean属性中的值。

​	依赖注入能够将组件及其协作的其他组件解耦,与之类似,AOP有助于将应用组件和跨多个组件的任务进行解耦。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值