《Spring實戰》-第七章:SpringMVC的高級技術

慢慢來比較快,虛心學技術

一、SpringMVC配置的替代方案

Ⅰ、註冊Filter

SpingMVC的AbstractAnnotationConfigDispatcherServletInitializer提供了十分方便的註冊過濾器的方法,通過重載getServletFilters()方法將我們自定義的過濾器註冊到上下文中

如下代碼:

public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /*AbstractAnnotationConfigDispatcherServletInitializer 會同時創
    建 DispatcherServlet 和 ContextLoaderListener 。 GetServlet-ConfigClasses() 方法返回的帶有 @Configuration 註解的
    類將會用來定義 DispatcherServlet 應用上下文中的 bean 。 getRootConfigClasses() 方法返回的帶有 @Configuration 註解的類將
    會用來配置 ContextLoaderListener 創建的應用上下文中的 bean 。*/

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        logger.debug("DispatcherServlet獲取匹配的前端控制器。。。。。。");
        return new String[]{"/"};
    }

    /**
     * 註冊過濾器
     */
    @Override
    protected Filter[] getServletFilters() {
        //將自定義過濾器實例數組返回
        return new Filter[]{new MyFilter()};
    }
}

/**
 * 過濾器類
 */
public class MyFilter implements Filter {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.debug("過濾器初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        logger.debug("執行過濾器");

        Map<String, String[]> parameterMap = servletRequest.getParameterMap();

        Set<String> keySet = parameterMap.keySet();

        for(String key : keySet){
            logger.debug("參數名:{},參數值:{}",key,parameterMap.get(key));
        }

        /**
         * 執行該方法,如果有下一個過濾器則執行下一個過濾器,如果沒有,則執行目標方法
         * 如果不執行該方法,將無法訪問目標路徑請求
         */
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

頁面訪問結果


2019-03-07 17:19:53.859 DEBUG com.my.spring.filter.MyFilter - 執行過濾器
2019-03-07 17:19:53.860 DEBUG com.my.spring.filter.MyFilter - 參數名:data,參數值:[{'id':0}]
2019-03-07 17:19:53.863 DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/SpringAction07/getBean?data={%27id%27:0}", parameters={masked}

Ⅱ、XML配置SpringMVC

如果是在Servlet3.0以下環境(tomcat7.0以下),使用純註解實現SpringMVC就不可能實現了,這時候我們需要藉助web.xml文件進行配置,但是我們並不希望全部使用xml進行配置,所以我們可以簡單配置初始化的內容,其他配置仍使用javaConfig配置方式。

將SpittrWebAppInitializer初始化類去除,使用web.xml代替:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <!--配置使用java配置-->
  <context-param>
   <param-name>contextClass</param-name>
   <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
  </context-param>

  <!--指定根配置類:RootConfig-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.my.spring.config.RootConfig</param-value>
  </context-param>

  <!--註冊ContextLoaderListener-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!--註冊DispatcherServlet-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--使用java配置-->
    <init-param>
      <param-name>contextClass</param-name>
      <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>

    <!--指定DispatcherServlet配置類:WebConfig-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>com.my.spring.config.WebConfig</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

啓動應用,訪問正常!

二、處理文件上傳multipart

前面所遇到的表單處理,我們處理的都是簡單的字符串形式提交,但是上傳文件進行提交是一個應用十分常見的需求,文件上傳提交的格式是multipart格式,自然不可以像處理字符串形式參數一樣去處理。SpringMVC提供了MultipartFile接口用來處理上傳的文件:

public interface MultipartFile extends InputStreamSource {
    String getName();

    @Nullable
    String getOriginalFilename();

    @Nullable
    String getContentType();

    boolean isEmpty();

    long getSize();

    byte[] getBytes() throws IOException;

    InputStream getInputStream() throws IOException;

    default Resource getResource() {
        return new MultipartFileResource(this);
    }

    void transferTo(File var1) throws IOException, IllegalStateException;

    default void transferTo(Path dest) throws IOException, IllegalStateException {
        FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
    }
}

可以看到,MultipartFile接口可以用來獲取文件名,文件大小等信息,還提供了一個InputStream,用來將文件以流的方式讀取,還提供了一個便利的 transferTo() 方法,它能夠幫助我們將上傳的文件寫入到文件系統中。

使用MultiparFile實現文件上傳之前需要先配置Multipart解析器MultipartResolver,Spring3.1後內置兩個MultipartResolver的實現供我們選擇:

  • CommonsMultipartResolver: 使用Jakarta Commons FileUpload解析multipart請求。
  • StandardServletMultipartResolver: 依賴於Servlet3.0對multipart請求的支持。

一般來說,StandardServletMultipartResolver會是更好的方案,因爲它並不需要依賴於其他項目,使用原生的Servlet支持。只不過它只支持Servlet3.0以上的版本,如果低於等於Servlet3.0環境,需要使用CommonsMultipartResolver實現

Ⅰ、使用StandardServletMultipartResolver實現文件上傳

①配置解析器,在上文代碼的WebConfig中配置

public class WebConfig extends WebMvcConfigurationSupport {

    /**
     * 定義一個視圖解析器
     *
     **/
    @Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();
        resourceViewResolver.setPrefix("/WEB-INF/view/");
        resourceViewResolver.setSuffix(".jsp");
        resourceViewResolver.setExposeContextBeansAsAttributes(true);
        resourceViewResolver.setViewClass(org.springframework.web.servlet.view.JstlView.class);
        return resourceViewResolver;
    }

    /**
     * 配置Multipart解析器
     *
     */
    @Bean
    public MultipartResolver multipartResolver(){
        return new StandardServletMultipartResolver();
    }

    @Override
    protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

②在SpittrWebAppInitializer中配置文件上傳的初始化參數(必須)

//通過重載customizeRegistration()方法來配置multipart的默認參數
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
    registration.setMultipartConfig(               
            new MultipartConfigElement("C:\\Users\\xxx\\Desktop\\uploads",2097152,4194304,20000000));
    //設置寫入的臨時路徑(可絕對路徑)
    //上傳文件的最大容量(字節爲單位),默認無限制。
    //整個multipart請求的最大容量(字節爲單位),默認無限制。
    //在上傳的過程中,如果文件大小達到了一個指定的最大容量,將會寫入到臨時文件路勁中。默認爲0,也就是上傳的文件都會寫入到磁盤上。
}

③編寫文件上傳controller方法

@RequestMapping(value = "/upload",method = RequestMethod.POST)
public String upload(@RequestPart("file") MultipartFile multipartFile, Model model) throws IOException {

    String fileName = new String(multipartFile.getOriginalFilename().getBytes("utf-8"));
    //使用multipartFile的transferTo方法將文件存放到桌面
    multipartFile.transferTo(new File("C:\\Users\\xxx\\Desktop\\"+multipartFile.getOriginalFilename()));

    model.addAttribute("fileName",fileName);
    model.addAttribute("fileSize",multipartFile.getSize());

    return "showFile";
}

④編寫上傳文件的JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<meta charset="UTF-8">
<body>
<h2>Hello World!</h2>
    <!--此處必須設置form的enctype屬性設置爲multipart/form-data,否則會報CrrentRquest not a MultipartFile Request-->
    <form action="./upload" method="post" enctype="multipart/form-data">
        <label>文件;</label><input type="file" name="file">
        <button type="submit">提交</button>
    </form>
</body>
</html>

⑤編寫文件信息視圖:showFile.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page isELIgnored="false" %>
<html>
<head>
    <title>文件展示</title>
</head>
<body>
文件名稱:
<c:out value="${fileName}"></c:out><br>
文件大小:
<c:out value="${fileSize}"></c:out>KB
</body>
</html>

頁面效果:


點擊提交,將文件提交至服務器,上傳成功


Ⅱ、使用CommonsMultipartResolver實現文件上傳

Spring內置了 CommonsMultipartResolver ,可以作爲 StandardServletMultipartResolver 的替代方案,但是,由於使用的是Commons FileUpload的上傳方式,需要引入jar支持:

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

最簡單的配置方式就是其構造函數:

@Bean
public MultipartResolver multipartResolver() throws IOException
{
    return new CommonsMultipartResolver();
}

配置文件上傳參數,與 StandardServletMultipartResolver 有所不同,CommonsMultipart-Resolver 不會強制要求設置臨時文件路徑。默認情況下,這個路徑就是 Servlet 容器的臨時目錄。不過,通過設置 uploadTempDir 屬性,我們可以將其指定爲一個不同的位置

@Bean
public MultipartResolver multipartResolver() throws IOException
{
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
   //設置臨時文件夾
    multipartResolver.setUploadTempDir(new FileSystemResource("C:\\Users\\xxx\\Desktop\\uploads"));
    //設置最大內存大小
    multipartResolver.setMaxInMemorySize(100000);
    //設置上傳文件的最大容量
    multipartResolver.setMaxUploadSize(2097152);
    return multipartResolver;
}

經測試,上傳成功

注:如果沒有引入上述兩個jar包,會報文件找不到的錯誤:

javax.servlet.ServletException: Servlet.init() for servlet dispatcher threw exception

Factory method 'multipartResolver' threw exception; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileItemFactory

Ⅲ、文件下載實現

文件下載使用Spring提供的ResponseEntity實現

ResponseEntity:可以添加HttpStatus狀態碼的HttpEntity的擴展類。被用於RestTemplate和Controller層方法

①編寫下載文件的controller方法:

@RequestMapping("/download")
public ResponseEntity<byte[]> filedownload(HttpServletRequest request, String filename) throws Exception{
     //此處指定只從桌面獲取文件
     String path = "C:\\Users\\xxx\\Desktop\\";
     File file = new File(path+File.separator+filename);

     //返回頭部設置
     HttpHeaders headers = new HttpHeaders();
     headers.setContentDispositionFormData("attachment",filename);
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

    //獲取文件輸入流
    InputStream is = new FileInputStream(file);

    //將文件轉換成byte數組
    byte[] bytes = new byte[is.available()];
    is.read(bytes);

    //封裝信息返回
    return new ResponseEntity<byte[]>(bytes,headers, HttpStatus.OK);
}

②編寫JSP文件下載資源:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page isELIgnored="false" %>
<html>
<head>
    <title>文件展示</title>
</head>
<body>
文件名稱:
<c:out value="${fileName}"></c:out><br>
文件大小:
<c:out value="${fileSize}"></c:out>KB
<a href="${pageContext.request.contextPath}/download?filename=${fileName}">文件下載</a>
</body>
</html>

頁面展示如下,點擊文件下載,彈出資源管理窗口,將其保存至目標路徑



下載完成

三、處理異常

自定義異常在SpringMVC中是十分普遍的,有時候我們需要控制異常的輸出樣式,而不是赤裸裸的將錯誤信息展示在用戶面前,那樣並不友好,可能還有點辣眼睛

那麼,SpringMVC中,怎麼捕獲異常並友好輸出呢?也許你會想到使用try-catch的方式去處理,但是那樣耦合性太強了,SpringMVC提供了兩個註解進行捕捉處理

Ⅰ、@ExceptionHandler(MyException.class)捕捉異常

@ExceptionHandler()註解可以捕捉當前控制器內所有方法拋出的特定異常,而不需要在控制器內每個方法上去標識

①編寫自定義異常類,繼承Exception

public class MyException extends Exception{
    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
    }

