spring(3)高级装配

【0】README

0)本文部分文字描述转自:“Spring In Action(中/英文版)”,旨在review  spring(3)高级装配 的相关知识;

【1】环境与profile(考虑数据库配置)

1)使用嵌入式数据库

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

2)使用JNDI从容器中获取DataSource

@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();
}

3)选择不同的DataSource配置

@Bean(destroyMethod="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;
}

Attention)我们必须要有一种方法来配置DataSource,使其在每种环境下都会选择最为合适的配置;其中一种方式是在单独的配置类(或XML 文件)中配置每个bean,然后在构建阶段(可能会使用Maven的profiles)确定要将哪一个配置编译到可部署的应用中;

problem+solution)

problem)从开发阶段迁移到QA 阶段,重新构建可能不会出大问题,但从QA阶段迁移到 生产阶段,重新构建可能会引入bug;

solution)Spring所提供 的 解决方案并不需要重新构建;

【1.1】配置 profile bean

1)intro:spring 在重新构建的过程中需要根据环境决定该创建那个 bean 和 不创建那个bean;不过spring 并不是在构建时做出决策,而是等待运行时再来确定;这样的结果就是同一个部署单元能够适用于所有的环境,没有必要进行重新构建;

2)spring 引入了bean profile:要使用profile,首先要将所有不同的 bean 定义整理到一个或多个profile中,在将应用部署到每个环境时,要确保对应的 profile 处于激活状态 ;

3)在javaConfig中,可以使用 @Profile 注解指定某个bean属于哪一个profile;

看个荔枝)注解@Profile(干货——注解@Profile的作用)

荔枝1)嵌入式数据库可能会配置为如下形式:

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {
@Bean(destroyMethod="shutdown")
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.H2)
        .addScript("classpath:schema.sql")
        .addScript("classpath:test-data.sql")
        .build();
    }
}

荔枝2)使用JNDI从容器中获取DataSource 的 Profile配置

@Configuration
@Profile("prod")
public class ProductionProfileConfig {
    @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();
    }
}

荔枝3)从spring3.2开始,@Profile注解既可以在方法级别上使用,也可以在类级别上使用;(还可以和 @Bean 注解一起使用)

Attention)

A1)没有定义在profile中的bean 都会被创建,无论profile 激活与否;

A2)而定义在 profile中的bean,当且仅当对应的 profile 被激活时才可以创建;

4)在XML中配置 profile

4.1)通过<beans>元素的profile属性,在XML 中配置profile bean;

4.2)还可以在根<beans>元素中嵌套定义 <beans> 元素,而不是为每个环境都创建一个 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" 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">
<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"
  destroy-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>

【1.2】激活profile

1)spring在确定哪个 profile处于激活状态时,需要依赖两个独立的属性: spring.profiles.active and spring.profiles.default ; 如果设置了 active属性的话,相应的Profile被激活,否则,查看default属性的值,如果default的值
没有设置的话,那就会忽略掉 profile中的bean的创建;

2)多种方法设置上述两个属性;

method1)作为 DispatcherServlet 的初始化参数;

method2)作为web 应用的上下文参数;

method3)作为 JNDI条目;

method4)作为环境变量;

method5)作为JVM的 系统属性;

method6)在集成测试类上, 使用 @ActiveProfiles 注解设置;(干货——注解@ActiveProfile的作用)

原书作者的设置方法:喜欢用DispatcherServlet 的参数将spring.profiles.default属性设置为开发环境的profile,会在Servlet上下文中进行设置;

看个荔枝)在web.xml 中设置 spring.profiles.default 属性

Attention) spring.profiles.active and spring.profiles.default 中的profile都是 复数形式: 这意味着可以同时激活多个 profile,列出多个profile 名称,以逗号分隔来实现;

3)使用profile进行测试

3.1)intro:spring提供了 @ActiveProfiles注解,来指定测试时要激活哪个profile;

3.2)下面的荔枝展示了 使用 @ActiveProfiles注解 激活 dev profile;

【2】条件化bean

1)intro:当满足给定条件时,才装配相应的bean;组合@Conditional注解 和 @Bean注解;(干货——注解@Conditional的作用)

