pom.xml引入webjars的官网

https://www.webjars.org/

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

静态资源映射规则

静态资源就是不经过servlet 或者说不通过controller绕业务代码 中的文件。

静态资源自动配置类 WebMvcAuotConfiguration.java

  1. @Override
  2. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  3. if (!this.resourceProperties.isAddMappings()) {
  4. logger.debug("Default resource handling disabled");
  5. return;
  6. }
  7. Integer cachePeriod = this.resourceProperties.getCachePeriod();
  8.        //jar包中静态资源文件夹映射
  9. if (!registry.hasMappingForPattern("/webjars/**")) {
  10. customizeResourceHandlerRegistration(
  11. registry.addResourceHandler("/webjars/**")
  12. .addResourceLocations(
  13. "classpath:/META-INF/resources/webjars/")
  14. .setCachePeriod(cachePeriod));
  15. }
  16. String staticPathPattern = this.mvcProperties.getStaticPathPattern();
  17. //静态资源文件夹映射
  18. if (!registry.hasMappingForPattern(staticPathPattern)) {
  19. customizeResourceHandlerRegistration(
  20. registry.addResourceHandler(staticPathPattern)
  21. .addResourceLocations(
  22. this.resourceProperties.getStaticLocations())
  23. .setCachePeriod(cachePeriod));
  24. }
  25. }

静态资源目录

在springboot默认的静态资源目录有

  • "classpath:/META-INF/resources/", (推荐)
  • "classpath:/resources/",   (不推荐使用)
  • "classpath:/static/", (推荐)
  • "classpath:/public/"
  • "/":当前项目的根路径

所以当访问以上5个目录中的文件时,都会直接获取,比如访问 http://localhost:8080/springbootdemo/abc.html , 另外静态资源目录下的index.html和favicon.ico也是默认静态文件可直接访问。

webjars静态资源目录

除了以上5个目录, 还有一个特殊的目录 "classpath:/META-INF/resources/webjars/"  , 用于访问jar包的方式引入静态资源 。

比如我们想访问jquery-3.3.1.jar中的jquery.js那么使用以下两个路径都可以直接访问到,

http://localhost:8080/springbootdemo/webjars/jquery/3.3.1/jquery.js

http://localhost:8080/springbootdemo/webjars/jquery/3.3.1/jquery.min.js

至于为什么不是.../jquery.js/jquery.min.js的形式访问目前暂不研究。

引静态jar

  1. <dependency>
  2. <!-- 前端jquery包 -->
  3. <groupId>org.webjars</groupId>
  4. <artifactId>jquery</artifactId>
  5. <version>3.3.1</version>
  6. </dependency>
  7.  
  8. <dependency>
  9. <!-- 前端bootstrap包 -->
  10. <groupId>org.webjars</groupId>
  11. <artifactId>bootstrap</artifactId>
  12. <version>4.0.0</version>
  13. </dependency>

静态资源属性配置类 ResourceProperties.java

可以设置静态资源目录, 静态资源缓存时间等等

  1. @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
  2. public class ResourceProperties implements ResourceLoaderAware {
  3. //可以设置和静态资源有关的参数,缓存时间等

在application.properties中对应配置如下, 我自己项目暂未启用,位于application-static-cache.properties中

  1. #配置静态资源映射
  2. spring.mvc.static-path-pattern=/static/**
  3.  
  4. #也可以自己指定静态资源, 这会使默认的静态资源失效
  5. spring.resources.static-locations=classpath:/mystatic,classpath:/yourstatic
  6.  
  7. # 资源缓存时间,单位秒,30天
  8. spring.resources.cache-period=2592000
  9.  
  10. # 开启gzip压缩
  11. spring.resources.chain.gzipped=true
  12.  
  13. # 启用缓存
  14. spring.resources.chain.cache=true
  15.  
  16. # Enable the Spring Resource Handling chain. Disabled by default unless at least one strategy has been enabled.
  17. spring.resources.chain.enabled=true
  18.  
  19. # 开启版本控制策略,默认为false
  20. spring.resources.chain.strategy.content.enabled=true
  21.  
  22. # 指定要应用的版本的路径,多个以逗号分隔,默认为:[/**]
  23. spring.resources.chain.strategy.content.paths=/**
  24.  
  25. # 设定Session的追踪模式(cookie, url, ssl)
  26. server.session.tracking-modes=cookie

thymeleaf模板

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

1、引入thymeleaf依赖

  1. <!-- 切换thymeleaf版本 -->
  2. <properties>
  3. <thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
  4. <!-- 布局功能的支持程序 thymeleaf3适配layout2以上版本 , thymeleaf2 适配 layout1版本 -->
  5. <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
  6. </properties>
  7.  
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  11. </dependency>

2、Thymeleaf使用

配置属性类

  1. @ConfigurationProperties(prefix = "spring.thymeleaf")
  2. public class ThymeleafProperties {

  3. private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");

  4. private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");

  5. public static final String DEFAULT_PREFIX = "classpath:/templates/";

  6. public static final String DEFAULT_SUFFIX = ".html";
  7. ...

日期格式化配置

日期格式化配置的地方有很

请求时--仅当前Controller接收指定格式的日期字符串

  1. @RequestMapping("/MyMybatisController")
  2. @Controller
  3. public class MyMybatisController {
  4. /**
  5. * web数据绑定器,在获取到数据前,做一些预处理,仅对当前Controller有效。
  6. *
  7. * @param binder
  8. * @throws Exception
  9. */
  10. @InitBinder
  11. public void initBinder(WebDataBinder binder) throws Exception {
  12. //允许Long类型为空
  13. binder.registerCustomEditor(Long.class, new CustomNumberEditor(Long.class, true));
  14. //设置接收的日期,把默认的yyyy/MM/dd HH:mm:ss格式改成yyyy-MM-dd HH:mm:ss
  15. binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true));
  16. }
  17.  
  18. }

请求时--application.properties统一配置接收指定日期格式字符串

对所有Controller都有效

  1. spring.mvc.date-format=yyyy-MM-dd HH:mm:ss

