1、SpringBoot依赖配置
SpringBoot是基于Spring框架开发的,所以模拟写SpringBoot需导入以下Spring、SpringMvc相关的包;
SpringBoot默认启动服务是tomcat,所以也需要导入tomcat 相关的jar包;
具体导入包如下:
<dependencies>
<!-- 依赖Spring、SpringMvc、Tomcat包 -->
<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>
2、模拟手写自动配置
2.1 模拟写条件注解
当条件注解匹配为true时,被标注的bean才会生效注入到容器当中,模拟某个类是否存在为例,对应的注解如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional({MyOnClassCondition.class})
public @interface MyConditionalOnClass {
String value();
}
当某个类标准了该注解,Spring解析该类时会走MyOnClassCondition中matches方法,进行匹配判断,对应的注解匹配解析逻辑如下:
public class MyOnClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
/**
* 装载类是否存在 存在则返回true
*/
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(MyConditionalOnClass.class.getName());
String value = (String) annotationAttributes.get("value");
try {
context.getClassLoader().loadClass(value);
return true;
} catch (Throwable throwable) {
return false;
}
}
}
2.2 模拟写自动配置类
自动配置是基于条件注解实现自动配置的,这里模拟写tomcat与jetty根据导入不同的包实现容器的切换,根据环境中存在某个class文件实行注入不通的容器,
实现方法如下:
@Configuration
public class WebServerAutoConfiguration implements AutoConfiguration {
@Bean
@MyConditionalOnClass("org.apache.catalina.startup.Tomcat")
public TomcatWebServer tomcatWebServer() {
return new TomcatWebServer();
}
@Bean
@MyConditionalOnClass("org.eclipse.jetty.server.Server")
public JettyWebServer jettyWebServer() {
return new JettyWebServer();
}
}
实现AutoConfiguration接口是为了模拟SpringBoot扫描自动配置类,使用Java的SPL机制进行读取
2.3 模拟发现自动配置类
SpringBoot是通过@Import将自动配置类导入,其中Spring是通过实现DeferredImportSelector接口,完成自动配置bean的导入;
其中DeferredImportSelector与ImportSelector的区别在与实现DeferredImportSelector会在自身的bean注入完成后才注入自配置的bean;
SpringBoot是通过读取spring.factories文件查找需要导入的配置类,这里模拟使用Java的SPL机制进行读取,实现如下:
/**
* DeferredImportSelector 与 ImportSelector的区别:
* DeferredImportSelector等用户的bean注入后进行注入,当自动配置类使用ConditionalOnMissingBean注解,用户配置了对应的bean可使
* SpringBoot自动配置类不生效
*/
public class MyImportSelect implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
/**
* 使用JAVA的SPI机制模拟SpringBoot的spring.factories机制
*/
ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class);
List<String> list = new ArrayList<>();
for (AutoConfiguration autoConfiguration : serviceLoader) {
list.add(autoConfiguration.getClass().getName());
}
return list.toArray(new String[0]);
}
}
3、模拟@SpringBootApplication注解
@SpringBootApplication注解主要是完成自定义的bean的扫描以及SpringBoot自动配置类的导入,实现如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(MyImportSelect.class)
public @interface MySpringBootApplication {
}
4、模拟SpringBoot实现启动
SpringBoot主程序启动,主要实现容器创建、主启动类装载使@SpringBootApplication生效、启动web容器,模拟如下:
public class MySpringApplication {
/**
*
* @param clazz
*/
public static void run(Class clazz) {
/**
* 1、创建一个Spring Web容器
*/
AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
/**
* 2、装载主启动类,使启动类上的注解生效
*/
webApplicationContext.register(clazz);
webApplicationContext.refresh();
/**
* 3、启动容器
*/
startWebServer(webApplicationContext);
}
/**
* 启动web容器
* @param webApplicationContext
*/
private static void startWebServer(AnnotationConfigWebApplicationContext webApplicationContext) {
Map<String, WebServer> webServers = webApplicationContext.getBeansOfType(WebServer.class);
if(webServers.isEmpty()) {
throw new NullPointerException("未注入相关web容器包");
}
if(webServers.size() > 1) {
throw new IllegalArgumentException("存在多个web容器");
}
webServers.values().stream().findFirst().get().start();
}
}
5、tomcat容器启动示例
当导入的事tomcat容器,Spring容器中将能获取到TomcatWebServer,其中实现如下:
/**
* @Author:qww
* @name:TomcatWebServer
* @Date:2023/6/6 14:22
* @Description tomcat容器启动
* ApplicationContextAware,可将IOC容器注入到类
*/
public class TomcatWebServer implements WebServer, ApplicationContextAware {
private WebApplicationContext webApplicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.webApplicationContext = (WebApplicationContext) applicationContext;
}
@Override
public void start() {
Tomcat tomcat = new Tomcat();
Server server = tomcat.getServer();
Service service = server.findService("Tomcat");
Connector connector = new Connector();
connector.setPort(8081);
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(webApplicationContext));
context.addServletMappingDecoded("/*", "dispatcher");
try {
tomcat.start();
System.out.println("Tomcat启动了");
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
6、测试
导入自定义的SpringBoot模块,使用模拟启动SpringBoot相关注解及启动类进行启动即可测试,如下:
<dependency>
<groupId>com.qww</groupId>
<artifactId>my-springboot</artifactId>
<version>1.0</version>
</dependency>
@MySpringBootApplication
public class MainApplication {
public static void main(String[] args) {
MySpringApplication.run(MainApplication.class);
}
}
7、源码地址
https://gitee.com/qianweiwei1994/spring-cloud-study/tree/master/01-write-springboot# 1、SpringBoot依赖配置
SpringBoot是基于Spring框架开发的,所以模拟写SpringBoot需导入以下Spring、SpringMvc相关的包;
SpringBoot默认启动服务是tomcat,所以也需要导入tomcat 相关的jar包;
具体导入包如下:
<dependencies>
<!-- 依赖Spring、SpringMvc、Tomcat包 -->
<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>
2、模拟手写自动配置
2.1 模拟写条件注解
当条件注解匹配为true时,被标注的bean才会生效注入到容器当中,模拟某个类是否存在为例,对应的注解如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional({MyOnClassCondition.class})
public @interface MyConditionalOnClass {
String value();
}
当某个类标准了该注解,Spring解析该类时会走MyOnClassCondition中matches方法,进行匹配判断,对应的注解匹配解析逻辑如下:
public class MyOnClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
/**
* 装载类是否存在 存在则返回true
*/
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(MyConditionalOnClass.class.getName());
String value = (String) annotationAttributes.get("value");
try {
context.getClassLoader().loadClass(value);
return true;
} catch (Throwable throwable) {
return false;
}
}
}
2.2 模拟写自动配置类
自动配置是基于条件注解实现自动配置的,这里模拟写tomcat与jetty根据导入不同的包实现容器的切换,根据环境中存在某个class文件实行注入不通的容器,
实现方法如下:
@Configuration
public class WebServerAutoConfiguration implements AutoConfiguration {
@Bean
@MyConditionalOnClass("org.apache.catalina.startup.Tomcat")
public TomcatWebServer tomcatWebServer() {
return new TomcatWebServer();
}
@Bean
@MyConditionalOnClass("org.eclipse.jetty.server.Server")
public JettyWebServer jettyWebServer() {
return new JettyWebServer();
}
}
实现AutoConfiguration接口是为了模拟SpringBoot扫描自动配置类,使用Java的SPL机制进行读取
2.3 模拟发现自动配置类
SpringBoot是通过@Import将自动配置类导入,其中Spring是通过实现DeferredImportSelector接口,完成自动配置bean的导入;
其中DeferredImportSelector与ImportSelector的区别在与实现DeferredImportSelector会在自身的bean注入完成后才注入自配置的bean;
SpringBoot是通过读取spring.factories文件查找需要导入的配置类,这里模拟使用Java的SPL机制进行读取,实现如下:
/**
* DeferredImportSelector 与 ImportSelector的区别:
* DeferredImportSelector等用户的bean注入后进行注入,当自动配置类使用ConditionalOnMissingBean注解,用户配置了对应的bean可使
* SpringBoot自动配置类不生效
*/
public class MyImportSelect implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
/**
* 使用JAVA的SPI机制模拟SpringBoot的spring.factories机制
*/
ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class);
List<String> list = new ArrayList<>();
for (AutoConfiguration autoConfiguration : serviceLoader) {
list.add(autoConfiguration.getClass().getName());
}
return list.toArray(new String[0]);
}
}
3、模拟@SpringBootApplication注解
@SpringBootApplication注解主要是完成自定义的bean的扫描以及SpringBoot自动配置类的导入,实现如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(MyImportSelect.class)
public @interface MySpringBootApplication {
}
4、模拟SpringBoot实现启动
SpringBoot主程序启动,主要实现容器创建、主启动类装载使@SpringBootApplication生效、启动web容器,模拟如下:
public class MySpringApplication {
/**
*
* @param clazz
*/
public static void run(Class clazz) {
/**
* 1、创建一个Spring Web容器
*/
AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
/**
* 2、装载主启动类,使启动类上的注解生效
*/
webApplicationContext.register(clazz);
webApplicationContext.refresh();
/**
* 3、启动容器
*/
startWebServer(webApplicationContext);
}
/**
* 启动web容器
* @param webApplicationContext
*/
private static void startWebServer(AnnotationConfigWebApplicationContext webApplicationContext) {
Map<String, WebServer> webServers = webApplicationContext.getBeansOfType(WebServer.class);
if(webServers.isEmpty()) {
throw new NullPointerException("未注入相关web容器包");
}
if(webServers.size() > 1) {
throw new IllegalArgumentException("存在多个web容器");
}
webServers.values().stream().findFirst().get().start();
}
}
5、tomcat容器启动示例
当导入的事tomcat容器,Spring容器中将能获取到TomcatWebServer,其中实现如下:
/**
* @Author:qww
* @name:TomcatWebServer
* @Date:2023/6/6 14:22
* @Description tomcat容器启动
* ApplicationContextAware,可将IOC容器注入到类
*/
public class TomcatWebServer implements WebServer, ApplicationContextAware {
private WebApplicationContext webApplicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.webApplicationContext = (WebApplicationContext) applicationContext;
}
@Override
public void start() {
Tomcat tomcat = new Tomcat();
Server server = tomcat.getServer();
Service service = server.findService("Tomcat");
Connector connector = new Connector();
connector.setPort(8081);
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(webApplicationContext));
context.addServletMappingDecoded("/*", "dispatcher");
try {
tomcat.start();
System.out.println("Tomcat启动了");
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
6、测试
导入自定义的SpringBoot模块,使用模拟启动SpringBoot相关注解及启动类进行启动即可测试,如下:
<dependency>
<groupId>com.qww</groupId>
<artifactId>my-springboot</artifactId>
<version>1.0</version>
</dependency>
@MySpringBootApplication
public class MainApplication {
public static void main(String[] args) {
MySpringApplication.run(MainApplication.class);
}
}
7、源码地址
https://gitee.com/qianweiwei1994/spring-cloud-study/tree/master/01-write-springboot