对以上代码的分析(Analysis):

A1)matches() 方法:会得到 ConditionContext 和 AnnotatedTypeMetadata 对象用来做出决策;

A2)ConditionContext是一个接口:

public interface ConditionContext {
     BeanDefinitionRegistry getRegistry();
     ConfigurableListableBeanFactory getBeanFactory();
     Environment getEnvironment();
     ResourceLoader getResourceLoader();
     ClassLoader getClassLoader();
}

A3)通过ConditionContext,可以做到如下几点(works):

work1)借助getRegistry方法返回的BeanDefinitionRegistry 检查bean的定义;

work2)借助getBeanFactory方法返回的ConfigurableListableBeanFactory 检查bean是否存在,查看bean的属性

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

work4)读取并检查getResourceLoader方法返回的ResourceLoader 所加载的资源;

work5)借助getRegistry方法返回的BeanDefinitionRegistry 检查bean的定义;

work6)借助getClassLoader方法返回的ClassLoader 加载并检查类是否存在;

A4)AnnotatedTypeMetadata 能够让我们检查带有 @Bean 注解的方法上还有什么其他的注解;

public interface AnnotatedTypeMetadata {
     boolean isAnnotated(String annotationName);  // 判断带有@Bean注解的方法是不是还有其他的注解.
     Map<String, Object> getAnnotationAttributes(String annotationName);
     Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
     MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
     MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}

2)从spring4 开始,@Profile注解进行了重构,使其基于@Conditional 和 Condition实现:@Profile注解如下所示:

对以上代码的分析(Analysis):

A1)ProfileCondition 通过 AnnotatedTypeMetadata  得到了用于 @Profile 注解的所有属性;

A2)根据 通过ConditionContext得到的 Environment 来检查(acceptProfiles()方法)该profile 是否处于激活状态;

【3】处理自动装配的歧义性

1)problem+solution

1.1)problem测试用例:(error info: No qualifying bean of type [com.spring.chapter3.Disc] is defined: expected single matching bean
but found 2: jayChou,leehom)

1.2)solution:spring 提供了多种可选方案来解决这样的问题。你可以将可选的bean 中的某个设为首选(primary)的bean,或者使用限定符来帮助 spring 将可选的bean的范围缩小到只有一个bean;

1.3)如果在XML 配置bean的话,设置primary属性为 true来指定;

Attention)如果标识了多个首选(Primary)bean的话,@Primary注解就无法正常工作了;

【3.2】限定自动装配的bean

1)problem+solution

1.1)problem:设置首选bean的 局限性: 在于@Primary 无法将可选方案的范围限定到唯一一个无歧义性的可选项中。当首选bean的数量超过一个四,我们并没有其他的方法来进一步缩小可选范围;

1.2)solution:spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到一个bena 满足所规定的限制条件;(使用@Qualifier注解)

看个荔枝)确保将jaychou 注入到CDPlayer中

2)创建自定义限定符:我们可以为bean设置自己的限定符,而不是依赖于将bean ID 作为限定符;

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

3)@Qualifier注解也可以和 @Bean注解一起使用

@Bean
@Qualifier("cold")
public Dessert iceCream() {
    return new IceCream();
}

4)使用自定义的限定符注解(干货——开发人员自己创建限定符注解)

4.1)problem+solution

@Component
@Qualifier("fashion")
public class JayChou implements Disc { ... }
@Component
@Qualifier("fashion")
public class Leehom implements Disc { ... }

4.1.1)problem:现在我们有两个带有“fashion”限定符的唱片,在自动装配Disc bean的时候,我们再次遇到了歧义性问题;

4.1.2)solution:需要更多的限定符来将可选范围限定到只有一个bean;(多个 @Qualifier 注解)

@Component
@Qualifier("fashion")
@Qualifier("cool")
public class JayChou implements Disc { ... }
@Component
@Qualifier("fashion")
@Qualifier("handsome")
public class Leehom implements Disc { ... }

5)problem+solution:

5.1)problem:java不允许在同一个条目上重复使用出现相同类型的多个注解;因为这样的话,编译器会报错;

