高级装配(advanced wiring)

本文深入探讨Spring框架中的高级装配技巧,包括使用profile配置环境特异性bean,条件化bean的创建,解决自动装配歧义性,以及如何利用运行时值注入增强应用程序的灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

高级装配(advanced wiring)

配置profile bean

使用profile,可以设置bean在什么环境下会创建。

使用注解@Profile
语法:

  • @Profile(“environment”)

@Profile是类级别和方法级别,就是可以在类上使用和方法上使用。在类使用,表示该类设置的bean都是这种环境下会创建;在方法上使用,只有这个方法设置的bean在某种环境下使用。

@Profile可以是方法级别的,意味着不同环境创建的bean可以放到同一个JavaConfig配置文件中。

如果没有用这个注解,表示bean在任何环境都会创建。

demo:

@Configuration
@Profile("dev")
public class DevelopConfig {
    @Bean
    public DataSource xxxDataSource() {
        //创建XxxDataSource xxxDataSource的code
        return xxxDataSource;
    }
}
@Configuration
public class ProductConfig {
    @Bean
    @Profile("pro")
    public DataSource yyyDataSource() {
        //创建YyyDataSource yyyDataSource的code
        return yyyDataSource;
    }
}

以上代码表示,在开发环境dev中,使用XxxDataSource数据源,在生产环境pro中,使用YyyDataSource数据源。

在xml中配置profile

标签有profile属性,表示标签里面的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"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
  profile="dev">

  <!--beans-->

</beans>

表示在开发环境dev激活时这个xml下的bean才会被创建。

可以在一个xml文件中定义不同环境激活的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"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <beans profile="dev">
    <bean id="dataSource" class="xxx">
        <!---->
    </bean>
  </beans>
  
  <beans profile="prod">
    <bean id="dataSource" class="yyy">
        <!---->
    </bean>
  </beans>

</beans>

表示在开发环境,第一个数据源对象会创建,在生产环境,第二个数据源对象会创建。

激活profile

使用profile的步骤是:

  1. 定义profile
  2. 激活profile
定义profile

profile有2个属性:spring.profiles.active和spring.profile.default。

  • 如果设置了spring.profiles.active属性,那么它的值就会用来确定是哪个profile是激活的;
  • 如果spring.profiles.active没有配置,spring就会查找spring.profiles.default,使用它的值来确定哪个profile是激活的;
  • 如果spring.profiles.active和spring.profiles.default都没有配置,那么,spring只会创建没有profile限制的bean。

在web.xml中设置profile

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  
  
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>u/WEB-INF/spring/root-context.xml</param-value>
  </context-param>
  
  <!--为上下文配置默认的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>
  
  <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>
  </servlet>
  
  <servlet-mapping>
  	<servlet-name>appServlet</servlet-name>
  	<url-pattern>/</url-pattern>
  </servlet-mapping>
 
</web-app>
激活profile

使用注解@ActiveProfile激活profile

语法:
@ActiveProfile(“environment”)

  • @ActiveProfile是类级别的

demo:

@RunWith(Spring4JUnitClassRunner.class)
@ContextConfiguration(classes="{PresistenceTestConfig.class}")
@ActiveProfile("dev")
public class PersistenceTest {
    //...
}

这是激活了dev profile。

条件化的bean

从spring4开始,可以使用注解@Conditional来设置是否创建bean的条件。

语法:

  • @Conditional(xxxCondition.class)
  • 注解用到带有@Bean注解的方法上
  • 这个xxxCondition类要实现Condition接口,实现matches方法
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
  • 如果matches方法返回真,则创建带有@Conditional(xxxConditon.class)注解的bean

这里假设如果环境存在magic属性,则创建bean。

public class MagicExistsCondition implements Conditon {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment e = context.getEnvironment();
        return e.containsProperty("magic");
    }
}
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {
    return new MagicBean();
}

matches方法的ConditionContext和AnnotatedTypeMetadata可以组合更多的条件。
ConditionContext的方法有:

  • BeanDefinitionRegistry getRegistry():可以通过返回的BeanDefinitionRegistry检查bean的定义。
  • ConfigurableListableBeanFactory getBeanFactory():通过获取的ConfigurableListableBeanFactory检查bean是否存在,还有探查bean的属性。
  • Environment getEnvironment():获取环境对象,可以判断环境中是否有某种属性和值
  • ResourceLoader getResourceLoader():通过返回的ResourceLoader读取和检查加载的资源内容你。
  • ClassLoader getClassLoader():通过ClassLoader加载并检查类是否存在。

