经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
SpringBoot超详细深入讲解底层原理
来源:jb51  时间:2022/7/19 19:04:51  对本文有异议

手写springboot

在日常开发中只需要引入下面的依赖就可以开发Servlet进行访问了。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>

那这是怎么做到的呢?今天就来一探究竟

首先新建一个maven项目rick-spring-boot,并创建两个子项目分别是spring-boot和user,其中spring-boot项目就是模拟手写一个简单springboot,user就是用来测试手写的spring-boot的。

user项目-测试工程

user项目包含pom.xml、UserController和UserService

  1. <dependencies>
  2. <dependency>
  3. <groupId>com.rick.spring.boot</groupId>
  4. <artifactId>spring-boot</artifactId>
  5. <version>1.0-SNAPSHOT</version>
  6. </dependency>
  7. </dependencies>
  1. @RestController
  2. public class UserController {
  3. @Autowired
  4. private UserService userService;
  5. @GetMapping("/user")
  6. public String getUser() {
  7. return userService.getUser();
  8. }
  9. }
  10. @Service
  11. public class UserService {
  12. public String getUser() {
  13. return "rick";
  14. }
  15. }

以及user项目的启动类RickApplication,而RickSpringApplication.run()是需要手写的启动类以及@RickSpringBootApplication注解,都是需要在spring-boot项目实现。

  1. import com.rick.spring.boot.RickSpringApplication;
  2. import com.rick.spring.boot.RickSpringBootApplication;
  3. @RickSpringBootApplication
  4. public class RickApplication {
  5. public static void main(String[] args) {
  6. RickSpringApplication.run(RickApplication.class);
  7. }
  8. }

Springboot项目

首先来看RickSpringApplication.run(RickApplication.class)方法需要做的事情:

(1)创建spring容器,并将传入的class注册到spring容器中

