《Spring实战》读书笔记2

一、高级装配

1、环境与profile

     当程序从一个环境移动到另一个环境,往往配置会不同,比如数据库配置、加密算法等配置。比如:数据库配置:

开发环境dev的数据库配置可能是这样的:

@Bean(destoryMethod="shutdown")
public DataSource dataSource() {
	return new EmbeddedDatabaseBuilder()
	.addScript("classpath:schema.sql")
	.addScript("classpath:test-data.sql")
	.build();
}

生产环境prod的数据库配置可能是这样的:

@Bean
public DataSource dataSource() {
	JndiObjectFactoryBean jndiObjectFactoryBean = 
		new JndiObjectFactoryBean();
	jndiObjectFactoryBean.setJndiName("jdbc/myDS");
	jndiObjectFactoryBean.setResourceRef(true);
	jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
	return (DataSource) jndiObjectFactoryBean.getObject();
}
而,QA环境中数据库配置可能是这样的:

@Bean(destoryMethod="close")
public DataSource dataSource() {
	BasicDataSource dataSource = new BasicDataSource();
	dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
	dataSource.setDriverClassName("org.h2.Driver");
	dataSource.setUsername("sa");
	dataSource.setPassword("password");
	dataSource.setInitialSize(20);
	dataSource.setMaxActive(30);
	
	return dataSource;
}


      针对上述三种不同的环境就有不同的配置,Spring会在运行时决定该创建哪个bean和不创建哪个bean。这样的结果是同一个部署单元能够适用于所有的环境,没有必要进行重新构建。

(1)、配置 profile bean

     在3.1版本中,Spring引入了bean profile的功能。

     使用@Profile注解指定某个bean属于某个profile,例如:指定dev环境的数据库配置bean:

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {
	@Bean(destoryMethod="shutdown")
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder()
		.addScript("classpath:schema.sql")
		.addScript("classpath:test-data.sql")
		.build();
	}
}
    类似的,可以指定属于prod环境的数据库配置,在Spring3.1中,只能在类级别上使用@Profile注解。不过,从Spring3.2开始,也可以在方法级别上使用@Profile注解,因此,可以将属于不同profile的bean配置在同一个配置类中,例如:

@Configuration
public class DataSourceConfig {
	
	@Bean(destoryMethod="shutdown")
	@Profile("dev")
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder()
		.addScript("classpath:schema.sql")
		.addScript("classpath:test-data.sql")
		.build();
	}
	
	@Bean
	@Profile("prod")
	public DataSource dataSource() {
		JndiObjectFactoryBean jndiObjectFactoryBean = 
			new JndiObjectFactoryBean();
		jndiObjectFactoryBean.setJndiName("jdbc/myDS");
		jndiObjectFactoryBean.setResourceRef(true);
		jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
		return (DataSource) jndiObjectFactoryBean.getObject();
	}
}
    注意:没有指定profile的bean始终都会被创建,与激活哪个profile没有关系

(2)、如何在XML中配置profile

      我们可以通过<beans>元素的profile属性,在XML中配置profile 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:jdbc="http://www.springframework.org/schema/jdbc"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jdbc
	http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"
	profile="dev">
  
  <jdbc:embedded-database id="dataSource">
	<jdbc:script location="classpath:schema.sql" />
	<jdbc:script location="classpath:test-data.sql" />
  </jdbc:embedded-database>
  
</beans>
     类似地,也可以将profile设置为prod,创建适用于生产环境的配置。

     另外,可以在跟元素<beans>元素中嵌套定义<beans>元素,而不是为每个环境都创建一个profile XML文件,这样可以将属于不同profile的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:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jee
	http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jdbc
	http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"
	profile="dev">
  <beans profile="dev">
	  <jdbc:embedded-database id="dataSource">
		<jdbc:script location="classpath:schema.sql" />
		<jdbc:script location="classpath:test-data.sql" />
	  </jdbc:embedded-database>
  </beans>
  
  <beans profile="qa">
	  <bean id="dataSource"
		class="org.apache.commons.dbcp.BasicDataSource"
		destory-method="close"
		p:url="jdbc:h2:tcp://dbserver/~/test"
		p:driverClassName="org.h2.Driver"
		p:username="sa"
		p:password="password"
		p:initialSize="20"
		p:maxActive="30">
  </beans>
  
  <beans profile="prod">
	   <jee:jndi-lookup id="dataSource"
		jndi-name="jdbc/myDatabase"
		resource-ref="true"
		proxy-interface="javax.sql.DataSource" />
  </beans>
