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/**"/>