Skip to content

12. Spring Boot的MVC开发配置

12.1. 新建项目

03-springboot-mvc

img

12.2. 自定义拦截器的配置

实现基础springmvc的拦截器

12.2.1. 自定义类实现HandlerInterceptor接口

java
package com.neuedu.config;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor.preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor.postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor.afterCompletion");
    }
}

12.2.2. 使用Java的形式配置拦截器的拦截路径

在WebMvcConfig中注册拦截器,实现WebMvcConfigrer接口,并声明Bean

java
package com.neuedu.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * proxyBeanMethods = false 让当前的类在IOC容器中 不产生 代理对象
 */
@Configuration(proxyBeanMethods = false)
public class BootWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new DateConverter()); //添加了日期转换
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册拦截
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

    }

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {

    }
}

12.3. 自定义页面跳转

在WebMvcConfiger的实现类中重写addViewConroller方法

img

img

12.5. 自定义资源映射

1、自动配置(在全局额配置文件中修改)

spring.mvc.static-path-pattern=/

spring.resources.static-locations= classpath:/static,classpath:/public,classpath:/resources,classpath:/META-INF/resources

2、自定义资源路径

自定义资源映射,重写WebMvcConfiger实现类的 重写addResourceHandler 方法

调用:addResourceHandler 用于处理哪些路径是静态资源

调用:addResourceLocations用于指定静态资源的实际目录

此方法会覆盖配置文件和默认的静态资源配置

java
 @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        /**
         * http://127.0.0.1:8080/images/holiday.png
         */
//        registry.addResourceHandler()
//        registry.addResourceHandler("/images/").addResourceLocations("classpath:/imgs/");
        registry.addResourceHandler("/images/**").
                addResourceLocations("classpath:/imgs/",
                        "classpath:/mystatic/" ,
                        "classpath:/static/" ,
                        "classpath:/public/" ,
                        "classpath:/META-INF/resources",
                        "classpath:/resources");

    }

img

12.6. 错误处理、异常处理

错误的类型,

1) 因为后台程序出错导致的Exception(http 500),

2) 因为参数传递错误(http 400)

3) 客户端的原因造成的路径不对(http 404)

当有错误产生,默认跳转到/error,指向的是BasicErrorController

定制错误响应

1、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的error文件夹下】,发生此状态码的错误就会来到对应的页面;

2、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;

3、以上都没有错误页面,就是默认来到Spring Boot默认的错误提示页面;

可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);

页面能获取的信息:

timestamp:时间戳

status:状态码

error:错误提示

exception:异常对象

message:异常消息

errors:JSR303数据校验的错误都在这里

12.6.1. 编写产生错误的控制器

java
@GetMapping("business")
String business(){
    int result = 0/0;
    return "/index.jsp";
}

12.6.2. 默认的错误页面

img

12.6.3. 指定动态的模板用于显示错误异常信息

1 引入Thymeleaf(类似于jsp、freemarker模板的引擎)模板的启动器

2 在classpath下新建templates/error文件夹

3 在上述文件夹下添加错误的处理页面:500.html、404.html

3 请求产生错误的控制器测试

img

10.6.4. 静态的错误处理页面

如果没有动态的模板,可以在static/error目录下创建错误的处理页面如404.html,静态页面**获取不到\动态的错误状态、时间戳、错误消息等信息。

img

10.6.4.1. 404.html

可以自定义404的显示页面,此处引入腾讯公益404页面

html
<!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8">   <title>Title</title>   <script type="text/javascript" src="//qzonestyle.gtimg.cn/qzone/hybrid/app/404/search_children.js" charset="utf-8"></script> </head> <body> </body> </html>

img

12.7. 使用Java处理异常内容(异常处理器)

1 ) 在Controller层面定义异常处理,哪里有异常哪里处理,只在某一个Controller中生效

java
package com.neuedu.controller;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
//@RestController
public class JspController {

