Предисловие
В этой статье анализируется принцип работы Spring Boot 1.3. пружинный ботинок 1.4. После этого было обнаружено, что структура упаковки меняется, и был добавлен каталог Boot-Inf, но основной принцип остается неизменным.
Для изменений в ClassLoader в Spring Boot 1.4.*, Пожалуйста, см.
Spring Boot Quick Start
В Spring Boot очень привлекательная особенность заключается в том, что приложение можно упаковать непосредственно в JAR/WAR, а затем эта JAR/WAR можно начать непосредственно без настройки другого веб -сервера.
Если вы раньше не использовали Spring Boot, вы можете испытать его через демонстрацию ниже.
Вот проект в качестве примера, чтобы продемонстрировать, как начать проект Spring Boot:
git clone [email protected]: hengyunabc/spring-boot-demo.gitmvn Spring-boot-demojava -jar target/demo-0.0.1-snapshot.jar
Если используемый IDE - это Spring STS или идея, вы можете создать проект Spring Boot через мастер.
Вы также можете обратиться к официальному уроку:
http://docs.spring.io/spring-boot/docs/current-snapshot/reference/htmlsingle/#getting-started-first-application
Два вопроса о весеннем ботине
Когда вы впервые начинаете связываться с Spring Boot, у вас обычно есть эти вопросы
Давайте проанализируем, как сделан Spring Boot.
Как запускается весенний ботинок при упаковке как одна банка
После того, как Maven упакована, будет сгенерировано два файла 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-это пакет JAR, сгенерированный плагином Spring Boot Maven, который содержит зависимости приложений и классы Spring Boot. Далее, это называется Fat Jar.
Во -первых, давайте проверим структуру каталогов пакетов, подготовленных Spring Boot (опустите его, если это не важно):
Meta-inf│ ├ack ├ ├ мобили. └acker ├acker ├ ├мобил.
Давайте посмотрим на это содержимое по очереди.
Manifest.mf
Manifest-версия: 1.0Start-Class: com.example.springbootdemoApplicationImplementation-Vendor-ID: Com.ExamplesPring-Boot-Version: 1.3.0.ReleaseeCreated-By: Apache Maven 3.3.3Build-JDK: 1.8.0_60Implation-Vendor: Pivotal Speckity-Class. 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 Directory
Вот файл пакета JAR, от которого зависит приложение.
Например, Spring-Beans, Spring-MVC и другие банки.
org/springframework/boot/goader Directory
Вот файл .class спенциального загрузчика.
Архивная концепция
В Spring Boot концепция архива абстрагирована.
Архив может быть JAR (JARFILEARCHIVE) или файловым каталогом (ExprededArchive). Это может быть понято как уровень ресурсов единого доступа, абстрагированный Spring Boot.
Вышеупомянутый Demo-0.0.1-Snapshot.jar-это архив, а затем каждый пакет JAR в каталоге /LIB в DeMO-0.0.1-Snapshot.jar также является архивом.
Общественный абстрактный класс Архив {открытый абстрактный URL geturl (); public String getMainClass (); Общественная абстрактная коллекция <purit> getEntries (); Общедоступный список абстрактных <Archive> getnestestarchives (фильтр entfilter);Вы можете видеть, что у архива есть свой URL, например:
Jar: File: /tmp/target/demo-0.0.1-snapshot.jar!/
Существует также функция Getnestestearchives, которая фактически возвращает список архивных банок в рамках Demo-0.0.1-Snapshot.jar/lib. Их URL -адреса:
JAR: File: /tmp/target/demo-0.0.1-snapshot.jar! /lib/aopalliance-1.0.jarjar: файл: /tmp/target/demo-0.0.1-snapshot.jar! /lib/spring-beans-4.2.3.release.jar
Jarlauncher
Из Manifest.mf мы видим, что основная функция - Jarlauncher. Давайте проанализируем его рабочий процесс ниже.
Структура наследования класса Jarlauncher:
Class Jarlauncher Extends executivearchivelaUncherclass executivearchivelauncher Extends Launcherer
Создайте архив с Demo-0.0.1-snapshot.jar:
Jarlauncher сначала находит путь банки, где он находится, то есть demo-0.0.1-snapshot.jar, а затем создает архив.
Следующий код показывает, как найти местоположение загрузки из класса:
Защищенный окончательный архив Createarchive () бросает исключение {ProtectionDomain ProtectDomain = getClass (). getProtectionDomain (); CodeSource CodeSource = ProtectionDomain.getCodesource (); URI местоположение = (CODESOURCE == NULL? NULL: CODESOURCE.GetLocation (). Touri ()); String path = (location == null? Null: location.getSchempecificpart ()); if (path == null) {бросить новое нелегальное состояние («Невозможно определить архив источника кода»); } Файл root = new File (path); if (! root.exists ()) {бросить новое allosalstateexception («Невозможно определить архив источника кода из" + root); } return (root.isdirectory ()? New ExprededArchive (root): new jarfilearchive (root));} Получите JAR ниже Lib/ и создайте запуск eduRlclassloader
После того, как Jarlauncher создает архив, он использует функцию GetnestEdarchives для получения всех файлов JAR ниже DEMO-0.0.1-SNAPSHOT.JAR/LIB и создает их в качестве списка.
Обратите внимание, что, как упоминалось выше, у архива есть свой собственный URL.
После получения этих архивных URL -адресов вы получите массив URL [] и используете его для построения пользовательского класса загрузчика: запущены.
После создания ClassLoader прочитайте start-class с manifest.mf, то есть com.example.springbootdemoapplication, а затем создайте новый поток, чтобы запустить основную функцию приложения.
/*** Запустите приложение с учетом файла архива и полностью настроенного класса загрузчика. *; Thread Runnerthread = новый поток (Runner); runnerthread.setContextClassLoader (ClassLoader); runnerthread.setname (think.currentthread (). getName ()); runnerthread.start ();}/*** Создать {@code mainmethodrunner}, используемый для запуска приложения. */Protected Runnable createmainmethodrunner (String mainclass, string [] args, classloader classloader) throws exection {class <?> RunnerClass = classloader.loadClass (runner_class); Constructor <?> Constructor = RunnerClass.getConstructor (String.class, String []. Class); return (runnable) constructor.newinstance (mainclass, args);} Запуск eduRlClassLoader
Разница между загрузчиком запуска и обычным UrlClassLoader заключается в том, что он дает возможность загружать .CLASS из архива.
Сочетая функцию GetEntries, предоставленную Archive, вы можете получить ресурс в архиве. Конечно, внутри есть много деталей, поэтому я опишу это ниже.
Сводка процесса запуска приложения Spring Boot
Увидев это, вы можете подвести итог процесса запуска приложения Spring Boot:
Подробности в Spring Bootler
Кодовый адрес: https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-loader
Расширения URL -адресов Jarfile
Весенний ботинок можно начать с толстой банки. Самое главное, что он реализует метод загрузки JAR в JAR.
Определение оригинального URL JDK JDK можно найти здесь:
http://docs.oracle.com/javase/7/docs/api/java/net/jarurlconnection.html
Оригинальный URL Jarfile выглядит так:
Jar: File: /tmp/target/demo-0.0.1-snapshot.jar!/
URL ресурса в пакете JAR:
Скопируйте код следующим образом: jar: file: /tmp/target/demo-0.0.1-snapshot.jar! /Com/example/springbootdemoapplication.class
Вы можете увидеть, что для ресурсов в JAR определение разделено '!/'. Оригинальный urfile URL поддерживает только один '!/'.
Spring Boot расширяет этот протокол, чтобы поддержать несколько '!/', Что может представлять JAR в JAR, JAR в ресурсах каталогов.
Например, следующий URL представляет Spring-Beans-4.2.3.release.jar в каталоге Lib Demo-0.0.1-snapshot.jar:
Скопируйте код следующим образом: jar: file: /tmp/target/demo-0.0.1-snapshot.jar! /Lib/spring-beans-4.2.3.3.reelease.jar! /Meta-inf/manifest.mf
Пользовательский Urlstreamhandler, расширить Jarfile и jarurlconnection
При построении URL вы можете передать обработчик, а JDK поставляется с классом обработчика по умолчанию. Приложение может зарегистрировать сам обработчик для обработки пользовательских URL -адресов.
Общедоступный URL (строковый протокол, строковый хост, int -порт, строковый файл, обработчик UrlStreamHandler) бросает 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 обрабатывает логику нескольких банок в банках путем регистрации пользовательского класса обработчика.
Этот обработчик будет использовать Softreference для кэша всех открытых Jarfiles.
При обработке URL -адресов, как следующее, сепаратор '!/' Обрабатывается велосипед. Начиная с верхнего слоя, сначала постройте Jarfile demo-0.0.1-snapshot.jar, затем постройте джарфильные пружинные бобы-4.2.3.3.release.jar, а затем постройте jarurlconnection, указывающий на Manifest.mf.
Скопируйте код следующим образом: jar: file: /tmp/target/demo-0.0.1-snapshot.jar! /Lib/spring-beans-4.2.3.3.reelease.jar! /Meta-inf/manifest.mf
//org.springframework.boot.loader.jar.handlerpublic Class Handler Extends urlStreamHandler {Private Static Final String Sepreator = "!/"; Частный статический softreference <map <file, jarfile >> rootfilecache; @Override Protected urlConnection OpenConnection (URL URL) бросает ioException {if (this.jarfile! = Null) {return new jarurlconnection (url, this.jarfile); } try {return new jarurlConnection (url, getRootjarfilefromurl (url)); } catch (Exception ex) {return venfallbackconnection (url, ex); }} public jarfile getRootjarfilefromurl (url url) бросает ioException {string spec = url.getFile (); int seleSatorAtraTex = spec.Indexof (сепаратор); if (sepreatorIndex == -1) {бросить новое malformedurlexception ("jar url не содержит!/ Разделитель"); } String name = spec.substring (0, separatorIndex); вернуть getRootjarfile (имя); } Как загрузчик читает ресурс
Какие способности это требуется для загрузчика класса?
Соответствующий API:
Public URL FindResource (String Name) Public InputStream getResourCeasStream (String name)
Как упомянуто выше, когда пружинные загрузочные конструкции запускают edurlclassloader, он проходит массив URL []. Массив является URL банки под справочником LIB.
Как JDK или ClassLoader знают, как прочитать в нем контент для URL?
На самом деле, процесс выглядит так:
Последний вызов - функция getInputStream () jarurlConnection.
//org.springframework.boot.loader.jar.jarurlConnection @Override public inputStream getInputStream () бросает ioException {connect (); if (this.jarentryname.isempty ()) {бросить новое ioexception ("Нет указанного имени записи"); } вернуть это.jarentryData.getInputStream (); }От URL -адреса до окончательного чтения контента в URL, весь процесс довольно сложный. Давайте обобщу:
Здесь есть много деталей, перечислены только некоторые важные моменты.
Тогда как urlclassloader Getresource?
При построении UrlClassLoader он имеет URL -параметры [] массивы, и он будет использовать этот массив для построения urlclasspath:
Urlclasspath ucp = new urlclasspath (urls);
В UrlClasspath будет построен погрузчик для всех URL -адресов, а затем при получении ресурса он попытается получить их от этих погрузчиков один за другим.
Если приобретение успешно, оно упаковано как ресурс, как показано ниже.
Ресурс getResource (имени окончательного строки, логический чек) {окончательный URL URL; try {url = new URL (base, parseutil.encodepath (имя, false)); } catch (malformedurlexception e) {бросить новый allosalargumentException ("name"); } final urlConnection UC; Попробуйте {if (check) {urlclasspath.check (url); } uc = url.openconnection (); InputStream in = uc.getInputStream (); if (uc ancessionof jarurlconnection) { / * нужно помнить файл JAR, чтобы его можно было закрыть * в спешке. */ 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 () бросает ioException {return ucleinputStream (); } public int getContentLength () бросает ioException {return uclecontentlength (); }};}Как вы можете видеть из кода, url.openconnection () фактически вызывается. Таким образом, полная цепь может быть подключена.
Обратите внимание, что код класса Urlclasspath не поставляется с собственным в JDK. Здесь вы можете увидеть http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/misc/urlclasspath.java#506
Приложение Spart Spring Boot в IDE/Open Directory
Выше упоминается только процесс начального приложения пружинного загрузки в жирной банке. Ниже приведен анализ того, как запускается Spring Boot в IDE.
В IDE основная функция, которая напрямую выполняется, - это применение собственной основной функции:
@Springbootapplicationpublic class springbootdemoapplication {public static void main (string [] args) {springapplication.run (springbootdemoapplication.class, args); }}На самом деле, начинающий приложение для Spring Boot в IDE является самым простым случаем, потому что банки зависимости помещаются в обезживание, поэтому Spring Boot только начинается.
Другая ситуация состоит в том, чтобы запустить Spring Boot в открытом каталоге. Так называемый открытый каталог заключается в распадке жирной банки, а затем напрямую начинать применение.
java org.springframework.boot.loader.jarlauncher
В настоящее время Spring Boot определит, находится ли он в настоящее время в каталоге. Если это так, постройте взрывной архив (предыдущий является JARFILEARCHIVE), а последующий процесс запуска похож на Fat JAR.
Excead Tomcat Process Startup
Определите, находится ли это в веб -среде
Когда запускается Spring Boot, сначала определите, находится ли он в веб -среде, просто поиск класса сервлета:
Приватная статическая конечная строка [] web_environment_classes = {"javax.servlet.servlet", "org.springframework.web.context.configewareWebApplicationContext"}; Private Boolean DeduceWebenVironment () {для (String ClassName: web_environment_classes) {ifpres). null)) {вернуть false; }} return true;}Если это так, будет создана AnnotationConfigeMbeddedWebApplicationContext, в противном случае контекст Spring - это AnnotationConfigApplicationContext:
//org.springframework.boot.springApplication Защищенное 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 allogalStateException («Невозможно создать приложение по умолчанию,« + », пожалуйста, укажите ApplicationContextClass», Ex); }} return (configurableapplicationcontext) beanutils.instantiate (contextclass); } Получить класс реализации EmbeddedServletContainerFactory
Spring Boot запускает соответствующий веб -сервер, получая EmbeddedServletContainerFactory.
Два обычно используемых класса реализации являются TomcatembeddesvletcontainerFactory и JettyembeddesvletContainerFactory.
Код для запуска Tomcat:
// tomcatembeddedservletcontainerfactory@overridepublic infdedservletcontainer getembeddedservletcontainer (ServletContextInitializer ... инициализаторы) {Tomcat tomcat = new Tomcat (); File foundir = (this.basedirectory! = Null? This.basedir: createTempdir ("tomcat")); tomcat.setbasedir (basedir.getabsolutepath ()); Разъемы разъема = новый разъем (this.protocol); tomcat.getService (). AddConnector (Connector); CumentizeConnector (разъемы); Tomcat.setConnector (Connector); tomcat.gethost (). setautodeploy (false); tomcat.getEngine (). Betbackgroundprocessordelay (-1); Для (Connector AproadConnector: this.AdtitionAltomCatConnectors) {tomcat.getService (). AddConnector (AfficeConnector); } prepareContext (tomcat.gethost (), initializers); return getTomCateMbeddedServletContainer (Tomcat);} Временный файловый каталог будет создан для Tomcat, например:
/tmp/tomcat.2233614112516545210.8080, как основам Tomcat. Временные файлы Tomcat, такие как работающий каталог, будут размещены внутри.
Некоторые сервлеты Tomcat также будут инициализированы, такие как более важные сервлеты по умолчанию/JSP:
private void AddDefaultservlet (контекст контекста) {warpper defauldervlet = context.createwrapper (); defaultservlet.setname ("по умолчанию"); defaultservlet.setservletclass ("org.apache.catalina.servlets.defaultservlet"); defaultservlet.addinitParameter ("Debug", "0"); defaultservlet.addinitParameter («Списки», «false»); defaultservlet.setLoAlonStartUp (1); // в противном случае местоположение по умолчанию пружинного диспетчеры не может быть установлено defauldservlet.setoverridable (true); context.addchild (defaultservlet); context.addservletmapping ("/", "default");} private void addjspservlet (контекст контекста) {warpper jspservlet = context.createwrapper (); jspservlet.setname ("jsp"); jspservlet.setservletclass (getjspservletclassname ()); jspservlet.addinitParameter ("fork", "false"); jspservlet.setLoAlOnStartUp (3); context.addchild (jspservlet); context.addservletmapping ("*. jsp", "jsp"); context.addservletmapping ("*. jspx", "jsp");} Как получить доступ к ресурсу с помощью Spring Boot Web Application
Как получить доступ к веб -ресурсу, когда приложение Spring Boot упаковано как жирная банка?
Он фактически реализуется через URL -адрес, предоставленный Archive, а затем через возможность получить доступ к ресурсу ClassPath, предоставленное ClassLoader.
index.html
Например, вам необходимо настроить index.html, который может быть размещен непосредственно в каталоге SRC/Main/Resources/Static в коде.
Для страницы приветствия index.html, когда инициализируется Spring Boot, будет создан ViewController для обработки:
// resourcepropertiespublic class resourceproperties реализует resourceloaderaware {private static final string [] servlet_resource_locations = {"/"}; Частная статическая конечная строка [] classpath_resource_locations = {"classpath:/meta-insf/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"}; // webmvcautoconfigurationAdapter @Override public void addViewControllers (реестр ViewControllerRegistry) {Presource Page = this.ResourceProperties.getWelComePage (); if (page! = null) {logger.info ("Добавление Presse Page:" + page); Registry.addviewController ("/"). setViewName ("forward: index.html"); }} шаблон
Например, файл шаблона страницы может быть размещен в каталоге SRC/Main/Resources/Template. Но это фактически обрабатывается самим классом реализации шаблона. Например, в классе Thymeleafproperties:
public Static Final String default_prefix = "classpath:/templates/";
JSP
Страница JSP аналогична шаблону. На самом деле он обрабатывается через JSTLView, построенный в Spring MVC.
Вы можете настроить spring.view.prefix, чтобы установить каталог страницы JSP:
spring.view.prefix:/web-inf/jsp/
Обработка единых страниц ошибок в Spring Boot
Для страниц ошибок Spring Boot также обрабатывается равномерно путем создания BasicErrorROController.
@Controller@requestmapping ("$ {server.error.path: $ {error.path:/erry}}") public class basicerrorcontroller extracterrorcontrollerСоответствующий вид - это простое напоминание HTML:
@Configuration@condityalonproperty (prefix = "server.error.whitelabel", name = "enabled", matchifmissing = true) @conditional (errortemplaymissingcondition.class) Protected Static Class WhiteLabelerRorviewConfiguration {Private Final SpelVeerROU "<html> <body> <h1> страница ошибок WhiteLabel </h1>" + "<p> Это приложение не имеет явного отображения для/ошибки, поэтому вы видите это как отступление. status = $ {status}). </div> " +" <div> $ {сообщение} </div> </body> </html> "); @Bean (name = "error") @conditionalonmissingbean (name = "error") public view defaulterrorview () {return this.defaulterrorview; }Spring Boot - это хорошая практика, которая позволяет избежать исключения по умолчанию, когда традиционные веб -приложения допускают ошибки, что облегчает утечку секретов.
Maven упаковочный процесс приложения Spring Boot
Сначала генерируйте JAR, содержащую зависимости через Maven-Shade-Plugin, а затем упаковывайте классы, связанные с загрузчиком пружины и Manifest.mf в банку через плагин Spring-Boot-Maven-Plugin.
Реализация цветных журналов в пружинной загрузке
При запуске приложения Spring Boot в оболочке вы обнаружите, что его выходной выход раскрашен, что очень интересно.
Эта настройка может быть выключена:
Spring.output.ansi.Enabled = false
Принцип состоит в том, чтобы получить эту конфигурацию через AnsiOutputApplicationListener, а затем установить вывод для вывода, добавить ColorConverter и отображать некоторые поля через org.springframework.boot.ansi.ansioutput.
Некоторые советы по коду
При реализации ClassLoader поддержите параллельную загрузку JDK7
Вы можете ссылаться на LockProvider в запуске eduRlclassloader
Public Class LANSTURLCLASSLOADER EXTENDS URLCLASSLOADER {Private Static LockProvider LOCK_PROVIDER = SETUPLOCKPROVIDER (); private static lockprovider setuplockprovider () {try {classloader.registerAsparallelCapable (); вернуть новый java7lockprovider (); } catch (nosuchmethoderror ex) {return new lockprovider (); }} @Override Protected Class <?> LoadClass (String name, Boolean Resivel) Throws classnotFoundException {synchronized (запуск edUrlClassLoader.lock_provider.getlock (this, name)) {class <?> Loadedclass = findloadclass (name); if (loadedclass == null) {handler.setusefastConnectionExceptions (true); try {loadedclass = doloadclass (name); } наконец {handler.setuseFastConnectionExceptions (false); }} if (Resolve) {ResolVeclass (loadedClass); } return LoadedClass; }} Проверьте, загружен ли пакет JAR через агент
InputArgumentsjavaagentdetector, принцип состоит в том, чтобы определить, имеет ли URL банки префикс «-Javaagent:».
Приватная статическая финальная строка java_agent_prefix = "-javaagent:";
Получить пид процесса
ApplicationPid, вы можете получить PID.
Private String getPid () {try {string jvmname = ManagementFactory.getRuntImemxbean (). getName (); вернуть jvmname.split ("@") [0]; } catch (throwable ex) {return null; }} Упаковка класса регистрации
Spring Boot имеет набор регистраторов, которые поддерживают Java, log4j, log4j2, журнал. Вы можете ссылаться на это, когда вам нужно будет упаковывать своих регистраторов самостоятельно в будущем.
Под пакетом org.springframework.boot.logging.
Получите оригинальную основную функцию
С помощью метода, полученного в стеке, судите основную функцию и найдите исходную началую основную функцию.
Частный класс <?> deducemainapplicationclass () {try {stacktraceElement [] stacktrace = new Runtimeexception (). getStackTrace (); for (StackTraceElement StackTraceElement: StackTrace) {if ("main" .equals (stacktraceElement.getMethodName ())) {return class.forname (stacktraceElement.getClassName ()); }}} catch (classnotfoundexception ex) {// глотать и продолжить} return null;} Некоторые недостатки Spirng Boot:
Когда приложение Spring Boot работает в жирной банке, возникнут некоторые проблемы. Вот мое личное мнение:
Суммировать
Spring Boot расширяет протокол JAR, абстрагирует концепцию архива, а также поддерживающий Jarfile, JarurlConnection и LaunchedUrlClassloader, таким образом, реализуя опыт разработки всех без восприятия приложения верхнего уровня. Хотя исполняемая война не является концепцией, предложенной весной, Spring Boot позволяет перенести его вперед.
Spring Boot - это удивительный проект, который можно сказать, что является второй весной. Спринг-клуб-конфиг, весенняя сессия, метрики, удаленная оболочка и т. Д.-все это проекты и функции, которые любят разработчики. Почти наверняка, что у дизайнера есть богатый опыт в разработке фронта и хорошо осведомлен о болевых точках разработчиков.
Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.