一、简介

Spring Boot简化了Spring应用的开发,采用约定大于配置的思想,去繁从简,很方便就能构建一个独立的、产品级别的应用。

1.传统J2EE开发的缺点

开发笨重、配置繁多复杂、开发效率低下、部署流程复杂、第三方技术集成难度大。

2.SpringBoot的优点

  • 快速重建独立运行的Spring项目以及与主流框架集成。
  • 使用嵌入式的Servlet容器,应用无需打成WAR包
  • starters自动依赖与版本控制
  • 大量的自动配置、简化开发,也可以修改其默认值
  • 无需配置XML,无代码生成
  • 准生产环境的运行时应用监控
  • 与云计算的天然继承

3.SpringBoot helloworld说明

1.starters

  • SpringBoot为我们提供了简化企业级开发绝大多数场景的starters pom(启动器),只要引入了相应场景的starters pom,相关技术的绝大部分配置将会消除(字段配置),从而简化我们的开发。业务中我们就会使用到SpringBoot为我们字段配置的Bean。
  • 这些starters几乎涵盖了javaee所有常用场景,SpringBoot对这些场景依赖的jar也做了严格的测试和版本控制。
  • spring-boot-dependencies里面定义了jar包的版本。

2.入口类和@SpringBootApplication

  • 程序从main方法开始运行。
  • 使用SpringApplication.run()加载主程序类
  • 主程序类需标注@SpringBootApplication
  • @EnableAutoConfiguration是核心注解
  • @Import导入所有的自动配置场景
  • @AutoConfigurationPackage定义默认的包扫描规则。
  • 程序启动扫描主程序类所在的包以及下面所有子包的组件

3.自动配置

自动配置xxxAutoConfiguration

  • SpringBoot中存现大量的这些类,这些类的作用就是帮我们进行自动装配
  • 它会将这个场景需要的所有组件都注册到容器中,并配置好
  • 他们在类路径下的META-INF/spring.factories文件中
  • spring-boot-autoconfigure.jar中包含了所有场景的字段配置类代码
  • 这些自动配置类是SpringBoot进行自动装配的关键。

二、SpringBoot配置

1.配置文件

  • SpringBoot使用一个全局的配置文件。配置文件名是固定的。

    -application.properties或者application.yml

  • 配置文件放在src/main/resources目录或者类路径/config下。

  • 全局配置文件的作用是对一些默认配置进行修改

2.配置文件值注入

  • @Value和@ConfigurationProperties为属性注入值进行对比
对比点 @ConfigurationProperties @Value
功能 批量注入配置文件中的属性 一个个指定
松散绑定(松散语法) 支持 不支持
SpEL 不支持 支持
JSR303数据校验 支持 不支持
复杂类型封装 支持 不支持
  • 属性名匹配规则

    -person.firstName 使用标准方式

    -person.first-name 使用-

    -person.first_name 使用_

    -PERSON_FIRST_NAME 推荐系统属性使用这种写法

  • @PropertySource

    加载指定的配置文件

  • ConfigurationProperties

    -与@Bean结合为属性赋值

    -与@PropertySource(只能用于properties文件)结合读取指定文件。

  • ConfigurationProperties Validation

    -支持JSR303进行配置文件值校验。


  1. @Component
  2. @PropertySource(value={"classpath:person.properties"})
  3. @ConfigurationProperties(prefix="person")
  4. @Validated
  5. public class Person{
  6. @Email
  7. @Value("${person.email}")
  8. private String email
  9. }
  • ImportResource读取外部配置文件

3.配置文件占位符

  • RandomValuePropertySource

    配置文件中可以使用随机数

    -${random.value}

    -${random.int}

    -${random.long}

    -${random.int(10)}

    -${random.int[1024,65536]}

  • 属性配置占用符

    -可以在配置文件中引用前面配置过的属性(Y优先级前面配置过的这里都可以使用)。

    -${app.name:默认值}来指定找不到属性时的默认值。

  1. app.name=MyApp
  2. app.description=${app.name} is a SpringBoot Application

4.profile

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活指定参数的方式快速切换环境。

1.多profile文件形式

  • 格式:application-{profile}.properties/yml

    application-dev.properties、application-prod.properties

2.多profile文档块模式

  1. spring.profiles.active=prod #激活指定配置
  2. spring.profiles=prod
  3. server.port=80
  4. # default表示未指定时的默认配置
  5. spring.profiles=default
  6. server.port=8080

3.激活方式

  • 命令行:--spring.profiles.active=dev
  • 配置文件:spring.profiles.active=dev
  • jvm参数:-Dspring.profiles.active=dev

5.配置文件加载位置

SpringBoot启动会扫描一下位置的application.properties或者application.yml文件作为SpringBoot的默认配置文件。

- file:./config/

- file:./

- classpath:/config/

-classpath:/