返回时--所有@ResponseBody返回时格式化Date类型成字符串

  1. @Configuration//指明当前类是一个配置类;就是来替代之前的Spring配置文件
  2. public class MyConfiguration {
  3. /**
  4. * 对@ResponseBody返回的Object类中的各对象对特殊处理
  5. * @return
  6. */
  7. @Bean
  8. public ObjectMapper getObjectMapper() {
  9. //返回时把日期从默认的long类似必成yyyy年MM月dd日 HH:mm:ss返回
  10. return new ObjectMapper().setDateFormat(new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"));
  11. }
  12. }

返回时--thymeleaf中格式化Date类型成字符串

  1. <td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm:ss') }"></td>

1. Spring MVC auto-configuration  (了解不够)

Spring Boot 自动配置好了SpringMVC

以下是SpringBoot对SpringMVC的默认配置:(WebMvcAutoConfiguration)

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

    • 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?))

    • ContentNegotiatingViewResolver:组合所有的视图解析器的;

    • 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;

  • Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars

  • Static index.html support. 静态首页访问

  • Custom Favicon support (see below). favicon.ico

  • 自动注册了 of Converter, GenericConverter, Formatter beans.

    • Converter:转换器; public String hello(User user):类型转换使用Converter

    • Formatter 格式化器; 2017.12.17===Date;

  1. @Bean
    @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")//在文件中配置日期格式化的规则
    public Formatter<Date> dateFormatter() {
    return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件
    }

自己添加的格式化器转换器,我们只需要放在容器中即可

  • Support for HttpMessageConverters (see below).

    • HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User---Json;

    • HttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverter;

      自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中(@Bean,@Component)

  • Automatic registration of MessageCodesResolver (see below).定义错误代码生成规则

  • Automatic use of a ConfigurableWebBindingInitializer bean (see below).

    我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)

    1. 初始化WebDataBinder
      请求数据=====JavaBean

org.springframework.boot.autoconfigure.web:web的所有自动场景;

If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver you can declare a WebMvcRegistrationsAdapter instance providing such components.

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

扩展SpringMVC(了解不够)

比如我们需要扩展SpringMVC中的视图跳转功能

  1. <mvc:view-controller path="/hello" view-name="success"/>
  2. <mvc:interceptors>
  3. <mvc:interceptor>
  4. <mvc:mapping path="/hello"/>
  5. <bean></bean>
  6. </mvc:interceptor>
  7. </mvc:interceptors>

在SpringBoot中只需编写一个配置类(@Configuration)继承 WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc; 既保留了所有的自动配置,也能用我们扩展的配置;

  1. //使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
  2. @Configuration
  3. public class MyMvcConfig extends WebMvcConfigurerAdapter {
  4.  
  5. @Override
  6. public void addViewControllers(ViewControllerRegistry registry) {
  7. // super.addViewControllers(registry);
  8. //浏览器发送 /atguigu 请求来到 success
  9. registry.addViewController("/atguigu").setViewName("success");
  10. }
  11. }

原理:

1)、WebMvcAutoConfiguration是SpringMVC的自动配置类

2)、在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)

  1. @Configuration
  2. public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
  3. private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
  4.  
  5. //从容器中获取所有的WebMvcConfigurer
  6. @Autowired(required = false)
  7. public void setConfigurers(List<WebMvcConfigurer> configurers) {
  8. if (!CollectionUtils.isEmpty(configurers)) {
  9. this.configurers.addWebMvcConfigurers(configurers);
  10. //一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用;
  11. @Override
  12. // public void addViewControllers(ViewControllerRegistry registry) {
  13. // for (WebMvcConfigurer delegate : this.delegates) {
  14. // delegate.addViewControllers(registry);
  15. // }
  16. }
  17. }
  18. }

3)、容器中所有的WebMvcConfigurer都会一起起作用;

4)、我们的配置类也会被调用;

效果:SpringMVC的自动配置和我们的扩展配置都会起作用;

3、全面接管SpringMVC

SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了

我们需要在配置类中添加@EnableWebMvc即可;

  1. //使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
  2. @EnableWebMvc
  3. @Configuration
  4. public class MyMvcConfig extends WebMvcConfigurerAdapter {
  5.  
  6. @Override
  7. public void addViewControllers(ViewControllerRegistry registry) {
  8. // super.addViewControllers(registry);
  9. //浏览器发送 /atguigu 请求来到 success
  10. registry.addViewController("/atguigu").setViewName("success");
  11. }
  12. }

这会让SpringBoot的WebMvcAutoConfiguration.java中各种自动配置Bean无效,在控制台也也就再打印如HiddenHttpMethodFilter等信息 , 且静态资源目录也失效,极不推荐做全面 接管。

  1. public class WebMvcAutoConfiguration {
  2.  
  3. public static final String DEFAULT_PREFIX = "";
  4.  
  5. public static final String DEFAULT_SUFFIX = "";
  6.  
  7. @Bean
  8. @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
  9. public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
  10. return new OrderedHiddenHttpMethodFilter();
  11. }
    ........

原理:

为什么@EnableWebMvc后SpringBoot的自动配置就失效了;

1)@EnableWebMvc

导入了DelegatingWebMvcConfiguration 类

  1. @Import(DelegatingWebMvcConfiguration.class)
  2. public @interface EnableWebMvc {

2)、DelegatingWebMvcConfiguration

继承了WebMvcConfigurationSupport

  1. @Configuration
  2. public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

3)、WebMvcAutoConfiguration

@ConditionalOnMissingBean 容器中没有这个组件才会生效, 而上面就是导入了这个组件,所以 WebMvcAutoConfiguration 无效了.

  1. @Configuration
  2. @ConditionalOnWebApplication
  3. @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class})
  4. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)//容器中没有这个组件的时候,这个自动配置类才生效
  5. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
  6. @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class})
  7. public class WebMvcAutoConfiguration {

4)、@EnableWebMvc将WebMvcConfigurationSupport组件导入进来;

5)、导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;

5、如何修改SpringBoot的默认配置

模式:

1)、SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;

2)、在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置

3)、在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置

国际化

1)、指定国际化配置文件位置

在application.properties中配置如下, 如果不配置,则默认文件为classpath:messages.properties / messages_zh_CN.properties

  1. spring.messages.basename=i18n.international

2)、编写国际化配置文件,抽取页面需要显示的国际化消息

\src\main\resources\i18n\international.properties

  1. # default location is "classpath:message.properties", changed in application.properties , add "spring.messages.basename=i18n.international" to specified internationalResource here
  2. login.btn=登陆~
  3. login.password=密码~
  4. login.rememberMe=记住我~
  5. login.tip=请登陆~
  6. login.username=用户名~

