自己手写的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 {
@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(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 {
@Override
public 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