SpringBoot超詳細深入講解底層原理

手寫springboot

在日常開發中隻需要引入下面的依賴就可以開發Servlet進行訪問瞭。

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

那這是怎麼做到的呢?今天就來一探究竟

首先新建一個maven項目rick-spring-boot,並創建兩個子項目分別是spring-boot和user,其中spring-boot項目就是模擬手寫一個簡單springboot,user就是用來測試手寫的spring-boot的。

user項目-測試工程

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

<dependencies>
    <dependency>
      <groupId>com.rick.spring.boot</groupId>
      <artifactId>spring-boot</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/user")
    public String getUser() {
        return userService.getUser();
    }
}
@Service
public class UserService {
    public String getUser() {
        return "rick";
    }
}

以及user項目的啟動類RickApplication,而RickSpringApplication.run()是需要手寫的啟動類以及@RickSpringBootApplication註解,都是需要在spring-boot項目實現。

import com.rick.spring.boot.RickSpringApplication;
import com.rick.spring.boot.RickSpringBootApplication;
@RickSpringBootApplication
public class RickApplication {
    public static void main(String[] args) {
        RickSpringApplication.run(RickApplication.class);
    }
}

Springboot項目

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

(1)創建spring容器,並將傳入的class註冊到spring容器中

(2)啟動web服務,如tomcat,用來處理請求,並通過DispatchServlet將請求分發到Servlet進行處理。

public class RickSpringApplication {
    public static void run(Class clz) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(clz);
        context.refresh();
        start(context);
    }
    public static void start(WebApplicationContext applicationContext) {
        System.out.println("start tomcat");
        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(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
        context.addServletMappingDecoded("/*", "dispatcher");
        try {
            tomcat.start();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }
}

RickApplication是被@RickSpringBootApplication註解修飾的,從如下代碼可以看出RickApplication是配置類,在被註冊到spring容器後,spring就會解析這個類。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration
@ComponentScan
public @interface RickSpringBootApplication {
}

啟動user項目RickApplication的main方法,

訪問UserController

至此一個簡單的spring-boot項目就整合完成瞭。

自動配置

實現tomcat和jetty的切換

在使用springboot時,如果我們不想使用tomcat作為請求處理服務,而是jetty或者其他的web服務,通常隻需要將相關的tomcat依賴進行排除,然後引入jetty的依賴就可以瞭,這就是springboot的自動裝配的機制。接下來看看是如何實現的

定義一個WebServer接口和兩個實現類(tomcat和jetty),並寫好啟動tomcat和jetty服務的代碼

public interface WebServer {
    void start();
}
public class JettyServer implements WebServer{
    @Override
    public void start() {
        System.out.println("start jetty");
    }
}
public class TomcatServer implements WebServer, ApplicationContextAware {
    private WebApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = (WebApplicationContext) applicationContext;
    }
    @Override
    public void start() {
        System.out.println("start tomcat");
        ...
        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
        context.addServletMappingDecoded("/*", "dispatcher");
        try {
            tomcat.start();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }
}

定義AutoConfiguration接口,用來標識需要自動裝配的類。再定義一個WebServerAutoConfiguration類,它被表示為spring的一個配置類,最終我們需要導入這個類由spring來解析它,隨後spring會解析@Bean註解的方法來加載Bean。註意這裡下面兩個方法還定義瞭@RickConditionalOnClass註解來決定是否需要解析這個bean,如果滿足條件則進行解析,即應用內存在Tomcat或者Server的Class,會解析對應方法的Bean,

public interface AutoConfiguration {
}
@Configuration
public class WebServerAutoConfiguration implements AutoConfiguration {
    @Bean
    @RickConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatServer tomcatServer() {
        return new TomcatServer();
    }
    @Bean
    @RickConditionalOnClass("org.eclipse.jetty.server.Server")
    public JettyServer jettyWebServer() {
        return new JettyServer();
    }
}

來看@RickConditionalOnClass註解:當spring解析被@RickConditionalOnClass註解的方法時,spring就知道它被@Conditional修飾,並會在解析時執行RickOnClassConditional的match()方法,來判斷是否滿足加載bean的條件。match()會嘗試加載傳入的類路徑名,如果應用內引入相關的jar則會加載成功返回true,反之,返回false。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Conditional(RickOnClassConditional.class)
public @interface RickConditionalOnClass {
    String value();
}
public class RickOnClassConditional implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> annotation = metadata.getAnnotationAttributes(RickConditionalOnClass.class.getName());
        try {
            context.getClassLoader().loadClass((String) annotation.get("value"));
        } catch (ClassNotFoundException e) {
            return false;
        }
        return true;
    }
}

引入WebServerAutoConfiguration,最簡單粗暴的方式就是通過@Import(WebServerAutoConfiguration.class)導入該類。但是spring-boot不可能這麼做,成千上百的自動配置寫在代碼裡肯定不好。spring通過SPI機制,在resources目錄下創建如下目錄和文件

定義一個類實現DeferredImportSelector接口,並實現selectImports(),通過JDK的ServiceLoader加載以上文件中的類。通過@Import(WebServerImportSelector.class)註解導入該類spring在解析配置類的時候就會執行selectImports(),從而將WebServerAutoConfiguration導入到spring容器中,spring就會解析這個配置類。

public class WebServerImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        ServiceLoader<AutoConfiguration> load = ServiceLoader.load(AutoConfiguration.class);
        List<String> list = new ArrayList<>();
        for (AutoConfiguration loader : load) {
            list.add(loader.getClass().getName());
        }
        return list.toArray(new String[list.size()]);
    }
}

至此,springboot就做到瞭隻需要修改user工程的maven依賴就能切換tomcat和jetty服務瞭

<dependency>
      <groupId>com.rick.spring.boot</groupId>
      <artifactId>spring-boot</artifactId>
      <version>1.0-SNAPSHOT</version>
      <exclusions>
        <exclusion>
          <groupId>org.apache.tomcat.embed</groupId>
          <artifactId>tomcat-embed-core</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>9.4.43.v20210629</version>
    </dependency>

重啟user項目

小結

通過手寫模擬springboot,加深對springboot底層原理的理解,對於開發和使用更加得心應手。springboot本章小結:

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

2、springboot通過條件註解、實現spring DeferredImportSelector接口和JDK自帶的SPI機制實現瞭自動裝配的功能

到此這篇關於SpringBoot超詳細深入講解底層原理的文章就介紹到這瞭,更多相關SpringBoot底層原理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: