12. Spring Boot的MVC开发配置
12.1. 新建项目
03-springboot-mvc
12.2. 自定义拦截器的配置
实现基础springmvc的拦截器
12.2.1. 自定义类实现HandlerInterceptor接口
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
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方法
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用于指定静态资源的实际目录
此方法会覆盖配置文件和默认的静态资源配置
@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");
}
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. 编写产生错误的控制器
@GetMapping("business")
String business(){
int result = 0/0;
return "/index.jsp";
}
12.6.2. 默认的错误页面
12.6.3. 指定动态的模板用于显示错误异常信息
1 引入Thymeleaf(类似于jsp、freemarker模板的引擎)模板的启动器
2 在classpath下新建templates/error文件夹
3 在上述文件夹下添加错误的处理页面:500.html、404.html
3 请求产生错误的控制器测试
10.6.4. 静态的错误处理页面
如果没有动态的模板,可以在static/error目录下创建错误的处理页面如404.html,静态页面**获取不到\动态的错误状态、时间戳、错误消息等信息。
10.6.4.1. 404.html
可以自定义404的显示页面,此处引入腾讯公益404页面
<!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>
12.7. 使用Java处理异常内容(异常处理器)
1 ) 在Controller层面定义异常处理,哪里有异常哪里处理,只在某一个Controller中生效
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的注解方法
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
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
12.8.1.2. 添加依赖
添加springboot的parent
添加web的启动器
添加common-fileupload依赖
Test的启动
Devtools 热部署
Springboot的maven插件
<?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
<!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. 控制器
接受前端提交的文件信息,保存到硬盘的目录
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. 列表页面
<%--
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. 下载
- 使用静态资源映射的形式(粗糙),常用与显示图片
/**
* 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\\");
}
- 编程的方式读写流
@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
@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)));
}
- 下载的超链接
<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. 自定义日期类型转换器
12.10. 数据校验
SpringBoot的2.2.x版本之前,如果使用starter-web默认包含校验框架,2.3.x之后或者没有使用starter-web,需要添加starter-validation的校验框架
<!-- 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>
1、bean 中添加校验规则
2、resource 下新建错误信息配置文件
3、Controller中开启验证
4、捕获错误信息
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、显示错误信息
添加虚拟机参数解决中文乱码问题
12.11. 注册Servlet三大组件
12.11.1. 使用servlet3.0的API声明,使用ServletComponentScan注解扫描
使用@WebServlet、@WebFilter、@WebListener申明
12.11.1.1. 在启动类上添加@ServletComponentScan
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
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
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
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类型注册
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定义
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定义
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定义
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
@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接口,并重写
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedHeaders("*").allowedOrigins("*").allowedMethods("*");
}