核心流程
模拟springboot注意事项(启动流程,自动装配,依赖控制):
- springboot选择一个webserver启动(只能有一个),run启动spring容器,将app作为配置类,随后启动webserver
- 根据依赖选择哪个bean生效(当存在哪些类或Bean且不存在哪些类或Bean时生效)
- 根据spi自动装配(DeferredImportSelector- 用户自定义bean优先)
- 最终项目通过自定义依赖来保证只有一个tomcat容器功能(核心项目实现其他容器功能时需要全部依赖,但是不将这些依赖打入自己的jar包)
- 使用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;
}
}
- 使用
ConditionalOnClass
保证只有引入Tomcat
依赖时这个Bean
才会生效 - 使用
ConditionalOnMissingBean
保证只在没有WebServer
时这个Bean
才会生效,保证只有一个WebServer
且用户自定义的优先 - 此处可以加入多个
WebServer
的实现,如Jetty
, 客户端控制引入的依赖决定使用哪个WebServer
- 打包时分开打包,不将这些依赖打入自己的
jar
包,最终spring-boot-starter-web
再引入默认的依赖,比如Tomcat
- 此处并不会因为依赖类的缺失而报错,因为使用了
ASM
技术绕过了类加载器进行class
文件注解的读取 - 但最终
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>