基于spring-context实现SpringBoot
MyApplication启动类是Spring的一个配置类,但是MyApplication是我们传递给SpringBoot,从而添加到 Spring容器中去的,而WebServiceAutoConfiguration就需要SpringBoot去自动发现,而不需要程 序员做任何配置才能把它添加到Spring容器中去,那SpringBoot中是如何实现的呢?SpringBoot中自己实现了一套SPI机
内容大纲
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一个类和注解:
- @SpringBootApplication,这个注解是加在应用启动类上的,也就是main方法所在的类
- 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方法中需要实现什么具体的逻辑呢?
分析一次请求过程:
- 首先,我们希望run方法一旦执行完,我们就能在浏览器中访问到UserController,那势必在run方法 中要启动Tomcat,通过Tomcat就能接收到请求了
- 在SpringMVC中有一个Servlet非常核心,那就是 DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器
- DispatcherServlet接收 到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法
在run方法逻辑:
- 创建一个AnnotationConfigWebApplicationContext容器(Spring容器 )
- 解析主启动类(@StartSpringBootApplication),然后进行扫描
- 通过getWebServer方法从Spring容器中获取WebServer类型的Bean
- 调用WebServer对象的start方法,启动web容器
- 创建Tomcat对象
- 生成DispatcherServlet对象,并且和前面创建出来的Spring容器进行绑定
- 将DispatcherServlet添加到Tomcat中
- 启动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的切换
- 如果项目中有Tomcat的依赖,那就启动Tomcat
- 如果项目中有Jetty的依赖就启动Jetty
- 如果两者都没有则报错
- 如果两者都有也报错
这个逻辑希望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)和文件:
文件内容: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]);
}
}
更多推荐
所有评论(0)