内容大纲

1、手写模拟SpringBoot启动过程
2、手写模拟SpringBoot条件注解功能
3、手写模拟SpringBoot自动配置功能
4、SpringBoot整合Tomcat底层源码分析

创建springboot模仿工程

首先,SpringBoot是基于的Spring,所以我们要依赖Spring,然后我希望我们模拟出来的 SpringBoot也支持Spring MVC的那一套功能,所以也要依赖Spring MVC,包括Tomcat等,所以在 SpringBoot模块中要添加以下依赖:

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.18</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.3.18</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.3.18</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
    </dependency>

    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
      <version>9.0.60</version>
    </dependency>
  </dependencies>

创建业务工程

依赖springboot模仿工程

<dependency>
  <groupId>org.example</groupId>
  <artifactId>springboot</artifactId>
  <version>1.0-SNAPSHOT</version>
  <exclusions>
    <exclusion>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
    </exclusion>
  </exclusions>
</dependency>

模仿springboot用法

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("test")
    public String test(){ return userService.test(); }
}
@Component
public class UserService {
    public String test(){ return "test";  }
}

模仿SpringBoot并希望MyApplication中的main方法,就直接启动了项 目,并能在浏览器中正常的访问到UserController中的某个方法。

模仿启动类

我们在真正使用SpringBoot时,核心会用到SpringBoot一个类和注解:

  1. @SpringBootApplication,这个注解是加在应用启动类上的,也就是main方法所在的类
  2. SpringApplication,这个类中有个run()方法,用来启动SpringBoot应用的

所以我们也来模拟实现他们:

启动注解@StartSpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration // 配置
@ComponentScan // 扫描
@Import(ZhouyuImportSeclet.class)
public @interface StartSpringBootApplication {
}

StartSpringBootApplication.class来实现启动逻辑

public class StartSpringBootApplication {
    public static void run(Class clazz) {
    }
}

启动类中使用@StartSpringBootApplication和StartSpringBootApplication.class

@StartSpringBootApplication
public class MyApplication { 
    public static void main(String[] args) {
        StartSpringBootApplication.run(MyApplication.class);
    }
}

run方法中需要实现什么具体的逻辑呢?
分析一次请求过程:

  1. 首先,我们希望run方法一旦执行完,我们就能在浏览器中访问到UserController,那势必在run方法 中要启动Tomcat,通过Tomcat就能接收到请求了
  2. 在SpringMVC中有一个Servlet非常核心,那就是 DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器
  3. DispatcherServlet接收 到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法

在run方法逻辑:

  1. 创建一个AnnotationConfigWebApplicationContext容器(Spring容器 )
  2. 解析主启动类(@StartSpringBootApplication),然后进行扫描
  3. 通过getWebServer方法从Spring容器中获取WebServer类型的Bean
  4. 调用WebServer对象的start方法,启动web容器
  5. 创建Tomcat对象
  6. 生成DispatcherServlet对象,并且和前面创建出来的Spring容器进行绑定
  7. 将DispatcherServlet添加到Tomcat中
  8. 启动Tomcat
public class StartSpringBootApplication {

    public static void run(Class clazz) {
        // Spring容器
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(clazz);
        applicationContext.refresh();
        // 获取web容器
        WebServer webServer = getWebServer(applicationContext);
        webServer.start(); // 启动web容器
    }

    public static WebServer getWebServer(WebApplicationContext applicationContext) {
        // 从spring容器中获取WebServer
        Map<String, WebServer> beansOfType = applicationContext.getBeansOfType(WebServer.class);

        if (beansOfType.isEmpty()) {
            throw new NullPointerException();
        }

        if (beansOfType.size() > 1) {
            throw new IllegalStateException();
        }
        return beansOfType.values().stream().findFirst().get();
    }
}

Tomcat容器

public class TomcatWebServer implements WebServer {
    private final int port = 8081;
    private final String host = "localhost";
    private final String contextPath = "";

    @Override
    public void start(WebApplicationContext applicationContext) {
        Tomcat tomcat = new Tomcat();

        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");

        Connector connector = new Connector();
        connector.setPort(port);

        Engine engine = new StandardEngine();
        engine.setDefaultHost(host);

        Host standardHost = new StandardHost();
        standardHost.setName(host);

        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());

        standardHost.addChild(context);
        engine.addChild(standardHost);

        service.setContainer(engine);
        service.addConnector(connector);
        // DispatcherServlet对象和一个Spring容器进行绑定
        DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext);
        tomcat.addServlet(contextPath, "dispatcher", dispatcherServlet);
        context.addServletMappingDecoded("/*", "dispatcher");
        try {
            tomcat.start();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }
}

实现Tomcat和Jetty的切换

  1. 如果项目中有Tomcat的依赖,那就启动Tomcat
  2. 如果项目中有Jetty的依赖就启动Jetty
  3. 如果两者都没有则报错
  4. 如果两者都有也报错

这个逻辑希望SpringBoot自动帮我实现,对于程序员用户而言,只要在Pom文件中添加相关依赖就可 以了,想用Tomcat就加Tomcat依赖,想用Jetty就加Jetty依赖
那SpringBoot该如何实现呢?
不管是Tomcat还是Jetty,它们都是应用服务器,或者是Servlet容器,所以我们可以定义
接口来表示它们,这个接口叫做WebServer(SpringBoot源码)

web服务接口:

public interface WebServer {
    void start(WebApplicationContext applicationContext);
}

Tomcat

public class TomcatWebServer implements WebServer {
    @Override
    public void start(WebApplicationContext applicationContext) {
    }
}

Jetty

public class JettyWebServer implements WebServer{
    @Override
    public void start(WebApplicationContext applicationContext) {
        System.out.println("启动Jetty");
    }
}

WebServer注入Spring容器

根据pom依赖注入不同的WebServer

模拟实现条件注解

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ClassCondition.class) // 条件注解
public @interface ConditionalOnClass {
    String value();
}
public class ClassCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
        String className = (String) annotationAttributes.get("value");
        try {
            context.getClassLoader().loadClass(className);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }

    }
}

具体逻辑为,拿到@ConditionalOnClass中的value属性,然后用类加载器进行加载,如果加 载到了所指定的这个类,那就表示符合条件,如果加载不到,则表示不符合条件

模拟实现自动配置类

package com.framework.springboot
@Configuration
public class WebServerAutoConfiguration implements AutoConfiguration {

    // 只有存在"org.apache.catalina.startup.Tomcat"类,那么才有TomcatWebServer这个Bean
    @Bean
    @ConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer(){
        return new TomcatWebServer();
    }

    // 只有存在"org.eclipse.jetty.server.Server"类,那么才有TomcatWebServer这个Bean
    @Bean
    @ConditionalOnClass("org.eclipse.jetty.server.Server")
    public JettyWebServer jettyWebServer(){
        return new JettyWebServer();
    }
}

获取WebServer,StartSpringApplication.getWebServer

public static WebServer getWebServer(WebApplicationContext applicationContext) {
    Map<String, WebServer> beansOfType = applicationContext.getBeansOfType(WebServer.class);
    if (beansOfType.isEmpty()) { throw new NullPointerException(); }
    if (beansOfType.size() > 1) {  throw new IllegalStateException();  }
    return beansOfType.values().stream().findFirst().get();
}

WebServiceAutoConfiguration解析

Spring要能解析到WebServiceAutoConfiguration这 个自动配置类?在SpringBoot在run方法中,能找到WebServiceAutoConfiguration这个配置类并添加到Spring容器中

MyApplication启动类是Spring的一个配置类,但是MyApplication是我们传递给SpringBoot,从而添加到 Spring容器中去的,而WebServiceAutoConfiguration就需要SpringBoot去自动发现,而不需要程 序员做任何配置才能把它添加到Spring容器中去,那SpringBoot中是如何实现的呢?SPI
注意:WebServiceAutoConfiguration包路径为"com.framework.springboot",只能扫描到主配置类MyApplication下的包

发现自动配置类

SpringBoot中自己实现了一套SPI机制(spring.factories文件),这儿不搞复杂了,直接用JDK自带的SPI机制

要在springboot项目中的resources目录下添加如下目录(META-INF/services)和文件:
image.png
文件内容:com.framework.springboot.WebServerAutoConfiguration

package com.framework.springboot;
public interface AutoConfiguration {}

相当于通过com.framework.springboot.AutoConfiguration文件配置了 springboot中所提供的配置类

加载自动配置类

利用spring中的@Import技术来导入这些配置类,我们在 @StartSpringBootApplication的定义上增加如下代码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(JdkSpiImportSeclet.class) // 导入配置类
public @interface ZhouyuSpringBootApplication {
}

通过jdk的spi机制加载AutoConfiguration配置类到spring容器中

public class JdkSpiImportSeclet implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 自动配置
        ServiceLoader<AutoConfiguration> loader = ServiceLoader.load(AutoConfiguration.class);
        List<String> list = new ArrayList<>();
        for (AutoConfiguration configuration : loader) {
            list.add(configuration.getClass().getName());
        }
        return list.toArray(new String[0]);
    }
}

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