最新最全面的Spring详解(三)——Resources,验证、数据绑定和类型转换与Spring表达式语言(SpEL)(中)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 最新最全面的Spring详解(三)——Resources,验证、数据绑定和类型转换与Spring表达式语言(SpEL)(中)

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() ‘方法相同)。 也接受空格作为分隔符,作为下划线的替代。 
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展示了如何建立这种关系:

<bean id="sample" class="example.DependsOnExoticType">
    <!-- 这里没有使用rel,二十使用value,这个会当做字符串进行解析 -->
    <property name="type" value="aNameForExoticType"/>
</bean>

【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中注册一下即可:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>

我们还可以使用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】的实例注入其中:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="propertyEditorRegistrars">
        <list>
            <ref bean="customPropertyEditorRegistrar"/>
        </list>
    </property>
</bean>
<bean id="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<S, T> {
    T convert(S source);
}


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


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


package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String,Integer> {
    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> T convert(Object source, Class<T> 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:


<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean"/>

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

<bean id="conversionService"
        class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="example.MyCustomConverter"/>
        </set>
    </property>
</bean>

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<ObjectError> 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】实例。 在这两种情况下,定义表达式的语法形式都是#{<expression string>}。


🍀(1)XML配置


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

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
    <!-- other properties -->
</bean>


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


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

<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
    <property name="defaultLocale" value="#{systemProperties['user.region'] }"/>
    <!-- other properties -->
</bean>

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

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

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
    <!-- other properties -->
</bean>
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
    <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
    <!-- other properties -->
</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;
    }
    // ...
}
相关文章
|
7月前
|
安全 Java 数据安全/隐私保护
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
508 1
|
2月前
|
JSON 安全 算法
|
5天前
|
Java 数据库 数据安全/隐私保护
轻松掌握Spring依赖注入:打造你的登录验证系统
本文以轻松活泼的风格,带领读者走进Spring框架中的依赖注入和登录验证的世界。通过详细的步骤和代码示例,我们从DAO层的创建到Service层的实现,再到Spring配置文件的编写,最后通过测试类验证功能,一步步构建了一个简单的登录验证系统。文章不仅提供了实用的技术指导,还以口语化和生动的语言,让学习变得不再枯燥。
18 2
|
1月前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
66 2
|
4月前
|
Java 开发者 Spring
|
6月前
|
前端开发 安全 Java
Spring EL表达式:概念、特性与应用深入解析
Spring EL表达式:概念、特性与应用深入解析
|
6月前
|
算法 Java API
在Spring Boot中实现接口签名验证通常涉及以下步骤
在Spring Boot中实现接口签名验证通常涉及以下步骤
498 4
|
5月前
|
Java 数据库连接 测试技术
在Spring Boot中实现数据校验与验证
在Spring Boot中实现数据校验与验证
|
5月前
|
存储 Java 数据库连接
Spring6(五):Resources、i18n、Validation(3)
Spring6(五):Resources、i18n、Validation(3)
20 0
|
5月前
|
XML Java 数据格式
Spring6(五):Resources、i18n、Validation(2)
Spring6(五):Resources、i18n、Validation(2)
50 0