-以上是按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级配置内容会覆盖低优先级配置内容。

-可以通过配置spring.config.location来改变默认配置。

6.外部配置加载顺序

  1. 命令行参数
  2. 来自java:comp/env的JNDI属性
  3. Java系统属性(System.getProperties())
  4. 操作系统环境变量
  5. RandomValuePropertySource配置的random.*属性值
  6. jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
  7. jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
  8. jar包外部的application.properties或application.yml(不带spring.profile)配置文件
  9. jar包内部的application.properties或application.yml(不带spring.profile)配置文件
  10. @Configuration注解类上的@PropertySource。
  11. 通过SpringApplication.setDefaultproperties指定的默认属性。

7.自动配置原理

1.SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration

2.@EnableAutoConfiguration作用

  • 利用EnableAutoConfigurationImportSelector给容器中导入一些组件。
  • 将类路径小META-INF/spring.factories里面配置的所有EnableAutoConfiguration的值加入到了容器中。

3.@Conditional派生注解

@Conditional扩展注解 作用(判断是否满足当期指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 容器中没有指定类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项
  • 作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效。

三、SpringBoot与日志

1.日志框架

市场上存在非常多的日志框架,JUL(java.util.logging)、JCL(Apache Commons Logging)、Log4J、Log4J2、Logback、SLF4j、jboss-logging等。

  • SpringBoot早框架内部使用JCL。spring-boot-starter-logging采用了slf4j+logback的形式,SpringBoot也能自动配置(jul、log4j2、logback)并简化配置。
日志门面 日志实现
JCL、SLF4J、jboss-logging log4j、JUL、Log4j2、Logback
日志系统 配置文件
Logback logback-spring.xml、logback-spring.groovy、logback.xml或logback.groovy
Log4j2 log4j2-spring.xml、log4j2.xml
JUL logging.properties

  • 总结:

    1.SpringBoot底层也是使用slf4j+logback的方式进行日志记录。

    2.SpringBoot也把其他的日志都替换成了slf4j。

    3.如果要引入其他日志框架,要排除Spring框架的commons-logging依赖。

四、Web开发

1.SpringBoot对静态资源的映射规则

  • 所有/webjars/**,都去classpath:/META-INF/resources/webjars/ 下面找资源。
  • "/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射。
  • 欢迎页;静态资源文件夹下的所有index.html页面,被"/**" 映射。
  • 所有的 **/favicon.ico都是在静态资源文件下找。

2.SpringMVC自动配置

1.SpringMVC auto-configuration

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

  • 包含了ContentNegotiatingViewResolver和BeanNameViewResolver。

    • 自动配置了ViewResolver
    • ContentNegotiatingViewResolver:组合所有的视图解析器
  1. - 支持静态资源,包括支持Wenjars
  2. - 静态首页访问
  3. - 支持favicon.ico
  4. - 自动注册了ConverterGenericConverterFormatter
  5. - Converter:转换器。
  6. - Formatter:格式化器
  • 支持HttpMessageConverters

    • HttpMessageConverter:SpringMVC用来转换Http请求和响应。
    • HttpMessageConverters:从容器中确定,获取所有的HttpMessageConverter;
  • 自动注入MessageCodesResolver,定义错误码生成规则。

  • 自动使用ConfigurableWebBindingInitializer。

2.扩展SpringMVC

编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型,不能标注@EnableWebMvc注解

  1. @Configuration
  2. public class MyMvcConfig extends WebMvcConfigurerAdapter {
  3. @Override
  4. public void addViewControllers(ViewControllerRegistry registry) {
  5. registry.addViewController("/desperado").setViewName("success");
  6. }
  7. }

原理

  1. WebMvcAutoConfiguration是SpringMVC的自动配置类。
  2. 在做其他自动配置时会导入。
  3. 容器中所有的WebMvcConfigurer都会一起被注册。
  4. 我们自定义的配置类也会被调用。

3.全面接管SpringMVC

如果想要使SpringMVC的自动配置失效,只需要在我们自定义的配置类中添加@EnableWebMvc注解即可。

  1. @EnableWebMvc
  2. @Configuration
  3. public class MyMvcConfig extends WebMvcConfigurerAdapter {
  4. @Override
  5. public void addViewControllers(ViewControllerRegistry registry) {
  6. registry.addViewController("/desperado").setViewName("success");
  7. }
  8. }

原理

  1. @EnableWebMvc的注解
  1. @Import(DelegatingWebMvcConfiguation.class)
  2. public @interface EnableWebMvc{}
  1. DelegatingWebMvcConfiguation
  1. @Configuration
  2. public class DelegatingWebMvcConfiguation extend WebMvcConfigurationSupport{}
  1. WebMvcAutoConfiguration
  1. @Configuration
  2. @ConditionalOnWebApplication
  3. @ConditionalOnClass({Servlet.class,DispatcherServlet.class,
  4. WebMvcConfigurerAdapter.class})
  5. //容器中没有这个组件,这个自动配置类才会生效
  6. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
  7. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
  8. @AutoConfigureAfter({DispatcherServletAutoConfiguration.class,
  9. ValidationAutoConfiguration.class})
  10. public class WebMvcAutoConfiguration{}
  1. @EnableWebMvc会将WebMvcConfigurationSupport组件导入进来。
  2. 导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能。

4.修改默认配置

  1. SpringBoot在自动配置很多组件的时候,显卡容器中有没有用户自己配置的(@Bean 、@Component),如果有就用用户配置的,如果没有,才会进行自动配置;如果某些组件可以有多个,将用户配置的和自己默认的组合起来。
  2. 在SpringBoot中有许多的xxxConfigurer帮助我们进行扩展配置
  3. 在SpringBoot中有许多的xxxCustomizer帮助我们进行定制配置

5.默认访问首页

使用自定义WebMvcConfigurationAdapter进行配置

  1. //使用WebMvcConfigurationAdapter可以扩展SpringMVC的功能
  2. @Configuration
  3. public class MyMvcConfig extends WebMvcConfigurerAdapter {
  4. @Override
  5. public void addViewControllers(ViewControllerRegistry registry) {
  6. //浏览器发送/desperado 请求来到success
  7. registry.addViewController("/desperado").setViewName("success");
  8. }
  9. //所有的webMvcConfigurerAdapter组件都会一起起作用
  10. @Bean //将组件注册到容器
  11. public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
  12. WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
  13. @Override
  14. public void addViewControllers(ViewControllerRegistry registry) {
  15. //配置默认路径的页面
  16. registry.addViewController("/").setViewName("login");
  17. registry.addViewController("/index.html").setViewName("login");
  18. }
  19. };
  20. return adapter;
  21. }
  22. }

