SpringBoot源码(自动装配、内嵌Tomcat)

时间:2024-06-07 19:00:24

文章目录

  • 依赖管理
      • pom依赖管理
      • Web依赖
      • 自定义starter
  • 一、WebMvcAutoConfiguration
    • 1.1 Filter
    • 1.2 Interceptor
  • 二、源码解析
    • 2.1 SpringApplication
      • 2.1.1 构造方法
        • 1、填充webApplicationType
        • 2、自动装配Initializers
        • 3、自动装配Listeners
      • 2.1.2 run(args)
    • 2.2 SpringApplicationRunListeners
    • 2.3 prepareEnvironment
      • 加载propertySources
      • 填充activeProfiles属性
      • 读取properties、xml、yaml后缀文件
    • 2.4 createApplicationContext
        • 创建DefaultListableBeanFactory
        • AnnotationBeanDefinitionReader
        • ClassPathBeanDefinitionScanner
    • 2.5 prepareContext
        • applyInitializers
        • 为beanFactory设置属性
        • 创建启动类beanDefinition
        • listeners.contextLoaded
    • 2.6 refreshContext
      • 自动装配
          • getAutoConfigurationMetadata
          • 去重 + 排除 + @conditional
            • 1、去重-getExclusions
            • 2、排除-getExclusions
            • 3、@conditional-filter
          • 自定义自动装配类
    • 2.7 tomcat组件
      • 2.7.1定义
      • 2.7.2 tomcat组件
        • Server
        • Service
        • Connector
          • 3.1 Endpoint
          • 3.2 Processor
          • 3.3 Adapter
        • Container
          • **Engine**
          • **Host 虚拟主机**
          • **Context上下文**
          • **Wrapper包装器**
      • 2.7.3 组件Lifecycle
    • 2.8 内嵌tomcat
      • 2.8.1 getWebServerFactory
      • 2.8.2 getWebServer
        • tomcat-所有组件init
        • tomcat-engine-启动
    • 2.9 finishRefresh
      • 2.9.1 startWebServer-NioEndpoint启动
        • 4、Thread.run:Acceptor监听
        • 5.处理请求:Handler处理请求
        • 6、processor.process
        • 7、CoyoteAdapter.service
        • 8、Container-Engineer
        • 9、Container-Host
        • 10、Container-Context
        • 11、Container-Wrapper
        • 12、后续内容
      • 2.9.2 publishEvent
    • 2.10 stop-tomcat
  • 注解
    • @SpringBootApplication

Springboot中文文档

依赖管理

pom依赖管理

1)在SpringBoot项目中,根pom内容

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.6.RELEASE</version>
</parent>

2)此spring-boot-starter-parent-2.1.6.RELEASE.pom中仍有父pom

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.1.6.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

3)spring-boot-dependencies-2.1.6.RELEASE.pom

<properties>
    <!-- 指定了我们常用中间件的版本号-->	
	<spring.version>5.1.8.RELEASE</spring.version>
	<byte-buddy.version>1.9.13</byte-buddy.version>
	<commons-lang3.version>3.8.1</commons-lang3.version>
    <elasticsearch.version>6.4.3</elasticsearch.version>
    <gson.version>2.8.5</gson.version>
    <jedis.version>2.9.3</jedis.version>
    <kafka.version>2.0.1</kafka.version>
    <lombok.version>1.18.8</lombok.version>
    <mysql.version>8.0.16</mysql.version>
    <netty.version>4.1.36.Final</netty.version>
    <rabbit-amqp-client.version>5.4.3</rabbit-amqp-client.version>
    <spring-amqp.version>2.1.7.RELEASE</spring-amqp.version>
    <spring-kafka.version>2.2.7.RELEASE</spring-kafka.version>
	----
</properties>
 

<!-- SpringBoot支持提供的场景启动器starter-->	
<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot</artifactId>
        <version>2.1.6.RELEASE</version>
      </dependency>
    
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test-autoconfigure</artifactId>
        <version>2.1.6.RELEASE</version>
      </dependency>
    
     <!-- 指定了我们常用中间件的版本号-->	
  </dependencies> 
</dependencyManagement>   

这样我们在项目根pom中引入中间件时,就无需再指定version。当然如果指定version,则以我们指定的version版本为主


Web依赖

1、我们在项目根pom中,引入了web依赖

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
     <version>3.0.6</version>
</dependency>

2、spring-boot-starter-web-3.0.6.pom中

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>3.0.6</version>
      <scope>compile</scope>
    </dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>6.0.8</version>
      <scope>compile</scope>
    </dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>6.0.8</version>
      <scope>compile</scope>
    </dependency>
    
  </dependencies>

