- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
再了解了校验器上下文ValidatorContext,知道它可以对校验器Validator的核心五大组件分别进行定制化设置,那么这些核心组件在校验过程中到底扮演着什么样的角色呢,本文一探究竟。
作为核心组件,是有必要多探究一分的。以此为基,再扩散开了解和使用其它功能模块便将如鱼得水。但是过程枯燥是真的,所以需要坚持呀。
Bean Validation校验器的这五大核心组件通过ValidatorContext可以分别设置:若没设置(或为null),那就回退到使用ValidatorFactory默认的组件。
准备好的组件,统一通过ValidatorFactory暴露出来予以访问:
public interface ValidatorFactory extends AutoCloseable {
...
MessageInterpolator getMessageInterpolator();
TraversableResolver getTraversableResolver();
ConstraintValidatorFactory getConstraintValidatorFactory();
ParameterNameProvider getParameterNameProvider();
@since 2.0
ClockProvider getClockProvider();
...
}
直译为:消息插值器。按字面不太好理解:简单的说就是对message内容进行格式化,若有占位符{}或者el表达式${}就执行替换和计算。对于语法错误应该尽量的宽容。
校验失败的消息模版交给它处理就成为了人能看得懂的消息格式,因此它能够处理消息的国际化:消息的key是同一个,但根据不同的Locale展示不同的消息模版。最后在替换/技术模版里面的占位符即可~
这是Bean Validation的标准接口,Hibernate Validator提供了实现:
Hibernate Validation它使用的是ResourceBundleMessageInterpolator来既支持参数,也支持EL表达式。
javax.el.ExpressionFactory
这个API来支持EL表达式${}
的,形如这样:must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
它是能够动态计算出${inclusive == true ? 'or equal to ' : ''}
这部分的值的。
public interface MessageInterpolator {
String interpolate(String messageTemplate, Context context);
String interpolate(String messageTemplate, Context context, Locale locale);
}
接口方法直接了当:根据上下文Context填充消息模版messageTemplate。它的具体工作流程我用图示如下:
context上下文里一般是拥有需要被替换的key的键值对的,如下图所示:
Hibernate对Context的实现中扩展出了如图的两个Map(非JSR标准),可以让你优先于 constraintDescriptor取值,取不到再fallback到标准模式的ConstraintDescriptor里取值,也就是注解的属性值。具体取值代码如下:
ParameterTermResolver:
private Object getVariable(Context context, String parameter) {
// 先从hibernate扩展出来的方式取值
if (context instanceof HibernateMessageInterpolatorContext) {
Object variable = ( (HibernateMessageInterpolatorContext) context ).getMessageParameters().get( parameter );
if ( variable != null ) {
return variable;
}
}
// fallback到标准模式:从注解属性里取值
return context.getConstraintDescriptor().getAttributes().get( parameter );
}
大部分情况下我们只用得到注解属性里面的值,也就是错误消息里可以使用{注解属性名}这种方式动态获取到注解属性值,给与友好错误提示。
上下文里的Message参数和Expression参数如何放进去的?在后续高级使用部分,会自定义k-v替换参数,也就会使用到本部分的高级应用知识,后文见。
能跨越的处理器。从字面是非常不好理解,用粗暴的语言解释为:确定某个属性是否能被ValidationProvider访问,当每访问一个属性时都会通过它来判断一下子,提供两个判断方法:
public interface TraversableResolver {
// 是否是可达的
boolean isReachable(Object traversableObject,
Node traversableProperty,
Class<?> rootBeanType,
Path pathToTraversableObject,
ElementType elementType);
// 是否是可级联的(是否标注有@Valid注解)
boolean isCascadable(Object traversableObject,
Node traversableProperty,
Class<?> rootBeanType,
Path pathToTraversableObject,
ElementType elementType);
}
该接口主要根据配置项来进行判断,并不负责。内部使用,调用者基本无需关心,也不见更改其默认机制,暂且略过。
约束校验器工厂。ConstraintValidator约束校验器我们应该不陌生:每个约束注解都得指定一个/多个约束校验器,形如这样:
@Constraint(validatedBy = { xxx.class })。
ConstraintValidatorFactory就是工厂:可以根据Class生成对象实例。
public interface ConstraintValidatorFactory {
// 生成实例:接口并不规定你的生成方式
<T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key);
// 释放实例。标记此实例不需要再使用,一般为空实现
// 和Spring容器集成时 .destroyBean(instance)时会调用此方法
void releaseInstance(ConstraintValidator<?, ?> instance);
}
Hibernate提供了唯一实现ConstraintValidatorFactoryImpl:使用空构造器生成实例 clazz.getConstructor().newInstance();。
小贴士:接口并没规定你如何生成实例,Hibernate Validator是使用空构造这么实现的而已~
参数名提供器。这个组件和Spring的ParameterNameDiscoverer作用是一毛一样的:获取方法/构造器的参数名。
public interface ParameterNameProvider {
List<String> getParameterNames(Constructor<?> constructor);
List<String> getParameterNames(Method method);
}
提供的实现:
@Test
public void test9() {
ParameterNameProvider parameterNameProvider = new DefaultParameterNameProvider();
// 拿到Person的无参构造和有参构造(@NoArgsConstructor和@AllArgsConstructor)
Arrays.stream(Person.class.getConstructors()).forEach(c -> System.out.println(parameterNameProvider.getParameterNames(c)));
}
运行程序,输出:
[arg0, arg1, arg2, arg3]
[]
一样的,若你想要打印出明确的参数名,请在编译参数上加上-parameters参数。
时钟提供器。这个接口很简单,就是提供一个Clock,给@Past、@Future等阅读判断提供参考。唯一实现为DefaultClockProvider:
public class DefaultClockProvider implements ClockProvider {
public static final DefaultClockProvider INSTANCE = new DefaultClockProvider();
private DefaultClockProvider() {
}
// 默认是系统时钟
@Override
public Clock getClock() {
return Clock.systemDefaultZone();
}
}
默认使用当前系统时钟作为参考。若你的系统有全局统一的参考标准,比如统一时钟,那就可以通过此接口实现自己的Clock时钟,毕竟每台服务器的时间并不能保证是完全一样的不是,这对于时间敏感的应用场景(如竞标)需要这么做。
以上就是对Validator校验器的五个核心组件的一个描述,总体上还是比较简单。其中第一个组件:MessageInterpolator插值器我认为是最为重要的,需要理解好了。对后面做自定义消息模版、国际化消息都有用。
值提取器。2.0版本新增一个比较重要的组件API,作用:把值从容器内提取出来。这里的容器包括:数组、集合、Map、Optional等等。
// T:待提取的容器类型
public interface ValueExtractor<T> {
// 从原始值originalValue提取到receiver里
void extractValues(T originalValue, ValueReceiver receiver);
// 提供一组方法,用于接收ValueExtractor提取出来的值
interface ValueReceiver {
// 接收从对象中提取的值
void value(String nodeName, Object object);
// 接收可以迭代的值,如List、Map、Iterable等
void iterableValue(String nodeName, Object object);
// 接收有索引的值,如List Array
// i:索引值
void indexedValue(String nodeName, int i, Object object);
// 接收键值对的值,如Map
void keyedValue(String nodeName, Object key, Object object);
}
}
容易想到,ValueExtractor的实现类就非常之多(所有的实现类都是内建的,非public的,这就是默认情况下支持的容器类型):
举例两个典型实现:
// 提取List里的值 LIST_ELEMENT_NODE_NAME -> <list element>
class ListValueExtractor implements ValueExtractor<List<@ExtractedValue ?>> {
static final ValueExtractorDescriptor DESCRIPTOR = new ValueExtractorDescriptor( new ListValueExtractor() );
private ListValueExtractor() {
}
@Override
public void extractValues(List<?> originalValue, ValueReceiver receiver) {
for ( int i = 0; i < originalValue.size(); i++ ) {
receiver.indexedValue( NodeImpl.LIST_ELEMENT_NODE_NAME, i, originalValue.get( i ) );
}
}
}
// 提取Optional里的值
@UnwrapByDefault
class OptionalLongValueExtractor implements ValueExtractor<@ExtractedValue(type = Long.class) OptionalLong> {
static final ValueExtractorDescriptor DESCRIPTOR = new ValueExtractorDescriptor( new OptionalLongValueExtractor() );
@Override
public void extractValues(OptionalLong originalValue, ValueReceiver receiver) {
receiver.value( null, originalValue.isPresent() ? originalValue.getAsLong() : null );
}
}
校验器Validator通过它把值从容器内提取出来参与校验,从这你应该就能理解为毛从Bean Validation2.0开始就支持验证容器内的元素了吧,形如这样:List<@NotNull @Valid Person>、Optional<@NotNull @Valid Person>,可谓大大的方便了使用。
若你有自定义容器,需要提取的需求,那么你可以自定义一个ValueExtractor实现,然后通过ValidatorContext#addValueExtractor()添加进去即可
有什么区别 和 您能解释一下这两者之间有什么区别吗? 最佳答案 它是一个 XML 命名空间,用于分隔可能存在冲突的元素名称,因此没有真正的区别。 XML Namespaces 关于java -
我刚刚浏览了两个 Web 应用程序,在两个项目的“applicationContext.xml”文件中都有一个标记 ... 另一个是 ... 谁能给我解释一下有什么区别吗? 最佳答案 没有语
我一直在尝试了解 WebSphere Commerce 中的 Java bean,但我真的很困惑。请帮帮我。我需要知道: What is the difference between EntityBe
自从我们更新到 grails 2.0.1(从 2.0.0 开始)以来,通过 bean 字段显示的所有 bean 都错误地显示为“withBean”字段的第一个属性。在我下面发布的示例中,所有 [fir
我有一个 bean,我将另一个 beanlist 放入其中,并且我想访问该内部 bean。 我的第一个 Bean 是: public class FirstDTO { private String F
我正在尝试将 CSS 和 JS 添加到 spring MVC 项目中的 JSP 页面,以便我在 dispatcher-servlet.xml 中包含了 js/css 文件夹的引用,如下所示:
当我将请求传递给 RestController 时,出现以下错误。 org.springframework.beans.factory.xml.XmlBeanDefinitionStoreExcept
我看到很多示例将 bean 标记为实体 bean (@Entity) 和命名 bean (CDI),以避免创建 2 个类(托管 bean 和实体 bean)并利用 Bean 验证以便可以执行验证在客户
在我的理解中, session 总是意味着有状态。考虑 servlet session 对象, 想想 cookie。如果 session 是无状态的,我认为我们根本不需要 session 。 在jee
我完全是Spring框架的初学者。我当时正在玩一个创建对象实例的示例。因此需要您的帮助! 看看这个例子: MainApp.java: import org.springframework.contex
这个问题在这里已经有了答案: What is a JavaBean exactly? (23 个回答) 关闭 7 年前。 我已经阅读了有关 EJB、Java Beans 的内容,但是我仍然对“bea
我刚开始使用 Spring-Framework,实际上我正在使用 spring-boot 库。我有以下问题: 我知道在 @Configuration 类中使用 @Bean 注册的 bean 默认是单例
我对下面提到的场景中使用Spring Framework时会创建的实例数量有疑问: bean配置是这样的 or 默认情况下,bean "a"有 singleton scope .所以
在我的 Spring-Module.xml 中,我有两个 bean: ... ... 我像这样实例化我的类: Applicat
@Autowired private Map departments; 我的 spring 配置文件 只要使用 @Autowired 需要日期,它就可以正常工作 同样, 如何使用没有属性
我已经为 ComboBox 设置了 ContainerDataSource this.comboBox.setContainerDataSource(container)。这个容器是一个 BeanIt
为了支持流畅的编程风格,我最近修改了我们的 Java Beans setter 方法以返回 Bean 类。但是现在 Java Beans Activation Framework (rel 1.1)
有人可以告诉我在我的 ApplicationContext 中我必须使用 beans:bean 而不是 bean 的什么以及如何修复它。
我有如下配置: batch:job id="reconciliationJob" job-repository="jobRepository" restartable="true" 在应用程序上下文启
我已经为 Test_flow 创建了简单的测试套件,但是当我尝试运行该流程时出现错误。 java.lang.RuntimeException: org.mule.api.config.Configur
我是一名优秀的程序员,十分优秀!