国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 詳解spring boot應用啟動原理分析

詳解spring boot應用啟動原理分析

2021-05-07 11:29hengyunabc Java教程

這篇文章主要介紹了詳解spring boot應用啟動原理分析,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

前言

本文分析的是spring boot 1.3. 的工作原理。spring boot 1.4. 之后打包結構發現了變化,增加了BOOT-INF目錄,但是基本原理還是不變的。

關于spring boot 1.4.* 里ClassLoader的變化,可以參考:http://www.jfrwli.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 git@github.com:hengyunabc/spring-boot-demo.git
mvn spring-boot-demo
java -jar target/demo-0.0.1-SNAPSHOT.jar

如果使用的IDE是spring sts或者idea,可以通過向導來創建spring boot項目。

也可以參考官方教程:
http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#getting-started-first-application

對spring boot的兩個疑問

剛開始接觸spring boot時,通常會有這些疑問

  1. spring boot如何啟動的?

  2. spring boot embed tomcat是如何工作的? 靜態文件,jsp,網頁模板這些是如何加載到的?

下面來分析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函數。

還有一個Start-Class是com.example.SpringBootDemoApplication,這個是我們應用自己的Main函數。

?
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的概念

  1. archive即歸檔文件,這個概念在linux下比較常見

  2. 通常就是一個tar/zip格式的壓縮包

  3. jar是zip格式

在spring boot里,抽象出了Archive的概念。

一個archive可以是一個jar(JarFileArchive),也可以是一個文件目錄(ExplodedArchive)。可以理解為Spring boot抽象出來的統一訪問資源的層。

上面的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函數,這個實際返回的是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函數是JarLauncher,下面來分析它的工作流程。

JarLauncher類的繼承結構是:

?
1
2
class JarLauncher extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher extends Launcher

以demo-0.0.1-SNAPSHOT.jar創建一個Archive:

JarLauncher先找到自己所在的jar,即demo-0.0.1-SNAPSHOT.jar的路徑,然后創建了一個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,并創建一個LaunchedURLClassLoader

JarLauncher創建好Archive之后,通過getNestedArchives函數來獲取到demo-0.0.1-SNAPSHOT.jar/lib下面的所有jar文件,并創建為List。

注意上面提到,Archive都是有自己的URL的。

獲取到這些Archive的URL之后,也就獲得了一個URL[]數組,用這個來構造一個自定義的ClassLoader:LaunchedURLClassLoader。

創建好ClassLoader之后,再從MANIFEST.MF里讀取到Start-Class,即com.example.SpringBootDemoApplication,然后創建一個新的線程來啟動應用的Main函數。

?
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函數,就可以獲取到Archive里的Resource。當然里面的細節還是很多的,下面再描述。

spring boot應用啟動流程總結

看到這里,可以總結下Spring Boot應用的啟動流程:

  1. spring boot應用打包之后,生成一個fat jar,里面包含了應用依賴的jar包,還有Spring boot loader相關的類

  2. Fat jar的啟動Main函數是JarLauncher,它負責創建一個LaunchedURLClassLoader來加載/lib下面的jar,并以一個新線程啟動應用的Main函數。

spring boot loader里的細節

代碼地址:https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-loader

JarFile URL的擴展

Spring boot能做到以一個fat jar來啟動,最重要的一點是它實現了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:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/com/example/SpringBootDemoApplication.class

 

 

可以看到對于Jar里的資源,定義以'!/‘來分隔。原始的JarFile URL只支持一個'!/‘。

Spring boot擴展了這個協議,讓它支持多個'!/‘,就可以表示jar in jar,jar in directory的資源了。

比如下面的URL表示demo-0.0.1-SNAPSHOT.jar這個jar里lib目錄下面的spring-beans-4.2.3.RELEASE.jar里面的MANIFEST.MF:

 

復制代碼 代碼如下:
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar!/META-INF/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

參考:
https://docs.oracle.com/javase/8/docs/api/java/net/URL.html#URL-java.lang.String-java.lang.String-int-java.lang.String-

Spring boot通過注冊了一個自定義的Handler類來處理多重jar in jar的邏輯。

這個Handler內部會用SoftReference來緩存所有打開過的JarFile。

在處理像下面這樣的URL時,會循環處理'!/‘分隔符,從最上層出發,先構造出demo-0.0.1-SNAPSHOT.jar這個JarFile,再構造出spring-beans-4.2.3.RELEASE.jar這個JarFile,然后再構造出指向MANIFEST.MF的JarURLConnection。

 

復制代碼 代碼如下:
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar!/META-INF/MANIFEST.MF

 

 

?
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,它需要哪些能力?

  1. 查找資源

  2. 讀取資源

對應的API是:

?
1
2
public URL findResource(String name)
public InputStream getResourceAsStream(String name)

上面提到,Spring boot構造LaunchedURLClassLoader時,傳遞了一個URL[]數組。數組里是lib目錄下面的jar的URL。

對于一個URL,JDK或者ClassLoader如何知道怎么讀取到里面的內容的?

