前言

本文为 【Spring】Resources与Spring表达式语言(SpEL) 等相关知识,下边将对Resources(包含:Resource接口、内置的 Resource的实现、ResourceLoader接口、应用环境和资源路径),验证、数据绑定和类型转换(包含:BeanWrapper、PropertyEditor属性编辑器、类型转换、配置 DataBinder进行数据验证)等,Spring表达式语言(SpEL)(具体包含:SpEL简介、Bean 定义中的表达式)等进行详尽介绍~

博主主页:小新要变强 的主页 Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~ 算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~ Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)

↩️本文上接:最新最全面的Spring详解(二)——classpath扫描和组件管理

目录

文章标题

前言目录三、Resources1️⃣Resource接口2️⃣内置的 Resource的实现3️⃣ResourceLoader接口4️⃣应用环境和资源路径

四、验证、数据绑定和类型转换1️⃣BeanWrapper2️⃣PropertyEditor属性编辑器3️⃣类型转换4️⃣配置 DataBinder进行数据验证

五、Spring表达式语言(SpEL)1️⃣SpEL简介2️⃣ Bean 定义中的表达式

后记

三、Resources

​ Java拥有标准【java.net.URL】类和各种URL前缀的标准处理程序,不幸的是,对于所有底层资源的访问来说,还不够充分。 例如,没有标准化的【URL】用来访问需要从类路径或相对于【ServletContext】获取资源的方式,而spring为我们解决了这些问题。

1️⃣Resource接口

Spring的【Resource】接口位于【org.springframework.core.io】 包,他抽象了对资源的访问的能力。 下面提供了【Resource】接口的概述, Spring本身广泛地使用了Resource接口。

public interface Resource extends InputStreamSource {

boolean exists();

boolean isReadable();

boolean isOpen();

boolean isFile();

URL getURL() throws IOException;

URI getURI() throws IOException;

File getFile() throws IOException;

ReadableByteChannel readableChannel() throws IOException;

long contentLength() throws IOException;

long lastModified() throws IOException;

Resource createRelative(String relativePath) throws IOException;

String getFilename();

String getDescription();

}

2️⃣内置的 Resource的实现

Spring包含了几个内置的 Resource 实现,如下所示:

(1)UrlResource

UrlResource包装了java.net.URL,可以用来访问任何需要通过URL访问的对象,例如文件、HTTPS目标、FTP目标等。 所有URL都用一个标准化的“String”表示,这样就可以使用适当的标准化前缀来表示不同类型的URL。 这包括用于访问文件系统路径的’ file: ‘,用于通过https协议访问资源的’ https: ‘,用于通过ftp访问资源的’ ftp: '等。

(2)ClassPathResource

该类表示应该从【类路径】中获取的资源。 它使用线程上下文类装入器、给定的类装入器或给定的类装入资源。

(3)FileSystemResource

这是【java.io】的【Resource】实现。

(4)PathResource

这是一个【java.nio.file】的【资源】实现。

(5)ServletContextResource

这是【ServletContext】资源的【Resource】实现,它解释了相关web应用程序根目录中的相对路径。

(6)InputStreamResource

一个【InputStreamResource】是一个给定的【InputStream】的【Resource】实现。 只有当没有特定的【资源】实现适用时,才应该使用它。 特别是,如果可能的话,最好使用【ByteArrayResource】或任何基于文件的【Resource】实现。

(7)ByteArrayResource

这是一个给定字节数组的【资源】实现。 它为给定的字节数组创建一个ByteArrayInputStream。 它可以从任何给定的字节数组加载内容,而不需要求助于一次性使用的InputStreamResource。

3️⃣ResourceLoader接口

ResourceLoader 接口定义了加载资源的基本能力和方式。 下面的例子显示了 ResourceLoader接口定义:

public interface ResourceLoader {

Resource getResource(String location);

ClassLoader getClassLoader();

}

所有应用程序上下文(applicationContext)都实现了【ResourceLoader】接口。 因此,可以所有的【应用程序上下文实现(ClassPathXmlA...)】都拥有加载资源的能力。

当您在特定的应用程序上下文中调用’ getResource() ‘时,如果指定的位置路径【没有特定的前缀】,您将返回【适合该特定应用程序上下文中】的’ Resource ‘类型。 例如,假设以下代码片段是在’ ClassPathXmlApplicationContext '实例上运行的:

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

