当前位置:首页 >> 硬件技术 >> 【SpringBoot】手写模拟SpringBoot核心流程,松下vs2

【SpringBoot】手写模拟SpringBoot核心流程,松下vs2

cpugpu芯片开发光刻机 硬件技术 2
文件名:【SpringBoot】手写模拟SpringBoot核心流程,松下vs2 【SpringBoot】手写模拟SpringBoot核心流程 依赖包

新建一个工程,包含两个 module:

springboot 模块,表示 springboot 源码实现;user 模块,表示业务系统,使用 springboot 模块;

依赖包:Spring、SpringMVC、Tomcat 等,引入依赖如下:

<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 模块下引入依赖:

<dependencies><dependency><groupId>org.example</groupId><artifactId>springboot</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>

定义对应的 controller 和 service:

@RestControllerpublic class UserController {@Autowiredprivate UserService userService;@GetMapping("test")public String test(){return userService.test();}}

最终希望通过启动 MyApplication 的 main 方法,启动项目,能访问到 UserController。

核心注解和核心类

SpringBoot 的核心类和注解:

@SpringBootApplication,这个注解是加在应用启动类上的,也就是 main 方法所在的类;SpringApplication,这个类中有个 run() 方法,用来启动 SpringBoot 应用的;

所以,自定义类和注解以实现上面的功能。@FireSpringBootApplication 注解:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Configuration@ComponentScanpublic @interface FireSpringBootApplication {}

FireSpringApplication 启动类:

public class FireSpringApplication {public static void run(Class clazz){}}

在 MyApplication 中使用:

@FireSpringBootApplicationpublic class MyApplication {public static void main(String[] args) {FireSpringApplication.run(MyApplication.class);}} run 方法

需要在 run 方法中启动 tomcat,通过 tomcat 接收请求;DispatchServlet 绑定 spring 容器,DispatchServlet 接收到请求后需要在 spring 容器中找到一个 controller 中对应的方法;

run 方法中需要实现的逻辑:

创建一个 Spring 容器创建 Tomcat 对象生成 DispatcherServlet 对象,并且和前面创建出来的 Spring 容器进行绑定将 DispatcherServlet 添加到 Tomcat 中启动 Tomcat 创建 Spring 容器 public class FireSpringApplication {public static void run(Class clazz){AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(clazz);applicationContext.refresh();}}

run 方法中传入的即使 MyApplication 类,被解析为 Spring 容器的配置类;默认会将 MyApplication 所在的包作为扫描路径,从而扫描到 UserController 和 UserService,所以在 spring 容器启动后就会存在两个 bean 了;

启动 Tomcat

使用内嵌的 Tomact,即 Embed-Tomcat,启动代码如下:

public 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);// 添加DispatcherServlet,并且绑定一个Spring容器tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));// 设置Mapping关系context.addServletMappingDecoded("/*", "dispatcher");try {tomcat.start();} catch (LifecycleException e) {e.printStackTrace();}}

在 run 方法中调用 startTomcat 方法启动 tomcat:

public static void run(Class clazz){AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(clazz);applicationContext.refresh();// 启动tomcatstartTomcat(applicationContext);}

到此,一个简单的 SpringBoot 就写出来了,运行 MyApplication 正常启动项目,通过浏览器就可以访问 UserController 了。

实现 Tomcat 和 Jetty 的切换

前面代码中默认启动的是 Tomcat,现在想改成这样子:

如果项目中有 Tomcat 的依赖,那就启动 Tomcat如果项目中有 Jetty的依赖就启动 Jetty如果两者都没有则报错如果两者都有也报错

这个逻辑希望 SpringBoot 自动实现,对于程序员用户而言,只要在 Pom 文件中添加相关依赖就可以了,想用 Tomcat 就加 Tomcat 依赖,想用 Jetty 就加 Jetty 依赖。Tomcat 和 Jetty 都是应用服务器,或者是 Servlet 容器,可以定义接口来表示它们,这个接口交 WebServer(SpringBoot 源码中也叫这个)。定义接口如下:

public interface WebServer {public void start();}

Tomcat 实现类:

public class TomcatWebServer implements WebServer{@Overridepublic void start() {System.out.println("启动Tomcat");}}

Jetty 实现类:

public class JettyWebServer implements WebServer{@Overridepublic void start() {System.out.println("启动Jetty");}}

在 FireSpringApplication 中的 run 方法中,去获取对应的 WebServer,然后启动对应的 webServer。代码如下:

public static void run(Class clazz){AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(clazz);applicationContext.refresh();// 自动获取配置的Tomcat或者Jetty容器WebServer webServer = getWebServer(applicationContext);webServer.start();}public static WebServer getWebServer(ApplicationContext applicationContext){return null;} 模拟实现条件注解

首先实现一个条件注解@FireConditionalOnClass,对应代码如下:

@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Conditional(FireOnClassCondition.class)public @interface FireConditionalOnClass {String value() default "";}

注意核心为@Conditional(FireOnClassCondition.class)中的 FireOnClassCondition,因为它才是真正得条件逻辑:

public class FireOnClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(FireConditionalOnClass.class.getName());String className = (String) annotationAttributes.get("value");try {context.getClassLoader().loadClass(className);return true;} catch (ClassNotFoundException e) {return false;}}}

具体逻辑为,拿到@FireConditionalOnClass中的 value 属性,然后用类加载器进行加载,如果加载到了所指定的这个类,那就表示符合条件,如果加载不到,则表示不符合条件。

模拟实现自动配置类

配置类代码如下:

@Configurationpublic class WebServiceAutoConfiguration {@Bean@FireConditionalOnClass("org.apache.catalina.startup.Tomcat")public TomcatWebServer tomcatWebServer(){return new TomcatWebServer();}@Bean@FireConditionalOnClass("org.eclipse.jetty.server.Server")public JettyWebServer jettyWebServer(){return new JettyWebServer();}}

表示org.apache.catalina.startup.Tomcat存在,则有 tomcatWebServer 这个bean;表示org.eclipse.jetty.server.Server存在,则有 jettyWebServer 这个bean;

FireSpringApplication#getWebServer()方法实现:

public static WebServer getWebServer(ApplicationContext applicationContext){// key为beanName, value为Bean对象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();}

这样整体 SpringBoot 启动逻辑就是这样的:

创建一个 AnnotationConfigWebApplicationContext 容器解析 MyApplication 类,然后进行扫描通过 getWebServer 方法从 Spring 容器中获取 WebServer 类型的 Bean调用 WebServer 对象的 start 方法 发现自动配置类

WebServiceAutoConfiguration 需要被 SpringBoot 发现,可以通过 SPI 机制实现,比较 JDK 自带的 SPI 来实现。

在 springboot 项目中的 resources 目录下添加目录META-INF/services和文件 org.example.springboot.AutoConfiguration,文件内容为org.example.springboot.WebServiceAutoConfiguration。

接口:

public interface AutoConfiguration {}

WebServiceAutoConfiguration 实现该接口:

@Configurationpublic class WebServiceAutoConfiguration implements AutoConfiguration {@Bean@FireConditionalOnClass("org.apache.catalina.startup.Tomcat")public TomcatWebServer tomcatWebServer(){return new TomcatWebServer();}@Bean@FireConditionalOnClass("org.eclipse.jetty.server.Server")public JettyWebServer jettyWebServer(){return new JettyWebServer();}}

再利用 spring 中的@Import技术来导入这些配置类,我们在@FireSpringBootApplication的定义上增加如下代码:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Configuration@ComponentScan@Import(FireImportSelect.class)public @interface FireSpringBootApplication {}

FireImportSelect:

public class FireImportSelect implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {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]);}}

如此,Spring 容器可以装载 WebServiceAutoConfiguration 配置类了,对于 user 模块而言,不需要修改代码就可以自动识别 Tomcat 和 Jetty 了。

总结

到此,实现了一个简单版本的 SpringBoot,因为 SpringBoot 首先是基于 Spring 的,而且提供的功能也更加强大,后面会对这些功能进行更深入的剖析。

协助本站SEO优化一下,谢谢!
关键词不能为空
同类推荐
«    2025年12月    »
1234567
891011121314
15161718192021
22232425262728
293031
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
搜索
最新留言
文章归档
网站收藏
友情链接