6.国际化

1.编写国际化配置文件

编写不同语言的配置文件,比如login.properties、login_en_US.properties、login_zh_CN.properties等。

2. SpringBoot自动配置好了管理国际化资源文件的组件。

  1. @EnableConfigurationProperties
  2. public class MessageSourceAutoConfiguration {
  3. private static final Resource[] NO_RESOURCES = new Resource[0];
  4. public MessageSourceAutoConfiguration() {
  5. }
  6. @Bean
  7. @ConfigurationProperties(
  8. prefix = "spring.messages"
  9. )
  10. public MessageSourceProperties messageSourceProperties() {
  11. return new MessageSourceProperties();
  12. }
  13. @Bean
  14. public MessageSource messageSource(MessageSourceProperties properties) {
  15. ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
  16. if (StringUtils.hasText(properties.getBasename())) {
  17. //设置国际化资源文件的基础名(去掉语言国家代码)
  18. messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
  19. }
  20. if (properties.getEncoding() != null) {
  21. messageSource.setDefaultEncoding(properties.getEncoding().name());
  22. }
  23. messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
  24. Duration cacheDuration = properties.getCacheDuration();
  25. if (cacheDuration != null) {
  26. messageSource.setCacheMillis(cacheDuration.toMillis());
  27. }
  28. messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
  29. messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
  30. return messageSource;
  31. }

原理

根据请求头带来的区域信息获取Locale进行国际化。

五、错误处理机制

1.默认的错误处理机制

  1. 浏览器,默认返回一个默认的错误页面。
  2. 其他客户端,默认响应一个json数据。

原理

  1. 在DefaultErrorAttributes中获取错误页面的信息
  1. public class DefaultErrorAttributes implements ErrorAttributes {
  2. //获取错误页面的信息
  3. public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
  4. Map<String, Object> errorAttributes = new LinkedHashMap();
  5. errorAttributes.put("timestamp", new Date());
  6. errorAttributes.put("path", request.path());
  7. Throwable error = this.getError(request);
  8. HttpStatus errorStatus = this.determineHttpStatus(error);
  9. errorAttributes.put("status", errorStatus.value());
  10. errorAttributes.put("error", errorStatus.getReasonPhrase());
  11. errorAttributes.put("message", this.determineMessage(error));
  12. this.handleException(errorAttributes, this.determineException(error), includeStackTrace);
  13. return errorAttributes;
  14. }
  15. }
  1. 在BasicErrorController中处理/error请求
  1. @Controller
  2. @RequestMapping({"${server.error.path:${error.path:/error}}"})
  3. public class BasicErrorController extends AbstractErrorController {
  4. //产生html类型的数据,浏览器发送的请求来打这个方法处理
  5. @RequestMapping(
  6. produces = {"text/html"}
  7. )
  8. public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  9. HttpStatus status = this.getStatus(request);
  10. Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  11. response.setStatus(status.value());
  12. //去哪个页面作为错误页面。包含页面地址和页面内容
  13. ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
  14. return modelAndView != null ? modelAndView : new ModelAndView("error", model);
  15. }
  16. //产生json数据,其他客户端来到这个方法处理
  17. @RequestMapping
  18. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  19. Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
  20. HttpStatus status = this.getStatus(request);
  21. return new ResponseEntity(body, status);
  22. }
  23. }
  1. ErrorPageCustomizer进行错误配置
  1. public class ErrorProperties {
  2. @Value("${error.path:/error}")
  3. private String path = "/error";
  4. private boolean includeException;
  5. private ErrorProperties.IncludeStacktrace includeStacktrace;
  6. private final ErrorProperties.Whitelabel whitelabel;
  7. }
  1. ErrorMvcAutoConfiguration生成错误页面
  1. public class ErrorMvcAutoConfiguration {
  2. private static class StaticView implements View {
  3. private static final Log logger = LogFactory.getLog(ErrorMvcAutoConfiguration.StaticView.class);
  4. private StaticView() {
  5. }
  6. public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  7. if (response.isCommitted()) {
  8. String message = this.getMessage(model);
  9. logger.error(message);
  10. } else {
  11. StringBuilder builder = new StringBuilder();
  12. Date timestamp = (Date)model.get("timestamp");
  13. Object message = model.get("message");
  14. Object trace = model.get("trace");
  15. if (response.getContentType() == null) {
  16. response.setContentType(this.getContentType());
  17. }
  18. builder.append("<html><body><h1>Whitelabel Error Page</h1>").append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>There was an unexpected error (type=").append(this.htmlEscape(model.get("error"))).append(", status=").append(this.htmlEscape(model.get("status"))).append(").</div>");
  19. if (message != null) {
  20. builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
  21. }
  22. if (trace != null) {
  23. builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
  24. }
  25. builder.append("</body></html>");
  26. response.getWriter().append(builder.toString());
  27. }
  28. }
  29. private String htmlEscape(Object input) {
  30. return input != null ? HtmlUtils.htmlEscape(input.toString()) : null;
  31. }
  32. private String getMessage(Map<String, ?> model) {
  33. Object path = model.get("path");
  34. String message = "Cannot render error page for request [" + path + "]";
  35. if (model.get("message") != null) {
  36. message = message + " and exception [" + model.get("message") + "]";
  37. }
  38. message = message + " as the response has already been committed.";
  39. message = message + " As a result, the response may have the wrong status code.";
  40. return message;
  41. }
  42. public String getContentType() {
  43. return "text/html";
  44. }
  45. }