    /**
     * http://127.0.0.1:8080/toview
     * @return
     */
    @GetMapping("toview")
    String toview(){
        return "/index.jsp";
    }


    @GetMapping("business")
    String business(){
        int result = 0/0;
        return "/index.jsp";
    }


    @ExceptionHandler(value = Exception.class )
    @ResponseBody
    String resolveException(HttpServletRequest request, HttpServletResponse response, Exception ex  ){

        System.out.println(ex.getMessage());
        return "{status:'unup',msg:'出错了'}";
    }
}

2) 定义全局的异常处理方式。

定义一个类使用@ControllerAdvice ,在类中定义@ExceptionHandler的注解方法

java
package com.neuedu.boot.controller;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
 * 项目:      springboot
 * 类名:       ExceptionController
 * 创建时间:  2024/3/28 09:50
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
@RestControllerAdvice     // ControllerAdvice +ResponseBody
public class ExceptionController {

    @ExceptionHandler(value=Exception.class)
    public String ex(Exception ex){
        ex.printStackTrace();
        if(ex instanceof NoHandlerFoundException){
            return "{code:404,msg:'请求路径不正确'}";
        }


        return "{code:500,msg:'系统异常全局的异常处理'}";
    }
}

当请求路径是404的时候默认是不抛出异常,但是在 ajax(前后端分离的项目时需要返回 json格式而不是页面),此时可以让springboot在找不到请求的Handler的时候 抛出一个NoHandlerFoundExsception

yml
spring:
  web:
    resources:
      add-mappings: false
  mvc:
    throw-exception-if-no-handler-found: true

12.8. 文件上传、下载

12.8.1. 上传

12.8.1.1. 创建项目

04-spriongboot-fileupload

img

12.8.1.2. 添加依赖

  1. 添加springboot的parent

  2. 添加web的启动器

  3. 添加common-fileupload依赖

  4. Test的启动

  5. Devtools 热部署

  6. Springboot的maven插件

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springboot-learn-parent</artifactId>
        <groupId>com.neuedu.boot</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <packaging>jar</packaging>
    <artifactId>05-boot-upload</artifactId>


    <dependencies>

        <!--devtools-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>


</project>

12.8.1.3. Form表单

Form表单中有form、input[type=file]用于选择上传的文件提交

在src/main/resources/static目录中创建user_icon.html

html
<!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8">   <title>上传用户头像</title> </head> <body>    <form action="upload" method="post" enctype="multipart/form-data">     选择头像:<input type="file" name="mypic" /> <br/>     <input type="submit" value="上传头像" />   </form>  </body> </html>
<%--
  Created by IntelliJ IDEA.
  User: root
  Date: 2021/4/7
  Time: 14:38
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>

<head>
    <title>Title</title>
</head>

<body>
<input type="submit" value="上传头像" >
</form>


</body>
</html>

12.8.1.4. 控制器

接受前端提交的文件信息,保存到硬盘的目录

java

package com.neuedu.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

@Controller
public class FileUploadController {


    @PostMapping("upload")
    String upload(MultipartFile pic) throws IOException {
        //1 将临时空间的文件,转储到 D:\\upload
        String originalFileName = pic.getOriginalFilename();
        //扩展名
        String ext = originalFileName.substring(originalFileName.lastIndexOf("."));
        //转储的新文件名
        String newFileName = UUID.randomUUID().toString() + ext;
        //转储到 D:\\upload 下的  uuid.png/uuid.jpg
        pic.transferTo(new File(BootConstants.UPLOAD_DIR, newFileName));
        //2 将数据存储到 db
        /**
         * 当前时间
         * 上传的ip
         * 操作的工号
         * 上传的文件名
         *
         */
    
        //3 跳转到列表页
    
        return "redirect:/list";
    }


    @GetMapping("list")
    String list(Model model) throws IOException {
    
        File[] list = new File(BootConstants.UPLOAD_DIR).listFiles();
    
        model.addAttribute("fileList",list);
    
        return "/list.jsp";
    }


}

