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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章