一、springboot 微服务开发利器

1.1)什么是微服务,微服务和微服务架构的区别?

目前而已,对于微服务业界 没有一个统一的标准定义 ,但是通常而言提倡把一个单一的应用程序划分为 一组小
的服务, 每个小的服务都会运行在自己的进程中,服务之间通 过轻量级的通信机制(http的rest api) 进行通信,那么 一个个的 小服务就是微服务。
①:单体架构与微服务架构图示
传统的的单一电商应用来说,订单,支付,用户,商品,库存等模块都在一个项目中,若某一个模块出
现线上bug,会导致整个版本发布回退.若把单一应用拆分为一个一微服务,比如订单微服务,用户微服务,商品微服务,积分微服务等,若某 一个微服务出错不会导致整个版本回退。

1.2)什么是微服务架构

  1. 微服务架构是一种架构模式( 用于服务管理微服务的 ),它把一组小的服务互相协调、互相配合,并且<br />完成功能。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相协作(通常是基<br />于HTTP 协议的RESTfulAPI )。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环<br />境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应<br />根据业务上下文,选择合适的语言、工具对其进行构建。

1.3)微服务的优缺点:

优点:
①:优点每个服务足够内聚,足够小,代码容易理解这样能聚焦一个指定的业务功能或业务需求(职责单
一)
②:开发简单、开发效率提高,一个服务可能就是专一的只干一件事,微服务能够被小团队单独开发,这
个小团队是 2 到 5 人的开发人员组成。
③:微服务能使用不同的语言开发。
④:易于和第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如
Jenkins,Hudson,bamboo。
⑤:微服务只是业务逻辑的代码,不会和 HTML,CSS或其他界面组件混合。
⑥:每个微服务都有自己的存储能力,可以有自己的数据库。也可以有统一数据库。
缺点:
开发人员要处理分布式系统的复杂性(分布式事物)
多服务运维难度,随着服务的增加,运维的压力也在增大
系统部署依赖
服务间通信成本
数据一致性

二、springboot快速开始

2.1)基于mavne版本构建

先把maven的配置文件设置为如下配置

  1. <profile>
  2. <profile>
  3. <id>jdk‐1.8</id>
  4. <activation>
  5. <activeByDefault>true</activeByDefault>
  6. <jdk>1.8</jdk>
  7. </activation>
  8. <properties>
  9. <maven.compiler.source>1.8</maven.compiler.source>
  10. <maven.compiler.target>1.8</maven.compiler.target>
  11. <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
  12. </properties>
  13. </profile>

配置IDE的环境(maven配置)

2.2)创建一个空的maven工程,然后导入springboot相关的jar包

//父工程依赖

  1. //父工程依赖
  2. <parent>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-parent</artifactId>
  5. <version>2.0.8.RELEASE</version>
  6. </parent>
  7. spring mvc-web的依赖
  8. <dependencies>
  9. <dependency>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-web</artifactId>
  12. </dependency>
  13. </dependencies>
  14. <!-- 引入一个spring boot插件,可以支持我们将web应用程序打成可运行jar包 -->
  15. <build>
  16. <plugins>
  17. <plugin>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-maven-plugin</artifactId>
  20. </plugin>
  21. </plugins>
  22. </build>

①:编写主入口程序

  1. @SpringBootApplication
  2. public class TestStartMain {
  3. public static void main(String[] args) {
  4. SpringApplication.run(TestStartMain.class,args);
  5. }
  6. }

②:其他业务组件 比如controller service repository compent注解标示的组件

  1. @RestController
  2. public class TestController {
  3. @RequestMapping("/test")
  4. public String testHelloWorld() {
  5. return "test,hello";
  6. }
  7. }

③:运行main函数启动程序,访问 http://localhost:8080/ test,或者执行mvn package将项目打成
jar包,用java -jar XXX.jar直接运行

2.3)通过sts/idea创建 一个springboot项目

编写自己的业务代码就maven构建springboot工程版本的一样 这里就不做累赘讲诉.

三、helloworld的探究,为啥我只要引入 spring-boot-starter-parent 和 spring-boot-starter-web就可以快速开发mvc的项目