针对 ClassPathXmlApplicationContext,该代码返回’ ClassPathResource '。针对FileSystemXmlApplicationContext实例运行相同的方法,它将返回 ‘FileSystemResource’。针对WebApplicationContext,它会返回’ ServletContextResource '。它同样会为每个上下文返回适当的对象。

另一方面,你也可以通过指定特殊的【’ classpath: '前缀】来强制使用【ClassPathResource】,无论应用程序的上下文类型是什么,如下面的示例所示:

Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");

类似地,您可以通过指定任何标准的java.net.URL前缀来强制使用【UrlResource】。 下面的例子使用了【file】和【https】前缀:

Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");

Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");

下表总结了将’ String ‘对象转换为’ Resource '对象的策略:

前缀举例说明classpath:classpath:com/myapp/config.xml从类路径加载。file:file:///data/config.xml作为一个“URL”从文件系统加载。 请参见’ FileSystemResource ’ Caveats。https:https://myserver/logo.png作为一个 URL加载。(none)/data/config.xml依赖于底层的 ApplicationContext。

4️⃣应用环境和资源路径

本节介绍如何【使用资源】创建应用程序上下文,包括使用XML的快捷方式、使用通配符以及其他细节。

(1)构建应用程序上下文

应用程序上下文构造函数通常采用【字符串或字符串数组】作为资源的位置路径,例如组成上下文定义的XML文件。

当这样的位置路径没有前缀时,从该路径构建并用于加载beanDifination的特定【Resource】类型取决于我们使用的这个特定的应用程序上下文。 例如,考虑下面的例子,它创建了一个’ ClassPathXmlApplicationContext ':

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

beanDifination是从类路径加载的,因此他使用了【ClassPathResource】。 但是,考虑下面的例子,它创建了一个’ FileSystemXmlApplicationContext ':

ApplicationContext ctx =

new FileSystemXmlApplicationContext("conf/appContext.xml");

现在从【文件系统】位置加载beanDifination(在本例中,相对于当前工作目录)。

注意,在位置路径上使用特殊的【classpath前缀】或标准URL前缀会覆盖为加载beanDifination而创建的【默认类型Resource】。 考虑以下例子:

ApplicationContext ctx =

new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");

使用【FileSystemXmlApplicationContext】从类路径加载beanDifination。 然而,它仍然是一个“FileSystemXmlApplicationContext”。 如果它随后被用作【ResourceLoader】,任何没有前缀的路径仍然被视为文件系统路径。

(2)源路径中的通配符

应用程序上下文构造函数值中的资源路径可以是简单路径,每个路径都有到【目标资源】的一对一映射。当然,也可以包含特殊的【classpath*:】前缀或【内部ant模式】, 后者实际上都是通配符。

注意,这种通配符特定于在应用程序上下文构造函数中使用资源路径(或直接使用“PathMatcher”实用程序类层次结构时),并在构造时解析。 它与“资源”类型本身无关。 你不能使用’ classpath*: '前缀来构造一个【实际的Resource】,因为一个resource一次只指向一个资源。

Ant-style的匹配原则

Ant-style 模式

路径位置可以包含ant样式的模式,如下例所示:

/WEB-INF/*-context.xml

com/mycompany/**/applicationContext.xml

file:C:/some/path/*-context.xml

classpath:com/mycompany/**/applicationContext.xml

当路径位置包含【ant样式模式】时,解析器将遵循更复杂的过程来尝试解析通配符。

classpath * :前缀

当构造基于xml的应用上下文时,位置字符串可以使用特殊的’ classpath*: '前缀,如下所示:

ApplicationContext ctx =

new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");

classpath:和classpath * :的区别

classpath: : 表示从该工程中的类路径中加载资源,classpath:和classpath:/是等价的,都是相对于类的根路径。资源文件库标准的在文件系统中,也可以在JAR或ZIP的类包中。classpath * : 假设多个JAR包或文件系统类路径都有一个相同的配置文件,classpath:只会在第一个加载的类路径下查找,而【classpath*:】会扫描所有这些JAR包及类路径下出现的同名文件。

四、验证、数据绑定和类型转换

1️⃣BeanWrapper

