《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中对该错误进行显式处理,结果却跳转到错误页面,证明处理成功

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