    public MyException(String message, Throwable cause) {
        super(message, cause);
    }
}

②編寫測試Controller

@Controller
public class ExceptionController {

    @ExceptionHandler(MyException.class)//捕捉當前控制器內任意方法拋出MyException
    public String toError(MyException myException, Model model){
        model.addAttribute("error",myException.getMessage());
        //返回指定的錯誤視圖,經過特殊編寫,會比瀏覽器默認錯誤頁面更美觀
        return "error";
    }

    @RequestMapping("/exception")
    public String testException() throws MyException {
        //爲了測試拋出異常,設置條件恆爲true
        if(1==1){
            throw new MyException("系統搞錯咯");
        }

        return "123";
    }
}

③編寫錯誤頁面:error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page isELIgnored="false" %>
<html>
<head>
    <title>錯誤頁面</title>
</head>
<body>
    <h1>您好,系統異常,請稍後重試</h1>
    <hr>
    <!--使用JSTL標籤和EL表達式從modle獲取錯誤信息-->
    <c:out value="${error}"></c:out>
</body>
</html>

測試結果:

Ⅱ、@ControllerAdvice,爲所有控制器處理異常

即便如上述代碼已經十分方便,我們仍有可能需要在每個有可能拋出異常的控制器內編寫@ExceptionHandler()註解方法,顯然Spring可以做的更好。

@ControllerAdvice註解標註的類可以捕捉應用內所有的錯誤,並結合@ExceptionHandler()在其內進行特殊處理,系統內拋出的所有錯誤,都會經過該類處理

①編寫統一處理類

@ControllerAdvice
public class ExceptionHelper {
    //對特殊異常進行處理
    @ExceptionHandler(MyException.class)
    public String toError(MyException myException, Model model){
        model.addAttribute("error",myException.getMessage());
        return "error";
    }
}

②普通controller方法

@Controller
public class ExceptionController {

    @RequestMapping("/exception")
    public String testException() throws MyException {
        //爲了測試拋出異常,設置條件恆爲true
        if(1==1){
            throw new MyException("系統搞錯咯");
        }

        return "123";
    }
}

測試結果:顯然我們並沒有在ExceptionController中對該錯誤進行顯式處理,結果卻跳轉到錯誤頁面,證明處理成功

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