實際上流程是這樣子的:

  1. LaunchedURLClassLoader.loadClass

  2. URL.getContent()

  3. URL.openConnection()

  4. Handler.openConnection(URL)

最終調用的是JarURLConnection的getInputStream()函數。

?
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里的內容,整個過程是比較復雜的,總結下:

  1. spring boot注冊了一個Handler來處理”jar:”這種協議的URL

  2. spring boot擴展了JarFile和JarURLConnection,內部處理jar in jar的情況

  3. 在處理多重jar in jar的URL時,spring boot會循環處理,并緩存已經加載到的JarFile

  4. 對于多重jar in jar,實際上是解壓到了臨時目錄來處理,可以參考JarFileArchive里的代碼

  5. 在獲取URL的InputStream時,最終獲取到的是JarFile里的JarEntryData

這里面的細節很多,只列出比較重要的一些點。

然后,URLClassLoader是如何getResource的呢?

URLClassLoader在構造時,有URL[]數組參數,它內部會用這個數組來構造一個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函數是應用自己的Main函數:

?
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環境

spring boot在啟動時,先通過一個簡單的查找Servlet類的方式來判斷是不是在web環境:

?
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;
}

如果是的話,則會創建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的實現類

spring boot通過獲取EmbeddedServletContainerFactory來啟動對應的web服務器。

常用的兩個實現類是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創建一個臨時文件目錄,如:
/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的能力來實現的。

index.html

比如需要配置一個index.html,這個可以直接放在代碼里的src/main/resources/static目錄下。

對于index.html歡迎頁,spring boot在初始化時,就會創建一個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目錄下。但這個實際上是模板的實現類自己處理的。比如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里統一的錯誤頁面的處理

對于錯誤頁面,Spring boot也是通過創建一個BasicErrorController來統一處理的。

?
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的這個做法很好,避免了傳統的web應用來出錯時,默認拋出異常,容易泄密。

spring boot應用的maven打包過程

先通過maven-shade-plugin生成一個包含依賴的jar,再通過spring-boot-maven-plugin插件把spring boot loader相關的類,還有MANIFEST.MF打包到jar里。

spring boot里有顏色日志的實現

當在shell里啟動spring boot應用時,會發現它的logger輸出是有顏色的,這個特性很有意思。

可以通過這個設置來關閉:

?
1
spring.output.ansi.enabled=false

原理是通過AnsiOutputApplicationListener ,這個來獲取這個配置,然后設置logback在輸出時,加了一個 ColorConverter,通過org.springframework.boot.ansi.AnsiOutput ,對一些字段進行了渲染。

一些代碼小技巧

實現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函數

通過堆棧里獲取的方式,判斷main函數,找到原始啟動的main函數。

?
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的
  • 數據目錄不知道放哪, jenkinns的做法是放到 ${user.home}/.jenkins 下面
  • 相對目錄API不能使用,servletContext.getRealPath(“/“) 返回的是NULL
  • spring boot應用喜歡把配置都寫到代碼里,有時會帶來混亂。一些簡單可以用xml來表達的配置可能會變得難讀,而且凌亂。

總結

spring boot通過擴展了jar協議,抽象出Archive概念,和配套的JarFile,JarUrlConnection,LaunchedURLClassLoader,從而實現了上層應用無感知的all in one的開發體驗。盡管Executable war并不是spring提出的概念,但spring boot讓它發揚光大。

spring boot是一個驚人的項目,可以說是spring的第二春,spring-cloud-config, spring-session, metrics, remote shell等都是深愛開發者喜愛的項目、特性。幾乎可以肯定設計者是有豐富的一線開發經驗,深知開發人員的痛點。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:http://hengyunabc.github.io/spring-boot-application-start-analysis/

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 成人aⅴ视频| 国产精品视频区 | 一级黄色片在线 | 在线欧美视频 | 久久成人18免费网站 | 中文字幕一区二区三区日韩精品 | 91视频免费看 | 亚洲成人一区二区三区 | 欧美视频免费在线 | 国产一级黄 | 婷婷天堂 | 久久午夜剧场 | 国产精品视频网 | 欧美日韩视频在线 | 国产午夜精品久久久久久久 | 亚洲综合中文字幕在线观看 | 综合另类 | 久久国产精品免费一区二区三区 | 日韩av成人 | 黄色在线观看 | 精品久久久久一区二区国产 | 日韩国产在线观看 | 久久亚洲精品中文字幕 | 国产男女做爰免费网站 | 成年人在线免费观看视频网站 | 黄色在线免费 | 亚洲电影一区 | 综合久久综合久久 | 人人干日日干 | 亚洲成人免费在线播放 | 欧美午夜一区二区三区免费大片 | 日韩成人精品 | 亚洲成人精品一区 | 91亚洲国产成人久久精品网站 | 少妇看av一二三区 | 成人av免费观看 | 欧美日韩一级在线观看 | 天堂视频在线 | 最新国产精品 | 久久久久久国产精品美女 | 在线黄色网 |