###0.前言
《Java EE互联网轻量级框架整合开发——SSM框架(Spring MVC+Spring+MyBatis)和Redis实现》
本文主要记录:
- 拦截器(HandlerInterceptor)
- 验证器(Validator)
- 转换器(Converter)
###1.拦截器 HandlerInterceptor
在Spring MVC中,运行时通过请求找到对应的HandlerMapping,然后构建HandlerExecutionChain(执行链)对象。
//HandlerExecutionChain.class
public class HandlerExecutionChain {
private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
private final Object handler;
private HandlerInterceptor[] interceptors;
private List<HandlerInterceptor> interceptorList;
//...
}
所以在开发过程中,一个执行器可以对应多个拦截器,类似责任链模式形成调用关系。
####1.1 拦截器的源码和执行流程HandlerInterceptorAdapter
实现AsyncHandlerInterceptor
接口,AsyncHandlerInterceptor
继承HandlerInterceptor
接口,所以此处直接列举HandlerInterceptorAdapter
源码。
//HandlerInterceptorAdapter.class
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
/**
* This implementation always returns {@code true}.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//处理器执行的前置方法,返回值会影响处理器的执行。(为false则终止处理器的执行,并跳回外层的责任链节点的post方法)
//如果为false,则可以使用response来响应请求
return true;
}
/**
* This implementation is empty.
*/
@Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
//处理器执行的后置方法,处理器执行完后运行它(在解析渲染之前)。可以对视图和模型做进一步渲染或修改
}
/**
* This implementation is empty.
*/
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
//视图解析和渲染视图后执行。可以做些资源清理工作,或日志记录等
}
/**
* This implementation is empty.
*/
@Override
public void afterConcurrentHandlingStarted(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//如果是异步请求的时候会触发此方法
}
}
DispatcherServlet类中相关代码:
//DispatcherServlet类简化代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
try {
//...省略代码
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//额外处理1. 处理拦截器,实际调用interceptor.preHandle(...),异常直接triggerAfterCompletion(...)结束
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//通过处理器适配器执行处理器方法,返回逻辑视图(ModelAndView)
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//判断是否异步请求,为真则直接return,然后执行finally
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//5.设置ModelAndView对应的viewName,使用ViewNameTranslator
applyDefaultViewName(processedRequest, mv);
//额外处理2. 处理拦截器,实际调用interceptor.postHandle(...)
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//5.渲染视图,里面会调用mappedHandler.triggerAfterCompletion(request, response, ex);
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {//里面会调用mappedHandler.triggerAfterCompletion(request, response, ex);
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {//里面会调用mappedHandler.triggerAfterCompletion(request, response, ex);
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
//为异步请求则调用applyAfterConcurrentHandlingStarted
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
从上述代码可以看出,一个HandlerExecutionChain(执行链)对象里面包含一个handler对象和多个HandlerInterceptor(拦截器)。在DispatcherServlet类的doDispatch
方法中,会通过以下方法处理拦截器:
- mappedHandler.applyPreHandle(processedRequest, response); //执行前
- mappedHandler.applyPostHandle(processedRequest, response, mv); //执行后
- mappedHandler.triggerAfterCompletion(request, response, ex);//完成后触发(包括异常)
- mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);//异步方法调用(finally中)
此处需要注意的是applyAfterConcurrentHandlingStarted(...)
方法的执行流程,在ha.handle(...)
执行后,需要判断当前请求是否异步请求isConcurrentHandlingStarted()
,如果为异步请求,则直接return停止向下执行,会执行finally操作.也就是不再执行applyPostHandle
和afterCompletion
。
####1.2 拦截器的使用
在开发中使用Spring MVC框架提供的HandlerInterceptorAdapter来实现自定义拦截器。
实现拦截器
public class RoleInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.err.println("preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.err.println("postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.err.println("afterCompletion"); } }
xml配置拦截器
//dispatcher-servlet.xml <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/role/*.do" /> <!--配置拦截的对应请求--> <bean class="com.ssm.chapter15.interceptor.RoleInterceptor" /> </mvc:interceptor> <!-- 多拦截器配置 <mvc:interceptor> <mvc:mapping path="/role/*.do" /> <bean class="com.ssm.chapter15.interceptor.RoleInterceptor1" /> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/role/*.do" /> <bean class="com.ssm.chapter15.interceptor.RoleInterceptor2" /> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/role/*.do" /> <bean class="com.ssm.chapter15.interceptor.RoleInterceptor3" /> </mvc:interceptor> --> </mvc:interceptors>
多拦截器下的执行顺序
拦截器的执行顺序和责任链一样,下面是拦截器的执行图
需要注意的是preHandle的返回值。如果返回值为false表示控制器逻辑和后续方法都将不会被执行。即:该拦截器的posthandle方法和afterCompletion方法将不会被执行。但是,因为外层的拦截器的preHandle
方法返回的是true,所以外层的afterCompletion方法将继续执行。
###2.验证器 Validator
在Spring MVC中,可以采用类似validation-api-1.1.0.Final.jar
提供的验证注解。也可以使用Spring提供的Validator接口进行校验。
注意:
- validation-api-1.1.0.Final.jar的引入还需要依赖:
- classmate-1.3.3.jar
- jboss-logging-3.3.1.Final.jar
- Validator接口:
- 属于Spring-context包底下的接口
此处记录使用Validator接口验证的方式:
Validator接口的定义:
public interface Validator {
/**
* Can this {@link Validator} {@link #validate(Object, Errors) validate}
* instances of the supplied {@code clazz}?
* <p>This method is <i>typically</i> implemented like so:
* <pre class="code">return Foo.class.isAssignableFrom(clazz);</pre>
* (Where {@code Foo} is the class (or superclass) of the actual
* object instance that is to be {@link #validate(Object, Errors) validated}.)
* @param clazz the {@link Class} that this {@link Validator} is
* being asked if it can {@link #validate(Object, Errors) validate}
* @return {@code true} if this {@link Validator} can indeed
* {@link #validate(Object, Errors) validate} instances of the
* supplied {@code clazz}
* 判断当前杨志强是否用于检验指定Class类型的POJO
* true:启动校验 false:不校验
*/
boolean supports(Class<?> clazz);
/**
* Validate the supplied {@code target} object, which must be
* of a {@link Class} for which the {@link #supports(Class)} method
* typically has (or would) return {@code true}.
* <p>The supplied {@link Errors errors} instance can be used to report
* any resulting validation errors.
* @param target the object that is to be validated (can be {@code null})
* @param errors contextual state about the validation process (never {@code null})
* @see ValidationUtils
*
* 检验指定POJO的对象的合法性。
*/
void validate(Object target, Errors errors);
}
上述代码中,看到Validator接口提供了两个方法:
- boolean supports(Class<?> clazz):判断是否进行校验
- void validate(Object target, Errors errors):检验方法,对象的传入和错误信息的设置。
具体使用方法示例:
实现Validator接口
public class TransactionValidator implements Validator { @Override public boolean supports(Class<?> clazz) { //判断验证是否为Transaction,如果是则进行判断[修改为:验证] return Transaction.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { Transaction trans = (Transaction) target; //求交易金额和价格×数量的差额 double dis = trans.getAmount() - (trans.getPrice() * trans.getQuantity()); //如果差额大于0.01,则认为业务错误 if (Math.abs(dis) > 0.01) { //加入错误信息 errors.rejectValue("amount", null, "交易金额和购买数量与价格不匹配"); } } }
配置使用校验类
@Controller @RequestMapping("/validate") public class ValidateController { @InitBinder public void initBinder(DataBinder binder) { // 数据绑定器加入验证器 binder.setValidator(new TransactionValidator()); } @RequestMapping("/validator") public ModelAndView validator(@Valid Transaction trans, Errors errors) { // 是否存在错误 if (errors.hasErrors()) { // 获取错误信息 List<FieldError> errorList = errors.getFieldErrors(); for (FieldError error : errorList) { // 打印字段错误信息 System.err.println("fied :" + error.getField() + "\t" + "msg:" + error.getDefaultMessage()); } } ModelAndView mv = new ModelAndView(); mv.setViewName("index"); return mv; } }
从上述代码中看到,通过
@InitBinder
将验证器 绑定到当前控制器中。通过@Valid
激活启动验证器,并通过Errors返回错误信息。
###3.转换器 Converter 和 GenericConverter
####3.1 Converter (一对一转换器)
在Spring MVC框架中,框架会将请求参数直接转化为POJO对象的参数。但这有一个限制条件,那就是只能对简单的数据类型进行转化。
在Spring MVC中已经内置了一些常用的转换器.
- CharacterToNumber:将字符转换为数字
- IntegerToEnum:将整型转换为枚举
- ObjectToStringConverter:将对象转换为字符串
- SerializingConverter:序列化转换器
- DeserializingConverter:反序列化转换器
- StringToBooleanConverter:字符串转布尔值
- StringToEnum:字符串转枚举
- StringToCurrencyConverter:字符串转金额
- EnumToStringConverter:枚举转字符串
它们都实现了转换器接口:
public interface Converter<S, T> {
/**
* Convert the source object of type {@code S} to target type {@code T}.
* @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
* @return the converted object, which must be an instance of {@code T} (potentially {@code null})
* @throws IllegalArgumentException if the source cannot be converted to the desired target type
*/
T convert(S source);
}
自定义Converter转换器步骤:
实现Converter接口
public class StringToRoleConverter implements Converter<String, Role> {
@Override public Role convert(String str) { //空串 if (StringUtils.isEmpty(str)) { return null; } //不包含指定字符 if (str.indexOf("-") == -1) { return null; } String[] arr = str.split("-"); //字符串长度不对 if (arr.length != 3) { return null; } Role role = new Role(); role.setId(Long.parseLong(arr[0])); role.setRoleName(arr[1]); role.setNote(arr[2]); return role; }
}
注册到服务类中
方式一(代码注册):
@Bean(name = "myConverter")
public List<Converter> initMyConverter() {
if (myConverter == null) {
myConverter = new ArrayList<Converter>();
}
//自定义的字符串和角色转换器
Converter roleConverter = new StringToRoleConverter();
myConverter.add(roleConverter);
//往转换服务类注册转换器
fcsfb.getObject().addConverter(roleConverter);
return myConverter;
}
方式二(xml配置注册):
<!-- 使用注解驱动 -->
<mvc:annotation-driven />
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="messageConverters">
<list>
<bean class="com.ssm.chapter16.converter.StringToRoleConverter" /><!--也可以用ref引用-->
</list>
</property>
</bean>
需要注意的是使用转换器需要配置<mvc:annotation-driven />
或者代码中的@EnableWebMvc
,让系统自动生成FormattingConversionServiceFactoryBean实例。通过它才能注册新的转换器。
####3.2 GenericConverter (一对多转换器)
通常除了一对一的转换,还存在一对多的转换关系,如:String转List
在Spring MVC中已经内置了一些常用的转换器.
- StringToArrayConverter
- StringToCollectionConverter
- ArrayToObjectConverter
- ArrayToCollectConverter
- ArrayToStringConverter
- ByteBufferConverter
- CollectionToArrayConverter
- CollectionToObjectConverter
- CollectionToStringConverter
上列内置的转换器都实现了接口:
/**
* A {@link GenericConverter} that may conditionally execute based on attributes
* of the {@code source} and {@code target} {@link TypeDescriptor}.
*
* <p>See {@link ConditionalConverter} for details.
*
* @author Keith Donald
* @author Phillip Webb
* @since 3.0
* @see GenericConverter
* @see ConditionalConverter
* 最常用的转换器接口,即能判断也能转换
*/
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
//根据条件匹配,只有返回true才会转换
public interface ConditionalConverter {
/**
* Should the conversion from {@code sourceType} to {@code targetType} currently under
* consideration be selected?
* @param sourceType 源数据类型
* @param targetType 目标数据类型
* @return true 进行下一步转换
*
*/
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface GenericConverter {
/**
* 返回可接受的转换类型
*/
Set<ConvertiblePair> getConvertibleTypes();
/**
* 转换方法
*/
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
/**
* 可转换匹配类
*/
final class ConvertiblePair {
//源类型
private final Class<?> sourceType;
//目标类型
private final Class<?> targetType;
/**
* Create a new source-to-target pair.
* @param sourceType the source type
* @param targetType the target type
*/
public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
Assert.notNull(sourceType, "Source type must not be null");
Assert.notNull(targetType, "Target type must not be null");
this.sourceType = sourceType;
this.targetType = targetType;
}
public Class<?> getSourceType() {
return this.sourceType;
}
public Class<?> getTargetType() {
return this.targetType;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || other.getClass() != ConvertiblePair.class) {
return false;
}
ConvertiblePair otherPair = (ConvertiblePair) other;
return (this.sourceType == otherPair.sourceType && this.targetType == otherPair.targetType);
}
@Override
public int hashCode() {
return (this.sourceType.hashCode() * 31 + this.targetType.hashCode());
}
@Override
public String toString() {
return (this.sourceType.getName() + " -> " + this.targetType.getName());
}
}
}
一般情况下使用Spring MVC提供的转换器已经足够。
###4. 总结
Spring MVC框架提供了许多便利的功能让开发者修改或者增强功能。
- 可以通过拦截器对控制器的方法的功能进行增强或者补充
- 通过验证器解耦校验代码,进行复用
- 转换器对自定义的数据结构进行转换成对象类型。
除了上述说明的,Spring MVC还提供了格式化器(Formatter),如常用的:
- @DataTimeFormat:日期格式化例如: @DataTimeFormat(iso = ISO.DATE)或者@DataTimeFormat(pattern = “yyyy-MM-dd”))
- @NumberFormat:数值格式化(例如:金额 @NumberFormat(pattern=”#,###.##”))
END
– Nowy
–2018.12.23