Spring MVC 实践 - Component
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的DAO、Service、Controller的异常都可以通过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的更多相关文章
- Spring MVC 实践 - Base
		
Spring MVC 实践 标签 : Java与Web Spring Web MVC Spring-Web-MVC是一种基于请求驱动的轻量级Web-MVC设计模式框架, Spring MVC使用MVC ...
 - Spring MVC实践
		
MVC 设计概述 在早期 Java Web 的开发中,统一把显示层.控制层.数据层的操作全部交给 JSP 或者 JavaBean 来进行处理,我们称之为 Model1: 出现的弊端: JSP 和 Ja ...
 - Spring MVC 实践笔记
		
1.了解 Maven 的用法:http://spring.io/guides/gs/maven/ .这篇英文非常详细的演示了 Maven 的用法,在命令行下执行.注意,运行Maven的时候,Maven ...
 - 搭建Spring4+Spring MVC web工程的最佳实践
		
Spring是个非常非常非常优秀的java框架,主要是用它的IOC容器帮我们依赖注入和管理一些程序中的Bean组件,实现低耦合关联,最终提高系统可扩展性和可维护性,用它来辅助我们构建web工程将会感觉 ...
 - IntelliJ IDEA:Getting Started with Spring MVC, Hibernate and JSON实践
		
原文:IntelliJ IDEA:Getting Started with Spring MVC, Hibernate and JSON实践 最近把编辑器换成IntelliJ IDEA,主要是Ecli ...
 - JSR-303 Bean Validation 介绍及 Spring MVC 服务端参数验证最佳实践
		
任何时候,当要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情. 应用程序必须通过某种手段来确保输入参数在上下文来说是正确的. 分层的应用很多时候同样的数据验证逻辑会出现在不同的层,这样 ...
 - JSR-303 Bean Validation 介绍及 Spring MVC 服务端验证最佳实践
		
任何时候,当要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情. 应用程序必须通过某种手段来确保输入参数在上下文来说是正确的. 分层的应用在很多时候,同样的数据验证逻辑会出现在不同的层, ...
 - 基于 Spring MVC 的开源测试用例管理系统以及开发自测的实践
		
早前公司领导提出让开发自测,测试么也做做开发.当然了,为了保证自测质量,测试用例仍需测试提供,所以为了提高开发自测的效率和质量,我们开发了捉虫记.捉虫记是一个完整的Spring MVC项目,现已开源, ...
 - Spring MVC基本配置和实践(一)
		
一.Spring MVC介绍 1. Spring MVC是什么? The Spring Web MVC framework和Struts2都属于表现层的框架,它是Spring框架的一部分,我们可以从S ...
 
随机推荐
- 使用hue查看hdfs系统报无法访问:/user/hadoop。 Note: you are a Hue admin but not a HDFS superuser, "hdfs" or part of HDFS supergroup, "supergroup".
			
出现这个问题,是因为默认的超级用户是hdfs ,我的是hadoop用户登录的, 也就是说首次登录hadoop这个用户是我的超级用户 此时只需要将hue.ini配置改为 然后重启即可.
 - Spring(5)——Spring 和数据库编程
			
传统 JDBC 回顾 JDBC 我们一定不陌生,刚开始学习的时候,我们写过很多很多重复的模板代码: public Student getOne(int id) { String sql = " ...
 - Windows下Java开发环境安装与配置
			
1. 前往Oracle网站下载JDK程序并安装. http://www.oracle.com/technetwork/java/javase/downloads/index.html 目前最新的版本为 ...
 - [NOIp 2012]国王游戏
			
Description 恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右手上面分别写下一个整数,国王自己也在左.右手上各写一个整数.然后,让这 n 位大臣排成一排,国 ...
 - 洛谷3794 签到题IV
			
题目描述 给定一个长度为n的序列$a_1,a_2...a_n$,其中每个数都是正整数. 你需要找出有多少对(i,j),$1 \leq i \leq j \leq n$且$gcd(a_i,a_{i+1} ...
 - Codeforces Round #408 (Div. 2)
			
C. Bank Hacking 题目大意:给出一棵n个节点的树,每个节点有一个权值,删掉一个点的代价为当前这个点的权值,并且会使其相邻点和距离为2且中间隔着未被删除的点的点权值加1,现在选一个点开始删 ...
 - django rest-framework 4.REST的认证和权限
			
目前,我们的API对谁可以编辑或删除代码段没有任何限制.我们想要一些更先进的行为,以确保:(这段话抄自官网) 代码段始终与创建者相关联. 只有身份验证的用户可以创建片段. 只有片段的创建者可以更新或删 ...
 - Windows下免安装版mysql5.7的初始密码
			
MySQL5.7之后,初始密码不在默认为空,而是随机生成的密码. 在mysql/data目录下,生成了一个.err文件(等同linux下的log日志文件,此文件会被mysql服务占用). 使用记事本可 ...
 - 第四周小组作业:Wordcount优化
			
1.小组github地址 https://github.com/muzhailong/wcPro 2.PSP表格 PSP2.1 PSP阶段 预计耗时(分钟) 实际耗时(分钟) Planning 计划 ...
 - 读书笔记-《Maven实战》-关于Maven依赖传递的思考 2018/4/26
			
上次读书笔记中,提到了依赖传递.看着依赖传递表,一直在思考为什么会是这样. 先看传递表: compile test provided runtime compile test provided run ...