又引入了tomcat 和 spring-webmvc

3、依赖传递

  • 因为spring-boot-starter-web-3.0.6.pom中有spring-webmvc的依赖
  • 又因为我们项目根pom中,依赖了spring-boot-starter-web-3.0.6
  • 所以根pom有spring-webmvc的依赖
  • 所以,在我们项目中,就可以使用@Controller注解(org.springframework.web.bind.annotation)是spring-web下的内容

4、其它场景的starter

除了spring-boot-starter-web引入Web,还可以引入其它场景的依赖启动器

地址:SpringBoot提供的常见依赖启动器


自定义starter

1、背景

有些中间件,SpringBoot没有为其提供启动器,如mybatis、Druid。这些中间件自己主动与SpringBoot完成整合,提供了启动器

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>

所以,从名称上看,mybaits不是spring-boot-starter-mybatis,而是mybatis-spring-boot-starter。

说明了mybaits不是SpringBoot本身就提供的启动器

注意:

  • SpringBoot提供的starter,在引入时,可以不指定版本号,因为parsent的parent:spring-boot-dependencies-2.1.6.RELEASE.pom中指定了默认版本号
  • 但是SpringBoot没有提供的starter就必须自己指定版本号

2、整合redis

1)starter

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
     <groupId>org.springframework.data</groupId>
     <artifactId>spring-data-redis</artifactId>
     <version>2.1.9.RELEASE</version>
</dependency>

2)配置redis

  • applicaiton.properties
spring.redis.host=127.0.0.1
spring.redis.port=6379

3)测试

@Resource
private StringRedisTemplate stringRedisTemplate;//或者RedisTemplate
  
@Test
public void t() {
    String name = "mjp";
    Integer age = 18;
    
    String key = stringRedisTemplate.opsForValue().get(name);
    if (StringUtils.isBlank(key)) {
        stringRedisTemplate.opsForValue().set(name, String.valueOf(age));
    }
}
  1. 去redis客户端工具中

redis的安装,参考我另一篇:redis安装

get name

一、WebMvcAutoConfiguration

在SpringBoot中主要作用于Web MVC的自动配置,包括视图解析器、静态资源配置、MVC配置、消息转换器和异常处理等。

1.1 Filter

1、背景

参考我设计模式:设计模式Java实战

2、使用

  • Filter1
public class MyFilter1 implements Filter{
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 先拦截req
		System.out.println("======================filter-req");
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String uri = httpRequest.getRequestURI();
        if (StringUtils.isNoneBlank(uri)) {
            String[] split = uri.split("/");
            if (split.length > 2) {
                String firstParam = split[2]; // 第一个参数
                //return;//终止执行业务逻辑
            }

        }
        chain.doFilter(request, response);//执行目标方法
        // 再拦截resp
        System.out.println("======================filter1-resp");
    }
}
  • Filter2
public class MyFilter2 implements Filter{
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("===========================filter2-req");
        chain.doFilter(request, response);
        System.out.println("===========================filter2-resp");
    }
}
  • 注入Spring
@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean<MyFilter> myFilter1 (){
        FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>(new MyFilter());
        bean.setOrder(2);//指定生效顺序
        bean.setUrlPatterns(Sets.newHashSet("/query/*"));//指定拦截的请求路径
        return bean;
    }

    @Bean
    public FilterRegistrationBean<MyFilter2> myFilter2 (){
        FilterRegistrationBean<MyFilter2> bean = new FilterRegistrationBean<>(new MyFilter2());
        bean.setOrder(1);
        bean.setUrlPatterns(Sets.newHashSet("/query/*"));// 不指定,则默认"/*"
        return bean;
    }
}

3、补充

  • 作用域是Tomcat容器级别的。所以默认对所有的Servlet进行Filter

1.2 Interceptor

1、作用域

MVC框架的一部分

2、自定义拦截器

@Component
public class MyHandlerInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // 为false会拦截请求,true会放行
        // 业务逻辑
        // eg:根据req内容查询,请求是否合法、用户是否存在等。如果不满足,则请求被拦截掉,return false
        System.out.println("=====================MyHandlerInterceptor-req符合,放行");
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                            @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("=====================MyHandlerInterceptor-resp");
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                 @Nullable Exception ex) throws Exception {
        // 一定会执行,类似finally
        System.out.println("finally==============================");
    }
}

3、将自定义拦截器添加到拦截器链

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Resource
    private MyHandlerInterceptor myHandlerInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加自定义拦截器,并指定要拦截的路径
        registry.addInterceptor(myHandlerInterceptor)