\src\main\resources\i18n\international_en_US.properties

  1. login.btn=Sign In
  2. login.password=Password
  3. login.rememberMe=Remember Me
  4. login.tip=Please sign in
  5. login.username=UserName

\src\main\resources\i18n\international_zh_CN.properties

  1. login.btn=登录
  2. login.password=密码
  3. login.rememberMe=记住我
  4. login.tip=请登录
  5. login.username=用户名

springboot资源国际化自动化配置组件

  1. @ConfigurationProperties(prefix = "spring.messages")
  2. public class MessageSourceAutoConfiguration {
  3.  
  4. /**
  5. * Comma-separated list of basenames (essentially a fully-qualified classpath
  6. * location), each following the ResourceBundle convention with relaxed support for
  7. * slash based locations. If it doesn't contain a package qualifier (such as
  8. * "org.mypackage"), it will be resolved from the classpath root.
  9. */
  10. private String basename = "messages";
  11. //我们的配置文件可以直接放在类路径下叫messages.properties;
  12.  
  13. @Bean
  14. public MessageSource messageSource() {
  15. ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
  16. if (StringUtils.hasText(this.basename)) {
  17. //设置国际化资源文件的基础名(去掉语言国家代码的)
  18. messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
  19. StringUtils.trimAllWhitespace(this.basename)));
  20. }
  21. if (this.encoding != null) {
  22. messageSource.setDefaultEncoding(this.encoding.name());
  23. }
  24. messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
  25. messageSource.setCacheSeconds(this.cacheSeconds);
  26. messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
  27. return messageSource;
  28. }

3)、idea配置properties防乱码

idea的properties中内容在页面中展示乱码解决方法==>https://www.cnblogs.com/whatlonelytear/articles/9430768.html

4)、html中引用

  1. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  2. <body>
  3. <h1 th:text="#{login.tip}">Please sign in</h1>
  4. <input type="checkbox" value="remember-me"/> [[#{login.rememberMe}]]
  5. </body>
  6. </html>

通过手动点击切换国际化资源

springboot自动化配置国际化的解析器为localeResolver()方法

国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象);

WebMvcAutoConfiguration.java 的自动化配置bean

当ConditionalOnMissingBean即LocaleResolver不存在时,本自动化配置Bean才有效, 所以我们可以自定义一个MyLocaleResolver implements LocaleResolver来替换当前自动化配置

  1. //在WebMvcAutoConfiguration.java中配有bean如下, 当ConditionalOnMissingBean即LocaleResolver不存在时,本自动化配置Bean才有效,
  2. //所以我们可以自定义一个MyLocaleResolver implements LocaleResolver来替换当前自动化配置
  3. @Bean
  4. @ConditionalOnMissingBean
  5. @ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
  6. public LocaleResolver localeResolver() {
  7. if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
  8. return new FixedLocaleResolver(this.mvcProperties.getLocale());
  9. }
  10. //默认的就是根据请求头带来的区域信息获取Locale进行国际化
  11. AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
  12. localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
  13. return localeResolver;
  14. }

自定义MyLocaleResolver.java

  1. /**
  2. * 可以在连接上携带区域信息
  3. */
  4. public class MyLocaleResolver implements LocaleResolver {
  5.  
  6. /**
  7. * 如果带了lang=zh_CN或lang=en_US形式的参数,则设置成lang的本地化,不然设置默认的
  8. * @param request
  9. * @return
  10. */
  11. @Override
  12. public Locale resolveLocale(HttpServletRequest request) {
  13. String l = request.getParameter("lang");
  14. Locale locale = Locale.getDefault();
  15. if (l != null && !"".equals(l)) {
  16. String[] split = l.split("_");
  17. locale = new Locale(split[0], split[1]);
  18. }
  19. return locale;
  20. }
  21.  
  22. @Override
  23. public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
  24.  
  25. }
  26. }

在任意一个@Configuration添加MyLocalResolver.java

  1. @Bean
  2. public LocaleResolver localeResolver(){
  3. return new MyLocaleResolver();
  4. }
  5. }

SpringBoot默认的错误处理机制

默认效果

浏览器返回Error Page页面 , 是因为请求时Header头Accept指定了明确的接收类型text/html ,SpringBoot后台做了特殊处理

 

postman返回的是json报文,是因为请求时Header头Accept没有指定明确的接收类型 ,SpringBoot后台做了特殊处理

 

基本流程

一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到 /error 请求;就会被BasicErrorController处理;

参照ErrorMvcAutoConfiguration配置类;它给容器中添加了以下错误处理的相关组件。

1、ErrorPageCustomizer.java (注册错误页)

该bean用于注册错误页面, 配置系统出现错误后跳转到哪

  1. private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
  2. .....
  3. @Override
  4. public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
  5. ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
  6. + this.properties.getError().getPath());
  7. errorPageRegistry.addErrorPages(errorPage);
  8. }
  9. .....
  10. }

其中getPath()内部其实使用了 "/error"  , ${error.path:/error}的意思是如果没有error.path配置属性, 则取"/error"值

  1. @Value("${error.path:/error}")
  2. private String path = "/error"; 系统出现错误以后来到/error请求进行处理;(web.xml注册的错误页面规则)

2、BasicErrorController (处理"/error"错误请求)

  1. @Controller
  2. @RequestMapping("${server.error.path:${error.path:/error}}")
  3. public class BasicErrorController extends AbstractErrorController {
  4. ...
  5.  
  6. @Override
  7. public String getErrorPath() {
  8. return this.errorProperties.getPath();
  9. }
  10.  
  11. @RequestMapping(produces = "text/html")//产生html类型的数据;浏览器发送的请求来到这个方法处理
  12. public ModelAndView errorHtml(HttpServletRequest request,
  13. HttpServletResponse response) {
  14. HttpStatus status = getStatus(request);
         //获取错误信息 , 而错误信息都在共享区的 DefaultErrorAttributes中
  15. Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
  16. request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  17. response.setStatus(status.value());
  18. //去哪个页面作为错误页面;包含页面地址和页面内容
  19. ModelAndView modelAndView = resolveErrorView(request, response, status, model);
  20. return (modelAndView != null ? modelAndView : new ModelAndView("error", model));
  21. }
  22.  
  23. @RequestMapping
  24. @ResponseBody//产生json数据,其他客户端来到这个方法处理;postman,java代码调用等场景
  25. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  26. Map<String, Object> body = getErrorAttributes(request,
  27. isIncludeStackTrace(request, MediaType.ALL));
  28. HttpStatus status = getStatus(request);
  29. return new ResponseEntity<Map<String, Object>>(body, status);
  30. }
  31. ...
  32.  
  33. }

resolveErrorView(...)方法内容

遍历所有ErrorViewResolver ,而接下来的DefaultErrorViewResolver这个在Configuration中注册的Bean就继承自ErrorViewResolver (在该类上Ctrl + T , 直接跳转到唯一实现类DefaultErrorViewResolver.java)

  1. protected ModelAndView resolveErrorView(HttpServletRequest request,
  2. HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
  3. //遍历所有ErrorViewResolver ,而接下来的DefaultErrorViewResolver这个在Configuration中注册的Bean就继承自ErrorViewResolver
  4. for (ErrorViewResolver resolver : this.errorViewResolvers) {
  5. ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
  6. if (modelAndView != null) {
  7. return modelAndView;
  8. }
  9. }
  10. return null;
  11. }

4、DefaultErrorViewResolver(默认的错误视图处理解析器)

根据resolve(...)方法可知thymeleaf模板引擎可用时, 去resources/templates/error/4xx.html或404.html中找, 如果模板引擎不可用, 则去静态资源文件夹 resources/static/4xx.html或400.html中找

  1. @Override
  2. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
  3. Map<String, Object> model) {
  4. ModelAndView modelAndView = resolve(String.valueOf(status), model);
  5. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  6. modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
  7. }
  8. return modelAndView;
  9. }
  10.  
  11. private ModelAndView resolve(String viewName, Map<String, Object> model) {
  12. //默认SpringBoot可以去找到一个页面? error/404
  13. String errorViewName = "error/" + viewName;
  14.  
  15. //试着获取模板引擎
  16. TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
  17. .getProvider(errorViewName, this.applicationContext);
  18. if (provider != null) {
  19. //如果模板引擎可用,则返回到errorViewName指定的视图地址
  20. return new ModelAndView(errorViewName, model);
  21. }
  22. //模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
  23. return resolveResource(errorViewName, model);
  24. }

4、DefaultErrorAttributes (页面错误共享信息对象)

DefaultErrorAttributes.java

  1. @Order(Ordered.HIGHEST_PRECEDENCE)
  2. public class DefaultErrorAttributes
  3. implements ErrorAttributes, HandlerExceptionResolver, Ordered {
  4. ...
  5. @Override
  6. public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
  7. boolean includeStackTrace) {
  8. Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
  9. errorAttributes.put("timestamp", new Date());
  10. addStatus(errorAttributes, requestAttributes);
  11. addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
  12. addPath(errorAttributes, requestAttributes);
  13. return errorAttributes;
  14. }
  15. ...
  16. }

如何定制错误响应

1)、如何定制错误的页面;

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

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

页面能获取的信息;

  1. timestamp:时间戳
    path: 请求路径
  2. status:状态码
  3. error:错误提示
  4. exception:异常对象
  5. message:异常消息
  6. errorsJSR303数据校验的错误都在这里

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

3. 以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面

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

4. 4xx.html / 5xx.html / 400.html / 500.html修改

所以在templates/error/4xx.html中可以自由改造默认的属性了

  1. <p th:text="${error}"></p>

2)、如何定制错误的json数据;

1)、自定义异常处理器 MyExceptionAdvice.java , 但是这种模式没有自适应性, 会导致浏览器访问也统一返回json

  1. @ControllerAdvice//自定义异常拦截处理器
  2. public class MyExceptionHandler {

  3. @ResponseBody//返回转成json
  4. @ExceptionHandler(UserNotExistException.class)//自定义异常
  5. public Map<String,Object> handleException(Exception e){
  6. Map<String,Object> map = new HashMap<>();
  7. map.put("code","user.notexist");
  8. map.put("message",e.getMessage());
  9. return map;
  10. }
  11. }

2)、转发到/error进行自适应响应效果处理

自定义异常处理器 MyExceptionAdvice.java

  1. @ControllerAdvice
  2. public class MyExceptionAdvice {
  3. @ExceptionHandler(UserNotExistException.class)
  4. public String handleException(Exception e, HttpServletRequest request){
  5. Map<String,Object> map = new HashMap<>();
  6. //传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
  7. /**
  8. * Integer statusCode = (Integer) request
  9. .getAttribute("javax.servlet.error.status_code");
  10. */
  11. request.setAttribute("javax.servlet.error.status_code",500);
  12. map.put("code","user.notexist");
  13. map.put("message",e.getMessage());
  14. //转发到/error
  15. return "forward:/error";
  16. }
  17. }

3)、将我们的定制数据携带出去;

自定义异常处理器 MyExceptionAdvice.java

  1. @ControllerAdvice
  2. public class MyExceptionAdvice {
  3. /**
  4. * 自定义异常处理&返回定制json数据 , 但是这种模式有自适应性, 浏览器返回网页, postman返回json
  5. * 此种方式的跳转,不能将自定义参数携带出去
  6. */
  7. @ExceptionHandler(UserNotFoundException.class)
  8. public String satifiedHandleException(Exception e, HttpServletRequest request){
  9. Map<String,Object> map = new HashMap<>();
  10.  
  11. //传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程,
  12. // 因为在BasicErrorController的父类AbstractErrorController方法getStatus(HttpServletRequest request)中Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");默认获取的是200,
  13. // 导致不会进入/error拦截界面.
  14. //而此处设置400,500目的正是为了跳到对应的error/400.html, error/500.html中去
  15. request.setAttribute("javax.servlet.error.status_code",500);
  16.  
  17. //但是此种方式的跳转,不能将自定义参数携带出去
  18. map.put("extInfo1","info1");
  19. map.put("extInfo2",e.getMessage());
  20. request.setAttribute("extInfo",map);//在自定义的MyErrorAttribute.java中获取
  21. //注意,转发到"/error"
  22. return "forward:/error";
  23. }
  24. }

出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);

方式1(不推荐)、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;

方式2(推荐)、页面上能用的数据,或者是json返回能用的数据都是通过ErrorAttributes.getErrorAttributes()得到;所以我们可以自定义一个ErrorAttributes

  1. //给容器中加入我们自己定义的ErrorAttributes
  2. @Component
  3. public class MyErrorAttributes extends DefaultErrorAttributes {

  4. @Override
  5. public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
  6. Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
  7. map.put("company","atguigu");
  8. return map;
  9. }
  10. }

