前言
本文分析的是spring boot 1.3. 的工作原理。spring boot 1.4. 之后打包結構發(fā)現(xiàn)了變化,增加了BOOT-INF目錄,但是基本原理還是不變的。
關于spring boot 1.4.* 里ClassLoader的變化,可以參考:http://www.ythuaji.com.cn/article/161572.html
spring boot quick start
在spring boot里,很吸引人的一個特性是可以直接把應用打包成為一個jar/war,然后這個jar/war是可以直接啟動的,不需要另外配置一個Web Server。
如果之前沒有使用過spring boot可以通過下面的demo來感受下。
下面以這個工程為例,演示如何啟動Spring boot項目:
1
2
3
|
git clone [email protected]:hengyunabc/spring-boot-demo.git mvn spring-boot-demo java -jar target/demo-0.0.1-SNAPSHOT.jar |
如果使用的IDE是spring sts或者idea,可以通過向導來創(chuàng)建spring boot項目。
對spring boot的兩個疑問
剛開始接觸spring boot時,通常會有這些疑問
-
spring boot如何啟動的?
-
spring boot embed tomcat是如何工作的? 靜態(tài)文件,jsp,網(wǎng)頁模板這些是如何加載到的?
下面來分析spring boot是如何做到的。
打包為單個jar時,spring boot的啟動方式
maven打包之后,會生成兩個jar文件:
1
2
|
demo-0.0.1-SNAPSHOT.jar demo-0.0.1-SNAPSHOT.jar.original |
其中demo-0.0.1-SNAPSHOT.jar.original是默認的maven-jar-plugin生成的包。
demo-0.0.1-SNAPSHOT.jar是spring boot maven插件生成的jar包,里面包含了應用的依賴,以及spring boot相關的類。下面稱之為fat jar。
先來查看spring boot打好的包的目錄結構(不重要的省略掉):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
├── META-INF │ ├── MANIFEST.MF ├── application.properties ├── com │ └── example │ └── SpringBootDemoApplication.class ├── lib │ ├── aopalliance-1.0.jar │ ├── spring-beans-4.2.3.RELEASE.jar │ ├── ... └── org └── springframework └── boot └── loader ├── ExecutableArchiveLauncher.class ├── JarLauncher.class ├── JavaAgentDetector.class ├── LaunchedURLClassLoader.class ├── Launcher.class ├── MainMethodRunner.class ├── ... |
依次來看下這些內容。
MANIFEST.MF
1
2
3
4
5
6
7
8
|
Manifest-Version: 1.0 Start-Class: com.example.SpringBootDemoApplication Implementation-Vendor-Id: com.example Spring-Boot-Version: 1.3.0.RELEASE Created-By: Apache Maven 3.3.3 Build-Jdk: 1.8.0_60 Implementation-Vendor: Pivotal Software, Inc. Main-Class: org.springframework.boot.loader.JarLauncher |
可以看到有Main-Class是org.springframework.boot.loader.JarLauncher ,這個是jar啟動的Main函數(shù)。
還有一個Start-Class是com.example.SpringBootDemoApplication,這個是我們應用自己的Main函數(shù)。
1
2
3
4
5
6
7
|
@SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication. class , args); } } |
com/example 目錄
這下面放的是應用的.class文件。
lib目錄
這里存放的是應用的Maven依賴的jar包文件。
比如spring-beans,spring-mvc等jar。
org/springframework/boot/loader 目錄
這下面存放的是Spring boot loader的.class文件。
Archive的概念
-
archive即歸檔文件,這個概念在linux下比較常見
-
通常就是一個tar/zip格式的壓縮包
-
jar是zip格式
在spring boot里,抽象出了Archive的概念。
一個archive可以是一個jar(JarFileArchive),也可以是一個文件目錄(ExplodedArchive)。可以理解為Spring boot抽象出來的統(tǒng)一訪問資源的層。
上面的demo-0.0.1-SNAPSHOT.jar 是一個Archive,然后demo-0.0.1-SNAPSHOT.jar里的/lib目錄下面的每一個Jar包,也是一個Archive。
1
2
3
4
5
|
public abstract class Archive { public abstract URL getUrl(); public String getMainClass(); public abstract Collection<Entry> getEntries(); public abstract List<Archive> getNestedArchives(EntryFilter filter); |
可以看到Archive有一個自己的URL,比如:
1
|
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/ |
還有一個getNestedArchives函數(shù),這個實際返回的是demo-0.0.1-SNAPSHOT.jar/lib下面的jar的Archive列表。它們的URL是:
1
2
|
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/aopalliance-1.0.jar jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar |
JarLauncher
從MANIFEST.MF可以看到Main函數(shù)是JarLauncher,下面來分析它的工作流程。
JarLauncher類的繼承結構是:
1
2
|
class JarLauncher extends ExecutableArchiveLauncher class ExecutableArchiveLauncher extends Launcher |
以demo-0.0.1-SNAPSHOT.jar創(chuàng)建一個Archive:
JarLauncher先找到自己所在的jar,即demo-0.0.1-SNAPSHOT.jar的路徑,然后創(chuàng)建了一個Archive。
下面的代碼展示了如何從一個類找到它的加載的位置的技巧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
protected final Archive createArchive() throws Exception { ProtectionDomain protectionDomain = getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); String path = (location == null ? null : location.getSchemeSpecificPart()); if (path == null ) { throw new IllegalStateException( "Unable to determine code source archive" ); } File root = new File(path); if (!root.exists()) { throw new IllegalStateException( "Unable to determine code source archive from " + root); } return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); } |
獲取lib/下面的jar,并創(chuàng)建一個LaunchedURLClassLoader
JarLauncher創(chuàng)建好Archive之后,通過getNestedArchives函數(shù)來獲取到demo-0.0.1-SNAPSHOT.jar/lib下面的所有jar文件,并創(chuàng)建為List。
注意上面提到,Archive都是有自己的URL的。
獲取到這些Archive的URL之后,也就獲得了一個URL[]數(shù)組,用這個來構造一個自定義的ClassLoader:LaunchedURLClassLoader。
創(chuàng)建好ClassLoader之后,再從MANIFEST.MF里讀取到Start-Class,即com.example.SpringBootDemoApplication,然后創(chuàng)建一個新的線程來啟動應用的Main函數(shù)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/** * Launch the application given the archive file and a fully configured classloader. */ protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { Runnable runner = createMainMethodRunner(mainClass, args, classLoader); Thread runnerThread = new Thread(runner); runnerThread.setContextClassLoader(classLoader); runnerThread.setName(Thread.currentThread().getName()); runnerThread.start(); } /** * Create the {@code MainMethodRunner} used to launch the application. */ protected Runnable createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) throws Exception { Class<?> runnerClass = classLoader.loadClass(RUNNER_CLASS); Constructor<?> constructor = runnerClass.getConstructor(String. class , String[]. class ); return (Runnable) constructor.newInstance(mainClass, args); } |
LaunchedURLClassLoader
LaunchedURLClassLoader和普通的URLClassLoader的不同之處是,它提供了從Archive里加載.class的能力。
結合Archive提供的getEntries函數(shù),就可以獲取到Archive里的Resource。當然里面的細節(jié)還是很多的,下面再描述。
spring boot應用啟動流程總結
看到這里,可以總結下Spring Boot應用的啟動流程:
-
spring boot應用打包之后,生成一個fat jar,里面包含了應用依賴的jar包,還有Spring boot loader相關的類
-
Fat jar的啟動Main函數(shù)是JarLauncher,它負責創(chuàng)建一個LaunchedURLClassLoader來加載/lib下面的jar,并以一個新線程啟動應用的Main函數(shù)。
spring boot loader里的細節(jié)
代碼地址:https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-loader
JarFile URL的擴展
Spring boot能做到以一個fat jar來啟動,最重要的一點是它實現(xiàn)了jar in jar的加載方式。
JDK原始的JarFile URL的定義可以參考這里:
http://docs.oracle.com/javase/7/docs/api/java/net/JarURLConnection.html
原始的JarFile URL是這樣子的:
1
|
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/ |
jar包里的資源的URL:
可以看到對于Jar里的資源,定義以'!/‘來分隔。原始的JarFile URL只支持一個'!/‘。
Spring boot擴展了這個協(xié)議,讓它支持多個'!/‘,就可以表示jar in jar,jar in directory的資源了。
比如下面的URL表示demo-0.0.1-SNAPSHOT.jar這個jar里lib目錄下面的spring-beans-4.2.3.RELEASE.jar里面的MANIFEST.MF:
自定義URLStreamHandler,擴展JarFile和JarURLConnection
在構造一個URL時,可以傳遞一個Handler,而JDK自帶有默認的Handler類,應用可以自己注冊Handler來處理自定義的URL。
1
2
3
4
5
6
|
public URL(String protocol, String host, int port, String file, URLStreamHandler handler) throws MalformedURLException |
Spring boot通過注冊了一個自定義的Handler類來處理多重jar in jar的邏輯。
這個Handler內部會用SoftReference來緩存所有打開過的JarFile。
在處理像下面這樣的URL時,會循環(huán)處理'!/‘分隔符,從最上層出發(fā),先構造出demo-0.0.1-SNAPSHOT.jar這個JarFile,再構造出spring-beans-4.2.3.RELEASE.jar這個JarFile,然后再構造出指向MANIFEST.MF的JarURLConnection。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
//org.springframework.boot.loader.jar.Handler public class Handler extends URLStreamHandler { private static final String SEPARATOR = "!/" ; private static SoftReference<Map<File, JarFile>> rootFileCache; @Override protected URLConnection openConnection(URL url) throws IOException { if ( this .jarFile != null ) { return new JarURLConnection(url, this .jarFile); } try { return new JarURLConnection(url, getRootJarFileFromUrl(url)); } catch (Exception ex) { return openFallbackConnection(url, ex); } } public JarFile getRootJarFileFromUrl(URL url) throws IOException { String spec = url.getFile(); int separatorIndex = spec.indexOf(SEPARATOR); if (separatorIndex == - 1 ) { throw new MalformedURLException( "Jar URL does not contain !/ separator" ); } String name = spec.substring( 0 , separatorIndex); return getRootJarFile(name); } |
ClassLoader如何讀取到Resource
對于一個ClassLoader,它需要哪些能力?
-
查找資源
-
讀取資源
對應的API是:
1
2
|
public URL findResource(String name) public InputStream getResourceAsStream(String name) |
上面提到,Spring boot構造LaunchedURLClassLoader時,傳遞了一個URL[]數(shù)組。數(shù)組里是lib目錄下面的jar的URL。
對于一個URL,JDK或者ClassLoader如何知道怎么讀取到里面的內容的?
實際上流程是這樣子的:
-
LaunchedURLClassLoader.loadClass
-
URL.getContent()
-
URL.openConnection()
-
Handler.openConnection(URL)
最終調用的是JarURLConnection的getInputStream()函數(shù)。
1
2
3
4
5
6
7
8
9
|
//org.springframework.boot.loader.jar.JarURLConnection @Override public InputStream getInputStream() throws IOException { connect(); if ( this .jarEntryName.isEmpty()) { throw new IOException( "no entry name specified" ); } return this .jarEntryData.getInputStream(); } |
從一個URL,到最終讀取到URL里的內容,整個過程是比較復雜的,總結下:
-
spring boot注冊了一個Handler來處理”jar:”這種協(xié)議的URL
-
spring boot擴展了JarFile和JarURLConnection,內部處理jar in jar的情況
-
在處理多重jar in jar的URL時,spring boot會循環(huán)處理,并緩存已經(jīng)加載到的JarFile
-
對于多重jar in jar,實際上是解壓到了臨時目錄來處理,可以參考JarFileArchive里的代碼
-
在獲取URL的InputStream時,最終獲取到的是JarFile里的JarEntryData
這里面的細節(jié)很多,只列出比較重要的一些點。
然后,URLClassLoader是如何getResource的呢?
URLClassLoader在構造時,有URL[]數(shù)組參數(shù),它內部會用這個數(shù)組來構造一個URLClassPath:
1
|
URLClassPath ucp = new URLClassPath(urls); |
在 URLClassPath 內部會為這些URLS 都構造一個Loader,然后在getResource時,會從這些Loader里一個個去嘗試獲取。
如果獲取成功的話,就像下面那樣包裝為一個Resource。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
Resource getResource( final String name, boolean check) { final URL url; try { url = new URL(base, ParseUtil.encodePath(name, false )); } catch (MalformedURLException e) { throw new IllegalArgumentException( "name" ); } final URLConnection uc; try { if (check) { URLClassPath.check(url); } uc = url.openConnection(); InputStream in = uc.getInputStream(); if (uc instanceof JarURLConnection) { /* Need to remember the jar file so it can be closed * in a hurry. */ JarURLConnection juc = (JarURLConnection)uc; jarfile = JarLoader.checkJar(juc.getJarFile()); } } catch (Exception e) { return null ; } return new Resource() { public String getName() { return name; } public URL getURL() { return url; } public URL getCodeSourceURL() { return base; } public InputStream getInputStream() throws IOException { return uc.getInputStream(); } public int getContentLength() throws IOException { return uc.getContentLength(); } }; } |
從代碼里可以看到,實際上是調用了url.openConnection()。這樣完整的鏈條就可以連接起來了。
注意,URLClassPath這個類的代碼在JDK里沒有自帶,在這里看到 http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/misc/URLClassPath.java#506
在IDE/開放目錄啟動Spring boot應用
在上面只提到在一個fat jar里啟動Spring boot應用的過程,下面分析IDE里Spring boot是如何啟動的。
在IDE里,直接運行的Main函數(shù)是應用自己的Main函數(shù):
1
2
3
4
5
6
7
|
@SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication. class , args); } } |
其實在IDE里啟動Spring boot應用是最簡單的一種情況,因為依賴的Jar都讓IDE放到classpath里了,所以Spring boot直接啟動就完事了。
還有一種情況是在一個開放目錄下啟動Spring boot啟動。所謂的開放目錄就是把fat jar解壓,然后直接啟動應用。
1
|
java org.springframework.boot.loader.JarLauncher |
這時,Spring boot會判斷當前是否在一個目錄里,如果是的,則構造一個ExplodedArchive(前面在jar里時是JarFileArchive),后面的啟動流程類似fat jar的。
Embead Tomcat的啟動流程
判斷是否在web環(huán)境
spring boot在啟動時,先通過一個簡單的查找Servlet類的方式來判斷是不是在web環(huán)境:
1
2
3
4
5
6
7
8
9
10
11
|
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet" , "org.springframework.web.context.ConfigurableWebApplicationContext" }; private boolean deduceWebEnvironment() { for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null )) { return false ; } } return true ; } |
如果是的話,則會創(chuàng)建AnnotationConfigEmbeddedWebApplicationContext,否則Spring context就是AnnotationConfigApplicationContext:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//org.springframework.boot.SpringApplication protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this .applicationContextClass; if (contextClass == null ) { try { contextClass = Class.forName( this .webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass" , ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); } |
獲取EmbeddedServletContainerFactory的實現(xiàn)類
spring boot通過獲取EmbeddedServletContainerFactory來啟動對應的web服務器。
常用的兩個實現(xiàn)類是TomcatEmbeddedServletContainerFactory和JettyEmbeddedServletContainerFactory。
啟動Tomcat的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//TomcatEmbeddedServletContainerFactory @Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); File baseDir = ( this .baseDirectory != null ? this .baseDirectory : createTempDir( "tomcat" )); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector( this .protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy( false ); tomcat.getEngine().setBackgroundProcessorDelay(- 1 ); for (Connector additionalConnector : this .additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); return getTomcatEmbeddedServletContainer(tomcat); } |
會為tomcat創(chuàng)建一個臨時文件目錄,如:
/tmp/tomcat.2233614112516545210.8080,做為tomcat的basedir。里面會放tomcat的臨時文件,比如work目錄。
還會初始化Tomcat的一些Servlet,比如比較重要的default/jsp servlet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private void addDefaultServlet(Context context) { Wrapper defaultServlet = context.createWrapper(); defaultServlet.setName( "default" ); defaultServlet.setServletClass( "org.apache.catalina.servlets.DefaultServlet" ); defaultServlet.addInitParameter( "debug" , "0" ); defaultServlet.addInitParameter( "listings" , "false" ); defaultServlet.setLoadOnStartup( 1 ); // Otherwise the default location of a Spring DispatcherServlet cannot be set defaultServlet.setOverridable( true ); context.addChild(defaultServlet); context.addServletMapping( "/" , "default" ); } private void addJspServlet(Context context) { Wrapper jspServlet = context.createWrapper(); jspServlet.setName( "jsp" ); jspServlet.setServletClass(getJspServletClassName()); jspServlet.addInitParameter( "fork" , "false" ); jspServlet.setLoadOnStartup( 3 ); context.addChild(jspServlet); context.addServletMapping( "*.jsp" , "jsp" ); context.addServletMapping( "*.jspx" , "jsp" ); } |
spring boot的web應用如何訪問Resource
當spring boot應用被打包為一個fat jar時,是如何訪問到web resource的?
實際上是通過Archive提供的URL,然后通過Classloader提供的訪問classpath resource的能力來實現(xiàn)的。
index.html
比如需要配置一個index.html,這個可以直接放在代碼里的src/main/resources/static目錄下。
對于index.html歡迎頁,spring boot在初始化時,就會創(chuàng)建一個ViewController來處理:
1
2
3
4
5
6
7
8
9
|
//ResourceProperties public class ResourceProperties implements ResourceLoaderAware { private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" }; private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/" , "classpath:/resources/" , "classpath:/static/" , "classpath:/public/" }; |
1
2
3
4
5
6
7
8
9
|
//WebMvcAutoConfigurationAdapter @Override public void addViewControllers(ViewControllerRegistry registry) { Resource page = this .resourceProperties.getWelcomePage(); if (page != null ) { logger.info( "Adding welcome page: " + page); registry.addViewController( "/" ).setViewName( "forward:index.html" ); } } |
template
像頁面模板文件可以放在src/main/resources/template目錄下。但這個實際上是模板的實現(xiàn)類自己處理的。比如ThymeleafProperties類里的:
1
|
public static final String DEFAULT_PREFIX = "classpath:/templates/" ; |
jsp
jsp頁面和template類似。實際上是通過spring mvc內置的JstlView來處理的。
可以通過配置spring.view.prefix來設定jsp頁面的目錄:
1
|
spring.view.prefix: /WEB-INF/jsp/ |
spring boot里統(tǒng)一的錯誤頁面的處理
對于錯誤頁面,Spring boot也是通過創(chuàng)建一個BasicErrorController來統(tǒng)一處理的。
1
2
3
|
@Controller @RequestMapping ( "${server.error.path:${error.path:/error}}" ) public class BasicErrorController extends AbstractErrorController |
對應的View是一個簡單的HTML提醒:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Configuration @ConditionalOnProperty (prefix = "server.error.whitelabel" , name = "enabled" , matchIfMissing = true ) @Conditional (ErrorTemplateMissingCondition. class ) protected static class WhitelabelErrorViewConfiguration { private final SpelView defaultErrorView = new SpelView( "<html><body><h1>Whitelabel Error Page</h1>" + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>" + "<div id='created'>${timestamp}</div>" + "<div>There was an unexpected error (type=${error}, status=${status}).</div>" + "<div>${message}</div></body></html>" ); @Bean (name = "error" ) @ConditionalOnMissingBean (name = "error" ) public View defaultErrorView() { return this .defaultErrorView; } |
spring boot的這個做法很好,避免了傳統(tǒng)的web應用來出錯時,默認拋出異常,容易泄密。
spring boot應用的maven打包過程
先通過maven-shade-plugin生成一個包含依賴的jar,再通過spring-boot-maven-plugin插件把spring boot loader相關的類,還有MANIFEST.MF打包到jar里。
spring boot里有顏色日志的實現(xiàn)
當在shell里啟動spring boot應用時,會發(fā)現(xiàn)它的logger輸出是有顏色的,這個特性很有意思。
可以通過這個設置來關閉:
1
|
spring.output.ansi.enabled=false |
原理是通過AnsiOutputApplicationListener ,這個來獲取這個配置,然后設置logback在輸出時,加了一個 ColorConverter,通過org.springframework.boot.ansi.AnsiOutput ,對一些字段進行了渲染。
一些代碼小技巧
實現(xiàn)ClassLoader時,支持JDK7并行加載
可以參考LaunchedURLClassLoader里的LockProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public class LaunchedURLClassLoader extends URLClassLoader { private static LockProvider LOCK_PROVIDER = setupLockProvider(); private static LockProvider setupLockProvider() { try { ClassLoader.registerAsParallelCapable(); return new Java7LockProvider(); } catch (NoSuchMethodError ex) { return new LockProvider(); } } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (LaunchedURLClassLoader.LOCK_PROVIDER.getLock( this , name)) { Class<?> loadedClass = findLoadedClass(name); if (loadedClass == null ) { Handler.setUseFastConnectionExceptions( true ); try { loadedClass = doLoadClass(name); } finally { Handler.setUseFastConnectionExceptions( false ); } } if (resolve) { resolveClass(loadedClass); } return loadedClass; } } |
檢測jar包是否通過agent加載的
InputArgumentsJavaAgentDetector,原理是檢測jar的URL是否有”-javaagent:”的前綴。
1
|
private static final String JAVA_AGENT_PREFIX = "-javaagent:" ; |
獲取進程的PID
ApplicationPid,可以獲取PID。
1
2
3
4
5
6
7
8
9
|
private String getPid() { try { String jvmName = ManagementFactory.getRuntimeMXBean().getName(); return jvmName.split( "@" )[ 0 ]; } catch (Throwable ex) { return null ; } } |
包裝Logger類
spring boot里自己包裝了一套logger,支持java, log4j, log4j2, logback,以后有需要自己包裝logger時,可以參考這個。
在org.springframework.boot.logging包下面。
獲取原始啟動的main函數(shù)
通過堆棧里獲取的方式,判斷main函數(shù),找到原始啟動的main函數(shù)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private Class<?> deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ( "main" .equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null ; } |
spirng boot的一些缺點:
當spring boot應用以一個fat jar方式運行時,會遇到一些問題。以下是個人看法:
- 日志不知道放哪,默認是輸出到stdout的
- 數(shù)據(jù)目錄不知道放哪, jenkinns的做法是放到 ${user.home}/.jenkins 下面
- 相對目錄API不能使用,servletContext.getRealPath(“/“) 返回的是NULL
- spring boot應用喜歡把配置都寫到代碼里,有時會帶來混亂。一些簡單可以用xml來表達的配置可能會變得難讀,而且凌亂。
總結
spring boot通過擴展了jar協(xié)議,抽象出Archive概念,和配套的JarFile,JarUrlConnection,LaunchedURLClassLoader,從而實現(xiàn)了上層應用無感知的all in one的開發(fā)體驗。盡管Executable war并不是spring提出的概念,但spring boot讓它發(fā)揚光大。
spring boot是一個驚人的項目,可以說是spring的第二春,spring-cloud-config, spring-session, metrics, remote shell等都是深愛開發(fā)者喜愛的項目、特性。幾乎可以肯定設計者是有豐富的一線開發(fā)經(jīng)驗,深知開發(fā)人員的痛點。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://hengyunabc.github.io/spring-boot-application-start-analysis/