序文
この記事では、Spring Boot 1.3の作業原則を分析します。スプリングブート1.4。その後、パッケージ構造が変更され、Boot-INFディレクトリが追加されましたが、基本原理は変更されていません。
Spring Boot 1.4。*のクラスローダーの変更については、http://www.vevb.com/article/141479.htmを参照してください。
スプリングブートクイックスタート
Spring Bootでは、非常に魅力的な機能は、アプリケーションがJAR/WARに直接パッケージ化できることです。その後、この瓶/戦争は、別のWebサーバーを構成せずに直接開始できます。
Spring Bootを以前に使用したことがない場合は、以下のデモでそれを体験できます。
Spring Bootプロジェクトを開始する方法を示す例としてのプロジェクトは次のとおりです。
git clone [email protected]:hengyunabc/spring-boot-demo.gitmvn spring-boot-demojava -jarターゲット/demo-0.0.1-snapshot.jar
使用されるIDEがSpring STSまたはIDEAの場合、ウィザードを介してスプリングブートプロジェクトを作成できます。
公式チュートリアルを参照することもできます。
http://docs.spring.io/spring-boot/docs/current-snapshot/reference/htmlsingle/#getting-started-first-application
スプリングブートに関する2つの質問
最初にスプリングブートに連絡し始めると、通常はこれらの質問があります
Spring Bootの完了方法を分析しましょう。
単一の瓶としてパッケージ化されたときのスプリングブーツの開始方法
Mavenがパッケージ化された後、2つのJARファイルが生成されます。
DEMO-0.0.1-SNAPSHOT.JARDEMO-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で準備されたパッケージのディレクトリ構造を確認しましょう(重要でない場合は省略してください):
Meta-inf│├├。�。。。 BOOT└。└。。
これらの内容を順番に見てみましょう。
manifest.mf
マニフェストバージョン:1.0Start-Class:com.example.springBootdemoApplicationImplementation-vendor-id:com.examplesspring-boot-version:releasecreated-by:apache maven 3.3.33build-jdk:1.8.0_60implementation-vendation-vendor:pivotal:pivotal org.springframework.boot.loader.jarlauncher
メインクラスはorg.springframework.boot.loader.jarlauncherであることがわかります。これはJARによって開始されたメイン関数です。
また、com.example.springbootdemoApplicationであるスタートクラスもあります。これは、適用される主な関数です。
@SpringBootApplicationPublic Class SpringBootDemoApplication {public static void main(string [] args){springApplication.run(springbootdemoApplication.class、args); }} com/例ディレクトリ
これがアプリケーションの.classファイルです。
libディレクトリ
以下は、アプリケーションが依存するJARパッケージファイルです。
たとえば、スプリングビーン、スプリング-MVC、その他の瓶。
org/springフレームワーク/ブート/ローダーディレクトリ
Spring Boot Loaderの.Classファイルは次のとおりです。
アーカイブコンセプト
Spring Bootでは、アーカイブの概念が抽象化されています。
アーカイブには、瓶(Jarfearchive)またはファイルディレクトリ(Explodedarchive)があります。 Spring Bootによって抽象化された統一アクセスリソースの層として理解できます。
上記のDemo-0.0.1-snapshot.jarはアーカイブであり、Demo-0.0.1-Snapshot.jarの /libディレクトリの下の各jarパッケージもアーカイブです。
パブリックアブストラクトクラスアーカイブ{public abstract url geturl(); public string getMainClass();パブリックアブストラクトコレクション<Entry> getEntries();パブリックアブストラクトリスト<Archive> getNestedarchives(EntryFilter Filter);アーカイブには、次のような独自のURLがあることがわかります。
jar:file:/tmp/target/demo-0.0.1-snapshot.jar!/
また、getnestedarchives関数もあり、実際にはdemo-0.0.1-snapshot.jar/libの下でjarのアーカイブリストを返します。彼らのURLは次のとおりです。
jar:file:/tmp/target/demo-0.0.1-snapshot.jar!/lib/aopalliance-1.0.jarjar:file:/tmp/target/demo-0.0.1-snapshot.jar!/lib/spring-beans-4.2.2.3.Release.jar
jarlauncher
Manifest.mfから、主な関数がJarlauncherであることがわかります。以下のワークフローを分析しましょう。
Jarlauncherクラスの継承構造は次のとおりです。
クラスJarlauncher拡張execuedivearchivelauncherclass exectivearchivelauncher拡張ランチャー
Demo-0.0.1-Snapshot.jarでアーカイブを作成します:
Jarlauncherは、最初に彼が配置されているjarのパス、つまりDemo-0.0.1-Snapshot.jarを見つけてから、アーカイブを作成します。
次のコードは、クラスからロード場所を見つける方法のトリックを示しています。
保護された最終アーカイブcreatearchive()throws exception {protectiondomain ProtectionDomain = getClass()。getProtectionDomain(); codeSource codeSource = protetrivedomain.getCodesource(); uri location =(codesource == null?null:codesource.getLocation()。touri()); string path =(location == null?null:location.getschemespificPart()); if(path == null){新しいIllegalStateException( "コードソースアーカイブを決定できない"); } file root = new file(path); if(!root.exists()){新しいIllegalStateExceptionをスロー( "" + rootからコードソースアーカイブを決定できません); } return(root.isdirectory()?new Explodedarchive(root):new Jarfilearchive(root));} lib/の下の瓶を取得し、launchedurlclassloaderを作成します
JarlauncherがArchiveを作成した後、GetNestedarchives関数を使用して、Demo-0.0.1-Snapshot.jar/libの下のすべてのJARファイルを取得し、リストとして作成します。
上記のように、アーカイブには独自のURLがあることに注意してください。
これらのアーカイブURLを取得した後、URL []アレイを取得し、これを使用してカスタムクラスローダーの構築:launchedurlclassloaderになります。
クラスローダーを作成した後、manifest.mfのスタートクラスを読み取り、つまりcom.example.springbootdemoApplicationを読み、新しいスレッドを作成してアプリケーションのメイン関数を開始します。
/***アーカイブファイルと完全に構成されたクラスローダーを考慮して、アプリケーションを起動します。 */Protected void起動(String [] Args、String Mainclass、classloader classloader)スロー例外{runnable runner = createmainmethodrunner(mainclass、args、classloader);スレッドrunnerthread = new Stread(Runner); runnerthread.setContextClassLoader(classloader); runnerthread.setname(thread.currentthread()。getname()); runnerthread.start();}/***アプリケーションの起動に使用される{@code mainmethodrunner}を作成します。 */保護されたRunnable CreateMainMethodRunner(String Mainclass、String [] args、classloader classloader)スロー例外{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関数を組み合わせることで、アーカイブでリソースを取得できます。もちろん、内部にはまだ多くの詳細があるので、以下に説明します。
Spring Bootアプリケーションスタートアッププロセスの概要
これを見た後、Spring Bootアプリケーションのスタートアッププロセスを要約できます。
スプリングブートローダーの詳細
コードアドレス:https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-loader
Jarfile URLの拡張
スプリングブーツは、太った瓶で開始できます。最も重要なことは、瓶の瓶の荷重方法を実装することです。
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は、1つの '!/'のみをサポートしています。
Spring Bootは、このプロトコルを拡張して複数の '!/'をサポートします。これは、JARのJAR、ディレクトリリソースのJARを表すことができます。
たとえば、次のURLは、demo-0.0.1-snapshot.jarのLIBディレクトリのSpring-Beans-4.2.3.Release.jarを表します。
次のようにコードをコピーします:jar:file:/tmp/target/demo-0.0.1-snapshot.jar!/lib/spring-beans-4.2.2.2.3.Release.jar!/meta-inf/manifest.mf
カスタムURLSTREAMHANDLER、JarfileおよびJarurlConnectionを拡張します
URLを構築するときは、ハンドラーを渡すことができ、JDKにはデフォルトのハンドラークラスが付属しています。アプリケーションは、カスタムURLを処理するためにハンドラー自体を登録できます。
public url(string protocol、string host、int port、string file、urlstreamhandlerハンドラー)
参照:
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は、カスタムハンドラークラスを登録することにより、JARの複数の瓶のロジックを処理します。
このハンドラーは、ソフトレファレンスを使用して、すべての開いたジャーファイルをキャッシュします。
次のようなURLを処理するとき、「!/」セパレーターはサイクルされます。上層から始めて、最初にJarfile Demo-0.0.1-Snapshot.jarを構築し、Jarfile Spring-Beans-4.2.3.Release.jarを構築し、Manifest.mfを指すJarurlConnectionを構築します。
次のようにコードをコピーします:jar:file:/tmp/target/demo-0.0.1-snapshot.jar!/lib/spring-beans-4.2.2.2.3.Release.jar!/meta-inf/manifest.mf
//org.springframework.boot.loader.jar.handlerpublic class Handlerは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 {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){新しいmalformedurlexception( "JAR urlに含まれていない!/分離"); } string name = spec.substring(0、separatorindex); return getrootjarfile(name); }クラスローダーがリソースへの読み取り方法
クラスローダーにはどのような能力が必要ですか?
対応するAPIは次のとおりです。
public url findResource(string name)public inputstream getResourceasStream(String name)
上記のように、Spring Boot ConstructionがLaunchEdurlClassloaderを構築すると、URL []アレイを渡します。配列は、libディレクトリの下のjarのURLです。
JDKまたはClassLoaderは、URLのコンテンツを読み取る方法をどのように知っていますか?
実際、プロセスは次のようになります。
最後の呼び出しは、jarurlconnectionのgetInputStream()関数です。
//org.springframework.boot.loader.jar.jarurlconnection @override public inputstream getInputStream()throws ioException {connect(); if(this.jarentryname.isempty()){throw new ioException( "エントリ名が指定されていない"); } this.jarentrydata.getInputStream(); }URLからURL内のコンテンツを最終的に読み取るまで、プロセス全体は非常に複雑です。要約しましょう:
ここには多くの詳細があり、いくつかの重要なポイントのみがリストされています。
では、urlclassloader getResourceはどのようにしますか?
URLClassloaderを構築するとき、URL []配列パラメーターがあり、この配列を使用してURLClassPathを構築します。
urlclasspath ucp = new urlclasspath(urls);
URLClassPathでは、すべてのURL用にローダーが構築され、GetSResourceのときにこれらのローダーから1つずつ取得しようとします。
買収が成功した場合、以下のようにリソースとしてパッケージ化されます。
リソースgetResource(最終文字列名、ブールチェック){final url url; try {url = new url(base、parseutil.encodepath(name、false)); } catch(malformedurlexception e){新しいIllegalargumentException( "name"); }最終urlconnection UC; try {if(check){urlclasspath.check(url); } uc = url.openconnection(); inputStream in = uc.getInputStream(); if(uc instanceof jarurlconnection){ / * jarファイルを覚えて、急いで閉じることができるようにする必要があります。 */ jarurlconnection juc =(jarurlconnection)uc; jarfile = jarloader.checkjar(juc.getjarfile()); }} catch(例外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/Open Directoryでスプリングブートアプリケーションを起動します
上記は、脂肪瓶でスプリングブートアプリケーションを開始するプロセスのみが言及されています。以下は、IDEでスプリングブーツがどのように開始されるかの分析です。
IDEでは、直接実行される主な関数は、独自のメイン関数を適用することです。
@SpringBootApplicationPublic Class SpringBootDemoApplication {public static void main(string [] args){springApplication.run(springbootdemoApplication.class、args); }}実際、IDEでスプリングブートアプリケーションを開始することが最も簡単なケースです。これは、依存関係のジャーがクラスパスに入れられるため、スプリングブーツが開始されたばかりです。
別の状況は、オープンディレクトリからスプリングブートを起動することです。いわゆるオープンディレクトリは、脂肪瓶を減圧してからアプリケーションを直接開始することです。
Java org.springframework.boot.loader.jarlauncher
この時点で、Spring Bootは、現在ディレクトリにあるかどうかを判断します。もしそうなら、爆発的なもの(前のものはジャーフィルハイブ)を構築し、その後のスタートアッププロセスはFAT JARに似ています。
Embead Tomcatスタートアッププロセス
Web環境にあるかどうかを判断します
Spring Bootが開始されたら、最初にサーブレットクラスを検索するだけでWeb環境にあるかどうかを判断します。
private static final string [] web_environment_classes = {"javax.servlet.servlet"、 "org.springframework.web.context.configurablewebapplicationcontext"}; private bolean deducewebenvironment(){for(for(for(for(for:web_environment)) (!classutils.ispresent(classname、null)){return false; }} return true;}もしそうなら、annotationConfigembededwebapplicationContextが作成されます。そうしないと、スプリングコンテキストは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){新しいIllegalStateExceptionをスロー( "デフォルトのApplicationContextを作成できません" + "ApplicationContextClass"を指定してください "、ex); }} return(configureableapplicationContext)beanutils.instantiate(contextclass); } EmbedDedServletContainerFactoryの実装クラスを取得します
Spring Bootは、EmbedDedServletContainerFactoryを取得することにより、対応するWebサーバーを起動します。
一般的に使用される2つの実装クラスは、TomCatemBededServletContainerFactoryとJetTyemdedDedServletContainerFactoryです。
Tomcatを開始するコード:
// tomcatembeddedservletcontainerfactory@overridepublic embeddedservletcontainer getemdedservletcontainer(servletcontextinitializer ... initializers){tomcat tomcat = new tomcat(); file badedir =(this.badedirectory!= null?this.basedir:createTempdir( "tomcat")); tomcat.setbasedir(beadir.getabsolutepath());コネクタコネクタ=新しいコネクタ(this.protocol); tomcat.getService()。addConnector(connector); customizeconnector(connector); tomcat.setConnector(コネクタ); tomcat.gethost()。setautodeploy(false); tomcat.getEngine()。setbackgroundprocessordelay(-1); for(connector extlideConnector:this.AdditionAltomCatConnectors){tomcat.getService()。addConnector(adthideConnector); } prevereContext(tomcat.gethost()、initializers); retumtomcatembededservletcontainer(tomcat);} Tomcat用の一時ファイルディレクトリが作成されます。
/tmp/tomcat.2233614112516545210.8080、Tomcatの基礎として。ワークディレクトリなどの一時的なTomcatファイルが内部に配置されます。
より重要なデフォルト/JSPサーブレットなど、一部のTomcatサーブレットも初期化されます。
private void adddefaultServlet(コンテキストコンテキスト){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); //それ以外の場合は、スプリングディスパッチャーサーブレットのデフォルトの場所を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アプリケーションでリソースにアクセスする方法
Spring Bootアプリケーションがファットジャーとしてパッケージ化されたときに、Webリソースにアクセスするにはどうすればよいですか?
実際には、Archiveが提供するURLを介して、次にClassPathリソースが提供するClassPathリソースにアクセスする機能を介して実装されます。
index.html
たとえば、index.htmlを構成する必要があります。HTMLは、コードのSRC/Main/Resources/Staticディレクトリに直接配置できます。
index.htmlウェルカムページの場合、Spring Bootが初期化されると、viewcontrollerが作成されます。
// ResourcePropertiesPublic Class ResourcePropertiesはResourceReCeloaderAware {private static final String [] servlet_resource_locations = {"/"}; private static final string [] classpath_resource_locations = {"classpath:/meta-inf/resources/"、 "classpath:/resources/"、 "classic/"、 "classpath:/public/"}; // webmvcautoconfigurationadapter @override public void addViewControllers(ViewControllerRegistryレジストリ){リソースページ= this.resourceProperties.getWelcomePage(); if(page!= null){logger.info( "ウェルカムページの追加:" +ページ); registry.addviewcontroller( "/")。setviewname( "forward:index.html"); }}テンプレート
たとえば、ページテンプレートファイルは、SRC/メイン/リソース/テンプレートディレクトリに配置できます。しかし、これは実際にはテンプレート実装クラス自体によって処理されます。たとえば、ThymeleafPropertiesクラスで:
public static final string default_prefix = "classpath:/templates/";
jsp
JSPページはテンプレートに似ています。実際、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は、AbstracterrorControllerを拡張します対応するビューは、単純なHTMLリマインダーです。
@configuration@conditionalonproperty(prefix = "server.error.whitelabel"、name = "enabled"、matchifmissing = true)@conditional(ererortemplatemissingcondition.class)保護された静的クラスWhitelabelerrorviewconfiguration {private final final final spelview = difaulterroview(新しいspeliew(新しいspelview) 「<html> <body> <h1>ホワイトラベルエラーページ</h1> " +" <p>このアプリケーションには/エラーの明示的なマッピングがないため、これをフォールバックと見なしています。 + "<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.output.ansi.enabled = false
原則は、AnsiOutputApplicationListenerを介してこの構成を取得し、ログバックを出力に設定し、ColorConverterを追加し、org.springframework.boot.ansi.ansioutputを介していくつかのフィールドをレンダリングすることです。
いくつかのコードのヒント
ClassLoaderを実装する場合は、JDK7の並列負荷をサポートします
LaunchEdurlClassloaderのLockProviderを参照できます
public class launchedurlclassloaderは、urlclassloaderを拡張します{private static lockprovider lock_provider = setuplockprovider(); private static lockprovider setuplockprovider(){try {classloader.registerasParallelcapabable();新しいJava7LockProvider()を返します。 } catch(nosuchmethoderror ex){return new lockProvider(); }} @Override Protected Class <? if(loadedclass == null){handler.setusefastConnectionExceptions(true); try {loadedclass = doloadclass(name); }最後に{handler.setusefastConnectionExceptions(false); }} if(Resolve){ResolveClass(LoadedClass); } loadedclassを返します。 }} JARパッケージがエージェントを介してロードされているかどうかを確認します
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; }}パッケージングロガークラス
Spring Bootには、Java、log4j、log4j2、logbackをサポートするロガーのセットがあります。将来、ロガーを自分でパッケージ化する必要がある場合は、これを参照できます。
org.springframework.boot.loggingパッケージの下。
オリジナルの開始メイン関数を取得します
スタックで取得された方法を介して、メイン関数を判断し、オリジナルが開始されたメイン関数を見つけます。
プライベートクラス<? for(stacktraceElement stacktraceElement:stacktrace){if( "main" .equals(stacktraceElement.getMethodName())){return class.forname(stacktraceElement.getClassName()); }}} catch(classNotFoundException ex){// swallow and Continue} return null;} spirngブーツのいくつかの欠点:
Spring Bootアプリケーションが脂肪瓶で実行されると、いくつかの問題が発生します。これが私の個人的な意見です:
要約します
Spring Bootは、JARプロトコルを拡張し、アーカイブの概念とサポートJarfile、JarurlConnection、LaunchedurlClassloaderを抽象化し、上層層アプリケーションを知覚せずにすべてのすべての開発体験を実現します。実行可能な戦争は春までに提案されている概念ではありませんが、春のブーツを使用することができます。
Spring Bootは素晴らしいプロジェクトであり、春の2番目の春と言えます。 Spring-Cloud-Config、春のセッション、メトリック、リモートシェルなどは、開発者が愛するすべてのプロジェクトと機能です。デザイナーが最前線の開発で豊富な経験を持ち、開発者の問題点を十分に認識していることはほぼ確実です。
上記はこの記事のすべての内容です。みんなの学習に役立つことを願っています。誰もがwulin.comをもっとサポートすることを願っています。