bean包中一个非常重要的类是【BeanWrappe】接口及其相应的实现(【BeanWrapperImpl】)。 正如在javadoc中引用的,【BeanWrapper】提供了【设置和获取属性值】、【获取属性描述符】等功能。 此外,【BeanWrapper】提供了对嵌套属性的支持,允许对子属性进行无限深度的检索。 说的简单一点,就是这个类能帮助我对使用更简单的api通过反射操作一个bean的属性。

我们以设置和获取基本和嵌套属性为例

设置和获取属性是通过【BeanWrapper】的’ setPropertyValue ‘和’ getPropertyValue '重载方法变体来完成的。 下表显示了这些约定的一些例子:

表达式释义name指示属性“name”对应于“getName()”或“isName()”和“setName(…)”方法。account.name指示属性’ account ‘的嵌套属性’ name ‘,该属性对应于(例如)’ getAccount(). setname() ‘或’ getAccount(). getname() '方法。account[2]指示索引属性’ account ‘的third元素。 索引属性的类型可以是’ array ‘、’ list '或其他自然有序的集合。account[COMPANYNAME]指示由“account”、“map”属性的“COMPANYNAME”键索引的映射条目的值。

下面两个示例类使用’ BeanWrapper '来获取和设置属性:

public class Company {

private String name;

private Employee managingDirector;

public String getName() {

return this.name;

}

public void setName(String name) {

this.name = name;

}

public Employee getManagingDirector() {

return this.managingDirector;

}

public void setManagingDirector(Employee managingDirector) {

this.managingDirector = managingDirector;

}

}

public class Employee {

private String name;

private float salary;

public String getName() {

return this.name;

}

public void setName(String name) {

this.name = name;

}

public float getSalary() {

return salary;

}

public void setSalary(float salary) {

this.salary = salary;

}

}

下面的代码片段展示了如何【检索和操作】实例化后的’ Company ’ 和’ Employee ’ 的一些属性:

BeanWrapper company = new BeanWrapperImpl(new Company());

// setting the company name..

company.setPropertyValue("name", "Some Company Inc.");

// ... can also be done like this:

PropertyValue value = new PropertyValue("name", "Some Company Inc.");

company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:

BeanWrapper jim = new BeanWrapperImpl(new Employee());

jim.setPropertyValue("name", "Jim Stravinsky");

company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company

Float salary = (Float) company.getPropertyValue("managingDirector.salary");

2️⃣PropertyEditor属性编辑器

Spring使用【PropertyEditor】的概念来实现【对象】和【字符串】之间的转换。