AnnotatedTypeMetadata的方法有:

  • boolean isAnnotated(String annotationName):可以判断带有@Bean注解的方法是否还有特定的其他注解。
  • Map<String, Object> getAnnotationAttributes(String annotationName):根据这个方法和下面的方法(这4个方法)可以检查@Bean注解的方法上其他注解的属性。
  • Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString)
  • MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName)
  • MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString)

处理自动装配的歧义性

如果用接口自动注入属性,而有1个以上的bean实现了这个接口,并且这些bean都放到了Spring的ApplicatonContext中,那么,到底注入哪个实现类bean,这里就会出现歧义,报错。

demo:

  • 接口
public interface Dessert {}
  • 实现类
@Component
public class Cake implements Dessert {}
@Component
public class Cookie implements Dessert {}
@Component
public class IceCream implements Dessert {}
  • 测试类
@Component
public class DessertTest {
    @Autowired
    private Dessert dessert;
}

运行时,汇报一下的错误:

nested exception is 
org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type [xxx.Dessert] is defined: 
expected single matching bean but found 3: Cake,Cookie,IceCream

但是通常只有一个实现类,所以没有这种歧义性。

但是如果出现歧义性时,有以下方法:

  • 将可选bean的某一个bean设置为首选(primary)的。
  • 使用限定符(qualifier)帮助Sprin将可选的bean的范围缩小到只有1个。
标示首选的bean

当自动装配出现歧义性时,我们可以设置某一个bean为首选(primary)的bean,在自动装配时,首先选择设置为首选(primary)的bean。我们可以使用注解@Primary来设置某个bean为首选bean。
语法:@Primary,有3种方式设置bean为首选。以下例子,设置Cake为首选bean。

  1. @Primary和@Component组合
@Component
@Primary
public class Cake implements Dessert {}
  1. @Bean和@primary组合
@Bean
@Primary
public Dessert cake() {
    return new Cake();
}
  1. 在xml中设置
<bean id="cake" class="com.desserteater.Cake" primary="true"></bean>

注意:如果有2个或以上的bean被设置为首选,那么,首选设置(@Primary或primary=“true”)将会失效,还是会出现歧义性。

为了解决歧义性,限定符是更加强大的机制。

限定自动装配的bean

在自动装配时,我们可以使用注解@Qualifier来把多个可选bean限定为只有一个。
知道@Component设置在class上时,bean的id是首字母小写的类名
语法:

  • @Qualifier(“限定符”)
  • @Qualifier和@Autowired组合来限定用某个bean

以下例子都把IceCream作为限定的一个bean。

关于限定符,我们可以使用bean的id。

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

但是,这里有个问题,如果我们重构了IceCream类,把类名改为了Gelato,那么,此时bean的id是gelato,不再是iceCream,上面的setter方法没有找到bean id为iceCream的bean。

对此,我们可以创建自定义的限定符,而不是使用bean的id作为限定符将@Component和@Qualifier组合

@Component
@Qualifier("cold")
public class IceCream implements Dessert {}
@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) {
    this.dessert = dessert;
}

这里创建了限定符cold,在setter方法中使用的是cold限定符,不是bean的id,所以,就算我们改变了类名,@Qualifier仍然生效。

通常,我们是使用特征或描述性的术语来作为限定符,如果有2个或以上的bean,他们的特征相同,那么,就会出现问题。

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

我们定义了一个bean,它的限定符也是cold,此时,setter方法就会出现歧义性。

这时,我们的思路是再定义一个限定符来区分这2个bean。

@Component
@Qualifier("cold")
@Qualifier("creamy")
public class IceCream implements Dessert {}
@Component
@Qualifier("cold")
@Qualifier("fruity")
public class Popsicle implements Dessert {}
@Autowired
@Qualifier("cold")
@Qualifier("creamy")
public void setDessert(Dessert dessert) {
    this.dessert = dessert;
}

但是,注意java不允许在同一个条目中使用相同类型的注解从Java8开始,在同一个条目中可以使用相同类型的注解,但是这个注解定义时要带有@Repeatable注解。但是@Qualifier注解定义中没有带有@Repeatable注解,所以不能使用2次@Qualifier注解。

对此,我们可以创建自定义的限定符注解,如下:

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

限定范围

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

Bean的作用域

所谓bean的作用域,就是bean的作用范围。

spring的作用域有以下几种:

  • 单例(singleton):在整个application中只有一个bean实例。
  • 原型(prototype):从application context中获取bean或者依赖注入的时候,都会创建一个新的bean实例。
  • 请求(request):在web应用中,每次请求,都会创建一个新的bean实例返回。
  • 回话(session):在web应用中,对每个会话,都创建一个新的bean实例返回。

