溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

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

發(fā)布時間:2020-09-29 18:38:02 來源:腳本之家 閱讀:212 作者:hengyunabc 欄目:編程語言

前言

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

關于spring boot 1.4.* 里ClassLoader的變化,可以參考:https://www.jb51.net/article/141479.htm

spring boot quick start

在spring boot里,很吸引人的一個特性是可以直接把應用打包成為一個jar/war,然后這個jar/war是可以直接啟動的,不需要另外配置一個Web Server。

如果之前沒有使用過spring boot可以通過下面的demo來感受下。

下面以這個工程為例,演示如何啟動Spring boot項目:

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,可以通過向導來創(chuàng)建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是如何工作的? 靜態(tài)文件,jsp,網(wǎng)頁模板這些是如何加載到的?

下面來分析spring boot是如何做到的。

打包為單個jar時,spring boot的啟動方式

maven打包之后,會生成兩個jar文件:

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打好的包的目錄結構(不重要的省略掉):

├── 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
    ├── ...

依次來看下這些內(nèi)容。

MANIFEST.MF

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ù)。

@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抽象出來的統(tǒng)一訪問資源的層。

上面的demo-0.0.1-SNAPSHOT.jar 是一個Archive,然后demo-0.0.1-SNAPSHOT.jar里的/lib目錄下面的每一個Jar包,也是一個Archive。

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,比如:

jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/

還有一個getNestedArchives函數(shù),這個實際返回的是demo-0.0.1-SNAPSHOT.jar/lib下面的jar的Archive列表。它們的URL是:

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類的繼承結構是:

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。

下面的代碼展示了如何從一個類找到它的加載的位置的技巧:

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ù)。

/**
 * 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應用的啟動流程:

  1. spring boot應用打包之后,生成一個fat jar,里面包含了應用依賴的jar包,還有Spring boot loader相關的類
  2. 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是這樣子的:

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擴展了這個協(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:

復制代碼 代碼如下:
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。

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內(nèi)部會用SoftReference來緩存所有打開過的JarFile。

在處理像下面這樣的URL時,會循環(huán)處理'!/‘分隔符,從最上層出發(fā),先構造出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

//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是:

public URL findResource(String name)
public InputStream getResourceAsStream(String name)

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

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

實際上流程是這樣子的:

  1. LaunchedURLClassLoader.loadClass
  2. URL.getContent()
  3. URL.openConnection()
  4. Handler.openConnection(URL)

最終調(diào)用的是JarURLConnection的getInputStream()函數(shù)。

//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里的內(nèi)容,整個過程是比較復雜的,總結下:

  1. spring boot注冊了一個Handler來處理”jar:”這種協(xié)議的URL
  2. spring boot擴展了JarFile和JarURLConnection,內(nèi)部處理jar in jar的情況
  3. 在處理多重jar in jar的URL時,spring boot會循環(huán)處理,并緩存已經(jīng)加載到的JarFile
  4. 對于多重jar in jar,實際上是解壓到了臨時目錄來處理,可以參考JarFileArchive里的代碼
  5. 在獲取URL的InputStream時,最終獲取到的是JarFile里的JarEntryData

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

然后,URLClassLoader是如何getResource的呢?

URLClassLoader在構造時,有URL[]數(shù)組參數(shù),它內(nèi)部會用這個數(shù)組來構造一個URLClassPath:

URLClassPath ucp = new URLClassPath(urls);

在 URLClassPath 內(nèi)部會為這些URLS 都構造一個Loader,然后在getResource時,會從這些Loader里一個個去嘗試獲取。
如果獲取成功的話,就像下面那樣包裝為一個Resource。

 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();
  }
 };
}

從代碼里可以看到,實際上是調(diào)用了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ù):

@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解壓,然后直接啟動應用。

java org.springframework.boot.loader.JarLauncher

這時,Spring boot會判斷當前是否在一個目錄里,如果是的,則構造一個ExplodedArchive(前面在jar里時是JarFileArchive),后面的啟動流程類似fat jar的。

Embead Tomcat的啟動流程

判斷是否在web環(huán)境

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

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:

//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的代碼:

//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:

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來處理:

//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/" };
 
//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類里的:

public static final String DEFAULT_PREFIX = "classpath:/templates/";

jsp

jsp頁面和template類似。實際上是通過spring mvc內(nèi)置的JstlView來處理的。

可以通過配置spring.view.prefix來設定jsp頁面的目錄:

spring.view.prefix: /WEB-INF/jsp/

spring boot里統(tǒng)一的錯誤頁面的處理

對于錯誤頁面,Spring boot也是通過創(chuàng)建一個BasicErrorController來統(tǒng)一處理的。

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController

對應的View是一個簡單的HTML提醒:

@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><h2>Whitelabel Error Page</h2>"
  + "<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輸出是有顏色的,這個特性很有意思。

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

spring.output.ansi.enabled=false

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

一些代碼小技巧

實現(xiàn)ClassLoader時,支持JDK7并行加載

可以參考LaunchedURLClassLoader里的LockProvider

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:”的前綴。

private static final String JAVA_AGENT_PREFIX = "-javaagent:";

獲取進程的PID

ApplicationPid,可以獲取PID。

 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ù)。

 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方式運行時,會遇到一些問題。以下是個人看法:

  1. 日志不知道放哪,默認是輸出到stdout的
  2. 數(shù)據(jù)目錄不知道放哪, jenkinns的做法是放到 ${user.home}/.jenkins 下面
  3. 相對目錄API不能使用,servletContext.getRealPath(“/“) 返回的是NULL
  4. 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ā)人員的痛點。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI