美文网首页JAVA学习记录Java 杂谈Java
《Spring实战》-第七章:SpringMVC的高级技术

《Spring实战》-第七章:SpringMVC的高级技术

作者: 廖小明的赖胖子 | 来源:发表于2019-03-24 23:54 被阅读6次

    慢慢来比较快,虚心学技术

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

    相关文章

      网友评论

        本文标题:《Spring实战》-第七章:SpringMVC的高级技术

        本文链接:https://www.haomeiwen.com/subject/ewhmvqtx.html