spring的bean默认的作用域是单例(singleton)。
通常情况下,单例模式的一种理想的方案,但是有例外,例如,如果每次获取bean对象,都需要这个bean对象是初始化那时的状态,那么如果使用单例模式的话,bean对象的状态被改变了(污染了)。

在3种配置方式中(自动配置、Java显式配置和xml显式配置),我们都可以设置bean的作用域。

自动配置

在自动配置中使用注解@Scope和@Component组合使用

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

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)可以写成@Scope(“prototype”),但是使用 ConfigurableBeanFactory.SCOPE_PROTOTYPE的方式会更安全和不容易出错。

Java显式配置

在Java显式配置中将@Scope和@Bean组合使用。

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad() {
    return new Notepad();
}
xml显式配置

在标签中使用scope属性。

<bean id="notepad" class="com.myapp.Notepad" scope="prototype" />

request和session作用域

在web应用中,通常我们在每个会话或请求中都创建一个bean实例是比较好的。
例如:在电商系统的购物车功能中,如果购物车bean是单例的话,那么每个用户的购物车bean是共享的,不是自己加入到购物车的物品也出现在自己的购物车中,这就乱套了。如果购物车bean是原型模式,如果在一个地方放入物品到购物车,再到另一个地方时,原来放入购物车的物品没有了,因为这里购物车bean使用原型模式,每次从应用上下文中获取或注入时都会创建一个新的购物车bean实例返回。
所以,在这里,使用session作用域是一个好的方案,一个会话中只有一个购物车bean实例,一个用户只有一个购物车。在一个会话或请求中,会话bean或请求bean,其实在这个会话或请求中就是一个单例的bean。

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

现在要说说@Scope中的proxyMode属性,proxyMode = ScopedProxyMode.INTERFACES解决了将会话/请求作用域的bean注入到单例的bean所产生的问题。

例如我们要把购物车bean注入到storeService中,让StoreService来处理购物车。

@Component
public class StoreService {
    private ShoppingCart shoppingCart;

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

StoreService是单例模式,在spring加载应用上下文时创建StoreService bean,那么就需要注入ShoppingCart bean,而ShoppingCart bean还没有创建,ShoppingCart是会话作用域,只有当某个用户登入进系统创建了会话时,才会创建ShoppingCart bean。
而且,ShoppingCart bean会有多个,注入固定的ShoppingCart bean到StoreService不合适,我们想要的效果是StoreService bean注入相应用户的ShoppingCart bean。

这时,就是proxyMode = ScopedProxyMode.INTERFACES起作用了,proxyMode = ScopedProxyMode.INTERFACES作用是创建一个这些ShoppingCart bean的代理,将这个代理注入到StoreService bean中,当StoreService要调用购物车时,代理会委托相应用户的购物车bean来执行功能。如下图。

在这里插入图片描述

注意:proxyMode = ScopedProxyMode.INTERFACES需要ShoppingCart是接口而不是实现类。如果ShoppingCart是实现类,要使用proxyMode = ScopedProxyMode.TARGET_CLASS,表明代理是目标类的扩展生成的。

将request作用域的bean注入到单例bean中也一样有这些问题,所以也要使用proxyMode属性创建代理来解决上面所说的问题。

xml的方式设置代理

使用aop命名空间实现设置代理。

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

<aop:scoped-proxy />默认情况是使用CGLib创建目标类的代理。如果想设置基于接口的代理,将<aop:scoped-proxy />的target-proxy-class属性设置为false

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

使用aop,要在xml中声明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>

运行时值注入

依赖注入,除了注入一个对象,或者说注入引用外,还有一种重要的方式,就是注入值,除了使用硬编码的方式注入值,还有在运行时才注入值的方式。
运行时注入值有2种方式:

  • 属性占位符(property placeholder)
  • Spring表达式语言(SpEL)

Java显示配置硬编码注入值

@Bean
public BlankDisc disc {
	new BlankDisc("Field of hope", "seed");
}
注入外部的值

我们可以使用注解@PropertySourceEnvironment来获取外部的值,外部值是存放到属性源(属性文件)中。
首先在内路径中需要有一个属性文件,我们命名为app.properties

disc.title=filed of hope
disc.artist=seed

在spring中获取属性文件的属性

@Configuration
@PropertySource("classpath:soundsystem/app.properties")
public class SourceConfig {
	@Autowired
	private Environment env;

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

@PropertySource语法