14.8.1.5. 列表页面

html
<%--
  Created by IntelliJ IDEA.
  User: root
  Date: 2021/4/7
  Time: 14:37
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

    <table border="1" cellspacing="0" cellpadding="0" width="100%">
        <tr>
            <th>序号</th>
            <th>文件名称</th>
            <th>操作</th>
        </tr>

        <c:forEach items="${fileList}" var="file" varStatus="stat">
            <tr>
                <td>${stat.count}</td>
                <td>${file.getName()}</td>
                <td>操作</td>
            </tr>

        </c:forEach>

    </table>

</body>
</html>

12.8.2. 下载

  • 使用静态资源映射的形式(粗糙),常用与显示图片
java
/**
 * http://127.0.0.1/down/ae37a66f-aec3-4e23-b2ed-7267adcfb2bb.png
 * @param registry
 */
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/down/**").addResourceLocations("file:D:\\upload\\");
}
  • 编程的方式读写流
java
@GetMapping("/d1/{path}")
    public void download(@PathVariable String path , HttpServletResponse response) throws IOException {

        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.addHeader(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename="+path);

        InputStream is = new FileInputStream(new File(BootConstants.UPLOAD_DIR,path));
        OutputStream os = response.getOutputStream();
        int len = -1;
        byte[] bytes = new byte[1024];
        while (   (len = is.read(bytes)) != -1){
            os.write(bytes,0,len);
        }
        os.close();
        is.close();
    }
  • 使用ResponseEitity
java
@GetMapping("/d2/{path}")
public ResponseEntity download2(@PathVariable String path ) throws IOException {

    return ResponseEntity.ok().
            header(HttpHeaders.CONTENT_TYPE,"application/octet-stream").
            header(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename="+path).
            body(new FileSystemResource(new File(BootConstants.UPLOAD_DIR,path)));
}
  • 下载的超链接
html
<a href="${pageContext.request.contextPath}/down/ae37a66f-aec3-4e23-b2ed-7267adcfb2bb.png">下载1</a>
                    <a href="${pageContext.request.contextPath}/d1/ae37a66f-aec3-4e23-b2ed-7267adcfb2bb.png">下载2</a>
                    <a href="${pageContext.request.contextPath}/d2/ae37a66f-aec3-4e23-b2ed-7267adcfb2bb.png">下载3</a>

12.9. 自定义日期类型转换器

详见Convert 和Formatter

12.10. 数据校验

SpringBoot的2.2.x版本之前,如果使用starter-web默认包含校验框架,2.3.x之后或者没有使用starter-web,需要添加starter-validation的校验框架

xml
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

或者

 <dependency>
     <groupId>org.hibernate.validator</groupId>
     <artifactId>hibernate-validator</artifactId>
</dependency>

img

img

img

1、bean 中添加校验规则

img

2、resource 下新建错误信息配置文件

img

3、Controller中开启验证

img

4、捕获错误信息

java

package com.neuedu.controller;  import com.neuedu.entity.User; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;  import java.util.List;  */ * * *项目   :* *springboot-java1 * * *创建时间 :2020/4/10  8:45 10 *  author*  *:jshand-root *  site   :  http://314649444.iteye.com * * *描述*   *: * / *@Controller public class UserController {     */* *     http://127.0.0.1/saveUser?name=abc&address=hlj&age=18 *   * *@param\* *user\ \*   * *@return\ \*   / *  @ResponseBody   @RequestMapping("saveUser")   public String saveUser(@Validated User user , BindingResult bindingResult){      System.*out\*.println(user);     if(bindingResult.getErrorCount()>0){       StringBuffer sb = new StringBuffer();       *//如果产生异常,将异常的字符串简单打印到浏览器 *      List<ObjectError> errors = bindingResult.getAllErrors();       for (ObjectError error : errors) {         sb.append(error.getDefaultMessage()+"  ");       }       return sb.toString();     }     return user.toString();   }   }