5.DefaultErrorViewResolver解析页面

  1. public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
  2. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
  3. ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
  4. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  5. modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
  6. }
  7. return modelAndView;
  8. }
  9. private ModelAndView resolve(String viewName, Map<String, Object> model) {
  10. //默认去找一个页, error/404
  11. String errorViewName = "error/" + viewName;
  12. //模板引擎可以解析这个页面地址就要模板引擎解析
  13. TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
  14. //模板引擎可用的情况下返回到errorViewName指定的视图地址
  15. //模板引擎不可以,就在静态资源文件夹下找对应的页面
  16. return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
  17. }

2.错误页面的优先级(自定义错误页面)

  1. 在模板引擎的情况下,error/状态码(将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的error文件夹下),发生此状态码的错误就会来到对应的页面;
  2. 没有模板引擎(模板引擎找不到错误页面),就在静态资源文件夹下找。
  3. 以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面。

3.如何定制错误的json数据

  1. 自定义异常处理&返回定制json数据(没有自适应效果)
  1. @ControllerAdvice
  2. public class MyExceptionHandler {
  3. @ResponseBody
  4. @ExceptionHandler(CustomException.class)
  5. public Map<String,Object> handleException(Exception e){
  6. HashMap<String, Object> map = new HashMap<>();
  7. map.put("code","错误信息");
  8. map.put("message",e.getMessage());
  9. return map;
  10. }
  11. }

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

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

4.将定制的数据发送出去

出现错误之后,回来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的。

  1. 编写一个ErrorController的实现类(或者是编写AbstractErrorController的子类),放到容器中。
  2. 页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;容器中DefaultErrorAttributes.getErrorAttributes()默认进行数据处理.
  1. //给容器中加入自定义的ErrorAttributes
  2. @Component
  3. public class MyErrorAttributes extends DefaultErrorAttributes {
  4. @Override
  5. public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
  6. //获取ErrorAttributes的map
  7. Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
  8. //加入自己属性字段
  9. map.put("name","desperado");
  10. return map;
  11. }
  12. }

六、配置嵌入式Servlet容器

SpringBoot默认使用Tomcat作为内嵌的Servlet容器

1.修改Servlet容器的配置

在配置文件application文件中修改和server有关的配置。

  1. server.port=8081
  2. server.context_path=/crud
  3. server.tomcat.uri-encoding=utf-8

