Spring Boot核心流程

项目组成

首先有两个mavne项目,lyra-spring-boot用于模拟spring boot的实现, say-hello项目则是我们的具体业务使用
image

注解

  • SpringBootApplication: 在启动类上,初始化要加载的容器的注解。
  • SpringApplication.run(): 初始化Spring容器。

我们要实现以下的逻辑:

  1. 初始化Spring容器。
  2. 启动tomcat。

创建Spring容器

run方法会解析class上的注解包含componentScan注解来将bean注入到容器中。
annotationConfigApplicationContext.register(c);这个语句就是做这件事的。

public class SpringBoot {
    public static void run(Class c) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext =
                new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(c);
        annotationConfigApplicationContext.refresh();
    }

}

启动tomcat

    public static void run(Class c) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext =
                new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(c);
        annotationConfigApplicationContext.refresh();

        // 创建web容器并启动
        WebServer webServer = new WebServer();
        webServer.start();


//        DeferredImportSelector bean = annotationConfigApplicationContext.getBean(DeferredImportSelector.class);
    }

实现tomcat与jetty的切换

上面那中方法只使用了tomcat,如果想要切换jetty的话,因为代码已经写死了。改起来会很麻烦。
我们可以创建一个接口,通过接口实现不同的类,根据类的不同启动不同的容器,比如有一个WebServer的接口

public interface WebServer {
    public void start();
}

tomcat:

public class TomcatServer implements WebServer {
    @Override
    public void start() {
        System.out.println("run tomcat");
    }
}

jetty

public class JettyServer implements WebServer {

    @Override
    public void start() {
        System.out.println("jetty run");
    }
}

在run方法中根据接口获取容器,然后启动即可

    public static void run(Class c) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext =
                new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(c);
        annotationConfigApplicationContext.refresh();

        // 创建web容器并启动
        WebServer webServer = getWebServer(annotationConfigApplicationContext);
        webServer.start();

//        DeferredImportSelector bean = annotationConfigApplicationContext.getBean(DeferredImportSelector.class);
    }
   private static WebServer getWebServer(AnnotationConfigApplicationContext annotationConfigApplicationContext) {
        return annotationConfigApplicationContext.getBean(WebServer.class);
    }

但是这样做还是不太行,我们需要根据导入的依赖来加载不同bean

模拟条件注解

在@Conditional(WebCondition.class)注解中依赖了一个类,在类中定义了条件注解的规则。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Conditional(WebCondition.class)
public @interface ConditionalOnClass {
    String value();
}

在类中调用条件定义的条件注解即可,条件注解会将value传入到@Conditional(WebCondition.class)的WebCondion注解中。

@Configuration
public class WenServerAutoConfiguration {
    @Bean
    @ConditionalOnClass(value = "org.apache.catalina.startup.Tomcat")
    public WebServer tomcatServer() {
        return new TomcatServer();
    }

    @Bean
    public WebServer jettyServer() {
        return new JettyServer();
    }
}

类实现了Condition接口。判断穿过来的value是否能被类加载器加载到,如果加载到则返回true,可以注入到spring容器中,否则返回false。

public class WebCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> conditionalOnClass = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());

        ClassLoader classLoader = context.getClassLoader();
        try {
            return classLoader.loadClass(conditionalOnClass.get("value").toString()) != null;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}

但是这样做还没完,我们需要将WenServerAutoConfiguration注入到Spring容器中,由于CompnentScan扫描不到我们定义的Spring boot的configuration,所以需要一个注解了完成这个操作:
@Import(value = WebServerAutoConfiguration.class)

但是单独一个一个使用Import注解导入太麻烦了,Spring Boot有几十个依赖需要导入,所以可以利用Spring的SPI技术来动态将包扫描。
在Resouce目录中定义创建META-INF/services/com.lyra.spring.boot.AutoConfigSPI文件,在文件中定义要扫描的Class的包路径,这里定义了com.lyra.spring.boot.WenServerAutoConfiguration
在Java中定义com.lyra.spring.boot.AutoConfigSPI接口,接口是空接口,什么东西都没有

package com.lyra.spring.boot;

public interface AutoConfigSPI {
}

想要使用SPI功能的需要在实现AutoConfigSPI接口并在META-INF/services/com.lyra.spring.boot.AutoConfigSPI文件中添加包路径。
image

@Configuration
public class WenServerAutoConfiguration implements AutoConfigSPI {
    @Bean
    @ConditionalOnClass(value = "org.apache.catalina.startup.Tomcat")
    public WebServer tomcatServer() {
        return new TomcatServer();
    }

    @Bean
    public WebServer jettyServer() {
        return new JettyServer();
    }
}

之后实现DeferredImportSelector,这个类可以批量@Import类,使用ServiceLoader.load方法可以获取到SPI中定义的SPI实现类,最后进行返回。

public class SelectImport implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        ServiceLoader<AutoConfigSPI> load = ServiceLoader.load(AutoConfigSPI.class);

        List<String> list = new ArrayList<>();

        for (AutoConfigSPI autoConfigSPI : load) {
            list.add(autoConfigSPI.getClass().getName());
        }

        return list.toArray(String[]::new);
    }
}

ServiceLoader.load(AutoConfigSPI.class);会返回实现AutoConfighSPI接口并在Resouce中定义的类,如下所示:
image

DeferredImportSelector所有配置类解析完才会执行selectImports方法,这个用于开发者自定义类扫描使用,

posted @   RainbowMagic  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2022-05-28 简单实现Spring依赖注入以及控制反转
点击右上角即可分享
微信分享提示