SpringMVC学习记录04——拦截器、验证器和转化器

###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操作.也就是不再执行applyPostHandleafterCompletion

####1.2 拦截器的使用
在开发中使用Spring MVC框架提供的HandlerInterceptorAdapter来实现自定义拦截器。

  1. 实现拦截器

    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");
        }
    
    }
    
  2. 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>
    
  3. 多拦截器下的执行顺序
    拦截器的执行顺序和责任链一样,下面是拦截器的执行图

拦截器的执行图

需要注意的是preHandle的返回值。如果返回值为false表示控制器逻辑和后续方法都将不会被执行。即:该拦截器的posthandle方法和afterCompletion方法将不会被执行。但是,因为外层的拦截器的preHandle方法返回的是true,所以外层的afterCompletion方法将继续执行。

###2.验证器 Validator
在Spring MVC中,可以采用类似validation-api-1.1.0.Final.jar提供的验证注解。也可以使用Spring提供的Validator接口进行校验。

注意:

  1. validation-api-1.1.0.Final.jar的引入还需要依赖:
    • classmate-1.3.3.jar
    • jboss-logging-3.3.1.Final.jar


  2. 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):检验方法,对象的传入和错误信息的设置。

具体使用方法示例:

  1. 实现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, "交易金额和购买数量与价格不匹配");
            }
        }
    }
    
  2. 配置使用校验类

    @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转换器步骤:

  1. 实现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;
    }
    

    }

  2. 注册到服务类中

方式一(代码注册):

@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或者String[]等。
在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

分享到