例如,【Date】可以用人类可读的方式表示(如"2007-14-09"),而我们仍然可以将人类可读的形式转换回原始日期(或者,更好的是,将任何以人类可读形式输入的日期转换回【Date 对象】。 这种行为可以通过注册类型为【java.beans.PropertyEditor 】的自定义编辑器来实现。

Spring中使用PropertyEditor的几个例子:

通过使用【PropertyEditor】实现来设置bean的属性。在Spring的MVC框架中解析HTTP请求参数是通过使用各种各样的【PropertyEditor】实现来完成的,后续学mvc的时候会讲。

Spring有许多内置的【PropertyEditor】实现,这使得我们的工作变得更加简单。 它们都位于【org.springframework.beans】中的propertyeditors包中。 默认情况下,大多数是由【BeanWrapperImpl】注册的。 下表描述了Spring提供的各种【PropertyEditor】实现:

分类释义ClassEditor将表示类的字符串解析为实际类,反之亦然。 当未找到类时,将抛出一个’ IllegalArgumentException ‘。 默认情况下,由’ BeanWrapperImpl '注册。CustomBooleanEditor【布尔属性】的属性编辑器。完成字符串和布尔值的转化。 默认情况下,由’ BeanWrapperImpl '注册。CustomCollectionEditor集合的属性编辑器,将给定的描述集合的字符串转化为目标【集合类型】。CustomDateEditor可自定义的属性编辑器,支持自定义【日期格式】。 默认未注册。 必须根据需要使用适当的格式进行用户注册。ByteArrayPropertyEditor 字节数组的编辑器, 将字符串转换为对应的字节表示形式。 默认情况下由’ BeanWrapperImpl '注册。CustomNumberEditor可自定义任何【数字类】的属性编辑器,如“整数”、“长”、“Float”或“Double”。 默认情况下,由’ BeanWrapperImpl '注册,但可以通过将其自定义实例注册为自定义编辑器来覆盖。FileEditor将字符串解析为【java.io.file】的对象。 默认情况下,由’ BeanWrapperImpl '注册。LocaleEditor可以将字符串解析为’ Locale ‘对象,反之亦然(字符串格式为’ [language][country][variant] ‘,与’ Locale ‘的’ toString() ‘方法相同)。 也接受空格作为分隔符,作为下划线的替代。 默认情况下,由’ BeanWrapperImpl '注册。PatternEditor可以将字符串解析为’ java.util.regex。 模式的对象,反之亦然。PropertiesEditor可以转换字符串到’ Properties ‘对象。 默认情况下,由’ BeanWrapperImpl '注册。StringTrimmerEditor修剪字符串的属性编辑器。 允许将空字符串转换为’ null '值。 默认情况下未注册-必须是用户注册的。URLEditor可以将URL的字符串表示形式解析为实际的’ URL ‘对象。 默认情况下,由’ BeanWrapperImpl '注册。

注册额外的自定义【PropertyEditor】实现

当将bean属性设置为【字符串值】时,Spring IoC容器最终使用标准JavaBeans的PropertyEditor实现将这些字符串转换为属性的复杂类型。 Spring预注册了许多自定义的【PropertyEditor】实现(例如,将一个表示为字符串的类名转换为’ class '对象)。 此外,Java的标准JavaBeans 【PropertyEditor】查找机制允许对类的【 PropertyEditor 】进行适当的命名,并将其放置在与其提供支持的类相同的包中,这样就可以自动找到它。

如果需要注册其他自定义的【propertyeEditors】,可以使用几种机制,其实本质是一样的。

第一种手动的方法(通常不方便也不推荐)是使用【ConfigurableBeanFactory】接口的【registerCustomEditor()】方法,这里您必须佣有一个【BeanFactory】引用,比如我们可以写一个【beanFactoryPostProccessor】。另一种(稍微方便一点)机制是使用名为【CustomEditorConfigurer】的特殊beanFactoryPostProccessor,这是spring给我们提供的,下边的案例演示了这个方式。

标准【PropertyEditor】实例用于将表示为字符串的属性值转换为属性的实际复杂类型。 你可以使用【CustomEditorConfigurer】,一个beanFactoryPostProccessor,来方便地添加对附加的【PropertyEditor】实例的支持到【ApplicationContext】。

考虑下面的例子,它定义了一个名为【ExoticType】的用户类和另一个名为【DependsOnExoticType】的类,后者需要将【ExoticType】设置为属性:

package example;

public class ExoticType {

private String name;

public ExoticType(String name) {

this.name = name;

}

}

public class DependsOnExoticType {

private ExoticType type;

public void setType(ExoticType type) {

this.type = type;

}

}

我们希望能够将type属性分配为字符串,【PropertyEditor】将其转换为实际的【ExoticType】实例。 下面的beanDifination展示了如何建立这种关系:

【PropertyEditor】实现类似如下:

// converts string representation to ExoticType object

package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

// 容器发现需要一个对象的实例,而只是找到了一个字符串,就会根据type的类型匹配这个转化器

// 这个转化器会进行构造

public void setAsText(String text) {

setValue(new ExoticType(text.toUpperCase()));

}

}

最后,下面的例子展示了如何使用【CustomEditorConfigurer】向【ApplicationContext】注册新的【PropertyEditor】,然后它将能够在需要时使用它:

public class CustomEditorConfigurer implements BeanFactoryPostProcessor, Ordered

这家伙是一个BeanFactoryPostProcessor,他会在创建好bean工厂后进行注册:

@Override

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

if (this.propertyEditorRegistrars != null) {

for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {

beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar);

}

}

if (this.customEditors != null) {

this.customEditors.forEach(beanFactory::registerCustomEditor);

}

}

需要我们写的仅仅是在xml中注册一下即可:

我们还可以使用PropertyEditorRegistrar

下面的例子展示了如何创建自己的【propertyeditorregistry】实现:

package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

public void registerCustomEditors(PropertyEditorRegistry registry) {

// it is expected that new PropertyEditor instances are created

registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());

// you could register as many custom property editors as are required here...

}

}

下一个例子展示了如何配置一个【CustomEditorConfigurer】,并将一个【CustomPropertyEditorRegistrar】的实例注入其中:

class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>

3️⃣类型转换

Spring 3核心包提供了一个【通用类型转换系统】。 在Spring容器中,您可以使用此系统作为【PropertyEditor】的替代方案,将外部化bean属性值字符串转换为所需的属性类型。

(1)Converter的API

实现类型转换逻辑很简单,如下面的接口定义所示:

package org.springframework.core.convert.converter;

public interface Converter {

T convert(S source);

}

​ 创建你自己的转换器,需要实现【转换器】接口,并使用泛型“S”作为你要转换的【原始类型】,“T”作为你要转换的【目标类型】。

core.convert中提供了几个转换器实现。 其中包括从字符串到数字和其他常见类型的转换器。 下面的例子显示了’ StringToInteger ‘类,它是一个典型的’ Converter '实现:

package org.springframework.core.convert.support;

final class StringToInteger implements Converter {

public Integer convert(String source) {

return Integer.valueOf(source);

}

}

(2)ConversionService的 API

【conversionservice】定义了一个用于在运行时执行类型转换逻辑的统一API:

package org.springframework.core.convert;

public interface ConversionService {

boolean canConvert(Class sourceType, Class targetType);

T convert(Object source, Class targetType);

boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}

​ 大多数【ConversionService】实现也实现【ConverterRegistry】,它提供了一个用于注册转换器的API。

​spring提供了一个强大的【ConversionService】实现,即 【GenericConversionService】 ,他是适合在大多数环境中使用的通用实现。Spring会选择’ ConversionService’,并在框架需要执行类型转换时使用它。

​要在Spring中注册默认的’ conververService ',请添加以下带有【converversionservice】id '的beanDifination:

class="org.springframework.context.support.ConversionServiceFactoryBean"/>

默认的【converversionservice】可以在字符串、数字、枚举、集合、映射和其他常见类型之间进行转换。 要使用您自己的【自定义转换器】来补充或覆盖默认转换器,请设置【converters】属性。 属性值可以实现任何’ Converter ‘、’ ConverterFactory ‘或’ GenericConverter '接口。

class="org.springframework.context.support.ConversionServiceFactoryBean">

4️⃣配置 DataBinder进行数据验证

从Spring 3开始,你就可以用一个【Validator】配置一个【DataBinder】实例。 一旦配置完成,您就可以通过调用【binder.validate() 】来调用【 Validator】。 任何验证’ Errors ‘都会自动添加到绑定的’ BindingResult '中。

下面的例子展示了如何通过编程方式使用DataBinder在绑定到目标对象后调用验证逻辑:

// 绑定一个要验证的实例

DataBinder dataBinder = new DataBinder(new User(105,"22","22"));

// 绑定一个验证的规则

dataBinder.addValidators(new Validator() {

@Override

public boolean supports(Class clazz) {

return clazz == User.class;

}

@Override

public void validate(Object target, Errors errors) {

User user = (User)target;

if (user.getId() > 100){

errors.rejectValue("id","202","值太大了");

}

}

});

// 开始验证

dataBinder.validate();

// 获取验证的结果

BindingResult bindingResult = dataBinder.getBindingResult();

List allErrors = bindingResult.getAllErrors();

for (ObjectError allError : allErrors) {

System.out.println(allError);

}

五、Spring表达式语言(SpEL)

1️⃣SpEL简介

本节介绍【SpEL接口及其表达式语言】的简单使用。 下面的代码引入了SpEL API来计算字符串字面表达式’ Hello World '。

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("'Hello World'");

String message = (String) exp.getValue();

消息变量的值是“Hello World”。

【ExpressionParser】接口【负责解析表达式字符串】。 在前面的示例中,表达式字符串是由单引号表示的字符串字面量。 【Expression】接口负责计算前面定义的表达式字符串。 当调用parser 时,可以抛出ParseException和EvaluationException两个异常。

【Expression】接口负责【计算前面定义的表达式字符串】。 SpEL支持广泛的特性,例如调用方法、访问属性和调用构造函数。

在下面的方法调用示例中,我们甚至可以在字符串字面量上调用【concat】方法:

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("'Hello World'.concat('!')");

String message = (String) exp.getValue();

’ message ‘的值现在是’Hello World!’。

下面的例子调用了’ String '属性【bytes】:

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'

Expression exp = parser.parseExpression("'Hello World'.bytes");

byte[] bytes = (byte[]) exp.getValue();

这一行将字面值转换为字节数组。

SpEL还通过使用标准点表示法(如’ prop1.prop2.prop3 ')和相应的属性值设置来支持嵌套属性。 也可以访问公共字段。

下面的例子展示了如何使用点表示法来获取文字的长度:

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes().length'

Expression exp = parser.parseExpression("'Hello World'.bytes.length");

int length = (Integer) exp.getValue();

还可以调用String的构造函数而不是使用字符串字面值,如下例所示:

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");

String message = exp.getValue(String.class);

从字面量构造一个新的’ String ',并使其为大写。

SpEL更常见的用法是提供一个针对特定对象实例(称为根对象)求值的表达式字符串。 下面的例子展示了如何从’ Inventor ‘类的实例中检索’ name '属性:

// Create and set a calendar

GregorianCalendar c = new GregorianCalendar();

c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.

Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("name"); // Parse name as an expression

String name = (String) exp.getValue(tesla);

// name == "Nikola Tesla"

// 这个表达式在比较连个名字是不是’Nikola Tesla‘

exp = parser.parseExpression("name == 'Nikola Tesla'");

boolean result = exp.getValue(tesla, Boolean.class);

// result == true

2️⃣ Bean 定义中的表达式

您可以使用SpEL表达式和基于xml或基于注解的配置元数据来定义【BeanDefinition】实例。 在这两种情况下,定义表达式的语法形式都是#{}。

(1)XML配置

属性或构造函数参数值可以通过使用表达式设置,如下例所示:

应用程序上下文中的所有bean都可以作为【具有公共bean名称】的预定义【变量】使用。 这包括用于访问运行时环境的标准上下文bean,如【environment】(类型为’ org.springframework.core.env.Environment ‘),以及【systemProperties】和【systemEnvironment 】(类型为’ Map ')。

下面的示例显示了对【systemProperties】 bean的SpEL变量访问:

注意,这里不需要在预定义变量前加上’ # '符号。

您还可以通过名称引用其他bean属性,如下例所示:

(2)注解配置

要指定默认值,可以在字段、方法和方法或构造函数参数上放置“@Value”注解。

设置字段的默认值的示例如下:

public class FieldValueTestBean {

@Value("#{ systemProperties['user.region'] }")

private String defaultLocale;

public void setDefaultLocale(String defaultLocale) {

this.defaultLocale = defaultLocale;

}

public String getDefaultLocale() {

return this.defaultLocale;

}

}

下面的例子展示了一个等价的属性setter方法:

public class PropertyValueTestBean {

private String defaultLocale;

@Value("#{ systemProperties['user.region'] }")

public void setDefaultLocale(String defaultLocale) {

this.defaultLocale = defaultLocale;

}

public String getDefaultLocale() {

return this.defaultLocale;

}

}

自动连接的方法和构造函数也可以使用’ @Value '注解,如下面的例子所示:

public class SimpleMovieLister {

private MovieFinder movieFinder;

private String defaultLocale;

@Autowired

public void configure(MovieFinder movieFinder,

@Value("#{ systemProperties['user.region'] }") String defaultLocale) {

this.movieFinder = movieFinder;

this.defaultLocale = defaultLocale;

}

// ...

}

public class MovieRecommender {

private String defaultLocale;

private CustomerPreferenceDao customerPreferenceDao;

public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,

@Value("#{systemProperties['user.country']}") String defaultLocale) {

this.customerPreferenceDao = customerPreferenceDao;

this.defaultLocale = defaultLocale;

}

// ...

}

(3)语法参考

本节描述Spring表达式语言的工作原理。 它涵盖以下主题:

(1) 文字表达方式

支持的文字表达式类型有字符串、数字值(int、real、hex)、布尔值和空值。 字符串由单引号分隔。 若要将单引号本身放入字符串中,请使用两个单引号字符。

​下面的例子显示了文字的简单用法。 通常,它们不会像这样单独使用,而是作为更复杂表达式的一部分使用——例如,在逻辑比较运算符的一侧使用文字。

ExpressionParser parser = new SpelExpressionParser();

// evals to "Hello World"

String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();

// evals to 2147483647

int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();

boolean trueValue = (Boolean) parser.parseExpression("true").getValue();

Object nullValue = parser.parseExpression("null").getValue();

数字支持使用负号、指数符号和小数点。 默认情况下,使用Double.parseDouble()解析实数。

(2) Arrays, Lists, Maps

使用句点来指示嵌套的属性值。

// evals to 1856

int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);