2. 定制Servlet容器的相关配置

编写一个EmbeddedServletContainerCustomizer(2.x中使用WebServerFactoryCustomizer),来修改Servlet容器的配置。

  1. @Bean
  2. public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
  3. return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>(){
  4. @Override
  5. public void customize(ConfigurableWebServerFactory factory) {
  6. factory.setPort(8081);
  7. }
  8. };
  9. }

3.注册Servlet三大组件

由于SpringBoot是默认以jar包的方式启动内嵌的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。所以注册Servlet、Filter、Listener的方式也不同

1. 注入Servlet

  1. @Bean
  2. public ServletRegistrationBean<MyServlet> myServlet(){
  3. ServletRegistrationBean<MyServlet> registrationBean =
  4. new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
  5. return registrationBean;
  6. }

2. 注入Filter

  1. @Bean
  2. public FilterRegistrationBean<MyFilter> myFilter(){
  3. FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
  4. registrationBean.setFilter(new MyFilter());
  5. registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
  6. return registrationBean;
  7. }

3. 注入Listener

  1. @Bean
  2. public ServletListenerRegistrationBean<MyListener> myListener(){
  3. ServletListenerRegistrationBean<MyListener> registrationBean =
  4. new ServletListenerRegistrationBean<>(new MyListener());
  5. return registrationBean;
  6. }

4.替换为其他嵌入式Servlet容器

替换为其他的Servlet非常简单,只需要在pom中引入其依赖,然后排除tomcat的依赖即可.

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <exclusions>
  5. <exclusion>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-tomcat</artifactId>
  8. </exclusion>
  9. </exclusions>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-jetty</artifactId>
  14. </dependency>

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

  1. EmbeddedServletContainerAutoConfiguration(2.x对应ServletWebServerFactoryConfiguration):嵌入式容器的自动配置
  1. @Configuration
  2. class ServletWebServerFactoryConfiguration {
  3. ServletWebServerFactoryConfiguration() {
  4. }
  5. @Configuration
  6. @ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
  7. @ConditionalOnMissingBean(
  8. value = {ServletWebServerFactory.class},
  9. search = SearchStrategy.CURRENT
  10. )
  11. public static class EmbeddedUndertow {
  12. public EmbeddedUndertow() {
  13. }
  14. @Bean
  15. public UndertowServletWebServerFactory undertowServletWebServerFactory() {
  16. return new UndertowServletWebServerFactory();
  17. }
  18. }
  19. @Configuration
  20. @ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
  21. @ConditionalOnMissingBean(
  22. value = {ServletWebServerFactory.class},
  23. search = SearchStrategy.CURRENT
  24. )
  25. public static class EmbeddedJetty {
  26. public EmbeddedJetty() {
  27. }
  28. @Bean
  29. public JettyServletWebServerFactory JettyServletWebServerFactory() {
  30. return new JettyServletWebServerFactory();
  31. }
  32. }
  33. @Configuration
  34. //判断当前是否引入了tomcat依赖
  35. @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
  36. ///判断当前容器没有用户自己定义ServletWebServerFactory:
  37. //嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器
  38. @ConditionalOnMissingBean(
  39. value = {ServletWebServerFactory.class},
  40. search = SearchStrategy.CURRENT
  41. )
  42. public static class EmbeddedTomcat {
  43. public EmbeddedTomcat() {
  44. }
  45. @Bean
  46. public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
  47. return new TomcatServletWebServerFactory();
  48. }
  49. }
  50. }
  1. 嵌入式Servlet容器工厂

  1. 嵌入式的Servlet容器

4.以tomcat为例

  1. public WebServer getWebServer(ServletContextInitializer... initializers) {
  2. Tomcat tomcat = new Tomcat();
  3. File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
  4. tomcat.setBaseDir(baseDir.getAbsolutePath());
  5. Connector connector = new Connector(this.protocol);
  6. tomcat.getService().addConnector(connector);
  7. this.customizeConnector(connector);
  8. tomcat.setConnector(connector);
  9. tomcat.getHost().setAutoDeploy(false);
  10. this.configureEngine(tomcat.getEngine());
  11. Iterator var5 = this.additionalTomcatConnectors.iterator();
  12. while(var5.hasNext()) {
  13. Connector additionalConnector = (Connector)var5.next();
  14. tomcat.getService().addConnector(additionalConnector);
  15. }
  16. this.prepareContext(tomcat.getHost(), initializers);
  17. return thisb.getTomcatWebServer(tomcat);
  18. }
  1. 容器中导入 WebServerFactoryCustomizerBeanPostProcessor

  1. public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
  2. //初始化之前
  3. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  4. //如果当前初始化的是一个WebServerFactory类型的组件
  5. if (bean instanceof WebServerFactory) {
  6. this.postProcessBeforeInitialization((WebServerFactory)bean);
  7. }
  8. return bean;
  9. }
  10. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  11. return bean;
  12. }
  13. private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
  14. // 获取所有的定制器,调用每一个定制器的customize方法来给servlet容器进行属性赋值
  15. ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
  16. customizer.customize(webServerFactory);
  17. });
  18. }
  19. private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
  20. if (this.customizers == null) {
  21. // 定制Servlet容器,给容器中可以添加一个WebServerFactoryCustomizer类型的组件
  22. this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans());
  23. this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
  24. this.customizers = Collections.unmodifiableList(this.customizers);
  25. }
  26. return this.customizers;
  27. }
  28. private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
  29. //从容器中获取所有这个类型的组件:WebServerFactoryCustomizer
  30. return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
  31. }
  32. }

