springboot應(yīng)用啟動(dòng)原理分析_第1頁
springboot應(yīng)用啟動(dòng)原理分析_第2頁
springboot應(yīng)用啟動(dòng)原理分析_第3頁
springboot應(yīng)用啟動(dòng)原理分析_第4頁
springboot應(yīng)用啟動(dòng)原理分析_第5頁
已閱讀5頁,還剩26頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、spring boot quick start在 spring boot 里,很吸引人的一個(gè)特性是可以直接把應(yīng)用打包成為一個(gè) jar/war ,然后這個(gè) jar/war 是可以直接啟動(dòng)的,不需要另外配置一個(gè) Web Server 。如果之前沒有使用過spring boot 可以通過下面的demo 來感受下。下面以這個(gè)工程為例,演示如何啟動(dòng)Spring boot 項(xiàng)目:git clone :hengyunabc/spring-boot-demo.gitmvn spring-boot-demojava -jar target/demo-0.0 . 1-SNAPSHOT.jar123如果使用的 ID

2、E 是 spring sts 或者 idea,可以通過向?qū)韯?chuàng)建spring boot 項(xiàng)目。也可以參考官方教程:對 spring boot 的兩個(gè)疑問剛開始接觸 spring boot 時(shí),通常會有這些疑問spring boot如何啟動(dòng)的?spring boot embed tomcat是如何工作的?靜態(tài)文件, jsp ,網(wǎng)頁模板這些是如何加載到的?下面來分析 spring boot 是如何做到的。1/31打包為單個(gè) jar 時(shí), spring boot 的啟動(dòng)方式maven 打包之后,會生成兩個(gè)jar 文件:demo- 0.0.1-SNAPSHOT.jardemo- 0.0.1-SNAPS

3、HOT.jar.original12其中 demo-0.0.1-SNAPSHOT.jar.original 是默認(rèn)的 maven-jar-plugin生成的包。demo-0.0.1-SNAPSHOT.jar 是 spring boot maven 插件生成的 jar 包,里面包含了應(yīng)用的依賴,以及 spring boot 相關(guān)的類。下面稱之為 fat jar 。先來查看 spring boot 打好的包的目錄結(jié)構(gòu)(不重要的省略掉):META-INFMANIFEST.MFpertiescomexampleSpringBootDemoApplication.class

4、lib2/31aopalliance-1.0 .jarspring-beans-4.2.3.RELEASE.springframeworkbootloaderExecutableArchiveLauncher.classJarLauncher.classJavaAgentDetector.classLaunchedURLClassLoader.classLauncher.classMainMethodRunner.class.12345678910111213143/3115161718192021依次來看下這些內(nèi)容。MANIFEST.MFManifest-Version:1.0

5、Start-Class:com.example.SpringBootDemoApplicationImplementation-Vendor-Id:com.exampleSpring-Boot-Version:1.3.0.RELEASECreated-By:Apache Maven 3.3.3Build-Jdk:1.8.0_60Implementation-Vendor:Pivotal Software, Inc.Main-Class:org.springframework.boot.loader.JarLauncher12345678可以看到有 Main-Class 是 org.spring

6、framework.boot.loader.JarLauncher,這個(gè)是 jar 啟動(dòng)的 Main 函數(shù)。還有一個(gè) Start-Class 是 com.example.SpringBootDemoApplication ,這個(gè)是我們應(yīng)用自己的 Main 函數(shù)。SpringBootApplicationpublicclassSpringBootDemoApplicationpublicstaticvoidmain(String args) SpringApplication.run(SpringBootDemoApplication.class, args);4/311234567com/ex

7、ampl e 目錄這下面放的是應(yīng)用的 .class 文件。lib 目錄這里存放的是應(yīng)用的Maven 依賴的 jar 包文件。比如 spring-beans , spring-mvc 等 jar 。org/springframework/boot/loader目錄這下面存放的是Spring boot loader 的 .class 文件。Archive 的概念archive 即歸檔文件,這個(gè)概念在linux 下比較常見通常就是一個(gè)tar/zip格式的壓縮包jar 是 zip 格式在 spring boot 里,抽象出了 Archive 的概念。一個(gè) archive 可以是一個(gè) jar (JarF

8、ileArchive ),也可以是一個(gè)文件目錄( ExplodedArchive )??梢岳斫鉃?Spring boot 抽象出來的統(tǒng)一訪問資源的層。5/31上面的 demo-0.0.1-SNAPSHOT.jar 是一個(gè) Archive ,然后demo-0.0.1-SNAPSHOT.jar里的 /lib 目錄下面的每一個(gè) Jar 包,也是一個(gè) Archive 。publicabstractclassArchivepublicabstractURL getUrl();publicString getMainClass();publicabstractCollection<Entry>