5.2)solution:自定义限定符注解;(不能再干货——创建自定义的限定符注解)

6)how to build diy @Qualifier annotaion.

step1)不再使用 @Qualifier("fashion") 注解,使用自定义的 @Fashion这,该注解 的定义如下:

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Fashion{ }

Attention)

A1)自定义注解本身实际上就成为了 限定符注解;

A2)通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会再有java 编译器的限制或错误了;

7)所以,我们可以添加

@Component
@Qualifier("fashion")
@Qualifier("cool")
public class JayChou implements Disc { ... }
// 改为
@Component
@Fashion // a qualifier annotaion.
@Cool// a qualifier annotaion.
public class JayChou implements Disc { ... }

(干货——说白了,自定义限定符注解 就是多个标识而已,用于区别不同bean 和 对bean 进行分组(因为他们都属于fashion 组))

【4】 bean的作用域

1)intro: default case下,spring应用上下文中所有 bean都是以单例的形式创建的;也就是说,不管给定一个bean被注入到其他bean中多少次,每次所注入的都是 同一个实例;

2)problem+solution:显然,让多个对象引用同一个bean 是不合理的,因为对象中的内容是易变的,一个对象对bean做了修改,这会波及到其他bean的;

3)spring定义了多种作用域,可以基于这些作用域创建bean,包括(scope):

scope1)单例(Singleton):在整个应用中,只创建bean的一个实例;(干货——默认情况下,bean以单例模式创建)

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

scope3)会话(Session):在web 应用中,为每个会话创建一个 bean实例;

scope4)请求(Request):在web 应用中,为每个请求创建一个 bean实例;

Attention)

A1)default case下: 是单例作用域;

A2)如果选择其他作用域,要使用 @Scope注解,它可以和 @Component 或 @Bean 联用;(干货——@Scope注解的作用)

A3)不管用哪一种方式来声明原型作用域,每次注入或从 spring 应用上下文中检索该bean的时候,都会创建新的实例;

看个荔枝)将其声明为原型bean:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad { ... }

用XML 来配置的话,代码形式如下:
<bean id="notepad"
    class="com.myapp.Notepad"
    scope="prototype" />

【4.1】使用会话和请求作用域

1)指定会话作用域: 使用 @Scope 注解,它的使用方式与 指定原型作用域是相同的;

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

对以上代码的分析(Analysis):

A1)这会创建多个 ShoppingCart  bean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean 实际上相当于单例的;

A2)@Scope注解:同时还有一个 proxyMode 属性,它被设置成了 proxyMode=ScopedProxyMode.INTERFACES;这个属性解决了将会话或请求作用域的bean 注入到单例bean中所遇到的问题;

2)proxyMode 代理模式所要解决的问题:

2.1)假设我们要将 ShoppingCart bean在注入到 单例 StoreService bean 的Setter 方法中,如下所示:

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

对以上代码的分析(Analysis):

A1)因为StoreService 是一个单例bean,会在spring 应用上下文加载的时候创建;

A2)当它创建的时候,spring会试图 将 ShoppingCart bean 注入到 setShoppingCart() 方法中;但是ShoppingCart bean 是会话作用域的,此时并不存在。直到某个client
进入系统,创建了会话后,才会出现 ShoppingCart 实例;(干货——我此时才体会到了为什么需要懒加载)

A3)而且,系统中有多个 ShoppingCart 实例(多个购物车):每个用户一个。我们并不想让spring注入 到某个固定的 ShoppingCart 实例到 StoreService中。我们希望的是当StoreService 处理ShoppingCart 功能时,它所使用的 ShoppingCart 实例恰好是当前会话所对应的那一个;

A4)spring 并不会将实际的ShoppingCart bean 注入到 StoreService 中。spring 会注入一个到 ShoppingCart bean 的代理,如下图所示。这个代理会暴露与 ShoppingCart 相同的方法,所以 StoreService 会认为他是一个ShoppingCart。但是,当StoreService
调用 ShoppingCart 的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的 ShoppingCart bean;

(干货——懒加载的调用过程,引入了代理proxy)