总结

  1. SpringBoot根据导入的依赖情况,给容器中添加相应的WebServerFactory【TomcatServletWebServerFactory】。
  2. 容器中某个组件要创建对象就会使用WebServerFactoryCustomizerBeanPostProcessor后置处理器,只要是嵌入式的Servlet工厂,后置处理器就会进行处理。
  3. 后置处理器从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法

6.嵌入式Servlet容器启动过程

  1. SpringBoot启动运行run方法。
  2. 调用refreshContext(context);刷新IOC容器【创建IOC容器,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigServletWebServerApplicationContext,如果是reactive应用创建AnnotationConfigReactiveWebServerApplicationContext,否则创建AnnotationConfigApplicationContext。
  1. protected ConfigurableApplicationContext createApplicationContext() {
  2. Class<?> contextClass = this.applicationContextClass;
  3. if (contextClass == null) {
  4. try {
  5. switch(this.webApplicationType) {
  6. case SERVLET:
  7. contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
  8. break;
  9. case REACTIVE:
  10. contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
  11. break;
  12. default:
  13. contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
  14. }
  15. } catch (ClassNotFoundException var3) {
  16. throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
  17. }
  18. }
  19. return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
  20. }
  1. 调用refresh(context);刷新上面创建好的IOC容器
  1. public void refresh() throws BeansException, IllegalStateException {
  2. Object var1 = this.startupShutdownMonitor;
  3. synchronized(this.startupShutdownMonitor) {
  4. //准备刷新的context
  5. this.prepareRefresh();
  6. //调用子类去刷新内部的实例工厂
  7. ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
  8. //准备在这个context中要使用的实例工厂
  9. this.prepareBeanFactory(beanFactory);
  10. try {
  11. // 允许在上下文子类中对bean工厂进行后置处理。
  12. this.postProcessBeanFactory(beanFactory);
  13. //在context中调用注册为bean的工厂处理器。
  14. this.invokeBeanFactoryPostProcessors(beanFactory);
  15. //注册拦截bean创建的bean处理器。
  16. this.registerBeanPostProcessors(beanFactory);
  17. //初始化此context的消息源
  18. this.initMessageSource();
  19. //初始化此上下文的事件多播器。
  20. this.initApplicationEventMulticaster();
  21. //在特定的context子类中初始化其他特殊bean。
  22. this.onRefresh();
  23. // 检查监听器bean并注册它们。
  24. this.registerListeners();
  25. // 实例化所有剩余(非延迟初始化)单例。
  26. this.finishBeanFactoryInitialization(beanFactory);
  27. //最后一步:发布相应的事件。
  28. this.finishRefresh();
  29. } catch (BeansException var9) {
  30. if (this.logger.isWarnEnabled()) {
  31. this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
  32. }
  33. //摧毁已经创建的单例以避免占用资源。
  34. this.destroyBeans();
  35. //重置 ‘active’ 标志
  36. this.cancelRefresh(var9);
  37. //Propagate exception to caller.
  38. throw var9;
  39. } finally {
  40. //从我们开始,重置Spring核心中的常见内省缓存
  41. //可能不再需要单例bean的元数据了...
  42. this.resetCommonCaches();
  43. }
  44. }
  45. }
  1. 调用onRefresh();web的IOC容器重写了onRefresh方法。
  2. Web IOC容器创建嵌入式的Servlet容器。
  1. private void createWebServer() {
  2. WebServer webServer = this.webServer;
  3. ServletContext servletContext = this.getServletContext();
  4. if (webServer == null && servletContext == null) {
  5. ServletWebServerFactory factory = this.getWebServerFactory();
  6. this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
  7. } else if (servletContext != null) {
  8. try {
  9. this.getSelfInitializer().onStartup(servletContext);
  10. } catch (ServletException var4) {
  11. throw new ApplicationContextException("Cannot initialize servlet context", var4);
  12. }
  13. }
  14. this.initPropertySources();
  15. }
  1. 获取嵌入式的Servlet容器工厂: ServletWebServerFactory factory = this.getWebServerFactory();从IOC容器中获取ServletWebServerFactory组件;

  2. 使用容器工厂获取嵌入式的Servlet容器:his.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});

  1. public WebServer getWebServer(ServletContextInitializer... initializers) {
  2. Tomcat tomcat = new Tomcat();
  3. File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
  4. tomcat.setBaseDir(baseDir.getAbsolutePath());
  5. Connector connector = new Connector(this.protocol);
  6. tomcat.getService().addConnector(connector);
  7. this.customizeConnector(connector);
  8. tomcat.setConnector(connector);
  9. tomcat.getHost().setAutoDeploy(false);
  10. this.configureEngine(tomcat.getEngine());
  11. Iterator var5 = this.additionalTomcatConnectors.iterator();
  12. while(var5.hasNext()) {
  13. Connector additionalConnector = (Connector)var5.next();
  14. tomcat.getService().addConnector(additionalConnector);
  15. }
  16. this.prepareContext(tomcat.getHost(), initializers);
  17. return this.getTomcatWebServer(tomcat);
  18. }
  1. 嵌入式的Servlet容器创建并启动Servlet容器。