3.1)pom分析

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.0.8.RELEASE</version>
  5. <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
  7. 真正的版本管理仲裁中心 来决定应用的版本
  8. <parent>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-dependencies</artifactId>
  11. <version>2.0.8.RELEASE</version>
  12. <relativePath>../../spring-boot-dependencies</relativePath>
  13. </parent>

以后我们导入依赖默认是不需要写版本;(没有在dependencies里面管理的依赖自然需要声明版本号)

3.2)我们来分析看下 spring-boot-starter-web(场景启动器)为我项目中导入 web开发需要的jar包依赖

四、多profile切换

我们在开发应用时,通常一个项目会被部署到不同的环境中,比如:开发、测试、生产等。其中每个环境的数据库地址、服务器端口等等配置都会不同,对于多环境的配置,大部分构建工具或是框架解决的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包

4.1)yml支持多模块文档块

  1. server:
  2. port: 8081
  3. servlet:
  4. context-path: /test01
  5. spring:
  6. profiles:
  7. active: dev
  8. ---
  9. 开发环境配置
  10. spring:
  11. profiles: dev
  12. server:
  13. port: 8082
  14. ---
  15. 生产环境配置
  16. spring:
  17. profiles: prod
  18. server:
  19. port: 8083

从上图看出,我们激活的配置是开发环境的配置,但是现在 我们还看到了 servlet:context-path的配置形成互补配置

4.2)多yml|properties文件的环境切换

  1. spring:
  2. profiles:
  3. active: dev

application-dev.yml

  1. server:
  2. port: 8081
  3. servlet:
  4. context-path: /dev

application-prod.yml

  1. server:
  2. port: 8082
  3. servlet:
  4. context-path: /prod

4.3)激活指定环境配置的方法

①:直接在application.yml的配置文件中使用 spring.profiles.active=dev|prod|test
②:设置虚拟机参数 -Dspring.profiles.active=dev|prod|test
③:命令行参数启动(打成Jar包时候) java -jar springboot-02-0.0.1-SNAPSHOT.jar —
spring.profiles.active=prod

4.4)设置jvm参数 然后我们看是否设置成功

java -Xms128m -Xmx128m -jar springboot-02-0.0.1-SNAPSHOT.jar —
server.port=8888

4.5) springboot关于打包问题总结

4.5.1)打成指定的jar名称的

  1. <build>
  2. <finalName>Springboot</finalName>
  3. <plugins>
  4. <plugin>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot-maven-plugin</artifactId>
  7. </plugin>
  8. </plugins>
  9. </build>

4.5.2)若出现工程中出现多个mainclass的时候需要指定主启动类

  1. <build>
  2. <finalName>Springboot</finalName>
  3. <plugins>
  4. <plugin>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot-maven-plugin</artifactId>
  7. <configuration>
  8. <mainClass>com.zhao.Springboot02Application</mainClass>
  9. </configuration>
  10. <goals>
  11. <goal>repackage</goal>
  12. </goals>
  13. </plugin>
  14. </plugins>
  15. </build>

4.5.3)如何打出一个war包

第一步:指定springboot pom中的打包方式 由jar改为war
第二步:在spring-boot-starter-web模块打包比依赖与 tomcat
第二步:在spring-boot-starter-web模块打包比依赖与 tomcat
第三步:主启动类上 实现SpringBootServletInitializer 从写confiure方法( 原理第三节课节讲 )

  1. @SpringBootApplication
  2. public class TestSpringboot03Application extends SpringBootServletInitializer {
  3. public static void main(String[] args) {
  4. SpringApplication.run(TestVipSpringboot03Application.class, args);
  5. }
  6. @Override
  7. protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
  8. return application.sources(TestVipSpringboot03Application.class);
  9. }
  10. }

第四步:打成war包 放在tomcat上运行.

五、springboot 的web开发

5.1)什么是webJar:以jar包的形式来引入前端资源,比如jquery 或者是BootStrap

https://www.webjars.org/

5.1.1)引入对应的jar包

  1. <dependency>
  2. <groupId>org.webjars</groupId>
  3. <artifactId>jquery</artifactId>
  4. <version>3.3.1-2</version>
  5. </dependency>