  • @PropertySource(“classpath:属性文件的类路径”)

Environment的方法

获取属性的方法
  • String getProperty(String propertyName):根据属性名获取属性值(字符串类型),如果没有这个属性,会返回null
  • String getProperty(String propertyName, String defaultValue):根据属性名获取属性值(字符串类型),如果没有这个属性,会返回defaultValue
  • T getProperty(String propertyName, Class type):根据属性名获取属性值,返回值类型是由参数type决定,如果没有这个属性,会返回null
  • T getProperty(String propertyName, Class type, T defaultValue):根据属性名获取属性值,返回值类型是由参数type决定,如果没有这个属性,会返回defaultValue

当getProperty(String propertyName)中属性名不存在时,会返回null,如果想返回一个默认值的话,可以使用getProperty(String propertyName, String defaultValue)。
demo:

@Bean
public BlankDisc disc() {
	new BlankDisc(env.getProperty("disc.title", "love story"), env.getProperty("disc.artist", "tailor"));
}

上面后2个方法和前2个方法类似,不同的就只有后2个方法设置了返回值的类型。例如如果在属性文件中属性值是连接池中数据源的数量,如果我们使用前2个方法获取属性值,那么就返回的是字符串类型,我们还要转成整型,使用后2个方法,就可以直接获取整型了。
demo:

db.connection.count=30
int count = env.getProperty("db.connection.count", Interger.class, 20);

如果不想返回null或者默认值,而是想要这个属性一定要定义,可以使用getRequiredProperty方法。

  • getRequiredProperty(String propertyName) throw IllegalStateException
@Bean
public BlankDisc disc() {
	new BlankDisc(env.getRequiredProperty("disc.title"), env.getRequiredProperty("disc.artist"));
}

如果属性文件中没有定义这个属性,会抛出IllegalStateException异常。

也可以使用containsProperty方法检查属性文件中是否有某个属性

  • boolean containsProperty(String propertyName);存在返回true

除了可以获取上面的这些属性类型外,还可以使用getPropertyAsClass方法解析属性成Class对象。

  • Class getPropertyAsClass(String propertyName, Class type)
Class<CompactDisc> clazz = env.getPropertyAsClass("disc.class", CompactDisc.class)

除了处理属性的方法外,Environment还有检查profile是否处于激活状态的方法。

  • String[] getActiveProfiles():返回激活状态的profile的名字的数组
  • String[] getDefaultProfiles():获取默认的profile的名字的数组
  • boolean acceptProfile(String… profiles):如果environment支持给定的profile(s),那么返回true

虽然Environment不是经常使用,但是知道Environment的方法对我们也有不少好处。

属性占位符

使用属性占位符可以把值的注入延迟到运行时。

使用属性占位符的步骤:

  1. 配置PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean
  2. 使用属性占位符
配置PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean

从Spirng3.1开始,建议使用PropertySourcesPlaceholderConfigurer。

配置有2种方式:

  1. Java显式配置
  2. xml显式配置
Java显式配置

在Java配置类中,配置PropertySourcesPlaceholderConfigurer bean。

@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
}
xml显式配置

使用标签<context:property-placeholder /> 配置PropertySourcesPlaceholderConfigurer bean。要使用context命名空间。

<?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>
使用属性占位符

使用属性占位符可以在xml或自动配置中使用。

语法:
使用${…}包裹属性名

在xml中使用
<bean id="compactDisc" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist}" />
在自动配置中使用

使用注解@Value

public BlankDisc(
    @Value("${disc.title}") String title,
    @Value("${disc.artist}") String artist) {
    this.title = title;
    this.artist = artist;
}
Spring表达式语言

Spring表达式语言(Spring Expression Language, SpEL)

语法:使用**#{…}**包裹Spring表达式体

  • #{表达式体}

Spring表达式语言可以在xml和自动配置中使用。

xml

在xml中,SpEL可以在、、p命名空间和c命名空间中使用。

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

把bean id为compactDisc的bean的title和artist属性注入到sgtPeppers中

如果SpEL的值是字符串,可以使用toUpperCase()方法转成大写。例如上面的例子中将歌手名字转成大写

#{compactDisc.artist.toUpperCase()}

但是,歌手名可能是null,这时调用toUpperCase()方法会抛出NullPointerException异常。为了解决这个问题,可以使用 ?. 的方式。

#{compactDisc.artist?.toUpperCase()}

如果compactDisc.artist为null,就不会执行toUpperCase()方法,直接返回null。

自动配置

在自动配置中,使用注解@Value

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