9.先启动嵌入式的Servlet容器,再将IOC容器中剩余的没有创建出来的对象获取出来、IOC容器启动就会创建嵌入式的Servlet容器。

7.使用外置的Servlet容器

1. 嵌入式Servlet容器优缺点

优点:简单、便捷。

缺点:默认不支持JSP,优化定制比较复杂。

2.使用外部Servlet容器步骤

  1. 必须创建一个war项目。
  2. 将嵌入式的Tomcat指定为provided。
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring‐boot‐starter‐tomcat</artifactId>
  4. <scope>provided</scope>
  5. </dependency>
  1. 必须编写一个SpringBootServletInitializer的子类,并调用configure方法。
  1. public class ServletInitializer extends SpringBootServletInitializer {
  2. @Override
  3. protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
  4. //传入SpringBoot应用的主程序
  5. return application.sources(SpringBoot04WebJspApplication.class);
  6. }
  7. }
  1. 启动服务器就可以了。

3.原理与规则

原理

启动服务器,服务器启动SpringBoot应用[SpringBootServletInitializer],启动IOC容器。

规则

  1. 服务器启动会创建当前web应用里面每一个jar包里面的ServletContainerInitializer实例。
  2. ServletContainerInitializer的实现放在jar包的META-INF/services文件下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer实现类的全类名。
  3. 还可以使用@HandlerType,在启动应用时加载指定的类。

4. 启动流程

  1. 启动tomcat。
  2. 加载spring-web包下META-INF/services下面的javax.servlet.ServletContainerInitializer文件。

  1. SpringServletContainerInitializer将@HandlerType标注的所有这个类型的类都传入搭配onStartup方法的Set中,为这些WebApplicationInitializer类型的类创建实例。
  1. @HandlesTypes({WebApplicationInitializer.class})
  2. public class SpringServletContainerInitializer implements ServletContainerInitializer {
  3. public SpringServletContainerInitializer() {
  4. }
  5. public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
  6. List<WebApplicationInitializer> initializers = new LinkedList();
  7. Iterator var4;
  8. if (webAppInitializerClasses != null) {
  9. var4 = webAppInitializerClasses.iterator();
  10. while(var4.hasNext()) {
  11. Class<?> waiClass = (Class)var4.next();
  12. if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
  13. try {
  14. initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
  15. } catch (Throwable var7) {
  16. throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
  17. }
  18. }
  19. }
  20. }
  21. if (initializers.isEmpty()) {
  22. servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
  23. } else {
  24. servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
  25. AnnotationAwareOrderComparator.sort(initializers);
  26. var4 = initializers.iterator();
  27. while(var4.hasNext()) {
  28. WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
  29. initializer.onStartup(servletContext);
  30. }
  31. }
  32. }
  33. }
  1. 每个WebApplicationInitializer到调用自己的onStartup()方法。

  1. 相当于SpringBootServletInitializer的类会被创建对象,并执行onStartup()方法。

  2. SpringBootServletInitializer实例执行onStartup的时候会crateRootApplicationContext创建容器。

  1. protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
  2. // 1.创建SpringApplicationBuilder
  3. SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
  4. builder.main(this.getClass());
  5. ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
  6. if (parent != null) {
  7. this.logger.info("Root context already created (using as parent).");
  8. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
  9. builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
  10. }
  11. builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
  12. builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
  13. // 2.调用configure方法,子类重新了这个方法,将SpringBoot的主程序类传入
  14. builder = this.configure(builder);
  15. builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
  16. // 3.使用builder创建一个Spring应用
  17. SpringApplication application = builder.build();
  18. if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
  19. application.addPrimarySources(Collections.singleton(this.getClass()));
  20. }
  21. Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
  22. //确保错误页被注册
  23. if (this.registerErrorPageFilter) {
  24. application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
  25. }
  26. // 4. q启动Spring应用
  27. return this.run(application);
  28. }
  1. Spring的应用启动并且创建IOC容器。

  2. 先启动Servlet容器,再启动SpringBoot应用。

