Spring Boot核心流程
项目组成
首先有两个mavne项目,lyra-spring-boot用于模拟spring boot的实现, say-hello项目则是我们的具体业务使用
注解
- SpringBootApplication: 在启动类上,初始化要加载的容器的注解。
- SpringApplication.run(): 初始化Spring容器。
我们要实现以下的逻辑:
- 初始化Spring容器。
- 启动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文件中添加包路径。
@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中定义的类,如下所示:
DeferredImportSelector所有配置类解析完才会执行selectImports方法,这个用于开发者自定义类扫描使用,
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2022-05-28 简单实现Spring依赖注入以及控制反转