(2)启动web服务,如tomcat,用来处理请求,并通过DispatchServlet将请求分发到Servlet进行处理。

  1. public class RickSpringApplication {
  2. public static void run(Class clz) {
  3. AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
  4. context.register(clz);
  5. context.refresh();
  6. start(context);
  7. }
  8. public static void start(WebApplicationContext applicationContext) {
  9. System.out.println("start tomcat");
  10. Tomcat tomcat = new Tomcat();
  11. Server server = tomcat.getServer();
  12. Service service = server.findService("Tomcat");
  13. Connector connector = new Connector();
  14. connector.setPort(8081);
  15. Engine engine = new StandardEngine();
  16. engine.setDefaultHost("localhost");
  17. Host host = new StandardHost();
  18. host.setName("localhost");
  19. String contextPath = "";
  20. Context context = new StandardContext();
  21. context.setPath(contextPath);
  22. context.addLifecycleListener(new Tomcat.FixContextListener());
  23. host.addChild(context);
  24. engine.addChild(host);
  25. service.setContainer(engine);
  26. service.addConnector(connector);
  27. tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
  28. context.addServletMappingDecoded("/*", "dispatcher");
  29. try {
  30. tomcat.start();
  31. } catch (LifecycleException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }

RickApplication是被@RickSpringBootApplication注解修饰的,从如下代码可以看出RickApplication是配置类,在被注册到spring容器后,spring就会解析这个类。

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Configuration
  4. @ComponentScan
  5. public @interface RickSpringBootApplication {
  6. }

启动user项目RickApplication的main方法,

访问UserController

至此一个简单的spring-boot项目就整合完成了。

自动配置

实现tomcat和jetty的切换

在使用springboot时,如果我们不想使用tomcat作为请求处理服务,而是jetty或者其他的web服务,通常只需要将相关的tomcat依赖进行排除,然后引入jetty的依赖就可以了,这就是springboot的自动装配的机制。接下来看看是如何实现的

定义一个WebServer接口和两个实现类(tomcat和jetty),并写好启动tomcat和jetty服务的代码

  1. public interface WebServer {
  2. void start();
  3. }
  4. public class JettyServer implements WebServer{
  5. @Override
  6. public void start() {
  7. System.out.println("start jetty");
  8. }
  9. }
  1. public class TomcatServer implements WebServer, ApplicationContextAware {
  2. private WebApplicationContext applicationContext;
  3. @Override
  4. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  5. this.applicationContext = (WebApplicationContext) applicationContext;
  6. }
  7. @Override
  8. public void start() {
  9. System.out.println("start tomcat");
  10. ...
  11. tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
  12. context.addServletMappingDecoded("/*", "dispatcher");
  13. try {
  14. tomcat.start();
  15. } catch (LifecycleException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }

定义AutoConfiguration接口,用来标识需要自动装配的类。再定义一个WebServerAutoConfiguration类,它被表示为spring的一个配置类,最终我们需要导入这个类由spring来解析它,随后spring会解析@Bean注解的方法来加载Bean。注意这里下面两个方法还定义了@RickConditionalOnClass注解来决定是否需要解析这个bean,如果满足条件则进行解析,即应用内存在Tomcat或者Server的Class,会解析对应方法的Bean,

  1. public interface AutoConfiguration {
  2. }
  3. @Configuration
  4. public class WebServerAutoConfiguration implements AutoConfiguration {
  5. @Bean
  6. @RickConditionalOnClass("org.apache.catalina.startup.Tomcat")
  7. public TomcatServer tomcatServer() {
  8. return new TomcatServer();
  9. }
  10. @Bean
  11. @RickConditionalOnClass("org.eclipse.jetty.server.Server")
  12. public JettyServer jettyWebServer() {
  13. return new JettyServer();
  14. }
  15. }

来看@RickConditionalOnClass注解:当spring解析被@RickConditionalOnClass注解的方法时,spring就知道它被@Conditional修饰,并会在解析时执行RickOnClassConditional的match()方法,来判断是否满足加载bean的条件。match()会尝试加载传入的类路径名,如果应用内引入相关的jar则会加载成功返回true,反之,返回false。

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. @Conditional(RickOnClassConditional.class)
  4. public @interface RickConditionalOnClass {
  5. String value();
  6. }
  7. public class RickOnClassConditional implements Condition {
  8. @Override
  9. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  10. Map<String, Object> annotation = metadata.getAnnotationAttributes(RickConditionalOnClass.class.getName());
  11. try {
  12. context.getClassLoader().loadClass((String) annotation.get("value"));
  13. } catch (ClassNotFoundException e) {
  14. return false;
  15. }
  16. return true;
  17. }
  18. }

引入WebServerAutoConfiguration,最简单粗暴的方式就是通过@Import(WebServerAutoConfiguration.class)导入该类。但是spring-boot不可能这么做,成千上百的自动配置写在代码里肯定不好。spring通过SPI机制,在resources目录下创建如下目录和文件

定义一个类实现DeferredImportSelector接口,并实现selectImports(),通过JDK的ServiceLoader加载以上文件中的类。通过@Import(WebServerImportSelector.class)注解导入该类spring在解析配置类的时候就会执行selectImports(),从而将WebServerAutoConfiguration导入到spring容器中,spring就会解析这个配置类。

  1. public class WebServerImportSelector implements DeferredImportSelector {
  2. @Override
  3. public String[] selectImports(AnnotationMetadata metadata) {
  4. ServiceLoader<AutoConfiguration> load = ServiceLoader.load(AutoConfiguration.class);
  5. List<String> list = new ArrayList<>();
  6. for (AutoConfiguration loader : load) {
  7. list.add(loader.getClass().getName());
  8. }
  9. return list.toArray(new String[list.size()]);
  10. }
  11. }

至此,springboot就做到了只需要修改user工程的maven依赖就能切换tomcat和jetty服务了

  1. <dependency>
  2. <groupId>com.rick.spring.boot</groupId>
  3. <artifactId>spring-boot</artifactId>
  4. <version>1.0-SNAPSHOT</version>
  5. <exclusions>
  6. <exclusion>
  7. <groupId>org.apache.tomcat.embed</groupId>
  8. <artifactId>tomcat-embed-core</artifactId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.eclipse.jetty</groupId>
  14. <artifactId>jetty-server</artifactId>
  15. <version>9.4.43.v20210629</version>
  16. </dependency>

重启user项目

小结

通过手写模拟springboot,加深对springboot底层原理的理解,对于开发和使用更加得心应手。springboot本章小结:

1、springboot主要是整合spring框架和内嵌web服务器的框架

2、springboot通过条件注解、实现spring DeferredImportSelector接口和JDK自带的SPI机制实现了自动装配的功能

到此这篇关于SpringBoot超详细深入讲解底层原理的文章就介绍到这了,更多相关SpringBoot底层原理内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号