3)如果 ShoppingCart 是接口不是类的话,这是可以的;但如果ShoppingCart 是具体的类,那么spring无法创建基于接口的代理了。这时,spring必须使用CGLIB 来生成基于类的代理;

3.1)所以,如果bean类型是具体的类的话,我们必须要将 proxyMode 属性设置为 ScopedProxy- Mode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理;

【4.2】在XML 中声明作用域代理

1)要设置代理模式,需要使用 spring aop 命名空间的一个新元素(<aop:scoped-proxy /> ):

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

2)为了使用 <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">
    ...
</beans>

【5】运行时值注入

1)problem+solution

1.1)problem:在实现的时候将值硬编码在配置类中。用XML 装配bean的话,同样值也会是硬编码的;

1.2)solution:为了避免硬编码,而是想让这些值在运行时再确定。spring 提供了两种在运行时求值的方式(ways):

way1)属性占位符(Property placeholder)

way2)spring 表达式语言(SpEL)

【5.1】注入外部的值

对以上代码的分析(Analysis):这个属性文件(app.properties)会加载到 spring 的 Environment中,稍后可以从这里通过 getProperty()方法 检索属性。

2)深入学习Spring的Environment

2.1)getProperty()方法有4个重载的变种形式(variant):

v1)String getProperty(String key)

v2)String getProperty(String key, String defaultValue)

v3)T getProperty(String key, Class<T> type)

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

2.2)稍微修改下源码,当指定属性不存在时,使用一个default value:

2.3)如果我们从属性文件中得到的是一个String类型的值,那么在使用前还需要将其转换为 integer,但是使用 getProperty()方法的重载形式,就能便利地解决这个问题:(干货——getProperty()重载方法的作用)

int connectionCount =  env.getProperty("db.connection.count", Integer.class, 30);

3)Environment方法概览(methods)

method1)getRequiredProperty()方法:如果你希望这个属性必须要定义;没有定义会抛出异常;

@Bean
public Disc disc() {
    return new JayChou(
        env.getRequiredProperty("disc.title"),
        env.getRequiredProperty("disc.artist"));
}

method2)containsProperty()方法:检查该属性是否存在;

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

method3)getPropertyAsClass()方法:如果想要吧属性解析为类的话;

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

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

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

method6)boolean acceptsProfiles(String... profiles):如果 environment 支持给定的 profile的话,返回ture;

4)解析属性占位符

4.1)intro:spring 一直支持将属性定义到 外部的属性文件中,并使用占位符将其插入到 spring bean中;

4.2)在spring装配中,占位符的形式为 使用 "${...}" 包装的属性名称;

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

4.3)如果我们依赖于组件扫描和自动装配来创建和初始化应用组建的话,就没有XML配置文件或者类了。此时,可以使用 @Value 注解,如下所示:

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

4.4)为了使用占位符,必须要配置一个 PropertySourcesPlaceholderConfigurer(spring3.1推荐使用),因为它能够基于spring
Environment 及其属性源来解析占位符;如下的@Bean方法配置了 PropertySourcesPlaceholderConfigurer

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

4.5)如果使用了XML配置 PropertySourcesPlaceholderConfigurer,推荐使用新元素 <context:property-placeholder>:

<?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 />
</beans>

Attention)

A1)解析外部属性能够将值的处理推迟到 运行时,但是它的关注点在于根据名称解析来自于 spring Environment和属性源的属性;

A2)而 spring 表达式语言提供了一种更通用的方式在 运行时计算所要注入的值;

【5.2】 使用spring 表达式语言进行装配

1)intro to spring表达式语言==Spring Expression Language, SpEL;

2)SpEL 拥有很多特性(characters):

c1)使用bean的ID 来引用bean;

c2)调用方法和访问对象的属性;

c3)对值进行算术,关系和逻辑运算 ;

c4)正则表达式匹配;

c5)集合操作;

3)SpEL 荔枝

3.1)first task : 要知道将 SpEL 表达式放到 " #{...} "之中,这与属性占位符有些类似,属性占位符需要放到 " ${...} " 之中;

3.2)荔枝组团来袭

#{T(System).currentTimeMillis()} : 计算表达式的那一刻当前时间的毫秒数;T() 表达式会将java.lang.System 视为 java中对应的类型;因此可以调用其 static 修饰的currentTimeMillis方法;
#{jaychou.artist}:得到id 为 jaychou 的bean 的artist属性;
#{systemProperties['disc.title']}:引用系统属性;

4)装配bean的时候如何使用这些表达式

4.1)通过组件扫描 创建bean的话,在注入属性和构造器参数时,我们可以使用 @Value 注解,这与之前看到的属性占位符有点类似,但现在我们要使用 SpEL表达式;

看个荔枝):从系统属性中获取专辑名称和艺术家的名字;

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

4.2)在XML配置中,可以将SpEL 表达式传入 <property> or <constructor-arg> 的value属性中,或者将其作为
p-命名空间或c-命名空间条目的值;

看个荔枝)构造器参数通过SePL 表达式设置;

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

5)SePL 表达式 表示字面量

组团荔枝)

#{3.14159} == 表示浮点值;
#{9.87E4} ==98700
#{'Hello'} ==计算String类型的字面值;
#{false} ==boolean类型的值;

6)引用bean,属性和方法(通过ID 引用其他bean)

组团荔枝)使用bean ID(jaychou) 作为SpEL 表达式;

#{jaychou}
#{jaychou.artist} // 对属性(artist)的引用;
#{jaychou.selectArtist()} // 调用bean上的方法;
#{jaychou.selectArtist().toUpperCase()} // 对方法的连续调用;
#{artistSelector.selectArtist()?.toUpperCase()} // 使用类型安全的运算符,当返回不为null时,才调用后面的方法;

7)在表达式中使用类型

7.1)T()运算符:如果要在 SpEL 中访问类作用域的方法和常量的话,要依赖T() 这个关键的运算符;(干货——T()
是 SpEL 中一个关键运算符)

组团荔枝,我们继续)

T(java.lang.Math) // 为了在SpEL 中表达java的Math类,可以像左侧这样使用 T() 运算符;
T(java.lang.Math).PI // 将PI 值装配到bean的属性中;
T(java.lang.Math).random() // 计算得到一个0~1 间的随机数;

8)SpEL 运算符

8.1)SpEL 提供了多个运算符,如下表所示:

组团荔枝来袭)

#{2 * T(java.lang.Math).PI * circle.radius}//计算圆周长;
#{T(java.lang.Math).PI * circle.radius ^ 2}//计算面积;
#{disc.title + ' by ' + disc.artist} // 连接字符串;
#{counter.total == 100} or #{counter.total eq 100}// 数字比较;
#{scoreboard.score > 1000 ? "Winner!" : "Loser"} //三元运算符的应用;
#{disc.title ?: 'Rattle and Hum'}// Elvis运算符,表达式判断disc.title是否为null,若是null的话,计算结果是 后面的字符串;

9)计算正则表达式

9.1)intro:SpEL 通过matches 运算符支持表达式中的模式匹配, 且matches() 函数会返回一个boolean 类型的值;

#{admin.email matches '[a-zA-Z0-9._%+-][email protected][a-zA-Z0-9.-]+\\.com'} // 判断一个字符串是否包含有效的邮件地址;

10)计算集合

组团荔枝来袭)

#{jaychou.songs[4].title}//计算songs集合中第5个(基于零开始)元素的title属性;(干货——SpEL 计算集合时的start index等于0);
#{jaychou.songs[T(java.lang.Math).random() * jaychou.songs.size()].title} // 随机取集合某个下标的song的title属性;
#{'This is a test'[3]} //返回第4个字母(s);
#{jaychou.songs.?[album eq '十二新作']}//SpEL 提供了查询运算符,用于对集合的过滤,得到集合的一个子集;(返回jaychou的十二新作专辑下的所有songs)

10.1)SpEL 提供了两个查询运算符: ".^[]", ".$[]";分别用于查询第一个匹配项和最后一个匹配项;

#{jaychou.songs.^[album eq '十二新作']}//查找列表中第一个属于十二新作专辑的歌曲;

10.2) SpEL提供了投影运算符:(.![]),它会从集合中的每个成员中选择特定的属性放到另一个集合中;