5.1.2)映射规则 /webjars/** 都会被映射到 classpath:/META-INF/resources/webjars/ 目录下去处理

5.1.3)前端资源映射规则 核心源代码:

  1. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  2. if(!this.resourceProperties.isAddMappings()) {
  3. logger.debug("Default resource handling disabled");
  4. } else {
  5. Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
  6. CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
  7. //处理映射webjar 的请求的
  8. if(!registry.hasMappingForPattern("/webjars/**")) {
  9. this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
  10. }
  11. //处理静态资源文件的
  12. String staticPathPattern = this.mvcProperties.getStaticPathPattern();
  13. if(!registry.hasMappingForPattern(staticPathPattern)) {
  14. this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
  15. }
  16. }
  17. }

5.1.4) http://localhost:8080/webjars/jquery/3.3.1-2/jquery.js 请求如何拦截处理请求的

①根据日志打印,我们发现如下突破口
②:第二步:
org.springframework.web.servlet.resource.ResourceHttpRequestHandler# handleRequest方法
>org.springframework.web.servlet.resource.ResourceHttpRequestHandler# getResource
> org.springframework.web.servlet.resource.ResourceResolverChain# resolveResource

>org.springframework.web.servlet.resource.PathResourceResolver# resolveResourceInternal

>org.springframework.web.servlet.resource.PathResourceResolver# getResource(真正的资源映射
处理逻辑)

5.1.5)访问静态html页面 我们直接把静态页面放在static的目录下,直接可以在路径直接访问

5.1.6)映射原理 /**请求都会被映射到

  1. private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
  2. "classpath:/META-INF/resources/", "classpath:/resources/",
  3. "classpath:/static/", "classpath:/public/" };

5.1.7)欢迎页; 静态资源文件夹下的所有index.html页面;被”/**”映射;

5.1.8)使用webjar的方式修前端页面修改引用路径

5.2)springboot是如何整合springmvc功能的(WebMvcAutoConfiguration)

5.2.1)自动装配的组件

①:ContentNegotiatingViewResolver 和 BeanNameViewResolver 视图解析器
视图解析器的作用:根据方法的值找到对应的视图
②:Support for serving static resources, including support for WebJars 支持静态资源和webJars
③:Converter ,日期格式化器 Formatter
④:消息装换器: HttpMessageConverters
⑤:首页设置index.html
⑥:图标支持 Favicon

5.2.2)如何扩展springmvc的配置(springboot提我们自己配置的springmvc的功能不丢失的情况

比如我需要使用自己定义的拦截器
我们需要自己写一个配置类 继承 WebMvcConfigurerAdapter 需要什么组件 就注册什么组件
A: 如何往容器中添加一个拦截器
第一步:创建一个拦截器

  1. @Component
  2. public class TestInterceptor implements HandlerInterceptor {
  3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
  4. System.out.println("我是TestInterceptor的preHandle方法");
  5. return true;
  6. }
  7. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception {
  8. System.out.println("我是TestInterceptor的postHandle方法");
  9. }
  10. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception {
  11. System.out.println("我是TestInterceptor的afterCompletion方法");
  12. }
  13. }

第二步:注册拦截器

  1. @Configuration
  2. public class TestConfig extends WebMvcConfigurerAdapter {
  3. @Autowired
  4. private TestInterceptor testInterceptor;
  5. /**
  6. * 注册拦截器
  7. * @param registry
  8. */
  9. public void addInterceptors(InterceptorRegistry registry) {
  10. registry.addInterceptor(testInterceptor).addPathPatterns("/**").excludePathPatterns("/index.html","/");
  11. }
  12. }

B:往容器中增加一个过滤器

  1. public class TestFilter implements Filter {
  2. @Override
  3. public void init(FilterConfig filterConfig) throws ServletException {
  4. }
  5. @Override
  6. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  7. System.out.println("TestFilter的doFilter方法");
  8. filterChain.doFilter(servletRequest,servletResponse);
  9. }
  10. @Override
  11. public void destroy() {
  12. }
  13. }
  14. /**
  15. * 注册一个filter
  16. * @return
  17. */
  18. @Bean
  19. public FilterRegistrationBean testFilter(){
  20. FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
  21. filterRegistrationBean.setFilter(new TestFilter());
  22. filterRegistrationBean.addUrlPatterns("/*");
  23. return filterRegistrationBean;
  24. }