5、显示错误信息

添加虚拟机参数解决中文乱码问题

img

12.11. 注册Servlet三大组件

12.11.1. 使用servlet3.0的API声明,使用ServletComponentScan注解扫描

使用@WebServlet、@WebFilter、@WebListener申明

12.11.1.1. 在启动类上添加@ServletComponentScan

java
package com.neuedu.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class Application07 {

	public static void main(String[] args) {
		SpringApplication.run(Application07.class, args);
	}

}

12.11.1.2. 定义Servlet

java
package com.neuedu.boot.servletcomponent;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * http://127.0.0.1:8080/my.do
 */
@WebServlet(urlPatterns = "/my")
public class FirstSerlvet extends HttpServlet {


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("FirstSerlvet.doPost");
        System.out.println("sessionid: "+req.getSession().getId());
    }
}

12.11.1.3. 定义的Filter

java
package com.neuedu.boot.servletcomponent;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;


/**
 * http://127
 */
@WebFilter(urlPatterns="/*")
public class FirstFilter  implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("FirstFilter.doFilter");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

14.11.1.4. 定义Listener

java
package com.neuedu.boot.servletcomponent;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

@WebListener
public class FirstListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("FirstListener.sessionCreated: Session被创建");
    }
}

12.11.2. 使用registration的Bean注册组件

三大组件使用代码(不需要,@WebListener、@WebServlet(urlPatterns = "/servlet")、@WebFilter(urlPatterns = "/*"))

使用@Bean配合RegistrationBean类型注册

java
package com.neuedu.servlet.config;

import com.neuedu.servlet.component.MySessionListener;
import com.neuedu.servlet.component.SecondFilter;
import com.neuedu.servlet.component.SecondServlet;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class Appconfig {


    /**
     *  http://127.0.0.1:80/sec
     *  http://127.0.0.1:80/second
     * @return
     */
    @Bean
    ServletRegistrationBean getServletRegistrationBean(){
        ServletRegistrationBean<SecondServlet> registrationBean = new ServletRegistrationBean<SecondServlet>();
        registrationBean.setServlet(new SecondServlet());
        registrationBean.addUrlMappings("/sec","/second");

        return registrationBean;
    }

    @Bean
    FilterRegistrationBean getFilterRegistrationBean(){
        FilterRegistrationBean<SecondFilter> registrationBean = new FilterRegistrationBean<SecondFilter>();
        registrationBean.setFilter(new SecondFilter());
        registrationBean.addUrlPatterns("/*");

        return registrationBean;
    }


    @Bean
    ServletListenerRegistrationBean getServletListenerRegistrationBean(){
        ServletListenerRegistrationBean registrationBean = new ServletListenerRegistrationBean();
        registrationBean.setListener(new MySessionListener());
        return registrationBean;
    }


}

14.11.2.1Servlet定义

java
package com.neuedu.servlet.component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SecondServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getSession();
        System.out.println("SecondServlet.doGet");
    }
}

14.11.2.2Filter定义

java
package com.neuedu.servlet.component;

import javax.servlet.*;
import java.io.IOException;

public class SecondFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("SecondFilter.doFilter 生效了");
        chain.doFilter(request, response);
    }
}

14.11.2.3Listener定义

java
package com.neuedu.servlet.component;

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class MySessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("Session对象被创建");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {

    }
}

12.11.3 使用Bean注解定义跨域的Filter

java
@Bean
FilterRegistrationBean<CorsFilter> cors(){
    FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<CorsFilter>();
    CorsConfiguration config = new CorsConfiguration();

    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);

    CorsFilter filter = new CorsFilter(source);
    registrationBean.setFilter(filter);
    registrationBean.addUrlPatterns("/*");

    return registrationBean;
}

另外一种跨域方式

实现WebMvcConfiger接口,并重写

java
@Override
public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**").allowedHeaders("*").allowedOrigins("*").allowedMethods("*");
}

Released under the MIT License.