那这个自定义的bean是如何替换原来的呢,重点在于@ConditionalOnMissingBean(...)

  1. public class ErrorMvcAutoConfiguration {
  2. ...
  3. @Bean
  4. //表示如果ErrorAttributes.class或其子类一开始全不存在, 才会创建本bean, 而当我们自定义的MyErrorAttributes正是继承自DefaultErrorAttributes(继承自ErrorAttributes)
  5. @ConditionalOnMissingBean(value = {ErrorAttributes.class},search = SearchStrategy.CURRENT )
  6. public DefaultErrorAttributes errorAttributes() {
  7. return new DefaultErrorAttributes();
  8. }
  9. ...
  10. }

最终的效果:响应是自适应的,通过定制ErrorAttributes改变需要返回的内容如下图

  1. {
  2. "timestamp": "2019年05月10日 14:01:23",
  3. "status": 500,
  4. "error": "Internal Server Error",
  5. "exception": "com.example.demo.bean.exception.UserNotFoundException",
  6. "message": "abc",
  7. "path": "/springbootdemo/ThymeleafController/thymeleaf",
  8. "company": "KingCompany",
  9. "extInfo": {
  10. "extInfo1": "info1",
  11. "extInfo2": "abc"
  12. }
  13. }

8、配置嵌入式Servlet容器

方式1、修改和server有关的配置(ServerProperties【也是EmbeddedServletContainerCustomizer】);

  1. server.port=8081
  2. server.context-path=/crud
  3.  
  4. server.tomcat.uri-encoding=UTF-8
  5.  
  6. #通用的Servlet容器设置
  7. server.xxx
  8. #Tomcat的设置
  9. server.tomcat.xxx

方式2、编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置

  1. @Bean //一定要将这个定制器加入到容器中
  2. public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
  3. return new EmbeddedServletContainerCustomizer() {
  4.  
  5. //定制嵌入式的Servlet容器相关的规则
  6. @Override
  7. public void customize(ConfigurableEmbeddedServletContainer container) {
  8. container.setPort(8083);
  9. }
  10. };
  11. }

Servlet三大组件配置【Servlet、Filter、Listener】

MyServlet.java--ServletRegistrationBean

  1. import javax.servlet.ServletException;
  2. import javax.servlet.http.HttpServlet;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. import java.io.IOException;
  6.  
  7. public class MyServlet extends HttpServlet {
  8.  
  9. @Override
  10. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  11. String ret = "MyServlet visited";
  12. System.err.println(ret);
  13. resp.getWriter().write(ret);
  14. }
  15.  
  16. @Override
  17. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  18. System.err.println("MyServlet visited");
  19. doGet(req,resp);
  20. }
  21. }

MyFilter.java--FilterRegistrationBean

  1. import javax.servlet.*;
  2. import java.io.IOException;
  3.  
  4. public class MyFilter implements Filter {
  5.  
  6. @Override
  7. public void init(FilterConfig filterConfig) throws ServletException {
  8. System.err.println("MyFilter init");
  9. }
  10.  
  11. @Override
  12. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  13. System.err.println("MyFilter visited");
  14. chain.doFilter(request, response);
  15. }
  16.  
  17. @Override
  18. public void destroy() {
  19. System.err.println("MyFilter destroy");
  20. }
  21. }

MyListener.java--ServletListenerRegistrationBean

listener分很多种类型, 以ServletContextListener为例

  1. import javax.servlet.ServletContextEvent;
  2. import javax.servlet.ServletContextListener;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8.  
  9. public class MyListener implements ServletContextListener {
  10. @Override
  11. public void contextInitialized(ServletContextEvent sce) {
  12. System.err.println("ServletContextListener contextInitialized");
  13. }
  14.  
  15. @Override
  16. public void contextDestroyed(ServletContextEvent sce) {
  17. System.err.println("ServletContextListener contextDestroyed");
  18. }
  19. }

最后把以上三大组合直接放在一个@Configuration中

  1. import com.example.demo.bean.Student;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import org.springframework.boot.web.servlet.FilterRegistrationBean;
  4. import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
  5. import org.springframework.boot.web.servlet.ServletRegistrationBean;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8.  
  9. import java.text.SimpleDateFormat;
  10. import java.util.Arrays;
  11.  
  12. @Configuration//指明当前类是一个配置类;就是来替代之前的Spring配置文件
  13. public class MyServletFilterListenerConfiguration {
  14.  
  15. // http://localhost:8080/springbootdemo/MyServlet2
  16. @Bean
  17. public ServletRegistrationBean myServlet(){
  18. ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/MyServlet2");
  19. return registrationBean;
  20. }
  21.  
  22. @Bean
  23. public FilterRegistrationBean myFilter(){
  24. FilterRegistrationBean registrationBean = new FilterRegistrationBean();
  25. registrationBean.setFilter(new MyFilter());
  26. registrationBean.setUrlPatterns(Arrays.asList("/MyServlet2","/myServlet3"));
  27. return registrationBean;
  28. }
  29.  
  30. @Bean
  31. public ServletListenerRegistrationBean myListener(){
  32. ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<MyListener>(new MyListener());
  33. return registrationBean;
  34. }
  35.  
  36. }

于是可以理解SpringBoot默认的Servlet配置

默认拦的就是所有请求

  1. @AutoConfigureOrder(-2147483648)
  2. @Configuration
  3. @ConditionalOnWebApplication
  4. @ConditionalOnClass({DispatcherServlet.class})
  5. @AutoConfigureAfter({EmbeddedServletContainerAutoConfiguration.class})
  6. public class DispatcherServletAutoConfiguration {
  7. ...
  8. @Configuration
  9. ...
  10. protected static class DispatcherServletRegistrationConfiguration {
  11. ...
  12. @Bean(name = {"dispatcherServletRegistration"})
  13. @ConditionalOnBean(value = {DispatcherServlet.class},name = {"dispatcherServlet"})
  14. public ServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
  15. //默认拦截: / 所有请求;包静态资源,但是不拦截jsp请求; /*会拦截jsp
  16. //可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径
  17. ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet, new String[]{this.serverProperties.getServletMapping()});
  18. registration.setName("dispatcherServlet");
  19. registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
  20. if (this.multipartConfig != null) {
  21. registration.setMultipartConfig(this.multipartConfig);
  22. }
  23. return registration;
  24. }
  25. }
  26. }