</beans>

     另外,Springi提供了@ActiveProfiles注解,可以用来指定运行测试时要激活哪个profile。例如:

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

2、条件化的bean

      条件化bean是满足特定的条件后bean才会被创建。

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

例如,我们再JavaConfig中配置一个条件化的bean:

@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {
	return new MagicBean();
}
      上述代码中,@Conditional注解中给定了一个class,它指明了条件。上例中的MagicExistsConditon类实现了Condition接口,@Conditional将会通过Condition接口进行条件对比,Condition接口的定义如下:

public interface Condition {
	boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata);
}
      MagicExistsConditon类实现了Condition接口,实现如下:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
	Environment env = context.getEnvironment();
	return env.containsProperty("magic");
}
      上述代码,matches方法检查环境变量中是否存在magic属性,如果存在返回true,不存在就返回false。因此在bean配置中的意思就是,如果MagicExistsCondition类的matches方法返回true,那么该标记的bean就会被创建,否则,该bean不被创建。

(1)、Condition接口中的matches()方法参数的含义

      matches()方法有两个参数:ConditionContext、AnnotatedTypeMetadata,这两个参数提供可能的条件信息,来辅助做出决策。

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

public interface ConditionContext {
	BeanDefinitionRegistry getRegistry();
	ConfigurableListableBeanFactory getBeanFactory();
	Environment getEnvironment();
	ResourceLoader getResourceLoader();
	ClassLoader getClassLoader();
}
     注:getRegistry()返回的BeanDefinitionRegistry检查bean定义;

            getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;

            借助getEnvironment()返回的Enrionment检查环境变量是否存在以及它的值是什么;

            读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;

            借助getClassLoader()返回的ClassLoader加载并检查类是否存在。

     AnnotatedTypeMetadata也是一个接口,大致如下:

public interface AnnotatedTypeMetadata {
	boolean isAnnotated(String annotationType);
	Map<String, Object> getAnnotationAttributes(String annotationType);
	Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);
	MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);
	MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);
}
     注:该接口能够让我们检查带有@Bean注解的方法上还有什么其他注解等信息。


(2)、Spring 4中, @Profile注解的实现

     从 Spring 4开始, @Profile注解进行了重构,使其基于@Conditional 和 Condition实现。如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileConditon.class)
public @interface Profile {
	String[] value();
}
class ProfileCondition implement Condition {
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		if (context.getEnvironment() != null) {
			MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
			if (attrs != null) {
				for (Object value : attrs.get("value")) {
					if (context.getEnvironment().acceptsProfiles(((String[])value))) {
						return true;
					}
				}
				return false;
			}
		}
		return true;
	}
}
 注:ProfileCondition通过AnnotatedTypeMetadata得到了用于@Profile注解的所有属性,借助该信息,它会明确地检查value属性,该属性包含了bean的profile名称,然后,它根据通过ConditionContext得到的Environment来检查(借助acceptsProfiles()方法)该Profile是否处于激活状态。


3、处理自动装配的歧义性

      当Spring试图装配属性时,发现没有唯一、无歧义的可选值(通常是由于存在多个实现类的情况),Spring会抛出NoUniqueBeanDefinitionException。

     当确实发生歧义性时,Spring提供了多种可选方案来解决这个问题。

(1)、标示首选的bean

      在声明bean的时候,将其中一个bean设置为首选(primary)bean能够避免自动装配的歧义性,当遇到歧义性的时候,Spring将会使用首选的bean,而不是其他可选的bean。

      标示首选bean的方法有多种:

第一种:与@Component组合用在组件扫描的bean上,例如:

@Component
@Primary
public class IceCream implements Dessert {
	...
}
第二种:与@Bean组合使用用在Java配置类的bean上,例如:

@Bean
@Primary
public Dessert iceCream() {
	return new IceCream();
}
第三种,如果使用xml配置bean的话,可以使用<bean>元素的primary属性,例如:

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


