我們一起聊聊如何手寫一個(gè)SpringBoot框架
你好,我是柳岸花開。
在這篇文章中,我們將手寫模擬SpringBoot的核心流程,讓大家能夠以一種簡單的方式了解SpringBoot的大概工作原理。
項(xiàng)目結(jié)構(gòu)
我們創(chuàng)建一個(gè)工程,包含兩個(gè)模塊:
- springboot模塊,表示SpringBoot框架的源碼實(shí)現(xiàn)。
- user包,表示用戶業(yè)務(wù)系統(tǒng),用來寫業(yè)務(wù)代碼來測試我們所模擬出來的SpringBoot。
首先,在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>
在user模塊下,我們進(jìn)行正常的開發(fā),比如先添加SpringBoot依賴,然后定義相關(guān)的Controller和Service:
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>springboot</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
定義UserController和UserService
在user包下定義UserController和UserService,希望能通過運(yùn)行MyApplication中的main方法,直接啟動項(xiàng)目,并能在瀏覽器中正常訪問到UserController中的某個(gè)方法。
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("test")
public String test(){
return userService.test();
}
}
核心注解和核心類:
- @SpringBootApplication:這個(gè)注解加在應(yīng)用啟動類上,即main方法所在的類。
- SpringApplication:這個(gè)類中有個(gè)run()方法,用來啟動SpringBoot應(yīng)用。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface BobSpringBootApplication {
}
public class BobSpringApplication {
public static void run(Class clazz){
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(clazz);
applicationContext.refresh();
startTomcat(applicationContext);
}
private static void startTomcat(WebApplicationContext applicationContext) {
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(context, "dispatcher", new DispatcherServlet(applicationContext));
context.addServletMappingDecoded("/", "dispatcher");
try {
tomcat.start();
} catch (LifecycleException e) {
e.printStackTrace();
}
tomcat.getServer().await();
}
}
@BobSpringBootApplication
public class MyApplication {
public static void main(String[] args) {
BobSpringApplication.run(MyApplication.class);
}
}
實(shí)現(xiàn)Tomcat和Jetty的切換
如果項(xiàng)目中有Tomcat的依賴,那就啟動Tomcat;如果有Jetty的依賴就啟動Jetty;如果兩者都沒有則報(bào)錯(cuò);如果兩者都有也報(bào)錯(cuò)。
public interface WebServer {
void start();
}
public class TomcatWebServer implements WebServer {
@Override
public void start() {
System.out.println("啟動Tomcat");
}
}
public class JettyWebServer implements WebServer {
@Override
public void start() {
System.out.println("啟動Jetty");
}
}
模擬實(shí)現(xiàn)條件注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(BobOnClassCondition.class)
public @interface BobConditionalOnClass {
String value() default "";
}
public class BobOnClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(BobConditionalOnClass.class.getName());
String className = (String) annotationAttributes.get("value");
try {
context.getClassLoader().loadClass(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
模擬實(shí)現(xiàn)自動配置類:
@Configuration
public class WebServiceAutoConfiguration {
@Bean
@BobConditionalOnClass("org.apache.catalina.startup.Tomcat")
public TomcatWebServer tomcatWebServer() {
return new TomcatWebServer();
}
@Bean
@BobConditionalOnClass("org.eclipse.jetty.server.Server")
public JettyWebServer jettyWebServer() {
return new JettyWebServer();
}
}
在BobSpringApplication中添加獲取WebServer的方法:
public static WebServer getWebServer(ApplicationContext applicationContext) {
Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);
if (webServers.isEmpty()) {
throw new NullPointerException();
}
if (webServers.size() > 1) {
throw new IllegalStateException();
}
return webServers.values().stream().findFirst().get();
}
通過以上步驟,我們實(shí)現(xiàn)了一個(gè)簡單的SpringBoot,并且可以根據(jù)依賴切換Tomcat和Jetty。