Spring表达式可以注入的值类型有:

  • 字面量
  • bean、属性和方法
  • 类、静态属性和静态方法
  • 系统属性(属性文件里面的属性)
  • 运算符
  • 集合
字面量

在#{}里面放入字面量值。
字面量可以是:

  • 整数
  • 浮点数
  • 字符串
  • boolean值

支持科学表示法

demo:

#{1}:代表整数1
#{3.14159}
#{1.23E4}:代表整数1230
#{'abc'}
#{false}

其实,Spring表达式体单纯只有一个字面量意义不大,常用是和其他类型组合使用,例如做运算。

bean、属性和方法

Spring表达式体里面可以是bean、属性和方法。使用bean是用bean的id。

  • #{compactDisc}:返回id为compactDisc bean,可以把这个值注入到类属性的类型是CompactDisc的属性中
  • #{compactDisc.title}:获取id为compactDisc bean的title属性值
  • #{artistSelector.selectArtist()}:把selectArtist方法的返回值作为这个SpEL的值
类、静态属性和静态方法

在SpEL中使用类、静态属性和静态方法,要使用操作符T()
语法:
T(类):获取这个类的Class对象

  • #{T(java.lang.Math)}:得到Math类的Class对象,可以注入到属性类型是Class类型的属性中。
  • #{T(java.lang.Math).PI}:获取Math的PI
  • #{T(java.lang.Math).random()}:调用Math的random方法,得到0(include) - 1(exclude)的随机数
系统属性(属性文件里面的属性)

SpEL可以读取属性文件里面的属性。
语法:
#{systemProperties[‘属性名’]}

  • #{systemProperties[‘disc.title’]}:获取属性名是disc.title的值
运算符

SpEL支持的运算符如下

运算符类型运算符
数学运算符+,-,*,/,%,^
关系运算符>,gt,<,lt,== eq,>=,ge,<=,le
逻辑运算符and,or,not
关系运算符?:(ternary),?:(Elvis)
正则表达式matches
数学运算符

^:乘方,2 ^ 4:2的4次方

关系运算符

>和gt,符号运算符和字母运算符作用是一样的

关系运算符
  • ?:(ternary):三元表达式,通常在用在如果为null,返回一个默认值
#{scoreBoard.score > 1000 ? "winner" : "loser"}

如果bean id是scoreBoard的score属性值大于1000,返回"winner",否则返回"loser"

三元表达式通常在用在如果为null,返回一个默认值。

demo:如果歌名为null,返回tailor

#{disc.title ?: 'tailor'}

这个表达式通常被称作Elvis表达式

正则表达式

matches的使用方式是在matches左边放字符串,右边放正则表达式

语法:

  • 字符串 matches 正则表达式

demo:检查某个字符串是否是邮箱地址

#{admin.email matches '[A-Za-z0-9-._]+@[A-Za-z0-9-]+(\.[A-Za-z0-9]+)*(\.[A-Za-z]{2,6})'}

这个正则表达式不一定完全满足邮箱地址的左右情况,这里只是用来举例。

集合

使用’[索引]'的方式得到集合或数组中的某个元素

获取某个元素
  • []
#{jukeBox.songs[3]}

获取bean id是jukeBox bean的songs集合的第4个元素(索引从0开始)

#{jukeBox[3].artist}

获取bean id是jukeBox bean的songs集合的第4个元素的歌手名

[]也可以获取字符串某个索引的字符(索引从0开始)。

#{'hello world'[3]}

获取第四个字符l

获得子集
  • .?[]
#{jukeBox.songs.?[artist eq 'tailor']}

获取歌手名称是tailor的元素,组成一个新的集合

获取第一个或最后一个符合条件的元素
  • .^[]

  • .$[]

  • 使用 .^[] :获取第一个符合条件的元素

  • 使用 .$[] :获取最后一个符合条件的元素

#{jukeBox.songs.^[artist eq 'tailor']}

获取第一个歌手名称的tailor的元素

投影元素的某个属性

语法

  • .![属性名]

SpEL可以使用投影操作符 .![] 将集合元素的某个属性投影出来,形成新的集合(就是取出所有元素的某个属性值,放到一个新的集合中)。

demo:取出所有的歌名

#{jukeBox.songs.![title]}

.![]投影可以和其他组合使用

demo:取出tailor的所有歌名

#{jukeBox.songs.?[artist == 'tailor'].![title]}

注意:虽然SpEL很强大,但是如果写的SpEL太过智能,那么测试这个SpEL也更加重要,所以,尽量写简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值