替换为其他嵌入式Servlet容器

Tomcat(默认使用)

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. 引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;
  5. </dependency>

Jetty

  1. <!-- 引入web模块 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. <exclusions>
  6. <exclusion>
  7. <artifactId>spring-boot-starter-tomcat</artifactId>
  8. <groupId>org.springframework.boot</groupId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>

  12. <!--引入其他的Servlet容器-->
  13. <dependency>
  14. <artifactId>spring-boot-starter-jetty</artifactId>
  15. <groupId>org.springframework.boot</groupId>
  16. </dependency>

Undertow

  1. <!-- 引入web模块 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. <exclusions>
  6. <exclusion>
  7. <artifactId>spring-boot-starter-tomcat</artifactId>
  8. <groupId>org.springframework.boot</groupId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>

  12. <!--引入其他的Servlet容器-->
  13. <dependency>
  14. <artifactId>spring-boot-starter-undertow</artifactId>
  15. <groupId>org.springframework.boot</groupId>
  16. </dependency>

嵌入式Servlet容器自动配置原理

步骤:

1)、SpringBoot根据EmbeddedServletContainerAutoConfiguration导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】

2)、容器中某个组件要创建对象就会触发后置处理器EmbeddedServletContainerCustomizerBeanPostProcessor;只要是嵌入式的Servlet容器工厂,后置处理器就工作;

3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法customize(ConfigurableEmbeddedServletContainer container) 给容器配置属性

EmbeddedServletContainerAutoConfiguration.java 嵌入式的Servlet容器自动配置

  1. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  2. @Configuration
  3. @ConditionalOnWebApplication
  4. //导入BeanPostProcessorsRegistrar,给容器中导入一些组件, 比较重要的有 EmbeddedServletContainerCustomizerBeanPostProcessor,它是一个后置处理器,用于在bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作
  5. @Import(BeanPostProcessorsRegistrar.class)
  6. public class EmbeddedServletContainerAutoConfiguration {
  7.  
  8. @Configuration
  9. @ConditionalOnClass({ Servlet.class, Tomcat.class })//判断当前是否引入了Tomcat依赖;
  10. //判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂(作用:创建嵌入式的Servlet容器)
  11. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  12. public static class EmbeddedTomcat {
  13. @Bean
  14. public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
  15. return new TomcatEmbeddedServletContainerFactory();
  16. }
  17. }
  18.  
  19. /**
  20. * Nested configuration if Jetty is being used.
  21. */
  22. @Configuration
  23. @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,WebAppContext.class })
  24. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  25. public static class EmbeddedJetty {
  26. @Bean
  27. public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
  28. return new JettyEmbeddedServletContainerFactory();
  29. }
  30. }
  31. ...

EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)和 EmbeddedServletContainer(嵌入式的Servlet容器)

public class TomcatEmbeddedServletContainerFactory  extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware

  1. public interface EmbeddedServletContainerFactory {
  2. //获取嵌入式的Servlet容器
  3. EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers);
  4. }

TomcatEmbeddedServletContainerFactory.java

该Factory通过ServerProperties(本身就是EmbeddedServletContainerCustomizer子类)、EmbeddedServletContainerCustomizer 定制化Servlet容器配置 , 而EmbeddedServletContainerCustomizer 又是通过EmbeddedServletContainerCustomizerBeanPostProcessor后置处理器配置的

  1. public class TomcatEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
  2. ...
  3. @Override
  4. public EmbeddedServletContainer getEmbeddedServletContainer(
  5. ServletContextInitializer... initializers) {
  6. //创建一个Tomcat
  7. Tomcat tomcat = new Tomcat();
  8.  
  9. //配置Tomcat的基本环节
  10. File baseDir = (this.baseDirectory != null ? this.baseDirectory
  11. : createTempDir("tomcat"));
  12. tomcat.setBaseDir(baseDir.getAbsolutePath());
  13. Connector connector = new Connector(this.protocol);
  14. tomcat.getService().addConnector(connector);
  15. customizeConnector(connector);
  16. tomcat.setConnector(connector);
  17. tomcat.getHost().setAutoDeploy(false);
  18. configureEngine(tomcat.getEngine());
  19. for (Connector additionalConnector : this.additionalTomcatConnectors) {
  20. tomcat.getService().addConnector(additionalConnector);
  21. }
  22. prepareContext(tomcat.getHost(), initializers);
  23.  
  24. //将配置好的Tomcat传入进去,返回一个EmbeddedServletContainer;并且启动Tomcat服务器
  25. return getTomcatEmbeddedServletContainer(tomcat);
  26. }
  27. ...
  28. }

EmbeddedServletContainerCustomizerBeanPostProcessor.java 后置处理器

  1. public class EmbeddedServletContainerCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
  2. //初始化之前
  3. @Override
  4. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  5. //如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
  6. if (bean instanceof ConfigurableEmbeddedServletContainer) {
  7. postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
  8. }
  9. return bean;
  10. }
  11.  
  12. private void postProcessBeforeInitialization(ConfigurableEmbeddedServletContainer bean) {
  13. //获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
  14. for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
  15. customizer.customize(bean);
  16. }
  17. }
  18.  
  19. private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
  20. if (this.customizers == null) {
  21. this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
  22. //从容器中获取所有这个类型的组件:EmbeddedServletContainerCustomizer
  23. this.beanFactory.getBeansOfType(EmbeddedServletContainerCustomizer.class,false, false).values());
  24. Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
  25. this.customizers = Collections.unmodifiableList(this.customizers);
  26. }
  27. return this.customizers;
  28. }
  29. }

嵌入式Servlet容器启动原理

什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;

获取嵌入式的Servlet容器工厂:

1)、SpringBoot应用启动运行run(xxx.class, arg)方法

2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext

3)、refresh(context);刷新刚才创建好的ioc容器;

  1. public void refresh() throws BeansException, IllegalStateException {
  2. synchronized (this.startupShutdownMonitor) {
  3. // Prepare this context for refreshing.
  4. prepareRefresh();
  5.  
  6. // Tell the subclass to refresh the internal bean factory.
  7. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  8.  
  9. // Prepare the bean factory for use in this context.
  10. prepareBeanFactory(beanFactory);
  11.  
  12. try {
  13. // Allows post-processing of the bean factory in context subclasses.
  14. postProcessBeanFactory(beanFactory);
  15.  
  16. // Invoke factory processors registered as beans in the context.
  17. invokeBeanFactoryPostProcessors(beanFactory);
  18.  
  19. // Register bean processors that intercept bean creation.
  20. registerBeanPostProcessors(beanFactory);
  21.  
  22. // Initialize message source for this context.
  23. initMessageSource();
  24.  
  25. // Initialize event multicaster for this context.
  26. initApplicationEventMulticaster();
  27.  
  28. // Initialize other special beans in specific context subclasses.
  29. onRefresh();
  30. // Check for listener beans and register them.
  31. registerListeners();
  32.  
  33. // Instantiate all remaining (non-lazy-init) singletons.
  34. finishBeanFactoryInitialization(beanFactory);
  35.  
  36. // Last step: publish corresponding event.
  37. finishRefresh();
  38. }
  39.  
  40. catch (BeansException ex) {
  41. if (logger.isWarnEnabled()) {
  42. logger.warn("Exception encountered during context initialization - " +
  43. "cancelling refresh attempt: " + ex);
  44. }
  45.  
  46. // Destroy already created singletons to avoid dangling resources.
  47. destroyBeans();
  48.  
  49. // Reset 'active' flag.
  50. cancelRefresh(ex);
  51.  
  52. // Propagate exception to caller.
  53. throw ex;
  54. }
  55.  
  56. finally {
  57. // Reset common introspection caches in Spring's core, since we
  58. // might not ever need metadata for singleton beans anymore...
  59. resetCommonCaches();
  60. }
  61. }
  62. }

4)、onRefresh(); web的ioc容器重写了onRefresh方法

5)、web ioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();

6)、获取嵌入式的Servlet容器工厂:

EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();

从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;

7)、使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());

8)、嵌入式的Servlet容器创建对象并启动Servlet容器;

先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;

IOC容器启动创建嵌入式的Servlet容器

IOC容器是什么? Spring框架IOC容器和AOP解析==>https://www.cnblogs.com/xiaoxing/p/5836835.html

使用外置Servlet容器

步骤

1. 创建打war包项目 , 非打 jar包项目

如果使用idea创建SpringBoot的war项目,则一开始就会自动创建SpringBootServletInitializer.java , 这是使用外围容器初始化必要的类。

  1. //类名随意,只要继承SpringBootServletInitializer即可
  2. public class ServletInitializer extends SpringBootServletInitializer {
  3. @Override
  4. protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
  5. return application.sources(SpringBootOuterContainerApplication.class);
  6. }
  7. }

2.  pom.xml中明确指定Tomcat的<scoper>为provided;

provided意为目标环境已经提供了, 打包时就不需要打进去了。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-tomcat</artifactId>
  4. <scope>provided</scope>
  5. </dependency>

3. 配置webapp目录及web.xml

idea中默认不会有该目录, 需要在Project Structure的Modules的Web模块中手动配置上下文根目录和web.xml

Project Structure | Modules | Web | 配置上下文路径(一开始该路径不存在)

Project Structure | Modules | Web | 配置web.xml (一开始该文件不存在, 注意添加时手动加上src\main\webapp路径)

4. 添加外置tomcat

右上 Edit Configurations | 添加tomcat配置 | Tomcat Server | Local | OK

Run/Debug Configurations | Tomcat Server | 取tomcat服务名 mytomcat8 | Server选项卡 | Configure | 配置Tomcat8路径 | OK

Run/Debug Configurations | Tomcat Server | 取tomcat服务名 mytomcat8 | Deployment选项卡 |添加需要布署的war包|  OK

5. 做一些测试用jsp和Controller

6. 启动服务器

将会自动打开chrome浏览器并自动访问http://localhost:8080/    (因为没有指定context上下文,默认即是/)

外部Servlet容器原理

  • jar包:执行SpringBoot主类的main方法 --> 启动ioc容器 --> 创建嵌入式的Servlet容器;
  • war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;

servlet3.0(Spring注解版): 8.2.4 Shared libraries / runtimes pluggability:

规则:

1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:

2)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名  (spring-web-5.1.6.RELEASE.jar里面有一个)

3)、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

流程:

1)、启动Tomcat

2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer:

Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer

3)、SpringServletContainerInitializer上的注解@HandlesTypes(WebApplicationInitializer.class) 标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例;

而WebApplicationInitializer是接口, 其实现类如下图

4)、每一个WebApplicationInitializer都调用自己的onStartup;

5)、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法 ,而自定义类ServletInitializer 正是SpringBootServletInitializer的子类,即WebApplicationInitializer 的子孙类。

6)、SpringBootServletInitializer实例(自定义类Servletinitializer)执行onStartup的时候会createRootApplicationContext;创建容器

  1. protected WebApplicationContext createRootApplicationContext(
  2. ServletContext servletContext) {
  3. //1、创建SpringApplicationBuilder
  4. SpringApplicationBuilder builder = createSpringApplicationBuilder();
  5. StandardServletEnvironment environment = new StandardServletEnvironment();
  6. environment.initPropertySources(servletContext, null);
  7. builder.environment(environment);
  8. builder.main(getClass());
  9. ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
  10. if (parent != null) {
  11. this.logger.info("Root context already created (using as parent).");
  12. servletContext.setAttribute(
  13. WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
  14. builder.initializers(new ParentContextApplicationContextInitializer(parent));
  15. }
  16. builder.initializers(
  17. new ServletContextApplicationContextInitializer(servletContext));
  18. builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
  19.  
  20. //调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
  21. builder = configure(builder);
  22.  
  23. //使用builder创建一个Spring应用
  24. SpringApplication application = builder.build();
  25. if (application.getSources().isEmpty() && AnnotationUtils
  26. .findAnnotation(getClass(), Configuration.class) != null) {
  27. application.getSources().add(getClass());
  28. }
  29. Assert.state(!application.getSources().isEmpty(),
  30. "No SpringApplication sources have been defined. Either override the "
  31. + "configure method or add an @Configuration annotation");
  32. // Ensure error pages are registered
  33. if (this.registerErrorPageFilter) {
  34. application.getSources().add(ErrorPageFilterConfiguration.class);
  35. }
  36. //启动Spring应用
  37. return run(application);
  38. }

7)、Spring的应用就启动并且创建IOC容器

  1. public ConfigurableApplicationContext run(String... args) {
  2. StopWatch stopWatch = new StopWatch();
  3. stopWatch.start();
  4. ConfigurableApplicationContext context = null;
  5. FailureAnalyzers analyzers = null;
  6. configureHeadlessProperty();
  7. SpringApplicationRunListeners listeners = getRunListeners(args);
  8. listeners.starting();
  9. try {
  10. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
  11. args);
  12. ConfigurableEnvironment environment = prepareEnvironment(listeners,
  13. applicationArguments);
  14. Banner printedBanner = printBanner(environment);
  15. context = createApplicationContext();
  16. analyzers = new FailureAnalyzers(context);
  17. prepareContext(context, environment, listeners, applicationArguments,
  18. printedBanner);
  19.  
  20. //刷新IOC容器
  21. refreshContext(context);
  22. afterRefresh(context, applicationArguments);
  23. listeners.finished(context, null);
  24. stopWatch.stop();
  25. if (this.logStartupInfo) {
  26. new StartupInfoLogger(this.mainApplicationClass)
  27. .logStarted(getApplicationLog(), stopWatch);
  28. }
  29. return context;
  30. }
  31. catch (Throwable ex) {
  32. handleRunFailure(context, listeners, analyzers, ex);
  33. throw new IllegalStateException(ex);
  34. }
  35. }