#{jaychou.songs.![title]}//将title属性投影到一个新的String类型的集合中;

10.3)投影操作可以和其它任意的 SpEL 运算符一起使用;

#{jaychou.songs.?[album eq '十二新作'].![title]} // 获得十二新作专辑下的所有歌曲名称;
// 难道没有发现,上述表达式等同于  select ... where...
时间: 2024-10-12 22:20:54

spring(3)高级装配的相关文章

Spring 之高级装配

[环境与Profile] 暂略 [条件化的bean] 暂略 [处理自动装配歧义性] 暂略 [ bean 的作用域] 在 @Componen . @Bean 下以及 XML 中的声明方式如下所示, @Component("LonelyHeartsClub") @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // Singleton 作用域是默认的,Prototype 作用域需要特别声明,每次注入都会创建一个新的 Bean 实例 public

spring学习总结——高级装配学习一(处理自动装配的歧义性)

我们已经看到如何使用自动装配让Spring完全负责将bean引用注入到构造参数和属性中.自动装配能够提供很大的帮助.不过,spring容器中仅有一个bean匹配所需的结果时,自动装配才是有效的.如果不仅有一个bean能够匹配结果的话,Spring此时别无他法,只好宣告失败并抛出异常.更精确地讲,Spring会抛出NoUniqueBeanDefinitionException. 当确实发生歧义性时,Spring提供了多种可选方案来解决这样的问题.你可以将可选bean中的某一个设为首选(primar

Spring的自动装配在session监听器失效

先看代码 package com.oa.listener; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import org.springframework.beans.factory.annotation.Autowired; import com.oa.service.Syste

Spring之自动装配bean

Spring之自动装配bean 最近学习Spring框架,参考资料是Spring IN ACTION----第一张内容飘过去~~ 从第二章的自动装配bean开始,不过学习Spring核心最重要的还是ioc的注入模式吧! 书上是这么说的----(概念问题,哈哈),首先普及几个概念 --------------------------------------------------------------------------------------------------------------

Spring 之自动化装配 bean 尝试

[Spring之自动化装配bean尝试] 1.添加dependencies如下所示(不是每一个都用得到 <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.9.RELEASE</version> </dependency

高级装配

环境与profile 在开发软件的时候,将应用程序从开发环境,迁移到测试环境,或者是迁移到生产环境都是一项挑战.最常见的就是对于DataSource的配置,这三种环境我们会根据不同的策略来生成DataSource bean.我们需要有一种方式来配置DataSource,使其在每种环境下都会选择对应的配置. 配置profile bean 要使用profile,首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到不同环境中,要确保对应的profile文件处于激活状态. 在

spring学习总结——装配Bean学习三(xml装配bean)

通过XML装配bean Spring现在有了强大的自动化配置和基于Java的配置,XML不应该再是你的第一选择了.不过,鉴于已经存在那么多基于XML的Spring配置,所以理解如何在Spring中使用XML还是很重要的.但是,我希望本节的内容只是用来帮助你维护已有的XML配置,在完成新的Spring工作时,希望你会使用自动化配置和JavaConfig. 一.创建XML配置规范 在使用XML为Spring装配bean之前,你需要创建一个新的配置规范.在使用JavaConfig的时候,这意味着要创建

spring中自动装配bean

首先用@Component注解类: package soundsystem: import org.springframework.stereotype.Component; @Component public class TestBean{ …… } 开启组件扫描spring才能自动装配bean,创建一个@ComponentScan注解的类 package soundsystem: import org.springframework.context.annotation.componentS

spring的自动装配,骚话@Autowired的底层工作原理

前言 开心一刻 十年前,我:我交女票了,比我大两岁.妈:不行!赶紧分! 八年前,我:我交女票了,个比我小两岁,外地的.妈:你就不能让我省点心? 五年前,我:我交女票了,市长的女儿.妈:别人还能看上你?分了吧! 今年,我挺着大肚子踏进家门.妈:闺女啊,你终于开窍了 ! 前情回顾 Spring拓展接口之BeanPostProcessor,我们来看看它的底层实现中讲到了spring对BeanPostProcessor的底层支持,并且知道了BeanPostProcessor的两个方法:postProce