9、 getEntries();publicabstractList<Archive> getNestedArchives(EntryFilter filter);12345可以看到 Archive 有一個(gè)自己的 URL,比如:jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/1還有一個(gè) getNestedArchives 函數(shù),這個(gè)實(shí)際返回的是demo-0.0.1-SNAPSHOT.jar/lib 下面的 jar 的 Archive 列表。它們的 URL 是:jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.j

10、ar!/lib/aopalliance-1.0 .jarjar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3 .RELEASE.jar12JarLauncher從 MANIFEST.MF 可以看到 Main 函數(shù)是 JarLauncher,下面來分析它的工作流程。JarLauncher 類的繼承結(jié)構(gòu)是:class JarLauncher extends ExecutableArchiveLauncherclass ExecutableArchiveLauncher extends Launcher126/31以

11、 demo-0.0.1-SNAPSHOT.jar創(chuàng)建一個(gè) Archive :JarLauncher 先找到自己所在的 jar ,即 demo-0.0.1-SNAPSHOT.jar 的路徑,然后創(chuàng)建了一個(gè) Archive 。下面的代碼展示了如何從一個(gè)類找到它的加載的位置的技巧:protectedfinalArchive createArchive()throwsException ProtectionDomain protectionDomain = getClass().getProtectionDomain();CodeSource codeSource = protectionDomain

