spring boot應用啓動原理分析(轉載)

摘要: spring boot quick start 在spring boot裏,很吸引人的一個特性是能夠直接把應用打包成爲一個jar/war,而後這個jar/war是能夠直接啓動的,不須要另外配置一個Web Server。html

spring boot quick start
在spring boot裏,很吸引人的一個特性是能夠直接把應用打包成爲一個jar/war,而後這個jar/war是能夠直接啓動的,不須要另外配置一個Web Server。java

若是以前沒有使用過spring boot能夠經過下面的demo來感覺下。
下面以這個工程爲例,演示如何啓動Spring boot項目:linux

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項目。git

也能夠參考官方教程:
http://docs.spring.io/spring-...github

對spring boot的兩個疑問
剛開始接觸spring boot時,一般會有這些疑問web

spring boot如何啓動的?
spring boot embed tomcat是如何工做的? 靜態文件,jsp,網頁模板這些是如何加載到的?
下面來分析spring boot是如何作到的。spring

打包爲單個jar時,spring boot的啓動方式
maven打包以後,會生成兩個jar文件:shell

demo-0.0.1-SNAPSHOT.jar
demo-0.0.1-SNAPSHOT.jar.original
其中demo-0.0.1-SNAPSHOT.jar.original是默認的maven-jar-plugin生成的包。apache

demo-0.0.1-SNAPSHOT.jar是spring boot maven插件生成的jar包,裏面包含了應用的依賴,以及spring boot相關的類。下面稱之爲fat jar。api

先來查看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
            ├── ...

依次來看下這些內容。

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函數。

還有一個Start-Class是com.example.SpringBootDemoApplication,這個是咱們應用本身的Main函數。

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

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

JarLauncher類的繼承結構是:

class JarLauncher extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher extends Launcher
以demo-0.0.1-SNAPSHOT.jar建立一個Archive:
JarLauncher先找到本身所在的jar,即demo-0.0.1-SNAPSHOT.jar的路徑,而後建立了一個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,並建立一個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函數。

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

spring boot應用打包以後,生成一個fat jar,裏面包含了應用依賴的jar包,還有Spring boot loader相關的類
Fat jar的啓動Main函數是JarLauncher,它負責建立一個LaunchedURLClassLoader來加載/lib下面的jar,並以一個新線程啓動應用的Main函數。
spring boot loader裏的細節
代碼地址:https://github.com/spring-pro...

JarFile URL的擴展
Spring boot能作到以一個fat jar來啓動,最重要的一點是它實現了jar in jar的加載方式。

JDK原始的JarFile URL的定義能夠參考這裏:

http://docs.oracle.com/javase...

原始的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擴展了這個協議,讓它支持多個’!/’,就能夠表示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/javas...

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

public URL findResource(String name)
public InputStream getResourceAsStream(String name)
上面提到,Spring boot構造LaunchedURLClassLoader時,傳遞了一個URL[]數組。數組裏是lib目錄下面的jar的URL。

對於一個URL,JDK或者ClassLoader如何知道怎麼讀取到裏面的內容的?

實際上流程是這樣子的:

LaunchedURLClassLoader.loadClass
URL.getContent()
URL.openConnection()
Handler.openConnection(URL)
最終調用的是JarURLConnection的getInputStream()函數。

//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:」這種協議的URL
spring boot擴展了JarFile和JarURLConnection,內部處理jar in jar的狀況
在處理多重jar in jar的URL時,spring boot會循環處理,並緩存已經加載到的JarFile
對於多重jar in jar,其實是解壓到了臨時目錄來處理,能夠參考JarFileArchive裏的代碼
在獲取URL的InputStream時,最終獲取到的是JarFile裏的JarEntryData
這裏面的細節不少,只列出比較重要的一些點。

而後,URLClassLoader是如何getResource的呢?

URLClassLoader在構造時,有URL[]數組參數,它內部會用這個數組來構造一個URLClassPath:

URLClassPath ucp = new URLClassPath(urls);
在 URLClassPath 內部會爲這些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();
    }
};

}
從代碼裏能夠看到,其實是調用了url.openConnection()。這樣完整的鏈條就能夠鏈接起來了。

注意,URLClassPath這個類的代碼在JDK裏沒有自帶,在這裏看到 http://grepcode.com/file/repo...

在IDE/開放目錄啓動Spring boot應用
在上面只提到在一個fat jar裏啓動Spring boot應用的過程,下面分析IDE裏Spring boot是如何啓動的。

在IDE裏,直接運行的Main函數是應用本身的Main函數:

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

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

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:

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

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

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

//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目錄下。但這個其實是模板的實現類本身處理的。好比ThymeleafProperties類裏的:

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

jsp頁面和template相似。其實是經過spring mvc內置的JstlView來處理的。

能夠經過配置spring.view.prefix來設定jsp頁面的目錄:

spring.view.prefix: /WEB-INF/jsp/
spring boot裏統一的錯誤頁面的處理
對於錯誤頁面,Spring boot也是經過建立一個BasicErrorController來統一處理的。

@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><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輸出是有顏色的,這個特性頗有意思。

能夠經過這個設置來關閉:

spring.output.ansi.enabled=false
原理是經過AnsiOutputApplicationListener ,這個來獲取這個配置,而後設置logback在輸出時,加了一個 ColorConverter,經過org.springframework.boot.ansi.AnsiOutput ,對一些字段進行了渲染。

一些代碼小技巧
實現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函數

經過堆棧裏獲取的方式,判斷main函數,找到原始啓動的main函數。

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等都是深愛開發者喜好的項目、特性。幾乎能夠確定設計者是有豐富的一線開發經驗,深知開發人員的痛點。

相關文章
相關標籤/搜索