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; } // ... }