Spring-boot-starter-web(了解)
Spring Boot 是在 Spring 的基础上创建一款开源框架,它提供了 spring-boot-starter-web(Web 场景启动器) 来为 Web 开发予以支持。spring-boot-starter-web 为我们提供了嵌入的 Servlet 容器以及 SpringMVC 的依赖,并为 Spring MVC 提供了大量自动配置,可以适用于大多数 Web 开发场景。
Spring Boot Web 快速开发
Spring Boot 为 Spring MVC 提供了自动配置,并在 Spring MVC 默认功能的基础上添加了以下特性:
引入了 ContentNegotiatingViewResolver 和 BeanNameViewResolver(视图解析器)
对包括 WebJars 在内的静态资源的支持
自动注册 Converter、GenericConverter 和 Formatter (转换器和格式化器)
对 HttpMessageConverters 的支持(Spring MVC 中用于转换 HTTP 请求和响应的消息转换器)
自动注册 MessageCodesResolver(用于定义错误代码生成规则)
支持对静态首页(index.html)的访问
自动使用 ConfigurableWebBindingInitializer
只要我们在 Spring Boot 项目中的 pom.xml 中引入了 spring-boot-starter-web ,即使不进行任何配置,也可以直接使用 Spring MVC 进行 Web 开发。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
注意:由于 spring-boot-starter-web 默认替我们引入了核心启动器 spring-boot-starter,因此,当 Spring Boot 项目中的 pom.xml 引入了 spring-boot-starter-web 的依赖后,就无须在引入 spring-boot-starter 核心启动器的依赖了。
Spring Boot 静态资源映射(了解)
Spring Boot 默认为我们提供了 3 种静态资源映射规则:
- WebJars 映射
- 默认资源映射
- 静态首页(欢迎页)映射
WebJars 映射
为了让页面更加美观,让用户有更多更好的体验,Web 应用中通常会使用大量的 JS 和 CSS,例如 jQuery,Backbone.js 和 Bootstrap 等等。通常我们会将这些 Web 前端资源拷贝到 Java Web 项目的 webapp 相应目录下进行管理。但是 Spring Boot 项目是以 JAR 包的形式进行部署的,不存在 webapp 目录,那么 Web 前端资源该如何引入到 Spring Boot 项目中呢?
WebJars 可以完美的解决上面的问题,它可以 Jar 形式为 Web 项目提供资源文件。
WebJars 可以将 Web 前端资源(JS,CSS 等)打成一个个的 Jar 包,然后将这些 Jar 包部署到 Maven 中央仓库中进行统一管理,当 Spring Boot 项目中需要引入 Web 前端资源时,只需要访问 WebJars 官网,找到所需资源的 pom 依赖,将其导入到项目中即可。
所有通过 WebJars 引入的前端资源都存放在当前项目类路径(classpath)下的“/META-INF/resources/webjars/” 目录中。WebJars 的映射路径为“/webjars/**”,即所有访问“/webjars/**”的请求,都会去“classpath:/META-INF/resources/webjars/”查找 WebJars 前端资源。
默认静态资源映射
当访问项目中的任意资源(即“/**”)时,Spring Boot 会默认从以下路径中查找资源文件(优先级依次降低):
1.classpath:/META-INF/resources/
2.classpath:/resources/
3.classpath:/static/
4.classpath:/public/
这些路径又被称为静态资源文件夹,它们的优先级顺序为:classpath:/META-INF/resources/ > classpath:/resources/ > classpath:/static/ > classpath:/public/ 。
当我们请求某个静态资源(即以“.html”结尾的请求)时,Spring Boot 会先查找优先级高的文件夹,再查找优先级低的文件夹,直到找到指定的静态资源为止。
静态首页(欢迎页)映射
静态资源文件夹下的所有 index.html 被称为静态首页或者欢迎页,它们会被被 /** 映射,换句话说就是,当我们访问“/”或者“/index.html”时,都会跳转到静态首页(欢迎页)。
注意,访问静态首页或欢迎页时,其查找顺序也遵循默认静态资源的查找顺序,即先查找优先级高的目录,在查找优先级低的目录,直到找到 index.html 为止。
Spring Boot 定制Spring MVC(了解)
Spring Boot 抛弃了传统 xml 配置文件,通过配置类(标注 @Configuration 的类,相当于一个 xml 配置文件)以 JavaBean 形式进行相关配置。
Spring Boot 对 Spring MVC 的自动配置可以满足我们的大部分需求,但是我们也可以通过自定义配置类(标注 @Configuration 的类)并实现 WebMvcConfigurer 接口来定制 Spring MVC 配置,例如拦截器、格式化程序、视图控制器等等。
SpringBoot 1.5 及以前是通过继承 WebMvcConfigurerAdapter 抽象类来定制 Spring MVC 配置的,但在 SpringBoot 2.0 后,WebMvcConfigurerAdapter 抽象类就被弃用了,改为实现 WebMvcConfigurer 接口来定制 Spring MVC 配置。
WebMvcConfigurer 是一个基于 Java 8 的接口,该接口定义了许多与 Spring MVC 相关的方法,其中大部分方法都是 default 类型的,且都是空实现。因此我们只需要定义一个配置类实现 WebMvcConfigurer 接口,并重写相应的方法便可以定制 Spring MVC 的配置。
在 Spring Boot 项目中,我们可以通过以下 形式定制 Spring MVC:
- 扩展 Spring MVC
- 全面接管 Spring MVC
扩展 Spring MVC
如果 Spring Boot 对 Spring MVC 的自动配置不能满足我们的需要,我们还可以通过自定义一个 WebMvcConfigurer 类型(实现 WebMvcConfigurer 接口)的配置类(标注 @Configuration,但不标注 @EnableWebMvc 注解的类),来扩展 Spring MVC。这样不但能够保留 Spring Boot 对 Spring MVC 的自动配置,享受 Spring Boot 自动配置带来的便利,还能额外增加自定义的 Spring MVC 配置。
package net.biancheng.www.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.*; //实现 WebMvcConfigurer 接口可以来扩展 SpringMVC 的功能 //@EnableWebMvc 不要接管SpringMVC @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { //当访问 “/” 或 “/index.html” 时,都直接跳转到登陆页面 registry.addViewController("/").setViewName("login"); registry.addViewController("/index.html").setViewName("login"); } }
全面接管 Spring MVC
在一些特殊情况下,我们可能需要抛弃 Spring Boot 对 Spring MVC 的全部自动配置,完全接管 Spring MVC。此时我们可以自定义一个 WebMvcConfigurer 类型(实现 WebMvcConfigurer 接口)的配置类,并在该类上标注 @EnableWebMvc 注解,来实现完全接管 Spring MVC。
注意:完全接管 Spring MVC 后,Spring Boot 对 Spring MVC 的自动配置将全部失效。
package net.biancheng.www.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.*; //实现 WebMvcConfigurer 接口可以来扩展 SpringMVC 的功能 @EnableWebMvc // 完全接管SpringMVC @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { //当访问 “/” 或 “/index.html” 时,都直接跳转到登陆页面 registry.addViewController("/").setViewName("login"); registry.addViewController("/index.html").setViewName("login"); } }
Spring Boot 能够访问位于静态资源文件夹中的静态文件,这是在 Spring Boot 对 Spring MVC 的默认自动配置中定义的,当我们全面接管 Spring MVC 后,Spring Boot 对 Spring MVC 的默认配置都会失效,此时再访问静态资源文件夹中的静态资源就会报 404 错误。
Spring Boot 国际化 (了解)
国际化(Internationalization 简称 I18n,其中“I”和“n”分别为首末字符,18 则为中间的字符数)是指软件开发时应该具备支持多种语言和地区的功能。换句话说就是,开发的软件需要能同时应对不同国家和地区的用户访问,并根据用户地区和语言习惯,提供相应的、符合用具阅读习惯的页面和数据,例如,为中国用户提供汉语界面显示,为美国用户提供提供英语界面显示。
在 Spring 项目中实现国际化,通常需要以下 3 步:
1.编写国际化资源(配置)文件;
2.使用 ResourceBundleMessageSource 管理国际化资源文件;
3.在页面获取国际化内容。
代码演示:http://c.biancheng.net/spring_boot/global.html
Spring Boot 拦截器 (熟悉)
我们对拦截器并不陌生,无论是 Struts 2 还是 Spring MVC 中都提供了拦截器功能,它可以根据 URL 对请求进行拦截,主要应用于登陆校验、权限验证、乱码解决、性能监控和异常处理等功能上。Spring Boot 同样提供了拦截器功能。
在 Spring Boot 项目中,使用拦截器功能通常需要以下 3 步:
1.定义拦截器;
2.注册拦截器;
3.指定拦截规则(如果是拦截所有,静态资源也会被拦截)。
定义拦截器
在 Spring Boot 中定义拦截器十分的简单,只需要创建一个拦截器类,并实现 HandlerInterceptor 接口即可。
HandlerInterceptor 接口中定义以下 3 个方法,如下表。
注册拦截器
创建一个实现了 WebMvcConfigurer 接口的配置类(使用了 @Configuration 注解的类),重写 addInterceptors() 方法,并在该方法中调用 registry.addInterceptor() 方法将自定义的拦截器注册到容器中。
@Configuration public class MyMvcConfig implements WebMvcConfigurer { ...... @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()); } }
指定拦截规则
修改 MyMvcConfig 配置类中 addInterceptors() 方法的代码,继续指定拦截器的拦截规则,代码如下。
@Slf4j @Configuration public class MyMvcConfig implements WebMvcConfigurer { ...... @Override public void addInterceptors(InterceptorRegistry registry) { log.info("注册拦截器"); registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") //拦截所有请求,包括静态资源文件 .excludePathPatterns("/", "/login", "/index.html", "/user/login", "/css/**", "/images/**", "/js/**", "/fonts/**"); //放行登录页,登陆操作,静态资源 } }
在指定拦截器拦截规则时,调用了两个方法,这两个方法的说明如下:
addPathPatterns:该方法用于指定拦截路径,例如拦截路径为“/**”,表示拦截所有请求,包括对静态资源的请求。
excludePathPatterns:该方法用于排除拦截路径,即指定不需要被拦截器拦截的请求。
代码演示:http://c.biancheng.net/spring_boot/interceptor.html
拦截器与过滤器的区别(了解)
1、实现原理不同
过滤器和拦截器底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的。
2、使用范围不同
我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。 而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。
3、触发时机不同
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
4、拦截的请求范围不同
过滤器Filter执行了两次,拦截器Interceptor只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。
5、注入Bean情况不同
这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。
6、控制执行顺序不同
过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。 拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行
Filter的执行顺序在Interceptor之前,具体的流程见下图
Spring Boot 默认异常处理(熟悉)
Spring Boot 默认异常处理机制
Spring Boot 提供了一套默认的异常处理机制,一旦程序中出现了异常,Spring Boot 会自动识别客户端的类型(浏览器客户端或机器客户端),并根据客户端的不同,以不同的形式展示异常信息。
1. 对于浏览器客户端而言,Spring Boot 会响应一个“ whitelabel”错误视图,以 HTML 格式呈现错误信息
2. 对于机器客户端而言,Spring Boot 将生成 JSON 响应,来展示异常消息。
{ "timestamp": "2021-07-12T07:05:29.885+00:00", "status": 404, "error": "Not Found", "message": "No message available", "path": "/m1ain.html" }
Spring Boot 异常处理自动配置原理
Spring Boot 通过配置类 ErrorMvcAutoConfiguration 对异常处理提供了自动配置,该配置类向容器中注入了以下 4 个组件。
ErrorPageCustomizer:该组件会在在系统发生异常后,默认将请求转发到“/error”上。
BasicErrorController:处理默认的“/error”请求。
DefaultErrorViewResolver:默认的错误视图解析器,将异常信息解析到相应的错误视图上。
DefaultErrorAttributes:用于页面上共享异常信息。
ErrorPageCustomizer
ErrorMvcAutoConfiguration 向容器中注入了一个名为 ErrorPageCustomizer 的组件,它主要用于定制错误页面的响应规则。
@Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { //将请求转发到 /errror(this.properties.getError().getPath())上 ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath())); // 注册错误页面 errorPageRegistry.addErrorPages(errorPage); }
ErrorPageCustomizer 通过 registerErrorPages() 方法来注册错误页面的响应规则。当系统中发生异常后,ErrorPageCustomizer 组件会自动生效,并将请求转发到 “/error”上,交给 BasicErrorController 进行处理
BasicErrorController
ErrorMvcAutoConfiguration 还向容器中注入了一个错误控制器组件 BasicErrorController
BasicErrorController 的定义如下
//BasicErrorController 用于处理 “/error” 请求 @Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { ...... /** * 该方法用于处理浏览器客户端的请求发生的异常 * 生成 html 页面来展示异常信息 * @param request * @param response * @return */ @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { //获取错误状态码 HttpStatus status = getStatus(request); //getErrorAttributes 根据错误信息来封装一些 model 数据,用于页面显示 Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); //为响应对象设置错误状态码 response.setStatus(status.value()); //调用 resolveErrorView() 方法,使用错误视图解析器生成 ModelAndView 对象(包含错误页面地址和页面内容) ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } /** * 该方法用于处理机器客户端的请求发生的错误 * 产生 JSON 格式的数据展示错误信息 * @param request * @return */ @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity<>(body, status); } ...... }
Spring Boot 通过 BasicErrorController 进行统一的错误处理(例如默认的“/error”请求)。Spring Boot 会自动识别发出请求的客户端的类型(浏览器客户端或机器客户端),并根据客户端类型,将请求分别交给 errorHtml() 和 error() 方法进行处理。
换句话说,当使用浏览器访问出现异常时,会进入 BasicErrorController 控制器中的 errorHtml() 方法进行处理,当使用安卓、IOS、Postman 等机器客户端访问出现异常时,就进入error() 方法处理。
在 errorHtml() 方法中会调用父类(AbstractErrorController)的 resolveErrorView() 方法,代码如下。
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { //获取容器中的所有的错误视图解析器来处理该异常信息 for (ErrorViewResolver resolver : this.errorViewResolvers) { //调用错误视图解析器的 resolveErrorView 解析到错误视图页面 ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; }
从上述源码可以看出,在响应页面的时候,会在父类的 resolveErrorView 方法中获取容器中所有的 ErrorViewResolver 对象(错误视图解析器,包括 DefaultErrorViewResolver 在内),一起来解析异常信息。