//                .addPathPatterns("/**") // 拦截所有路径,或者指定具体的路径
                .addPathPatterns("/query/*") // 拦截所有路径,或者指定具体的路径
                .excludePathPatterns("/login"); // 排除某些路径
    }
}

4、Filter + HandlerInterceptor整体执行流程

=====================filter-req
=====================MyHandlerInterceptor-req符合,放行
=====do业务方法=======
=====================MyHandlerInterceptor-resp
=====================MyHandlerInterceptor-finally
=====================filter-resp

1)http请求 -->> Filter1#doFilter

  • 过滤req1
  • chain.doFilter -->> ApplicationFilterChain#doFilter ,找到下一个Filter2,Filter2#doFilter
  • 过滤req2
  • chain.doFilter -->> ApplicationFilterChain#doFilter,找不到下一个Filter了,执行servlet.service

2)servlet.service

->子关系 以及 各自方法
    
HttpServlet(3.service、4.doGet)
	FrameworkServlet(2.service、5.doGet、6.processRequest 、7.doService)
		DispatcherServlet(1.service 、8.doService、9.doDispatch)

servlet.service-- >> DispatcherServlet#service -->>父类FrameworkServlet#service -->> super.service即HttpServlet#service

  • 获取方法类型GET(或Post) -->>doGet(或doPost) -->> 子类FrameworkServlet重写#doGet

    –>>processRequest -->> doService

    –>> 子类DispatcherServlet#doService -->> doDispatch

3)doDispatch

// Determine handler for the current request.
// 1.找到我们的Controller
mappedHandler = getHandler(processedRequest);

// 2.适配
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// 3.是否前置拦截applyPreHandle,ture则放行,false则拦截。不执行后续的业务方法
// 执行拦截器链的一系列applyPreHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
	return;
}

// 4.执行业务方法,即本次请求的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

// 5.后置方法
mappedHandler.applyPostHandle(processedRequest, response, mv);

二、源码解析

2.1 SpringApplication

SpringApplication.run(ApplicationLoader.class, args);

内部代码等价

SpringApplication springApplication = new SpringApplication(ApplicationLoader.class);
springApplication.run(args);

SpringApplication类定义

执行以下步骤来启动Spring应用程序

  • 创建ApplicationContext-Spring上下文
  • 刷新应用上下文,加载所有单例bean

2.1.1 构造方法

1、填充webApplicationType

属性:REACTIVE、NONE、SERVLET(这里是SERVLET)

2、自动装配Initializers

1)读取spring.factories内容

spring-boot-2.1.6.RELEASE.jar!/META-INF/spring.factories,封装为map

补充:springboot中总共有2个spring.factories文件,一个为上述路径,还有一个路径为:

spring-boot-autoconfigure-2.1.6.RELEASE.jar!\META-INF\spring.factories,这个和自动装配@Import(AutoConfigurationImportSelector.class)注解有关

2)根据key,获取val集合

  • key:ApplicationContextInitializer
  • val:spring.factories中key是ApplicationContextInitializer类对应的val

3)将val集合List<String全类名>实例化

  • 通过ClassForName实例化

4)将上述自动装配map,存cache,便于下一此直接从缓存中读取,避免再次加载


3、自动装配Listeners

过程同2,不过map根据key获取val,此map注解从cache中获取。

key为:ApplicationListener

2.1.2 run(args)

springApplication.run(args);

其中,可以定义args:在IDEA中设置方式如图3所示
在这里插入图片描述

这样在启动时,args就为"–debug"

作用:可以人为设置参数,比如"–debug",可以打印自动装配过程日志信息


2.2 SpringApplicationRunListeners

1、内容

1)从spring-boot-2.1.6.RELEASE.jar!/META-INF/spring.factories中

获取key:SpringApplicationRunListener

对应的val:org.springframework.boot.context.event.EventPublishingRunListener

这里可以直接从cache中读取,无需从spring.factories中加载了

2)创建SpringApplicationRunListeners

将val:EventPublishingRunListener作为属性,通过构造方法,填充给SpringApplicationRunListeners

2、启动监听

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

–>> EventPublishingRunListener#starting

this.initialMulticaster.multicastEvent(
	new ApplicationStartingEvent(this.application, this.args)
);

这里发布的第一个事件:1、ApplicationStartingEvent

Spring观察者模式内容参考我另一篇:Spring源码剖析-Spring观察者模式

  • 创建一个事件:应用程序启动事件

  • 获取多播器initialMulticaster

    • 内含监听器listener
    • 这些监听器就是SpringApplication构造方法中,