More Actions允许属性名称的首字母不区分大小写。 因此,上面例子中的表达式可以写成“生日”。 “年+ 1900”和“出生地点”。 分别城”。 此外,可以通过方法调用访问属性——例如,’ getPlaceOfBirth(). getcity() ‘而不是’ placeOfBirth.city '。

使用方括号表示法获取数组和列表的内容,示例如下:

ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// Inventions Array

// evaluates to "Induction motor"

String invention = parser.parseExpression("inventions[3]").getValue(

context,tesla,String.class);

// Members List

// evaluates to "Nikola Tesla"

String name = parser.parseExpression("members[0].name").getValue(

context,ieee,String.class);

// List and Array navigation

// evaluates to "Wireless communication"

String invention = parser.parseExpression("members[0].inventions[6]").getValue(

context,ieee,String.class);

(3) 内联列表

可以使用{}符号在表达式中直接表示列表。

// evaluates to a Java list containing the four numbers

List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);

{} 它本身就是一个空列表。 出于性能原因,如果列表本身完全由固定的字面值组成,则创建一个常量列表来表示表达式(而不是在每次求值时构建一个新列表)。

(4) 内联映射

您还可以使用{key:value} 表示法在表达式中直接表示映射。 下面的例子展示了如何做到这一点:

// evaluates to a Java map containing the two entries

Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);