启动Servlet容器,再启动SpringBoot应用

样例下载地址: https://files.cnblogs.com/files/whatlonelytear/spring-boot-outer-container.zip

参考

Thymeleaf入门(一)——入门与基本概述==>https://www.cnblogs.com/jiangbei/p/8462294.html

springboot web开发【转】【补】的更多相关文章

  1. SpringBoot Web开发(5) 开发页面国际化+登录拦截

    SpringBoot Web开发(5) 开发页面国际化+登录拦截 一.页面国际化 页面国际化目的:根据浏览器语言设置的信息对页面信息进行切换,或者用户点击链接自行对页面语言信息进行切换. **效果演示 ...

  2. SpringBoot Web开发(4) Thymeleaf模板与freemaker

    SpringBoot Web开发(4) Thymeleaf模板与freemaker 一.模板引擎 常用得模板引擎有JSP.Velocity.Freemarker.Thymeleaf SpringBoo ...

  3. 【SpringBoot】SpringBoot Web开发(八)

    本周介绍SpringBoot项目Web开发的项目内容,及常用的CRUD操作,阅读本章前请阅读[SpringBoot]SpringBoot与Thymeleaf模版(六)的相关内容 Web开发 项目搭建 ...

  4. SpringBoot(四): SpringBoot web开发 SpringBoot使用jsp

    1.在SpringBoot中使用jsp,需要在pom.xml文件中添加依赖 <!--引入Spring Boot内嵌的Tomcat对JSP的解析包--> <dependency> ...

  5. Spring-boot -Web开发

    1).创建SpringBoot应用,选中我们需要的模块: 2).SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来 3).自己编写业务代码: 文件名的功能 x ...

  6. SpringBoot Web开发(3) WebMvcConfigurerAdapter过期替代方案

    springboot2.0中 WebMvcConfigurerAdapter过期替代方案 最近在学习尚硅谷的<springboot核心技术篇>,项目中用到SpringMVC的自动配置和扩展 ...

  7. SpringBoot——Web开发(静态资源映射)

    静态资源映射 SpringBoot对于SpringMVC的自动化配置都在WebMVCAutoConfiguration类中. 其中一个静态内部类WebMvcAutoConfigurationAdapt ...

  8. [SpringBoot——Web开发(使用Thymeleaf模板引擎)]

    [文字只能描述片段信息,具体细节参考代码] https://github.com/HCJ-shadow/SpringBootPlus 引入POM依赖 <properties> <ja ...

  9. web开发-CORS支持

    一.简介 Web 开发经常会遇到跨域问题,解决方案有:jsonp,iframe,CORS 等等 1.1.CORS与JSONP相比 1.JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求 ...

随机推荐

  1. 【DM642】ICELL Interface—Cells as Algorithm Containers

    ICELL Interface—Cells as Algorithm Containers: DSP的算法标准(XDAIS)为算法提供了一个标准的接口.这样我们就可以使用第三方的算法.For tech ...

  2. Flink中的多source+event watermark测试

    这次需要做一个监控项目,全网日志的指标计算,上线的话,计算量应该是百亿/天 单个source对应的sql如下 最原始的sql select pro,throwable,level,ip,`count` ...

  3. css之页面三列布局之左右两边宽度固定,中间自适应

    左右两边宽度固定,中间自适应 左右两边绝对定位 可以利用浮动,左边的左浮动,右边的右浮动 css3 flex布局(html http://www.cnblogs.com/myzy/p/5919814. ...

  4. hive启动一些错误记录

    java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMeta ...

  5. https://vjudge.net/problem/2198221/origin

    https://vjudge.net/problem/2198221/origin逆向思维,原题是人出来,我们处理成人进去,算出来每个人的曼哈顿距离,然后从大到小排序,距离长的先入.走的距离+这个人从 ...

  6. MySQL系列(一)--基础知识(转载)

    安装就不说了,网上多得是,我的MySQL是8.0版本,可以参考:CentOS7安装MySQL8.0图文教程和MySQL8.0本地访问设置为远程访问权限 我的MySQL安装在阿里云上面,阿里云向外暴露端 ...

  7. 【html、CSS、javascript-3】几个基本元素

    HTML 元素指的是从开始标签到结束标签的所有代码. 开始标签 元素内容 结束标签 <h1> h标签用来表示标题 </h1> <p> p标签表示一个段落 </ ...

  8. Centos 设置时区

    参考网址: http://jingyan.baidu.com/article/636f38bb268a82d6b84610bd.html //打开设置 tzselect //选择 )Asia → )c ...

  9. 核K-均值聚类(Kernel K-means Clustering)

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/wxcdzhangping/article/details/31366143 问题:        设 ...

  10. 洛谷P1970 [NOIP2013提高组Day2T2] 花匠

    P1970 花匠 题目描述 花匠栋栋种了一排花,每株花都有自己的高度.花儿越长越大,也越来越挤.栋栋决定 把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空间长大,同时,栋栋希 望剩下的花排 ...