自己手写的SpringBoot启动器, 是一个学习了解SpringBoot启动逻辑和了解springboot原理的不错的实践Demo. 废话不多说,直接上代码:
项目结构
maven多项目结构,
myspringboot 自己手写的SpringBoot启动器
service-demo 用来测试SpringBoot启动器的示例项目
项目pom依赖
1. 主项目Maven Pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>cn.tekin</groupId><artifactId>myspringboot-app</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>myspringboot</artifactId><packaging>jar</packaging><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring-version>5.3.29</spring-version><tomcat-version>9.0.80</tomcat-version></properties><dependencies><!-- https://mavenlibs.com/maven/dependency/org.springframework/spring-web --><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>${spring-version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring-version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring-version}</version></dependency><!-- https://mavenlibs.com/maven/dependency/org.apache.tomcat.embed/tomcat-embed-core --><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId><version>${tomcat-version}</version></dependency></dependencies><build><plugins><!-- 这个插件用于生成可执行jar文件或者war文档 --><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
2. 测试项目Maven Pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>cn.tekin</groupId><artifactId>myspringboot-app</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>service-demo</artifactId><packaging>jar</packaging><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>cn.tekin</groupId><artifactId>myspringboot</artifactId><version>1.0-SNAPSHOT</version></dependency><!-- https://mavenlibs.com/maven/dependency/org.redisson/redisson --><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.23.3</version></dependency><dependency><groupId>de.ruedigermoeller</groupId><artifactId>fst</artifactId><version>2.57</version></dependency></dependencies>
</project>
手写SpringBoot启动器项目核心代码
自定义的SpringBootApplication启动类
package cn.tekin;import cn.tekin.webserver.MyWebServer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;import java.util.Map;/*** 自定义的SpringBootApplication启动类*/
public class MySpringbootApplication {public static void run(Class clazz, String[] args) {// 创建一个Spring容器AnnotationConfigWebApplicationContext applicationContext= new AnnotationConfigWebApplicationContext();// 注册启动类applicationContext.register(clazz);// 刷新容器applicationContext.refresh();// 从spring容器中获取WebServer bean 这样就可以解耦 避免if else判断要使用那个WebServer了MyWebServer webserver = getWebServer(applicationContext);webserver.start(applicationContext);}/*** 这里通过使用上下文中的 getBeansOfType 方法,通过将接口类来获取容器中的所有实现类,从而达到解耦的目的.* @param applicationContext* @return*/private static MyWebServer getWebServer(WebApplicationContext applicationContext) {// 从spring容器中获取WebServer Bean对象Map<String, MyWebServer> webservers = applicationContext.getBeansOfType(MyWebServer.class);if (webservers.isEmpty()) {throw new NullPointerException();}// 返回map中的第一个对象; 如果有多个Bean, 这里只返回第一个return webservers.values().stream().findFirst().get();}}
自定义的SpringBoot注解 @MySpringbootApp
package cn.tekin.anno;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 自定义的Spingboot注解** 注意:这里@ComponentScan的没有指定扫描的基础包, spring默认会扫描run参数中传递的类所在的包下面的所有类*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan(basePackages = {"cn.tekin.config","ws.yunnan.demo"})
public @interface MySpringbootApp {}
自定义的webserver服务自动配置类 MyWebServerAutoConfiguration
package cn.tekin.config;import cn.tekin.condition.MyJettyCondition;
import cn.tekin.webserver.MyJettyWebServer;
import cn.tekin.condition.MyTomcatCondition;
import cn.tekin.webserver.MyTomcatWebServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyWebServerAutoConfiguration implements MyAutoConfiguration {@Bean@Conditional(MyTomcatCondition.class)public MyTomcatWebServer tomcatWebServer() {return new MyTomcatWebServer();}@Bean@Conditional(MyJettyCondition.class)public MyJettyWebServer jettyWebServer() {return new MyJettyWebServer();}}
自定义的Tomcat服务启动类 MyTomcatWebServer
package cn.tekin.webserver;import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;public class MyTomcatWebServer implements MyWebServer {@Overridepublic void start(WebApplicationContext applicationContext) {Tomcat tomcat = new Tomcat();Server server = tomcat.getServer();Service service = server.findService("Tomcat");Connector connector = new Connector();connector.setPort(8089);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");try {tomcat.start();} catch (LifecycleException e) {e.printStackTrace();}System.out.println("Tomcat Started");}
}
@Conditional条件注解 条件判断类 MyTomcatCondition
package cn.tekin.condition;import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;public class MyTomcatCondition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {try {// 通过从上下文中 加载Tomcat的核心类来判断pom中是否添加了Tomcat依赖conditionContext.getClassLoader().loadClass("org.apache.catalina.startup.Tomcat");return true;} catch (ClassNotFoundException e) {return false;}}
}
service-demo MySpringBootApp服务测试代码
package ws.yunnan.demo;import cn.tekin.MySpringbootApplication;
import cn.tekin.anno.MySpringbootApp;@MySpringbootApp
public class MyApplication {public static void main(String[] args) {// 这里的MyApplication.class 就是传入Spring容器的配置类, 一般是使用的当前的类, 也可以是其他的类MySpringbootApplication.run(MyApplication.class, args);}}
完整项目代码见
Gitee码云
https://gitee.com/tekintian/myspringboot-app
Github
https://github.com/tekintian/myspringboot-app