C:往容器中增加一个servlet

  1. public class TestServlet extends HttpServlet {
  2. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  3. resp.getWriter().write("hello......");
  4. }
  5. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  6. doPost(req,resp);
  7. }
  8. }
  9. public class TestServlet extends HttpServlet {
  10. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  11. resp.getWriter().write("hello......");
  12. }
  13. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  14. doPost(req,resp);
  15. }
  16. }

六、如何全面接管springboot的mvc配置 ( 让springboot给我们自动配置的功能失效,自己像如何整合ssm一样的整合springmvc ,不推荐)

官网原话:
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc . If you wish to provide custom instances of RequestMappingHandlerMapping , RequestMappingHandlerAdapter ,or ExceptionHandlerExceptionResolver , you can declare a WebMvcRegistrationsAdapter instance to provide such components.
大概意思说,在配置文件中使用一个@EnableWebMvc来标识到配置类上,就会导致配置失效 why?为什么会失
效?????????????????
原理: @EnableWebMvc 为容器中导入了DelegatingWebMvcConfiguration的组件

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Documented
  4. @Import(DelegatingWebMvcConfiguration.class)
  5. public @interface EnableWebMvc {
  6. }

1)我们来分析一下DelegatingWebMvcConfiguration是一个什么东西?????
我们发现DelegatingWebMvcConfiguration是WebMvcConfiurationSupport(只保证了springmvc的基本功能)类
型的

  1. @Configuration
  2. public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport

2)我们来看下WebMvcAutoConfiguration上的注解

  1. @Configuration
  2. @ConditionalOnWebApplication(type = Type.SERVLET)
  3. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
  4. //容器中 没有WebMvcConfigurationSupport 该配置文件才生生效,但是我们使用了 @EnableWebMvc 导入了WebMvcConfiurationSupport的配置,所以导致该配置类失效,
  5. //只保存了springmvc的最基本的功能
  6. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
  7. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
  8. @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
  9. ValidationAutoConfiguration.class })
  10. public class WebMvcAutoConfiguration

3)我们的webJar 欢迎页 等全部失效

七、springboot错误处理机制?如何定制错误页面?

案例:浏览器模拟发送的错误请求 http://localhost:8080/aaaaaaaaaaaaaa
案例2:通过postman 或者restlet 发送的请求 http://localhost:8080/test/dddd
我们可以看出 不同的终端发送的请求 会返回不同的错误异常类容 是根据什么原理?
原理: 是根据不同客户端发送的请求的请求头来区分是 返回页面还是json数据

7.1)我们来看springboot为我们自动配置的异常处理的一些bean

org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration

  1. @Bean
  2. @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
  3. public DefaultErrorAttributes errorAttributes() {
  4. return new DefaultErrorAttributes(
  5. this.serverProperties.getError().isIncludeException());
  6. }
  7. @Bean
  8. @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
  9. public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
  10. return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
  11. this.errorViewResolvers);
  12. }
  13. @Bean
  14. public ErrorPageCustomizer errorPageCustomizer() {
  15. return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
  16. }
  17. @Bean
  18. @ConditionalOnBean(DispatcherServlet.class)
  19. @ConditionalOnMissingBean
  20. public DefaultErrorViewResolver conventionErrorViewResolver() {
  21. return new DefaultErrorViewResolver(this.applicationContext,
  22. this.resourceProperties);
  23. }
  24. @Configuration
  25. @ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
  26. @Conditional(ErrorTemplateMissingCondition.class)
  27. protected static class WhitelabelErrorViewConfiguration {
  28. private final SpelView defaultErrorView = new SpelView(
  29. "<html><body><h1>Whitelabel Error Page</h1>"
  30. + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
  31. + "<div id='created'>${timestamp}</div>"
  32. + "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
  33. + "<div>${message}</div></body></html>");
  34. @Bean(name = "error")
  35. @ConditionalOnMissingBean(name = "error")
  36. public View defaultErrorView() {
  37. return this.defaultErrorView;
  38. }

我们具体来分析上诉源代码的组件 A: org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.ErrorPageCustomizer(错 误页面定制器) 作用:系统出现错误以后来到/error请求进行处理;

  1. /**
  2. * Path of the error controller.
  3. */
  4. @Value("${error.path:/error}")
  5. private String path = "/error";

那么当我们 发生错误,需要/error 的请求映射来请求 接下来就会引出另外一个组件 来处理/error请求 B:org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController (基础错误控制器)

  1. @Controller
  2. @RequestMapping("${server.error.path:${error.path:/error}}")
  3. public class BasicErrorController extends AbstractErrorController {
  4. //处理浏览器页面异常
  5. @RequestMapping(produces = "text/html")
  6. public ModelAndView errorHtml(HttpServletRequest request,
  7. HttpServletResponse response) {
  8. HttpStatus status = getStatus(request);
  9. Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
  10. request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  11. response.setStatus(status.value());
  12. ModelAndView modelAndView = resolveErrorView(request, response, status, model);
  13. return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
  14. }
  15. //处理postman 请求的Json数据异常错误
  16. @RequestMapping
  17. @ResponseBody
  18. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  19. Map<String, Object> body = getErrorAttributes(request,
  20. isIncludeStackTrace(request, MediaType.ALL));
  21. HttpStatus status = getStatus(request);
  22. return new ResponseEntity<>(body, status);
  23. }
  24. }