注意:如果标示了不止一个的首选bean,那么Spring还是无法从多个可选的bean中做出选择,仍然会抛出异常。

(2)、限定自动装配的bean

      设置首选bean的局限性在于@Primary无法将可选方案的范围限定到唯一一个无歧义性的选项中。它只能标示一个优先的可选方案,当首选bean的数量超过一个时,我们并没有其他的方法进一步缩小可选范围。

      Spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限定条件。如果将所有的限定符都用上后依然存在歧义性,那么你可以继续使用更多的限定符来缩小选择范围。

     @Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。例如:

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
	this.dessert = dessert;
}
      注:上例中,为Qualifier注解所设置的参数就是想要注入的bean的ID。

上述方法也存在问题:例如:当IceCream类被重命名为Gelato的话,bean的ID和默认的限定符会变为gelato,就无法匹配setDessert()方法中的限定符,自动装配就会失败。原因是:setDessert()方法上所指定的限定符与要注入的bean的名称是紧耦合的。


(3)、创建自定义的限定符

      鉴于上述方法的缺点,我们可以为bean设置自己的限定符,例如:

@Component
@Qualifier("cold")
public class IceCream implements Dessert {...}
     注:上述代码中的,“cold”限定符是我们自定义的,这样就可以不与类名紧耦合了,在注入的地方,如下使用:

@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) {
	this.dessert = dessert;
}
    @Qualifier也可以与@Bean一起使用,例如:

@Bean
@Qualifier("cold")
public Dessert iceCream() {
	return new IceCream();
}
(4)、使用自定义的限定符注解

     上述方法有时会出现问题,比如,当我们存在多个bean都带有“cold”自定义的限定符,那么在自动装配时Spring还是无法解决歧义性的问题。而且Java不允许在同一个条目上重复出现相同类型的多个注解,因此,不能通过再增加自定义限定符的方式解决歧义性问题。

       但是,Spring提供了另外的方法,那就是自定义的限定符注解。借助这样的注解来表达bean所希望限定的特性。及创建自己的注解。

例如:创建@Cold注解

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, 
		 ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {}
例如:创建@Creamy注解

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, 
		 ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy {}
      这样,在组件扫描时,可以为组件添加上自己的注解,例如:

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

@Component
@Cold
@Fruity
public class Popsicle implements Dessert {....}
     在注入点,可以使用必要的限定符注解进行任意组合,从而将可选的范围缩小到只有一个bean满足需求。例如:

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

4、bean的作用域

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

【】单例(Singleton): 在整个应用中,只创建bean的一个实例。

【】原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。

【】会话(Session):在Web应用中,为每个会话创建一个bean实例。

【】请求(Rquest):在Web应用中,为每个请求创建一个bean实例。

     注:单例是默认的作用域,如果需要显式地指定作用域,要使用@Scope注解,它可以和@Component或@Bean一起使用。

例如:如果使用组件扫描来发现和声明bean,可以在bean的类上使用@Scope注解:

@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" />

(1)、使用会话和请求作用域

      在Web应用中,常常需要实例化在会话和请求范围内共享的bean。为bean指定会话作用域的方式跟指定原型作用域相同,例如:

