Spring MVC 实践

标签 : Java与Web


Converter

Spring MVC的数据绑定并非没有任何限制, 有案例表明: Spring在如何正确绑定数据方面是杂乱无章的. 比如: Spring总是试图用默认的语言区域将日期输入绑定到java.util.Data, 如果想要使用不同的日期格式(format),就需要Converter的协助.

Spring提供了Converter接口来供开发者自定义Converter类:

/**
 * @since 3.0
 * @param <S> the source type
 * @param <T> the target type
 */
public interface Converter<S, T> {
    T convert(S source);
}
  • 自定义Converter:
/**
 * @author jifang.
 * @since 2016/6/19 7:23.
 */
public class StringDateConverter implements Converter<String, Date> {

    private String pattern;

    public StringDateConverter(String pattern) {
        this.pattern = pattern;
    }

    @Override
    public Date convert(String source) {
        try {
            return new SimpleDateFormat(pattern).parse(source);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 配置

    为了能够让Spring MVC使用我们自定义的Converter, 需要在配置文件中配置一个ConversionServiceFactoryBean:
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="com.fq.mvc.converter.StringDateConverter">
                <constructor-arg type="java.lang.String" value="yyyy-MM-dd hh:mm:ss"/>
            </bean>
        </set>
    </property>
</bean>

然后为<annotation-driven/>配置conversion-service属性:

<mvc:annotation-driven conversion-service="conversionService"/>

注: 还可以使用FormattingConversionServiceFactoryBean来加载Converter, 由于其配置方法与ConversionServiceFactoryBean, 故在此就不再赘述.

  • Controller
@RequestMapping("/add_user.do")
public String addUser(User user, BindingResult binding) {
    if (binding.hasErrors()) {
        FieldError error = binding.getFieldError();
        // log ...
    }

    service.addUser(user);
    return "redirect: users.do";
}

BindingResult参数中放置了Spring的所有绑定错误.


Interceptor

Spring MVC的拦截器类似于Servlet中的Filter(关于Filter,详细可参考Servlet - Listener、Filter、Decorator),用于Controller进行预处理和后处理.

Spring提供了Interceptor接口来供开发者自定义Interceptor类:

public interface HandlerInterceptor {

    /**
     * 进入Controller方法前执行
     * 应用场景: 身份认证、身份授权等
     */
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception;

    /**
     * 进入Controller方法后, 返回ModelAndView前执行
     * 应用场景: 将公共模型数据填充到ModelAndView、统一指定视图等
     */
    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception;

    /**
     * 执行完Controller方法后执行
     * 应用场景: 统一日志处理、统一异常处理等
     */
    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;

}

示例: 统计Controller执行耗时.
  • 自定义Interceptor
/**
 * @author jifang
 * @since 16/7/4 上午10:35.
 */
public class HandleTimeInterceptor implements HandlerInterceptor {

    private static final String START_TIME = "start_time";

    private static final String HANDLE_TIME = "handle_time";

    private static final Logger LOGGER = LoggerFactory.getLogger(HandleTimeInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.setAttribute(START_TIME, System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long start = (long) request.getAttribute(START_TIME);
        request.setAttribute(HANDLE_TIME, System.currentTimeMillis() - start);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String uri = request.getRequestURI();
        long consume = (long) request.getAttribute(HANDLE_TIME);
        LOGGER.info("uri: {} consume {}s", uri, consume / 1000);
    }
}
  • 配置
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.fq.mvc.interceptor.HandleTimeInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

Upload

Spring MVC提供了对Servlet 3.0文件上传的支持(关于Servlet 3.0文件上传可参考博客Servlet - Upload、Download、Async、动态注册).

Spring MVC提供了MultipartFile接口,上传到应用中的文件都被包装在一个MultipartFile对象中:

MultipartFile 描述
String getName() Return the name of the parameter in the multipart form.
String getOriginalFilename() Return the original filename in the client’s filesystem.
long getSize() Return the size of the file in bytes.
boolean isEmpty() Return whether the uploaded file is empty, that is, either no file has been chosen in the multipart form or the chosen file has no content.
String getContentType() Return the content type of the file.
byte[] getBytes() Return the contents of the file as an array of bytes.
InputStream getInputStream() Return an InputStream to read the contents of the file from.
void transferTo(File dest) Transfer the received file to the given destination file.

在Servlet 3.0及更高版本的容器中进行文件上传编程,总是围绕着@MultipartConfig注解和Part接口,处理上传文件的Servlet必须以@MultipartConfig注解标注, 但DispatcherServlet是Spring jar包已经编译好的类, 无法进行修改,值得庆幸的是Servlet 3.0还可以使用部署描述符web.xml将一个Servlet变为MultipartConfig Servlet:

<servlet>
    <servlet-name>mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/mvc-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <multipart-config>
        <max-file-size>20848820</max-file-size>
        <file-size-threshold>1048576</file-size-threshold>
    </multipart-config>
</servlet>
<servlet-mapping>
    <servlet-name>mvc</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

此外, 在mvc-servlet.xml文件中配置一个MultipartResolver:

<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>

此时就可以进行文件上传编程了:

@RequestMapping("/upload.do")
public String upload(MultipartFile file) throws IOException {
    String name = file.getOriginalFilename();
    String fileName = String.format("/data/file/%s", name);
    file.transferTo(new File(fileName));
    return "file_upload";
}

Exception

系统异常包含两类: 预期异常运行时异常RuntimeException.前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生.

基于Spring MVC的DAOServiceController的异常都可以通过throw向上层抛出,最后统一由DispatcherServlet的异常处理器进行处理.

  • 自定义异常

    如果Controller/Service/DAO抛出此类异常说明是预期异常:
/**
 * @author jifang.
 * @since 2016/6/21 16:28.
 */
public class MVCException extends Exception {

    private String message;

    public MVCException(String message) {
        super(message);
        this.message = message;
    }

    @Override
    public String getMessage() {
        return this.message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
  • 异常处理器
/**
 * @author jifang.
 * @since 2016/6/21 16:33.
 */
public class MVCExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

        String message;
        if (ex instanceof MVCException) {
            message = ex.getMessage();
        } else {
            message = "未知异常";
        }

        return new ModelAndView("error", "message", message);
    }
}
  • error.vm
<html>
<head>
    <title>错误信息</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
</head>
<body>
${message}
</body>
</html>
  • 注册异常处理器
<bean class="com.fq.mvc.exception.MVCExceptionResolver"/>

JSON

JSON数据格式形式简单, 解析方便, 因此常用在接口调用、HTML页面中.

Spring MVC对其提供了如下支持:在Controller方法上添加@ResponseBody注解, Spring MVC会自动将Java对象转换成JSON字符串输出; 在方法形参上添加@RequestBody注解, Spring MVC会自动将JSON串转换成Java对象:

@ResponseBody
@RequestMapping("/user_json.do")
public User userJSON(@RequestBody User user) {
    return user;
}
  • fastjson

    Spring MVC默认使用jackson对request/response进行JSON转换,而在此我们选用性能更高的fastjson, 因此需要在<annotation-driven/>中另做配置.

首先, 使用fastjson需要在pom.xml中添加如下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.7</version>
</dependency>

然后在mvc-servlet.xml中做如下配置:

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="false">
        <bean id="fastJsonHttpMessageConverter"
              class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html;charset=UTF-8</value>
                    <value>application/json;charset=UTF-8</value>
                </list>
            </property>
            <property name="features">
                <array value-type="com.alibaba.fastjson.serializer.SerializerFeature">
                    <value>DisableCircularReferenceDetect</value>
                </array>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

Other

1. POST Encoder

在web.xml配置一个编码Filter可以解决POST乱码问题:

<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2. GET Encoder

对于GET乱码, 由于Tomcat 8.0之前版本默认使用ISO-8859-1编码, 因此有两种解决方案:

  • 修改tomcat配置文件

    修改tomcat配置文件server.xml设置编码与工程编码一致:
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
  • 重新编码

    将经Tomcat编码的内容解码后再重新编码为UTF-8:
String name = new String(request.getParamter("name").getBytes("ISO8859-1"),"utf-8");

注: Tomcat 8.0及更高版本的容器不用此配置.


3. Static Resources Mapping

如果将DispatherServlet配置成拦截所有请求<url-pattern>/</url-pattern>, 则必须额外配置静态资源的映射规则, 否则Spring MVC会对像js/css之类的文件也做转发.

Spring MVC使用<mvc:resources/>元素配置对静态资源的映射:

<mvc:resources location="/js/" mapping="/js/**"/>

Spring MVC 实践 - Component的更多相关文章

  1. Spring MVC 实践 - Base

    Spring MVC 实践 标签 : Java与Web Spring Web MVC Spring-Web-MVC是一种基于请求驱动的轻量级Web-MVC设计模式框架, Spring MVC使用MVC ...

  2. Spring MVC实践

    MVC 设计概述 在早期 Java Web 的开发中,统一把显示层.控制层.数据层的操作全部交给 JSP 或者 JavaBean 来进行处理,我们称之为 Model1: 出现的弊端: JSP 和 Ja ...

  3. Spring MVC 实践笔记

    1.了解 Maven 的用法:http://spring.io/guides/gs/maven/ .这篇英文非常详细的演示了 Maven 的用法,在命令行下执行.注意,运行Maven的时候,Maven ...

  4. 搭建Spring4+Spring MVC web工程的最佳实践

    Spring是个非常非常非常优秀的java框架,主要是用它的IOC容器帮我们依赖注入和管理一些程序中的Bean组件,实现低耦合关联,最终提高系统可扩展性和可维护性,用它来辅助我们构建web工程将会感觉 ...

  5. IntelliJ IDEA:Getting Started with Spring MVC, Hibernate and JSON实践

    原文:IntelliJ IDEA:Getting Started with Spring MVC, Hibernate and JSON实践 最近把编辑器换成IntelliJ IDEA,主要是Ecli ...

  6. JSR-303 Bean Validation 介绍及 Spring MVC 服务端参数验证最佳实践

    任何时候,当要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情. 应用程序必须通过某种手段来确保输入参数在上下文来说是正确的. 分层的应用很多时候同样的数据验证逻辑会出现在不同的层,这样 ...

  7. JSR-303 Bean Validation 介绍及 Spring MVC 服务端验证最佳实践

    任何时候,当要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情. 应用程序必须通过某种手段来确保输入参数在上下文来说是正确的. 分层的应用在很多时候,同样的数据验证逻辑会出现在不同的层, ...

  8. 基于 Spring MVC 的开源测试用例管理系统以及开发自测的实践

    早前公司领导提出让开发自测,测试么也做做开发.当然了,为了保证自测质量,测试用例仍需测试提供,所以为了提高开发自测的效率和质量,我们开发了捉虫记.捉虫记是一个完整的Spring MVC项目,现已开源, ...

  9. Spring MVC基本配置和实践(一)

    一.Spring MVC介绍 1. Spring MVC是什么? The Spring Web MVC framework和Struts2都属于表现层的框架,它是Spring框架的一部分,我们可以从S ...

随机推荐

  1. 数据库性能优化(database tuning)性能优化绝不仅仅只是索引

    一毕业就接触优化方面的问题,专业做优化也有至少5年之多的时间了,可现在还是经常听到很多人认为优化很简单,就是建索引的问题,这确实不能怪大家,做这行20多年的时间里,在职业生涯的每个阶段,几乎都能听到这 ...

  2. Android基础字符串String.md

    问题抛出 String这个常量在我们代码中会经常被用到,那么我们了解 String stringbuffer StringBudilder三者之间的区别吗 问题解答 String 字符串常量,位于常量 ...

  3. 前端开发必备之chrome插件

    Chrome浏览器目前是网络上可用的最好的浏览器之一,并且自2011年11月超越了Firefox浏览器之后,已经成为了互联网上占主导地位的浏览器. 本篇文章将与大家分享一些与前端开发有关的实用的Chr ...

  4. 【微信小程序】对微信http请求API的封装,方便对错误码进行处理

    /**   * App 微信配置文件app.js   * author: nujey   * versions: 1.0.0   */   App({   /**   * @param {Object ...

  5. java中关于&、&&、|、||之间的区别和运算

    关于&.&&.|.||之间的区别和运算 在逻辑运算中: 普通与&:要判断所有的判断的条件 短路与&&:如果前面一个判断条件出现false,则后续的判断条 ...

  6. C#利用微软企业库Enterprise Library配置mysql数据库

    在C#项目中,很多时候到要用到Enterprise Library.这里只是用一个很简单的小例子来演示一下Enterprise Library在VS2010中操作MySQL数据库的流程. 1,利用En ...

  7. 机器学习基石:01 The Learning Problem

    什么时候适合用机器学习算法? 1.存在某种规则/模式,能够使性能提升,比如准确率: 2.这种规则难以程序化定义,人难以给出准确定义: 3.存在能够反映这种规则的资料. 所以,机器学习就是设计算法A,从 ...

  8. Linux提示字符设置

    当我们登陆linux后,显示的提示字符究竟是什么意思呢?又可不可以设置呢. 首先来看看默认的显示: 普通用户: [fuwh@localhost ~]$ root用户: [root@localhost ...

  9. [BZOJ 3456]城市规划

    Description 题库链接( bzoj 权限题,可以去 cogs 交♂ 题库链接2 求含有 \(n\) 个点有标号的简单无向联通图的个数.方案数对 \(1004535809(479\times ...

  10. bzoj4559[JLoi2016]成绩比较 容斥+拉格朗日插值法

    4559: [JLoi2016]成绩比较 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 261  Solved: 165[Submit][Status ...