{:}它本身就是一个空映射。 出于性能原因,如果映射本身由固定的文字或其他嵌套的常量结构(列表或映射)组成,则创建一个常量映射来表示表达式(而不是在每次求值时构建一个新映射)。 map键的引用是可选的(除非键包含句号(’ . '))。 上面的例子没有使用引号键。

(5) 数组结构

可以使用熟悉的Java语法构建数组,也可以提供一个初始化式,以便在构造时填充数组。 下面的例子展示了如何做到这一点:

int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

// Array with initializer

int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);

// Multi dimensional array

int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

You cannot currently supply an initializer when you construct a multi-dimensional array.

(6) 方法调用

您可以使用典型的Java编程语法来调用方法。 您还可以在文字上调用方法。 也支持变量参数。 下面的例子展示了如何调用方法:

// string literal, evaluates to "bc"

String bc = parser.parseExpression("'abc'.substring(1,3)").getValue(String.class);

// evaluates to true

boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(

societyContext, Boolean.class);

(7) 运算符

Spring表达式语言支持以下类型的操作符:

关系运算符逻辑运算符数学运算符赋值运算符

关系运算符

使用标准操作符表示法支持关系操作符(等于、不等于、小于、小于或等于、大于和大于或等于)。 下面的例子展示了一些操作符示例:

// evaluates to true

boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// evaluates to false

boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// evaluates to true

boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

除了标准的关系操作符外,SpEL还支持instanceof和基于正则表达式的matches操作符。 下面的例子展示了两者的例子:

// evaluates to false

boolean falseValue = parser.parseExpression(

"'xyz' instanceof T(Integer)").getValue(Boolean.class);

// evaluates to true

boolean trueValue = parser.parseExpression(

"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

// evaluates to false

boolean falseValue = parser.parseExpression(

"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

每个符号运算符也可以指定为纯字母等效符。 这避免了所使用的符号对嵌入表达式的文档类型(例如XML文档)具有特殊意义的问题。 对应文本为:

lt (<)gt (>)le (<=)ge (>=)eq (==)ne (!=)div (/)mod (%)not (!).

所有的文本操作符都是不区分大小写的。

逻辑运算符

SpEL支持以下逻辑操作符:

and (&&)or (||)not (!)

下面的示例演示如何使用逻辑运算符:

// -- AND --

// evaluates to false

boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

// evaluates to true

String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";

boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- OR --

// evaluates to true

boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

// evaluates to true

String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";

boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- NOT --

// evaluates to false

boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);

// -- AND and NOT --

String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";

boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

数学运算符

你可以在数字和字符串上使用加法运算符(+)。 您可以只在数字上使用减法(-)、乘法(*)和除法(/)操作符。 您还可以对数字使用模(%)和指数幂(^)运算符。 执行标准操作符优先级。 下面的例子展示了使用中的数学运算符:

// Addition

int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2

String testString = parser.parseExpression(

"'test' + ' ' + 'string'").getValue(String.class); // 'test string'

// Subtraction

int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4

double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000

// Multiplication

int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6

double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0

// Division

int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2

double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0

// Modulus

int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3

int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1

// Operator precedence

int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21

赋值运算

要设置属性,请使用赋值操作符(=)。 这通常是在调用 setValue中完成的,但也可以在调用getValue中完成。 下面的例子展示了使用赋值操作符的两种方法:

Inventor inventor = new Inventor();

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");

// alternatively

String aleks = parser.parseExpression(

"name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);

(8) 类型

你可以使用特殊的’ T ‘操作符来指定一个’ java.lang.Class ‘(类型)的实例。 静态方法也可以通过使用此操作符来调用。 ’ StandardTypeLocator ‘(它可以被替换)是建立在对’ java。 朗的包。 这意味着’ T() ‘引用’ java。 Lang '包不需要完全限定,但所有其他类型引用必须是完全限定的。 下面的示例演示如何使用“T”操作符:

Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean trueValue = parser.parseExpression(

"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")

.getValue(Boolean.class);

(9)构造函数

你可以使用new操作符来调用构造函数。 你应该对所有类型使用完全限定类名,除了那些位于 java. lang package ( Integer , Float , String ,等等)。 下面的例子展示了如何使用new操作符来调用构造函数:

Inventor einstein = p.parseExpression(

"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")

.getValue(Inventor.class);

// create new Inventor instance within the add() method of List

p.parseExpression(

"Members.add(new org.spring.samples.spel.inventor.Inventor(

'Albert Einstein', 'German'))").getValue(societyContext);

(10) 变量

可以使用#variableName语法引用表达式中的变量。 变量是通过在EvaluationContext实现上使用setVariable方法设置的。

下面的例子展示了如何使用变量。

Inventor tesla = new Inventor("Nikola Tesla","Serbian");

// 我们必须创建一个上下文,在上下文中定义变量

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

context.setVariable("newName","Mike Tesla");

parser.parseExpression("name = #newName").getValue(context,tesla);

System.out.println(tesla.getName()) // "Mike Tesla"

(11) Bean 的引用

如果计算上下文已经配置了bean解析器,那么您可以使用@符号从表达式中查找bean。

下面的例子展示了如何做到这一点:

// 定义一个容器

ApplicationContext ctx = new AnnotationConfigApplicationContext(A.class);

// 创建一个解析器

ExpressionParser parser = new SpelExpressionParser();

// 定义一个表达式上下文

StandardEvaluationContext context = new StandardEvaluationContext();

// 这个地方规定了我要从哪里查找bean,我们的具体实现是BeanFactoryResolver,代表了从容器中获取

context.setBeanResolver(new BeanFactoryResolver(ctx));

Object bean = parser.parseExpression("@messageListener").getValue(context);

要访问FactoryBean本身,应该在bean名称前加上’ & '符号。 下面的例子展示了如何做到这一点:

ExpressionParser parser = new SpelExpressionParser();

StandardEvaluationContext context = new StandardEvaluationContext();

context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation

Object bean = parser.parseExpression("&foo").getValue(context);

(12) 三元运算符 (If-Then-Else)

可以使用三元运算符在表达式中执行if-then-else条件逻辑。 下面的例子显示了一个最小的示例:

String falseString = parser.parseExpression(

"false ? 'trueExp' : 'falseExp'").getValue(String.class);

在这种情况下,布尔值false导致返回字符串值’false exp ’ 。 下面是一个更现实的例子:

Expression exp = parser.parseExpression("'Hello World'.bytes.length gt 2 ? 2:3")

后记

Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~ 算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~

相关链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: