主页 > 互联网  > 

【SpringBoot】手写模拟SpringBoot核心流程

【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:

@RestController public class UserController { @Autowired private 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 @ComponentScan public @interface FireSpringBootApplication { }

FireSpringApplication 启动类:

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

在 MyApplication 中使用:

@FireSpringBootApplication public 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(); // 启动tomcat startTomcat(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{ @Override public void start() { System.out.println("启动Tomcat"); } }

Jetty 实现类:

public class JettyWebServer implements WebServer{ @Override public 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 { @Override public 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 属性,然后用类加载器进行加载,如果加载到了所指定的这个类,那就表示符合条件,如果加载不到,则表示不符合条件。

模拟实现自动配置类

配置类代码如下:

@Configuration public 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 实现该接口:

@Configuration public 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 { @Override public 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 的,而且提供的功能也更加强大,后面会对这些功能进行更深入的剖析。

标签:

【SpringBoot】手写模拟SpringBoot核心流程由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【SpringBoot】手写模拟SpringBoot核心流程