Для изучения исходного кода, я думаю, будет лучше, если мы рассмотрим его вместе с вопросами.
1. Каков принцип запуска Springboot?
Без лишних слов, давайте перейдем к [start.spring.io] Скачиваем демо на сайте, выбираем версию springboot2.1.4
, а затем давайте шаг за шагом разберем суть, чтобы понять принцип запуска Springboot.
Каталог нашего проекта выглядит следующим образом:
все будет от нашегоDemoApplication.java
файл запускается. код показывает, как показано ниже:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
【Навык один】
я часто вижу друзейDemoApplication
реализован в классеApplicationContextAware
интерфейс, а затем получитьApplicationContext
объект, например следующий код:
@SpringBootApplication
public class DemoApplication implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
// 获取某个bean
System.out.println(applicationContext.getBean("xxxx"));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
DemoApplication.applicationContext = applicationContext;
}
}
Конечно, это возможно, но на самом делеSpringApplication.run
Метод вернул контекст Spring, мы можем использовать его напрямую~~~ Код выглядит следующим образом:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
// 获取某个bean
System.out.println(applicationContext.getBean("xxxx"));
}
}
код перейти кSpringApplication
сорт263
Ряд
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 1、初始化一个类加载器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 2、启动类集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 3、当前应用类型,有三种:NONE、SERVLET、REACTIVE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 4、初始化Initializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 5、初始化Listeners
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 6、初始化入口类
this.mainApplicationClass = deduceMainApplicationClass();
}
О шагах 1-3 говорить нечего, просто инициализируем некоторые флаги и списки.Сосредоточьтесь на пунктах 4 и 5. Пункты 4 и 5 помогают нам загрузить все зависимости.ApplicationListener
а такжеApplicationContextInitializer
Элемент конфигурации, код перемещается вSpringFactoriesLoader
первый132
Хорошо, мы видим, что Springboot загрузит этот файл в каждую банку.META-INF/spring.factories
content, а также начиная с загрузчика классовClassLoader
Для значения ключа создается Map cache для всех конфигураций.
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// cache做了缓存,我们可以指定classloader,默认为Thread.currentThread().getContextClassLoader();
// (可在ClassUtils类中getDefaultClassLoader找到答案)
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
// FACTORIES_RESOURCE_LOCATION的值就是META-INF/spring.factories
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
давайте просто посмотримspring-boot-autoconfigure-2.1.4.RELEASE.jar
внизspring.factories
Взгляните на следующее:
# Initializers初始化器
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners监听器
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configure自动配置(下文将会有讲原理)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
......
【Навык 2】
Теперь давайте посмотрим на шаг 6. Здесь мы можем научиться небольшому трюку: как получить информацию о классе промежуточного метода в текущей цепочке вызовов методов? Давайте посмотрим на исходный код:
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;
}
Пока мы сделали толькоSpringApplication
Инициализация этого класса работает, у нас естьMETA-INF/spring.factories
Все имена классов, включая прослушиватели и инициализаторы, настроены в каталоге, и эти классы создаются и, наконец, сохраняются вSpringApplication
в этом классе.
код перемещается вSpringApplication.java
первый295
строка, код выглядит следующим образом
public ConfigurableApplicationContext run(String... args) {
// 1、计时器,spring内部封装的计时器,用于计算容器启动的时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2、创建一个初始化上下文变量
ConfigurableApplicationContext context = null;
// 3、这是spring报告之类的,没深入了解
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 4、获取配置的SpringApplicationRunListener类型的监听器,并且启动它
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 5、准备spring上下文环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 6、打印banner
Banner printedBanner = printBanner(environment);
// 7、为context赋值
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 8、准备好context上下文各种组件,environment,listeners
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 9、刷新上下文
refreshContext(context);
afterRefresh(context, applicationArguments);
// 10、计时器关闭
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
// 11、调用runners,后面会讲到
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
【Третий навык】
Таймер использовался на шаге 1StopWatch
Этот инструмент, этот инструмент мы также можем использовать напрямую, обычно мы считаем время выполнения куска кода и метод, который мы будем использоватьSystem.currentTimeMillis
Для этого мы также можем использоватьStopWatch
вместо,StopWatch
Его сила в том, что он может подсчитывать долю времени, отнимающую каждый период времени, которая примерно выглядит следующим образом:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start("startContext");
ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
stopWatch.stop();
stopWatch.start("printBean");
// 获取某个bean
System.out.println(applicationContext.getBean(DemoApplication.class));
stopWatch.stop();
System.err.println(stopWatch.prettyPrint());
}
}
Код шага 4 перемещается вSpringApplication
первый413
строка, код выглядит следующим образом:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
Видно, что springboot еще идетMETA-INF/spring.factories
НаходитьSpringApplicationRunListener
Настроены классы и старт.
Шаг 5 Создайте модуль Spring Environment по умолчаниюStandardServletEnvironment
Стандартная среда.
Тип контекста, созданный по умолчанию на шаге 7:AnnotationConfigServletWebServerApplicationContext
, видно, что это контекст сервлета на основе аннотаций в контексте Spring, поэтому наш первыйDemoApplication.java
Аннотации, объявленные в классе@SpringBootApplication
будут просканированы и проанализированы.
Шаг 9. Обновление контекста является ядром, и вы знаете, что после прочтения исходного кода Spring этотrefresh()
Метод очень классический, за подробностями вы можете обратиться к другой статье редактора.Процесс инициализации IOC контейнера Spring
Шаг 11 будет выполнять весь контекст, все реализованоApplicationRunner
а такжеCommandLineRunner
фасоль,SpringApplication
первый787
код показывает, как показано ниже:
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 对所有runners进行排序并执行
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
【Четвертый навык】
В обычной разработке мы можем захотеть выполнить некоторые операции после запуска контейнера Spring.Например, если у нас есть определенная запланированная задача, которую необходимо выполнить один раз при запуске приложения, после прочтения исходного кода шага 11 выше, речь идет о следующем Код вдруг осознает, о, так этот код вSpringApplication
называется в этом классе.
@Component
public class MyRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.err.println("执行了ApplicationRunner~");
}
}
@Component
public class MyCommandRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("执行了commandrunner");
}
}
будь осторожен:
- 1. Период выполнения CommandLineRunner и ApplicationRunner выполняется после запуска контейнера Spring.
- 2. Весь жизненный цикл контейнера выполняется только один раз
2. Какова функция аннотации @EnableAutoConfiguration?
При нормальных обстоятельствах bean-компоненты, объявленные в файле jar, представленном java, не будут сканироваться Spring, так как же наши различные стартеры инициализируют свои собственные bean-компоненты? Ответ вMETA-INF/spring.factories
заявление вorg.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx
, как вspring-cloud-netflix-zuul
Содержимое, заявленное в этом стартере, следующее:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration
Что означает это утверждение? То есть во время процесса запуска Springboot он будетorg.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx
Объявленный экземпляр класса становится bean-компонентом и регистрируется в контейнере Ниже приведен тестовый пример:
мы вmydemo
Объявите bean-компонент в коде следующим образом:
@Service
public class MyUser {
}
существуетdemo
, напечатайте bean-компонент MyUser и напечатайте следующим образом:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'css.demo.user.MyUser' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:335)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)
at com.example.demo.DemoApplication.main(DemoApplication.java:15)
насmydemo
Добавьте эту конфигурацию в проект:
demo
Проект печатается следующим образом:
2019-08-02 19:31:34.814 INFO 21984 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-08-02 19:31:34.818 INFO 21984 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 2.734 seconds (JVM running for 3.254)
执行了ApplicationRunner~
执行了commandrunner
css.demo.user.MyUser@589b028e
Зачем его настраивать? На самом деле, во время процесса запуска springboot вAutoConfigurationImportSelector#getAutoConfigurationEntry
позвонюgetCandidateConfigurations
метод, исходный код этого метода выглядит следующим образом:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
// 此处会去调用EnableAutoConfiguration注解
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
getSpringFactoriesLoaderFactoryClass
Исходный код метода выглядит следующим образом:
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
По сути, все еще используюMETA-INF/spring.factories
Конфигурация в файле выполняется в сочетании с механизмом фабрик Springboot.
3. Резюме
В этой статье анализируется общий процесс запуска Springboot с общего направления.В некоторых местах мы не проводили углубленных исследований, но изучаем исходный код, чтобы впитать суть его кодирования и написать лучший код, а также понять соответствующие принципы, что удобнее и удобнее.Быстро найдите и решите проблему, если что-то не так, пожалуйста, поправьте меня, добро пожаловать, чтобы оставить сообщение в области комментариев, спасибо!