@Component
@Scope (
	value=WebApplicationContext.SCOPE_SESSION,
	proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() {...}
      注:上例中,通过WebApplicationContext.SCOPE_SESSION的设置,Spring会为Web应用中的每个会话创建一个ShoppingCart。另外proxyMode属性,用来解决将会话或请求作用域的bean注入到单例bean中所遇到的问题。

     会话或请求作用域bean注入到单例bean中时遇到的问题:

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

     另外,系统中可能存在多个ShoppingCart实例,每一个用户一个,但是我们并不希望Spring注入某个固定的ShoppingCart实例到StoreService中,而是注入当前会话的ShoppingCart。

      解决该问题的方法:

     Spring引入了作用域代理的方法,来解决该问题。代理会暴露与ShoppingCart相同的方法,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内的真正的ShoppingCart bean。(即作用域代理能够延迟注入请求和会话作用域的bean)

     如果,proxyMode属性值为ScopeProxyMode.INTERFACES,则表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean;

     如果,proxyMode属性值为ScopeProxyMode.TARGET_CLASS,则这个代理要以生成目标类扩展的方式创建。

(2)、在XML中声明作用域代理

     如果使用XML来声明会话或请求作用域的bean,除了需要使用<bean>元素的scope属性设置bean的作用域外,还要使用Spring aop命名空间的<aop:scoped-proxy>元素。

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

<bean id="cart"
	class="com.myapp.ShoppingCart"
	scope="session">
	<aop:scoped-proxy/>
</bean>
注:上述代码,声明作用域为会话的bean,同时指定代理模式为创建目标类的方式。

<bean id="cart"
	class="com.myapp.ShoppingCart"
	scope="session">
	<aop:scoped-proxy proxy-target-class="false" />
</bean>
注:上述代码,声明作用域为会话的bean,同时指定代理模式为基于接口的方式。


     另外,为了使用<aop:scoped-proxy>元素,必须在XML配置中声明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/aop
	http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
	...
</bean>

5、运行时值注入

      运行时值注入,是指将一个值注入到bean的属性或者构造器的参数中。

      为了实现这些功能,Spring提供了两种在运行时求值的方式:

    【】:属性占位符

    【】:Spring表达式语言(SpEL)

(1)、注入外部的值 (即直接从Environment中检索属性)

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

例如:下列程序使用外部属性来装配BlankDisc bean:

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig {
	
	@Autowired
	Environment env;
	
	@Bean
	public BlankDisc disc() {
		return 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中,稍后可以从这里检索属性。


     *关于Spring的Environment

     Environment提供了getProperty()方法的四个重载形式:

【】String  getProperty(String key)   // 返回String类型的值

【】String  getProperty(String key, String defaultValue)  // 指定默认值的版本(当指定的属性不存在时,会使用一个默认值)

【】T getProperty(String key, Class<T> type)   // 返回指定类型的值

【】T getProperty(String key, Class<T> type, T defaultValue)   

     注:如果没有指定默认值,当属性不存在时,getProperty()会返回null;


     相应地,getRequiredProperty()方法与getProperty()方法类似,但是getRequiredProperty()方法要求获取的属性必须定义,如果没有定义将会抛出IllegalStateException异常。


     containsProperty()方法:检查某个属性是否存在。

     getPropertyAsClass()方法:将属性解析为类。例如:

Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class);
    除了属性相关的功能外,Environment还提供了一些方法来检查哪些profile处于激活状态:

【】String[] getActiveProfiles() : 返回激活profile名称的数组

【】String[] getDefaultProfiles() : 返回默认profile名称的数组

【】boolean acceptsProfiles(String... profiles) : 如果environmet支持给定profile的话,返回true;


(2)、解析属性占位符

        直接从Environment中检索属性是非常方便的,尤其是在Java配置类中装配bean的时候,但是,Spring也提供了通过占位符装配属性的方法,这些占位符的值会来源于一个属性源。

        在Spring装配中,占位符的形式为使用“${...}”包装的属性名称。

如果要在XML中解析构造参数,可以如下所示:

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

如果是依赖组件扫描和自动装配来创建和初始化组件的话,可以使用@Value注解,例如:在BlankDisc类中,可以如下初始化:

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

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

      配置方法,如果使用java配置类,可以如下,例如:

@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
	return new PropertySourcesPlaceholderConfigurer();
}

    如果使用XML配置,Spring Context命名空间中的<context:property-placeholder>元素将会为你生成PropertySourcesPlaceholderConfigurer bean 如下:

<?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"
  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 />
	
</bean>


注:上述两种方法的关注点都是根据名称解析来自于Spring Environment和属性源的属性。而Spring表达式语言则提供了一种更加通用的方式在运行时计算所要注入的值。


(3)、使用Spring表达式语言进行装配

    Spring 3引入了Spring表达式语言(Spring Expression Language, SpEL), 它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数值中。

    SpEL表达式要放到“#{...}”之中,去除“#{...}”标记之后,剩下的就是SpEL表达式体。

例如,在Java类中使用SpEL注入值:

public BlankDisc(
	@Value("#{systemProperties['disc.title']}") String title,
	@Value("#{systemProperties['disc.artist']}")  String artist) {
	this.title = title;
	this.artist = artist;
}
上述代码,展示了SpEL从系统属性中获取属性值。

例如:

<bean id="sgtPeppers"
	class="soundsystem.BlankDisc"
	c:_title="#{systemProperties['disc.title']}"
	c:_title="#{systemProperties['disc.artist']}" />
上述代码,展示了SpEL在xml声明中的使用方法。

【】使用SpEL表示字面值:

      #{3.14159}   // 表示浮点值

      #{9.87E4} // 使用科学记数法的方式进行表示

      #{‘hello’}  // 表示String类型的字面值

      #{false} // 表示Boolean类型的值

【】引用bean、属性和方法

      SpEL可以通过ID引用其他的bean。

      例如:#{sgtPeppers} // 引用bean sgtPeppers

                #{sgtPeppers.artist} // 引用sgtPeppers的artist属性

                #{sgtPeppers.selectArtist()} // 调用sgtPeppers bean的selectArtist()方法,获得返回值

                #{sgtPeppers.selectArtist().toUpperCase()} // 调用selectArtist()方法,再对返回的字符串调用toUpperCase()方法

                #{sgtPeppers.selectArtist()?.toUpperCase()} // 与上句作用一样,不同点是如果selectArtist()方法返回null,不会抛出NullException异常,而是SpEL表达式的返回值为null。

【】在表达式中使用类型

      在SpEL中访问类的静态方法和常量时,要依赖T()运算符,例如:

      T(java.lang.Math)  // 这里T()运算符的结果是一个class对象,代表了java.lang.Math。

      T(java.lang.Math).PI  // 访问类的常量

      T(java.lang.Math).random() // 访问类的静态方法

【】SpEL运算符

      算术运算符:+ 、-、*、/、%、^

      比较运算符:<、>、==、<=、>=、lt、gt、eq、le、ge

      逻辑运算符:and、or、not、|

      条件运算符:?:(ternary)、?:(Elvis)

      正则表达式:matches

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

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

           #{disc.title + ' by ' + disc.artist}

           #{counter.total == 100}  等价于 #{counter.total eq 100}

           #{scoreboard.score > 1000 ? "Winnder!" : "Loser"}

           #{disc.title ?: 'Rattle and Hum'}  // 如果disc.title的值不是null, 表达式的结果就是disc.title的值, 如果disc.title的值为null,表达式的值就是'Rattle and Hum',  // 注:这种表达式通常称为Elvis运算符。

【】计算正则表达式

       SpEL通过matches运算符支持表达式中的模式匹配,如果与正则表达式匹配,则返回true,否则返回false。例如:

          #{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}

【】计算集合

      #{jukebox.songs[4].title} // 这个表达式会计算songs集合中下标为4的元素的title属性。

      #{jukebox.songs[T(java.lang.Math).random() * jukebox.songs.size()].title} // 从jukebox中随机选择一首歌

      #{‘This is a test’[3]} // 该表达式引用String中的第四个字符,也就是‘s’。

      #{jukebox.songs.?[artist eq 'Aerosmith']}  // 查询运算符(.?[])会用来对集合进行过滤,得到集合的一个子集,SpEL在迭代歌曲列表songs的时候,会对歌曲集合中的每一个条目计算[]内部的表达式,当[]内部表达式值为ture时,那么相应条目将会被放到新的集合中,否则不会,本例中,[]内部表达式检查歌曲的artist属性是不是等于Aerosmith。

     注:“.^[]” 运算符,用来在集合中查询第一个匹配项; “.$[]”运算符,用来在集合中查询最后一个匹配项。

     #{jukebox.songs.^[artist eq 'Aerosmith']} // 用来查找列表中第一个artist属性为Aerosmith的歌曲;

     注:“.![]” 运算符,投影运算符,它会从集合的每个成员中选择特定的属性放到另外一个集合中,例如:

     #{jukebox.songs.![title]} // 该表达式获得songs集合中所有歌曲title属性值,组成的名称列表。

     #{jukebox.songs.?[artist eq 'Aerosmith'].![title]} // 该表达式获得艺术家 Aerosmith的所有歌曲的名称列表。




     



























猜你喜欢

转载自blog.csdn.net/xiaoxiaoyusheng2012/article/details/78218700