关于SpringBoot的自动配置和启动过程的更多相关文章

  1. springboot mvc自动配置(三)初始化mvc的组件

    所有文章 https://www.cnblogs.com/lay2017/p/11775787.html 正文 在springboot mvc自动配置的时候,获得了DispatcherServlet和 ...

  2. 面试题: SpringBoot 的自动配置原理

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 3.Spring Boot 的自动配置原理 package com.mmall; import org. ...

  3. springboot(六)自动配置原理和@Conditional

    官方参考的配置属性:https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#common-appl ...

  4. 从源码角度解析 Springboot 2.6.2 的启动过程

    1. 概述 老话说的好:把简单的事情重复做,做到极致,你就成功了. 言归正传,Springboot的启动过程,一直都是面试的高频点,今天我们用当前最新的 Springboot 2.6.2 来聊一聊 S ...

  5. SpringBoot的自动配置

    1.根据条件来装配bean,SpringBoot的自动配置,根据条件进行自动配置. 首先创建一个接口,如下所示: package com.bie.encoding; /** * * @Descript ...

  6. springboot mvc自动配置(一)自动配置DispatcherServlet和DispatcherServletRegistry

    所有文章 https://www.cnblogs.com/lay2017/p/11775787.html 正文 springboot的自动配置基于SPI机制,实现自动配置的核心要点就是添加一个自动配置 ...

  7. spring-boot spring-MVC自动配置

    Spring MVC auto-configuration Spring Boot 自动配置好了SpringMVC 以下是SpringBoot对SpringMVC的默认配置:==(WebMvcAuto ...

  8. Springboot MVC 自动配置

    Springboot MVC 自动配置 官方文档阅读 https://docs.spring.io/spring-boot/docs/current/reference/html/web.html#w ...

  9. SpringBoot的自动配置原理过程解析

    SpringBoot的最大好处就是实现了大部分的自动配置,使得开发者可以更多的关注于业务开发,避免繁琐的业务开发,但是SpringBoot如此好用的 自动注解过程着实让人忍不住的去了解一番,因为本文的 ...

随机推荐

  1. Airbnb新用户的民宿预定结果预测

    1. 背景 关于这个数据集,在这个挑战中,您将获得一个用户列表以及他们的人口统计数据.web会话记录和一些汇总统计信息.您被要求预测新用户的第一个预订目的地将是哪个国家.这个数据集中的所有用户都来自美 ...

  2. android: Android 权限管理小结

    一. 概述 感谢郭神,自从Android6.0发布以来,在权限上做出了很大的变动,不再是之前的只要在manifest设置就可以任意获取权限,而是更加的注重用户的隐私和体验,不会再强迫用户因拒绝不该拥有 ...

  3. SQL-W3School-高级:SQL 通配符

    ylbtech-SQL-W3School-高级:SQL 通配符 1.返回顶部 1. 在搜索数据库中的数据时,您可以使用 SQL 通配符. SQL 通配符 在搜索数据库中的数据时,SQL 通配符可以替代 ...

  4. JAVA-开发构建Gradle项目安装使用教程

    一.简介: Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具.它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,目前也增加了基于Kotl ...

  5. js取url问号后的参数方法封装

    工具方法: function getRequest() { var url = location.search; // 获取url中?后面的字符串 var theRequest = new Objec ...

  6. web布局收集整理

    /*样式文件*/ .fgw-right-p{ height: 38px; line-height: 38px; margin-bottom: 20px; padding-left: 24px; spa ...

  7. DB2的web可视化客户端工具

    DB2 是IBM公司的产品,目前在银行等金融行业还在大量使用, DB2的客户端工具太,并且难用,这是一直为人所垢病的,  现在TreeSoft数据库管理系统已支持DB2了,直接在浏览器中就可以操作查看 ...

  8. 使用微软的WinAppDriver进行Windows客户端自动化测试

    一.WinAppDriver简介: 参见:https://github.com/microsoft/WinAppDriver Windows Application Driver(WinAppDriv ...

  9. 经典卷积神经网络——AlexNet

    一.网络结构 AlexNet由5层卷积层和3层全连接层组成. 论文中是把网络放在两个GPU上进行,为了方便我们仅考虑一个GPU的情况. 上图中的输入是224×224224×224,不过经过计算(224 ...

  10. Hogan.js的使用

    一个比较简单的前端模板引擎,参考一下两篇文件即可学会:https://www.cnblogs.com/zhangruiqi/p/8547268.htmlhttps://www.imooc.com/ar ...