B1:我们来看下浏览器的响应过程怎么来处理请求异常信息的?

  1. public ModelAndView errorHtml(HttpServletRequest request,
  2. HttpServletResponse response) {
  3. //获取状态码
  4. HttpStatus status = getStatus(request);
  5. //获取页面的模型数据
  6. Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
  7. request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  8. response.setStatus(status.value());
  9. //解析错误视图
  10. ModelAndView modelAndView = resolveErrorView(request, response, status, model);
  11. return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
  12. }
  13. protected ModelAndView resolveErrorView(HttpServletRequest request,
  14. HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
  15. //获取容器中的所有错误视图解析器 DefaultErrorViewResolver
  16. for (ErrorViewResolver resolver : this.errorViewResolvers) {
  17. ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
  18. if (modelAndView != null) {
  19. return modelAndView;
  20. }
  21. }
  22. return null;
  23. }

B2:我们接着分析 org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver#DefaultErrorViewResolver 错误视图解析器

  1. @Override
  2. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
  3. Map<String, Object> model) {
  4. //解析视图
  5. ModelAndView modelAndView = resolve(String.valueOf(status), model);
  6. //没有对应的解析精确匹配的状态码 使用模糊匹配比如4XX 5XX
  7. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  8. //返回4XX 5XX的页面
  9. modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
  10. }
  11. return modelAndView;
  12. }
  13. private ModelAndView resolve(String viewName, Map<String, Object> model) {
  14. // error/404
  15. String errorViewName = "error/" + viewName;
  16. //视图是否有模版引擎解析
  17. TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
  18. .getProvider(errorViewName, this.applicationContext);
  19. //有模版引擎解析直接返回
  20. if (provider != null) {
  21. return new ModelAndView(errorViewName, model);
  22. }
  23. //静态html的页面解析
  24. return resolveResource(errorViewName, model);
  25. }
  26. private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
  27. for (String location : this.resourceProperties.getStaticLocations()) {
  28. try {
  29. //在static模版下需要创建一个error/404.html
  30. Resource resource = this.applicationContext.getResource(location);
  31. resource = resource.createRelative(viewName + ".html");
  32. //存在该页面 直接返回
  33. if (resource.exists()) {
  34. return new ModelAndView(new HtmlResourceView(resource), model);
  35. }
  36. }
  37. catch (Exception ex) {
  38. }
  39. }
  40. return null;
  41. }

浏览器模拟发送异常请求的流程 视图解析过程 org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#resolveErrorView 开始解析视图,获取所有的异常错误视图解析器

org.springframework.boot.autoconfigure.web.servlet.error. DefaultErrorViewResolver #resolveErrorView 默认错误视图解析器解析视图

org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver#resolve 响应码精准匹配视图 1)判断模版引擎是否能够处理错误视图,能处理就处理,不能处理交给静态页面解析处理

org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver#resolveResource html资源视图 若不能精准匹配,那么就进行4XX 5XX模糊匹配 若不能精准匹配(error/状态码.html)的错误页面,也没有(error/状态码开头xx.html错误页面那 就使用默认的错误空白页面)

  1. private final SpelView defaultErrorView = new SpelView(
  2. "<html><body><h1>Whitelabel Error Page</h1>"
  3. + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
  4. + "<div id='created'>${timestamp}</div>"
  5. + "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
  6. + "<div>${message}</div></body></html>");
  7. @Bean(name = "error")
  8. @ConditionalOnMissingBean(name = "error")
  9. public View defaultErrorView() {
  10. return this.defaultErrorView;
  11. }

我们怎么包含一个自己的错误异常信息的 自适应的效果 浏览器效果:(需要返回自己定义的错误页面 包含了自定义的错误异常信息)

其他客户端的效果: 第一步:我们定义一个全局异常处理器,然后返回看执行效果

  1. @ControllerAdvice
  2. public class TestExceptionHanlder {
  3. /**
  4. * 浏览器和其他客户端都返回了json 数组,不满足自适应
  5. * @param e
  6. * @param request
  7. * @return
  8. */
  9. @ExceptionHandler(value= TestException.class)
  10. @ResponseBody
  11. public Map<String,Object> dealException(TestException e, HttpServletRequest request){
  12. Map<String,Object> retInfo = new HashMap<>();
  13. retInfo.put("code",e.getCode());
  14. retInfo.put("msg",e.getMsg());
  15. return retInfo;
  16. }
  17. }

效果: 浏览器不满足 自适应效果返回的是一个json字符串,而不是一个页面 其他客户端满足要求,返回自己定义的错误异常信息
第二步:在异常处理器中 进行重定向
根据第一步的效果来看 浏览器不能满足自适应效果 ,那么我们看下BasicErrorController的类

  1. @Controller
  2. @RequestMapping("${server.error.path:${error.path:/error}}")
  3. public class BasicErrorController extends AbstractErrorController

他处理的请求是/error的请求,那么我们就想到 在全局异常处理器进行重定向

  1. @ControllerAdvice
  2. public class TestExceptionHanlder {
  3. @ExceptionHandler(value= TestException.class)
  4. public String dealException(TestException e, HttpServletRequest request){
  5. Map<String,Object> retInfo = new HashMap<>();
  6. retInfo.put("code",e.getCode());
  7. retInfo.put("msg",e.getMsg());
  8. //重定向,把请求转发到BasicErrorController来处理 /error
  9. return "forward:/error";
  10. }

执行效果:
分析过程
①:根据上述执行效果我们发现 进行转发后 他的http状态码变为200 那么错误异常处理就不能进行正常 流程的处理 ②:那么我们需要分析 错误异常处理器 看下是如何获取异常状态码的. org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#getStatus 很明显,BasicErrorController 的getStatus的过程中,都是从request中获取 javax.servlet.error.status_code属性

  1. protected HttpStatus getStatus(HttpServletRequest request) {
  2. Integer statusCode = (Integer) request
  3. .getAttribute("javax.servlet.error.status_code");
  4. if (statusCode == null) {
  5. return HttpStatus.INTERNAL_SERVER_ERROR;
  6. }
  7. try {
  8. return HttpStatus.valueOf(statusCode);
  9. }
  10. catch (Exception ex) {
  11. return HttpStatus.INTERNAL_SERVER_ERROR;
  12. }
  13. }

那么我们需要在我们的全局异常处理器中request中设置该属性
页面返回的属性字段是在哪里配置的???
那我们来着重分析一下 org.springframework.boot.web.servlet.error.DefaultErrorAttributes# getErrorAttributes 疑问:我们来看下这个类的自动装配原理,发现容器中有ErrorAttributes主键,那么就不进行自动装配,我 们可以来自己写一个类来继承他

  1. @Component
  2. public class TestErrorAttribute extends DefaultErrorAttributes {
  3. public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
  4. //获取父类的封装字段结果
  5. Map<String, Object> retInfo = super.getErrorAttributes(webRequest,includeStackTrace);
  6. //获取全局异常自定义的结果
  7. Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext",0);
  8. //封装自定义的错误信息
  9. retInfo.put("company","test");
  10. retInfo.put("ext",ext);
  11. return retInfo;
  12. }
  13. }