12、.getCodeSource();URI location = (codeSource =null?null: codeSource.getLocation().toURI();String path = (location =null?null: location.getSchemeSpecificPart();if(path =null) thrownew IllegalStateException("Unable to determine code sourcearchive");File root =new File(path);if(!root.exists()

13、thrownew IllegalStateException("Unable to determine code source archive from "+ root);return(root.isDirectory() ?new ExplodedArchive(root):new JarFileArchive(root);123456789107/31111213141516獲 取lib/下 面 的jar , 并 創(chuàng) 建 一 個(gè)LaunchedURLClassLoaderJarLauncher 創(chuàng)建好 Archive 之后,通過 getNestedArchives 函數(shù)

14、來獲取到 demo-0.0.1-SNAPSHOT.jar/lib 下面的所有 jar 文件,并創(chuàng)建為 List 。注意上面提到, Archive 都是有自己的 URL 的。獲取到這些 Archive 的 URL 之后,也就獲得了一個(gè) URL 數(shù)組,用這個(gè)來構(gòu)造一個(gè)自定義的 ClassLoader:LaunchedURLClassLoader。創(chuàng)建好 ClassLoader 之后,再從 MANIFEST.MF 里讀取到 Start-Class,即com.example.SpringBootDemoApplication ,然后創(chuàng)建一個(gè)新的線程來啟動(dòng)應(yīng)用的 Main 函數(shù)。/* Launch th

15、e application given the archive file and a fully configured c lassloader.*/protectedvoidlaunch(String args, String mainClass, ClassLoader classLoader)throwsException Runnable runner = createMainMethodRunner(mainClass, args, classLoader);Thread runnerThread =new Thread(runner);runnerThread.setContext

16、ClassLoader(classLoader);runnerThread.setName(Thread.currentThread().getName();runnerThread.start();8/31/* Create the code MainMethodRunner used to launch the application.*/protectedRunnable createMainMethodRunner(String mainClass, String args,ClassLoader classLoader)throwsException Class<?> r

17、unnerClass = classLoader.loadClass(RUNNER_CLASS);Constructor<?> constructor = runnerClass.getConstructor(String.class,String.class);return(Runnable) constructor.newInstance(mainClass, args);12345678910111213141516171819202122LaunchedURLClassLoaderLaunchedURLClassLoader 和普通的 URLClassLoader 的不同之

18、處是,它提供了從 Archive 里加載 .class 的能力。9/31結(jié)合 Archive 提供的 getEntries 函數(shù),就可以獲取到 Archive 里的 Resource。當(dāng)然里面的細(xì)節(jié)還是很多的,下面再描述。spring boot 應(yīng)用啟動(dòng)流程總結(jié)看到這里,可以總結(jié)下Spring Boot 應(yīng)用的啟動(dòng)流程:1.spring boot 應(yīng)用打包之后,生成一個(gè)fat jar ,里面包含了應(yīng)用依賴的jar 包,還有Spring boot l oader 相關(guān)的類2.Fat jar 的啟動(dòng) Main 函數(shù)是 JarLauncher ,它負(fù)責(zé)創(chuàng)建一個(gè)LaunchedURLClassLoad

19、 er來加載 /lib 下面的 jar ,并以一個(gè)新線程啟動(dòng)應(yīng)用的Main函數(shù)。spring boot loader 里的細(xì)節(jié)代碼地址:JarFile URL 的擴(kuò)展Spring boot 能做到以一個(gè) fat jar 來啟動(dòng),最重要的一點(diǎn)是它實(shí)現(xiàn)了 jar in jar 的加載方式。JDK 原始的 JarFile URL 的定義可以參考這里:原始的 JarFile URL 是這樣子的:jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/1jar 包里的資源的 URL:jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.ja

20、r!/ com/example/SpringBootDemoApplication.class110/31可以看到對于 Jar 里的資源,定義以 !/來分隔。原始的 JarFile URL 只支持一個(gè) !/。Spring boot 擴(kuò)展了這個(gè)協(xié)議,讓它支持多個(gè) !/,就可以表示 jar in jar ,jar in directory 的資源了。比如下面的 URL 表示 demo-0.0.1-SNAPSHOT.jar 這個(gè) jar 里 lib 目錄下面的spring-beans-4.2.3.RELEASE.jar 里面的 MANIFEST.MF:jar:file:/tmp/target/dem

21、o-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar!/META-INF/MANIFEST.MF1自定義 URLStreamHandler,擴(kuò)展 JarFile 和 JarURLConnection在構(gòu)造一個(gè) URL 時(shí),可以傳遞一個(gè) Handler ,而 JDK 自帶有默認(rèn)的 Handler 類,應(yīng)用可以自己注冊 Handler 來處理自定義的 URL。publicURL(String protocol,String host,intport,String file,URLStreamHandler handler)throwsMa

22、lformedURLException123456參考:Spring boot 通過注冊了一個(gè)自定義的Handler 類來處理多重 jar in jar 的邏輯。這個(gè) Handler 內(nèi)部會用 SoftReference 來緩存所有打開過的JarFile。11/31在處理像下面這樣的URL 時(shí),會循環(huán)處理 !/分隔符,從最上層出發(fā),先構(gòu)造出demo-0.0.1-SNAPSHOT.jar這個(gè) JarFile,再構(gòu)造出 spring-beans-4.2.3.RELEASE.jar 這個(gè) JarFile,然后再構(gòu)造出指向 MANIFEST.MF 的 JarURLConnection。jar:file

23、:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar!/META-INF/MANIFEST.MF1/org.springframework.boot.loader.jar.HandlerpublicclassHandlerextendsURLStreamHandlerprivatestaticfinalString SEPARATOR ="!/"privatestaticSoftReference<Map<File, JarFile>> rootFileCac

24、he;OverrideprotectedURLConnection openConnection(URL url)throwsIOException if( this.jarFile !=null) returnnew JarURLConnection(url,this.jarFile);tryreturnnew JarURLConnection(url, getRootJarFileFromUrl(url);catch(Exception ex) returnopenFallbackConnection(url, ex);publicJarFile getRootJarFileFromUrl

25、(URL url)throwsIOException String spec = url.getFile();intseparatorIndex = spec.indexOf(SEPARATOR);if(separatorIndex = -1 ) thrownew MalformedURLException("Jar URL does not contain !/ separator");String name = spec.substring(0, separatorIndex);returngetRootJarFile(name);1234512/31678910111

26、213141516171819202122232425ClassLoader 如何讀取到 Resource對于一個(gè) ClassLoader,它需要哪些能力?查找資源讀取資源對應(yīng)的 API 是:publicURL findResource(String name)publicInputStream getResourceAsStream(String name)12上面提到, Spring boot 構(gòu)造 LaunchedURLClassLoader 時(shí),傳遞了一個(gè) URL 數(shù)組。數(shù)組里是 lib 目錄下面的 jar 的 URL。13/31對于一個(gè) URL, JDK或者 ClassLoader

27、如何知道怎么讀取到里面的內(nèi)容的?實(shí)際上流程是這樣子的:LaunchedURLClassLoad er.loadClassURL.getContent()URL.openConnection()Handler.openConnection(URL)最終調(diào)用的是 JarURLConnection 的 getInputStream() 函數(shù)。/org.springframework.boot.loader.jar.JarURLConnectionOverridepublicInputStream getInputStream()throwsIOException connect();if( this

28、.jarEntryName.isEmpty() thrownew IOException("no entry name specified");returnthis.jarEntryData.getInputStream();123456789從一個(gè) URL,到最終讀取到 URL 里的內(nèi)容,整個(gè)過程是比較復(fù)雜的,總結(jié)下:spring boot注冊了一個(gè)Handler 來處理 ” jar:這種”協(xié)議的URLspring boot擴(kuò)展了 JarFile 和 JarURLConnection ,內(nèi)部處理jar in jar的情況在處理多重jar in jar的 URL 時(shí),spr

29、ing boot會循環(huán)處理,并緩存已經(jīng)加載到的JarFile14/31對于多重jar in jar ,實(shí)際上是解壓到了臨時(shí)目錄來處理,可以參考JarFileArchive 里的代碼在獲取 URL 的 InputStream 時(shí),最終獲取到的是JarFile 里的 JarEntryData這里面的細(xì)節(jié)很多,只列出比較重要的一些點(diǎn)。然后, URLClassLoader 是如何 getResource 的呢?URLClassLoader 在構(gòu)造時(shí),有 URL 數(shù)組參數(shù),它內(nèi)部會用這個(gè)數(shù)組來構(gòu)造一個(gè)URLClassPath:URLClassPath ucp =new URLClassPath(urls

30、);1在 URLClassPath 內(nèi)部會為這些 URLS 都構(gòu)造一個(gè) Loader ,然后在 getResource時(shí),會從這些 Loader 里一個(gè)個(gè)去嘗試獲取。如果獲取成功的話,就像下面那樣包裝為一個(gè)Resource。Resource getResource(finalString name,booleancheck) finalURL url;tryurl =new URL(base, ParseUtil.encodePath(name,false);catch(MalformedURLException e) thrownew IllegalArgumentException(&qu

31、ot;name" );finalURLConnection uc;tryif(check) URLClassPath.check(url);uc = url.openConnection();InputStream in = uc.getInputStream();if(ucinstanceofJarURLConnection) /* Need to remember the jar file so it can be closed* in a hurry.*/JarURLConnection juc = (JarURLConnection)uc;15/31jarfile = Jar

32、Loader.checkJar(juc.getJarFile();catch(Exception e) returnnull;returnnew Resource() publicString getName() returnname; publicURL getURL() returnurl; public URL getCodeSourceURL() public InputStream getInputStream()returnbase; throwsIOException returnuc.getInputStream();publicintgetContentLength()ret

33、urnuc.getContentLength();throwsIOException ;1234567891011121314151617181920212216/312324252627282930313233343536從代碼里可以看到,實(shí)際上是調(diào)用了url.openConnection() 。這樣完整的鏈條就可以連接起來了。注意, URLClassPath這個(gè)類的代碼在JDK 里沒有自帶,在這里看到在 IDE/ 開放目錄啟動(dòng) Spring boot 應(yīng)用在上面只提到在一個(gè)fat jar 里啟動(dòng) Spring boot 應(yīng)用的過程,下面分析IDE 里Spring boot 是如何啟動(dòng)的。在

34、 IDE 里,直接運(yùn)行的Main 函數(shù)是應(yīng)用自己的Main 函數(shù):SpringBootApplicationpublicclassSpringBootDemoApplicationpublicstaticvoidmain(String args) SpringApplication.run(SpringBootDemoApplication.class, args);1234517/3167其實(shí)在 IDE 里啟動(dòng) Spring boot 應(yīng)用是最簡單的一種情況,因?yàn)橐蕾嚨腏ar 都讓IDE 放到 classpath 里了,所以 Spring boot 直接啟動(dòng)就完事了。還有一種情況是在一個(gè)開放

35、目錄下啟動(dòng) Spring boot 啟動(dòng)。所謂的開放目錄就是把 fat jar 解壓,然后直接啟動(dòng)應(yīng)用。java org.springframework.boot.loader.JarLauncher1這時(shí), Spring boot 會判斷當(dāng)前是否在一個(gè)目錄里,如果是的,則構(gòu)造一個(gè)ExplodedArchive (前面在 jar 里時(shí)是 JarFileArchive ),后面的啟動(dòng)流程類似fatjar 的。Embead Tomcat 的啟動(dòng)流程判斷是否在web 環(huán)境spring boot 在啟動(dòng)時(shí),先通過一個(gè)簡單的查找 Servlet 類的方式來判斷是不是在 web 環(huán)境:privatesta

36、ticfinalString WEB_ENVIRONMENT_CLASSES = "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"privatebooleandeduceWebEnvironment() for(String className : WEB_ENVIRONMENT_CLASSES) if(!ClassUtils.isPresent(className,null) returnfalse;returntrue

37、;1218/3134567891011如果是的話,則會創(chuàng)建AnnotationConfigEmbeddedWebApplicationContext,否則 Spring context 就是 AnnotationConfigApplicationContext :/org.springframework.boot.SpringApplicationprotectedConfigurableApplicationContext createApplicationContext() Class<?> contextClass =this.applicationContextClass;

38、if(contextClass =null) trycontextClass = Class.forName(this.webEnvironment? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);catch(ClassNotFoundException ex) thrownew IllegalStateException("Unable create a default ApplicationContext, "+"please specify an ApplicationContextClass"

39、;,ex);return(ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);1234567819/3191011121314151617獲取 EmbeddedServletContainerFactory 的實(shí)現(xiàn)類spring boot 通過獲取 EmbeddedServletContainerFactory 來啟動(dòng)對應(yīng)的 web 服務(wù)器。常用的兩個(gè)實(shí)現(xiàn)類是TomcatEmbeddedServletContainerFactory和JettyEmbeddedServletContainerFactory 。啟

40、動(dòng) Tomcat 的代碼:/TomcatEmbeddedServletContainerFactoryOverridepublicEmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer. initializers) Tomcat tomcat =new Tomcat();File baseDir = (this.baseDirectory !=null?this.baseDirectory: createTempDir("tomcat");tomcat.setBaseDir

41、(baseDir.getAbsolutePath();Connector connector =new Connector(tocol);tomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);tomcat.getHost().setAutoDeploy(false);tomcat.getEngine().setBackgroundProcessorDelay(-1 );for(Connector additionalConn

42、ector :this.additionalTomcatConnectors) tomcat.getService().addConnector(additionalConnector);prepareContext(tomcat.getHost(), initializers);20/31returngetTomcatEmbeddedServletContainer(tomcat);1234567891011121314151617181920會為 tomcat 創(chuàng)建一個(gè)臨時(shí)文件目錄,如:/tmp/tomcat.2233614112516545210.8080 ,做為 tomcat 的 ba

43、sedir 。里面會放 tomcat 的臨時(shí)文件,比如 work 目錄。還會初始化 Tomcat 的一些 Servlet ,比如比較重要的default/jsp servlet :privatevoid addDefaultServlet(Context context) Wrapper defaultServlet = context.createWrapper();defaultServlet.setName("default");defaultServlet.setServletClass("org.apache.catalina.servlets.Defa

44、ultServlet");defaultServlet.addInitParameter("debug" ,"0");defaultServlet.addInitParameter("listings","false");defaultServlet.setLoadOnStartup(1);/ Otherwise the default location of a Spring DispatcherServlet cannot be set21/31defaultServlet.setOverridabl

45、e(true);context.addChild(defaultServlet);context.addServletMapping("/","default");privatevoidaddJspServlet(Context context) Wrapper jspServlet = context.createWrapper();jspServlet.setName("jsp");jspServlet.setServletClass(getJspServletClassName();jspServlet.addInitParam

46、eter("fork","false");jspServlet.setLoadOnStartup(3);context.addChild(jspServlet);context.addServletMapping("*.jsp","jsp");context.addServletMapping("*.jspx","jsp");123456789101112131415161718192021222322/31spring boot 的 web 應(yīng)用如何訪問Resource當(dāng) spring boot 應(yīng)用被打包為一個(gè) fat jar 時(shí),是如何訪問到 web resource 的?實(shí)際上是通過 Archive 提供的 URL,然后通過 Classlo

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論