SpringBoot-核心流程模拟实现


核心流程

模拟springboot注意事项(启动流程,自动装配,依赖控制):

  1. springboot选择一个webserver启动(只能有一个),run启动spring容器,将app作为配置类,随后启动webserver
  2. 根据依赖选择哪个bean生效(当存在哪些类或Bean且不存在哪些类或Bean时生效)
  3. 根据spi自动装配(DeferredImportSelector- 用户自定义bean优先)
  4. 最终项目通过自定义依赖来保证只有一个tomcat容器功能(核心项目实现其他容器功能时需要全部依赖,但是不将这些依赖打入自己的jar包)
  5. 使用ASM绕过类加载器读取class文件注解,避免没有相关依赖时运行报错(class文件不会在编译期报错)

第四条和第五条由于过于复杂,并未实现,这里只是简单说明

项目结构
    boot-core 模拟springboot
    boot-bean 模拟自定义项目的自动装配,需要boot-core依赖
    boot-app  模拟引用boot-core和boot-bean创建app

boot 默认启动 Tomcat(boot-core)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(AutoImport.class)
public @interface SpringBootApp {
}
public class SpringApp {
    public static void run(Class<?> app, String[] args) {
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(app);
        applicationContext.refresh();

        WebServer webServer = getWebServer(applicationContext);
        webServer.start();
    }

    @NonNull
    private static WebServer getWebServer(@NonNull AnnotationConfigWebApplicationContext applicationContext) {
        Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);

        if (webServers.isEmpty()) {
            throw new NullPointerException("请引入WebServer的依赖,如Tomcat");
        } else if (webServers.size() > 1) {
            throw new IllegalArgumentException("只能保留一个WebServer");
        }

        return webServers.values().stream().findFirst().get();
    }
}
@Configuration
@PropertySource("classpath:application.properties")
public class WebServerAutoConfig implements ApplicationContextAware, AutoConfig {
    private WebApplicationContext applicationContext;

    @Value("${server.port}")
    private String port;
     
    @Bean
    @ConditionalOnClass(Tomcat.class)
    @ConditionalOnMissingBean(WebServer.class)
    public TomcatWebServer tomcatWebServer() {
        Tomcat tomcat = new Tomcat();
        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");
        Connector connector = new Connector();
        connector.setPort(Integer.parseInt(port));
        Engine engine = new StandardEngine();
        engine.setDefaultHost("localhost");
        Host host = new StandardHost();
        host.setName("localhost");
        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());
        host.addChild(context);
        engine.addChild(host);
        service.setContainer(engine);
        service.addConnector(connector);
        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
        context.addServletMappingDecoded("/*", "dispatcher");
        return new TomcatWebServer(tomcat);
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = (WebApplicationContext) applicationContext;
    }

}
  1. 使用 ConditionalOnClass 保证只有引入 Tomcat 依赖时这个 Bean 才会生效
  2. 使用 ConditionalOnMissingBean 保证只在没有 WebServer 时这个 Bean 才会生效,保证只有一个 WebServer 且用户自定义的优先
  3. 此处可以加入多个 WebServer 的实现,如 Jetty, 客户端控制引入的依赖决定使用哪个 WebServer
  4. 打包时分开打包,不将这些依赖打入自己的 jar 包,最终 spring-boot-starter-web 再引入默认的依赖,比如 Tomcat
  5. 此处并不会因为依赖类的缺失而报错,因为使用了 ASM 技术绕过了类加载器进行 class 文件注解的读取
  6. 但最终 ConditionalOnClass 验证是否存在这个类时,还是使用了类加载器是否报错来确定的

使用SPI支持自动配置(boot-core)

public interface AutoConfig {
}

配置SPI文件,对应的类必须实现了 AutoConfig 接口
META-INF/services/org.jxch.study.studyspringcloud.boot.core.AutoConfig

org.jxch.study.studyspringcloud.boot.core.webserver.WebServerAutoConfig

使用DeferredImportSelector保证先扫描用户自定义的Bean

public class AutoImport implements DeferredImportSelector {
    @NonNull
    @Override
    public String[] selectImports(@NonNull AnnotationMetadata importingClassMetadata) {
        return StreamSupport.stream(ServiceLoader.load(AutoConfig.class).spliterator(), false)
                .map(autoConfig -> autoConfig.getClass().getName())
                .toArray(String[]::new);
    }
}

SPI自定义项目的自动装配(boot-bean)

public record BeanTest(String name) {
}

使用 ConditionalOnMissingBean 保证只在没有 BeanTest 时这个 Bean 才会生效,即用户自定义的 Bean 优先配置

@Configuration
public class BeanAutoConfig implements AutoConfig {
    @Bean
    @ConditionalOnMissingBean(BeanTest.class)
    public BeanTest beanTest() {
        return new BeanTest("test");
    }
}

配置SPI文件
META-INF/services/org.jxch.study.studyspringcloud.boot.core.AutoConfig

org.jxch.study.studyspringcloud.boot.bean.BeanAutoConfig

测试(boot-app)

server.port=8088
@SpringBootApp
public class BootApp {
    public static void main(String[] args) {
        SpringApp.run(BootApp.class, args);
    }
}
@RestController
public class AppController {
    @Autowired
    private BeanTest beanTest;

    @RequestMapping(value = "/test", method = {RequestMethod.GET, RequestMethod.POST})
    public ResponseEntity<String> test() {
        return ResponseEntity.ok(beanTest.name());
    }
}
@Configuration
public class AppConfig {
    // 验证用户自定义的 Bean 优先使用
    @Bean
    public BeanTest beanTest() {
        return new BeanTest("app");
    }
}

依赖(boot-core)

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.9</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>6.0.9</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>6.0.9</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>11.0.0-M7</version>
    </dependency>
</dependencies>

文章作者: 钱不寒
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 钱不寒 !
  目录