SpringBoot超詳細(xì)深入講解底層原理
手寫springboot
在日常開發(fā)中只需要引入下面的依賴就可以開發(fā)Servlet進(jìn)行訪問了。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
那這是怎么做到的呢?今天就來一探究竟
首先新建一個(gè)maven項(xiàng)目rick-spring-boot,并創(chuàng)建兩個(gè)子項(xiàng)目分別是spring-boot和user,其中spring-boot項(xiàng)目就是模擬手寫一個(gè)簡單springboot,user就是用來測試手寫的spring-boot的。
user項(xiàng)目-測試工程
user項(xiàng)目包含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項(xiàng)目的啟動(dòng)類RickApplication,而RickSpringApplication.run()是需要手寫的啟動(dòng)類以及@RickSpringBootApplication注解,都是需要在spring-boot項(xiàng)目實(shí)現(xiàn)。
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項(xiàng)目
首先來看RickSpringApplication.run(RickApplication.class)方法需要做的事情:
(1)創(chuàng)建spring容器,并將傳入的class注冊(cè)到spring容器中
(2)啟動(dòng)web服務(wù),如tomcat,用來處理請(qǐng)求,并通過DispatchServlet將請(qǐng)求分發(fā)到Servlet進(jìn)行處理。
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是配置類,在被注冊(cè)到spring容器后,spring就會(huì)解析這個(gè)類。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Configuration @ComponentScan public @interface RickSpringBootApplication { }
啟動(dòng)user項(xiàng)目RickApplication的main方法,
訪問UserController
至此一個(gè)簡單的spring-boot項(xiàng)目就整合完成了。
自動(dòng)配置
實(shí)現(xiàn)tomcat和jetty的切換
在使用springboot時(shí),如果我們不想使用tomcat作為請(qǐng)求處理服務(wù),而是jetty或者其他的web服務(wù),通常只需要將相關(guān)的tomcat依賴進(jìn)行排除,然后引入jetty的依賴就可以了,這就是springboot的自動(dòng)裝配的機(jī)制。接下來看看是如何實(shí)現(xiàn)的
定義一個(gè)WebServer接口和兩個(gè)實(shí)現(xiàn)類(tomcat和jetty),并寫好啟動(dòng)tomcat和jetty服務(wù)的代碼
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接口,用來標(biāo)識(shí)需要自動(dòng)裝配的類。再定義一個(gè)WebServerAutoConfiguration類,它被表示為spring的一個(gè)配置類,最終我們需要導(dǎo)入這個(gè)類由spring來解析它,隨后spring會(huì)解析@Bean注解的方法來加載Bean。注意這里下面兩個(gè)方法還定義了@RickConditionalOnClass注解來決定是否需要解析這個(gè)bean,如果滿足條件則進(jìn)行解析,即應(yīng)用內(nèi)存在Tomcat或者Server的Class,會(huì)解析對(duì)應(yīng)方法的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注解:當(dāng)spring解析被@RickConditionalOnClass注解的方法時(shí),spring就知道它被@Conditional修飾,并會(huì)在解析時(shí)執(zhí)行RickOnClassConditional的match()方法,來判斷是否滿足加載bean的條件。match()會(huì)嘗試加載傳入的類路徑名,如果應(yīng)用內(nèi)引入相關(guān)的jar則會(huì)加載成功返回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)導(dǎo)入該類。但是spring-boot不可能這么做,成千上百的自動(dòng)配置寫在代碼里肯定不好。spring通過SPI機(jī)制,在resources目錄下創(chuàng)建如下目錄和文件
定義一個(gè)類實(shí)現(xiàn)DeferredImportSelector接口,并實(shí)現(xiàn)selectImports(),通過JDK的ServiceLoader加載以上文件中的類。通過@Import(WebServerImportSelector.class)注解導(dǎo)入該類spring在解析配置類的時(shí)候就會(huì)執(zhí)行selectImports(),從而將WebServerAutoConfiguration導(dǎo)入到spring容器中,spring就會(huì)解析這個(gè)配置類。
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服務(wù)了
<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項(xiàng)目
小結(jié)
通過手寫模擬springboot,加深對(duì)springboot底層原理的理解,對(duì)于開發(fā)和使用更加得心應(yīng)手。springboot本章小結(jié):
1、springboot主要是整合spring框架和內(nèi)嵌web服務(wù)器的框架
2、springboot通過條件注解、實(shí)現(xiàn)spring DeferredImportSelector接口和JDK自帶的SPI機(jī)制實(shí)現(xiàn)了自動(dòng)裝配的功能
到此這篇關(guān)于SpringBoot超詳細(xì)深入講解底層原理的文章就介紹到這了,更多相關(guān)SpringBoot底層原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
application.yml的格式寫法和pom.xml讀取配置插件方式
這篇文章主要介紹了application.yml的格式寫法和pom.xml讀取配置插件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07java finally塊執(zhí)行時(shí)機(jī)全面分析
下面小編就為大家?guī)硪黄猨ava finally塊執(zhí)行時(shí)機(jī)全面分析。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-08-08Springboot?上傳文件或頭像(MultipartFile、transferTo)
本文主要介紹了Springboot?上傳文件或頭像(MultipartFile、transferTo),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04Java 中如何使用 JavaFx 庫標(biāo)注文本顏色
這篇文章主要介紹了在 Java 中用 JavaFx 庫標(biāo)注文本顏色,在本文中,我們將了解如何更改標(biāo)簽的文本顏色,并且我們還將看到一個(gè)必要的示例和適當(dāng)?shù)慕忉專员愀菀桌斫庠撝黝},需要的朋友可以參考下2023-05-05