1. Запись Spring Boot — основной метод
@SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
Как видно из приведенного выше кода, определение Annotation (@SpringBootApplication) и определение класса (SpringApplication.run) являются наиболее ослепительными, поэтому подробно разберем принцип автоматической сборки и процесс запуска, отсюда
2. Основные аннотации
2.1 @SpringBootApplication
@SpringBootApplication — наиболее часто используемая и почти необходимая аннотация, исходный код выглядит следующим образом:
/**
* Indicates a {@link Configuration configuration} class that declares one or more
* {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
* auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
* annotation that is equivalent to declaring {@code @Configuration},
* {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
* 标示一个声明有一个或多个的@Bean方法的Configuration类并且触发自动配置(EnableAutoConfiguration)
* 和组建扫描(ComponentScan)。
* 这是一个相当于@Configuration、@EnableAutoConfiguration、@ComponentScan 三合一的组合注解。
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
Как видно из объявления исходного кода,@SpringBootApplication эквивалентен @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan, поэтому непосредственно разбираем его для анализа.
2.2 @SpringBootConfiguration
@SpringBootConfiguration — это аннотация @Configuration, унаследованная от Spring, а @SpringBootConfiguration эквивалентна @Configuration..
Микросервисная галантерея, справочные материалы:Woohoo. Появляется в 1314. Talent/forum-64-1. …
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
В Spring 3.0 добавлены @Configuration, @Bean. Компоненты в контейнере Spring могут быть настроены более интуитивно на основе формы JavaConfig. SpringBoot рекомендует использовать форму конфигурации на основе JavaConfig.
На основе конфигурации xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-lazy-init="true">
<bean id="mockService" class="..MockServiceImpl">
...
</bean>
</beans>
На основе конфигурации JavaConfig:
@Configuration
public class MockConfiguration{
@Bean
public MockService mockService(){
return new MockServiceImpl();
}
}
Таким образом, @Configuration эквивалентен XML-файлу Spring.С помощью аннотации @Bean вы можете настроить компоненты, которыми должен управлять контейнер Spring.
2.3 @ComponentScan
Исходный код @ComponentScan:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* 对应的包扫描路径 可以是单个路径,也可以是扫描的路径数组
* @return
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 和value一样是对应的包扫描路径 可以是单个路径,也可以是扫描的路径数组
* @return
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 指定具体的扫描类
* @return
*/
Class<?>[] basePackageClasses() default {};
/**
* 对应的bean名称的生成器 默认的是BeanNameGenerator
* @return
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* 处理检测到的bean的scope范围
*/
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
/**
* 是否为检测到的组件生成代理
*/
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
/**
* 控制符合组件检测条件的类文件 默认是包扫描下的
* @return
*/
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
/**
* 是否对带有@Component @Repository @Service @Controller注解的类开启检测,默认是开启的
* @return
*/
boolean useDefaultFilters() default true;
/**
* 指定某些定义Filter满足条件的组件
* @return
*/
Filter[] includeFilters() default {};
/**
* 排除某些过来器扫描到的类
* @return
*/
Filter[] excludeFilters() default {};
/**
* 扫描到的类是都开启懒加载 ,默认是不开启的
* @return
*/
boolean lazyInit() default false;
}
На основе конфигурации xml:
<context:component-scan base-package="com.youzan" use-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
На основе конфигурации JavaConfig:
@Configuration
@ComponentScan(value = "com.youzan", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class ScanConfig {
}
Резюме: @ComponentScan обычно используется в сочетании с @Configuration, что эквивалентно @ComponentScan в xml.<context:component-scan>
, используемый, чтобы сообщить Spring, какие пакеты или классы сканировать. Если значение не задано, класс на том же уровне, что и аннотация @ComponentScan, и все классы в каталоге того же уровня сканируются по умолчанию, поэтому для проекта Spring Boot класс входа обычно помещается в каталог верхнего уровня, чтобы убедиться, что каталог исходного кода Все классы могут быть просканированы.
2.4 @EnableAutoConfiguration 🚩
Исходный код @EnableAutoConfiguration выглядит следующим образом:
/**
* Enable auto-configuration of the Spring Application Context, attempting to guess and
* configure beans that you are likely to need. Auto-configuration classes are usually
* applied based on your classpath and what beans you have defined. For example, If you
* have {@code tomcat-embedded.jar} on your classpath you are likely to want a
* {@link TomcatEmbeddedServletContainerFactory} (unless you have defined your own
* {@link EmbeddedServletContainerFactory} bean).
*
* 启用Spring应用程序上下文的自动配置,试图猜测和配置您可能需要的bean。 自动配置类通常基于您的类路径和您
* 定义的bean应用。 例如,如果您在类路径中有tomcat-embedded.jar,那么您可能需要一个
* TomcatEmbeddedServletContainerFactory(除非您已定义了自己的 EmbeddedInvletContainerFactory
* bean)。
* <p>
*
* Auto-configuration classes are regular Spring {@link Configuration} beans. They are
* located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
* Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
* often using {@link ConditionalOnClass @ConditionalOnClass} and
* {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
*
* 自动配置类是 @Configuration 注解的bean。 它们使用 SpringFactoriesLoader 机制(针对这个类键入)。
* 通常,自动配置bean是 @Conditional bean(通常使用 @ConditionalOnClass 和
* @ConditionalOnMissingBean 注释)。
*
* @author Phillip Webb
* @author Stephane Nicoll
* @see ConditionalOnBean
* @see ConditionalOnMissingBean
* @see ConditionalOnClass
* @see AutoConfigureAfter
* @see SpringBootApplication
*/
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
Здесь представлено несколько новых аннотаций: @Import, @Conditional, @ConditionalOnClass, @ConditionalOnMissingBean и т. д. Ядром аннотации @EnableAutoConfiguration является@Import(EnableAutoConfigurationImportSelector.class)
л импортировано вEnableAutoConfigurationImportSelector.class
.
/**
* 核心方法,加载spring.factories文件中的
* org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置类
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class, 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;
}
Существует множество конфигураций aoto, настроенных по умолчанию в META-INF/spring.factories в пакете spring-boot-autoconfigure.jar, а именно:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
......
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
Например, WebMvcAutoConfiguration.class:
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@Bean
@ConditionalOnMissingBean(HttpPutFormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)
public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
return new OrderedHttpPutFormContentFilter();
}
......etc
}
Введение этого класса эквивалентно представлению базовой конфигурации webmvc.Как и многие другие классы, этот класс сильно зависит от аннотаций Spring Boot.
В целом, @EnableAutoConfiguration выполняет следующие функции:
Ищите все файлы конфигурации Meta-inf / Spring.Factory из классов и создают элементы конфигурации, соответствующие org.springframework.boot.autoconfigure.enableutoconfiggencuge до соответствующей конфигурации контейнера IOC в форме javaconfig, отмеченной @configgation через классы отражения, которые затем агрегируются в один и загружены в контейнер IOC.
2.5 @Import
Внутри эквивалентного XML<import/>
, который позволяет импортировать класс аннотаций Configuration, классы реализации ImportSelector и ImportBeanDefinitionRegistrar, а также общий класс Component.
2.6 @Conditional
Сила Spring Boot заключается в использовании новой функции среды Spring 4: аннотации @Conditional, которая позволяет включать некоторые конфигурации только при соблюдении определенных условий. Это также является ключевой характеристикой «умности» Spring Boot. Условное семейство выглядит следующим образом:
-
@ConditionalOnBean
-
@ConditionalOnClass
-
@ConditionalOnExpression
-
@ConditionalOnMissingBean
-
@ConditionalOnMissingClass
-
@ConditionalOnNotWebApplication
-
@ConditionalOnResource
-
@ConditionalOnWebApplication
В качестве примера возьмем аннотацию @ConditionalOnClass:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
Основной класс реализацииOnClassCondition.class
, эта аннотация реализует интерфейс класса Condition:
public interface Condition {
/**
* 决定是否满足条件的方法
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
2.7 Резюме
Все приведенные выше аннотации делают одно:зарегистрировать bean-компонент в контейнере spring. Делают это по-разному, в разных условиях:
- @SpringBootConfiguration завершает настройку JavaConfig Bean путем объединения с @Bean;
- @ComponentScan сканирует классы, аннотированные определенными аннотациями, посредством сканирования диапазона и регистрирует их в контейнере Spring;
- @EnableAutoConfiguration завершает регистрацию bean-компонента с помощью конфигурации spring.factories и в сочетании с условиями @Condition;
- @Import разрешает регистрацию указанного класса в контейнере Spring путем импорта;
3. Процесс запуска Spring Boot
3.1 Создание SpringApplication
Приступим к разбору ключевых методов:
SpringApplication.run(Application.class, args);
Соответствующая реализация:
// 参数对应的就是Application.class以及main方法中的args
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
// 最终运行的这个重载方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
Это в конечном итоге создаст экземпляр SpringApplication, а затем запустит его метод запуска.
думать:
1. Зачем создавать экземпляр в статическом методе?
2. Нельзя ли его инстанцировать?
// 构造实例
public SpringApplication(Object... sources) {
initialize(sources);
}
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
// 推断是否为web环境
this.webEnvironment = deduceWebEnvironment();
// 设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断应用入口类
this.mainApplicationClass = deduceMainApplicationClass();
}
В конструкторе делаются четыре основные вещи:
3.1.1 Сделать вывод, является ли тип приложения веб-средой
// 相关常量
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;
}
Здесь судят о том, является ли это веб-средой, исходя из наличия классов Servlet и ConfigurableWebApplicationContext.Упомянутая выше аннотация @Conditional также оценивает среду на основе класса, поэтому ссылка на пакет jar в проекте Spring Boot не должна быть произвольной. , а самые ненужные зависимости лучше всего удалить.
3.1.2 Установка инициализатора (Initializer)
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
А вот и концепция — инициализаторы.
Давайте сначала посмотрим на код, а затем попробуйте объяснить, что это делает:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 这里的入参type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 使用Set保存names来去重 避免重复配置导致多次实例化
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根据names来进行实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 对实例进行排序 可用 Ordered接口 或 @Order注解 配置顺序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
Этот метод загружает все настроенные ApplicationContextInitializers и создает их экземпляры.SpringFactoriesLoader.loadFactoryNames
Внутри метода:
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
Этот метод попытается прочитать соответствующий файл конфигурации из META-INF/spring.factories в пути к классам, а затем перейти, чтобы прочитать значение файла конфигурации с ключом: org.springframework.context.ApplicationContextInitializer. Взяв в качестве примера пакет spring-boot, его раздел META-INF/spring.factories определяется следующим образом:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
Таким образом, два имени класса считываются и помещаются в коллекцию, готовую к запуску следующего экземпляра:
// 关键参数:
// type: org.springframework.context.ApplicationContextInitializer.class
// names: 上一步得到的names集合
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<T>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
Шаг инициализации очень интуитивно понятен: загрузка класса, подтверждение того, что загруженный класс действительно является подклассом org.springframework.context.ApplicationContextInitializer, затем инициализация конструктора и, наконец, добавление его в список экземпляров.
Поэтому так называемый инициализатор — это класс реализации org.springframework.context.ApplicationContextInitializer, который определяется следующим образом:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
ApplicationContextInitializer — это интерфейс обратного вызова, который будет вызываться перед вызовом метода refresh() контейнера ConfigurableApplicationContext для выполнения некоторой инициализации контейнера.
3.1.3 Настройка слушателя
После установки инициализатора приступим к настройке слушателя:
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
Реализация такая же, как у Initializer:
// 这里的入参type是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
Точно так же возьмите spring.factories в пакете spring-boot в качестве примера, чтобы увидеть соответствующее значение ключа:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
Что касается интерфейса ApplicationListener, то это довольно простой интерфейс в фреймворке Spring, код выглядит следующим образом:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
Этот интерфейс основан на интерфейсе EventListener в JDK и реализует шаблон наблюдателя. Для реализации шаблона наблюдателя среды Spring он определяет, что интересующий тип события должен быть подклассом типа ApplicationEvent, который также наследуется от класса EventObject в JDK.
3.1.4 Вывод класса входа приложения (основной)
this.mainApplicationClass = deduceMainApplicationClass();
Реализация этого метода немного интересна:
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;
}
Он создает исключение времени выполнения и получает имя класса входа через кадр стека метода с именем main в стеке исключений.
думать:
1. Как получить информацию о стеке?
Thread.currentThread().getStackTrace();
new RuntimeException().getStackTrace();
На этом процесс инициализации экземпляра SpringApplication завершен.
3.2 Метод SpringApplication.run
После завершения инсалиции метод запуска называется ниже:
// 运行run方法
public ConfigurableApplicationContext run(String... args) {
// 此类通常用于监控开发过程中的性能,而不是生产应用程序的一部分。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 设置java.awt.headless系统属性,默认为true
// Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。
configureHeadlessProperty();
// KEY 1 - 获取SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
// 通知监听者,开始启动
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// KEY 2 - 根据SpringApplicationRunListeners以及参数来准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = printBanner(environment);
// KEY 3 - 创建Spring上下文
context = createApplicationContext();
// 注册异常分析器
analyzers = new FailureAnalyzers(context);
// KEY 4 - Spring上下文前置处理
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// KEY 5 - Spring上下文刷新
refreshContext(context);
// KEY 6 - Spring上下文后置处理
afterRefresh(context, applicationArguments);
// 发出结束执行的事件
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
}
Этот метод запуска содержит много контента, и он анализируется один за другим в соответствии с ключевыми шагами, перечисленными выше:
3.2.1 Получить RunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
Здесь по-прежнему используется метод getSpringFactoriesInstances для получения экземпляра:
// 这里的入参:
// type: SpringApplicationRunListener.class
// parameterTypes: new Class<?>[] { SpringApplication.class, String[].class };
// args: SpringApplication实例本身 + main方法传入的args
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
Итак, здесь мы все еще читаем Values, Key которых — org.springframework.boot.SpringApplicationRunListener из META-INF/spring.factories:
Например, spring.factories, определенные в пакете spring-boot:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
Давайте посмотрим, что делает этот EventPublishingRunListener:
/**
* {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
* <p>
* Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
* before the context is actually refreshed.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
// ...
}
Как видно из документации класса, он в первую очередь отвечает за выпуск события SpringApplicationEvent, он будет использовать внутренний ApplicationEventMulticaster для обработки события перед обновлением фактического контекста. Что касается конкретных сценариев применения, давайте проанализируем их использование позже.
3.2.2 Подготовка среды
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 创建环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 发布环境准备好的事件
listeners.environmentPrepared(environment);
// 非Web环境处理
if (!this.webEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
return environment;
}
Как настроить окружение:
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
Таким образом, здесь на самом деле задействованы два шага:
- Настройка источников свойств
- Настройте профили, чтобы указать, какие профили активны для среды приложения.
Для веб-приложений результирующая переменная среды является экземпляром StandardServletEnvironment. После получения экземпляра будет вызван метод environmentPrepared в предыдущих RunListeners:
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
Здесь пригодится определенный широковещательный сервер, который публикует событие ApplicationEnvironmentPreparedEvent.
Затем будет мониторинг, если есть выпуск.При создании экземпляра SpringApplication некоторые ApplicationListeners не инициализируются, и Listeners могут прослушивать событие ApplicationEnvironmentPreparedEvent и затем обрабатывать его соответствующим образом.
Следовательно, назначение и предназначение SpringApplicationRunListeners здесь также более очевидно: это фактически ретранслятор событий, который может воспринимать события, генерируемые в процессе запуска Spring Boot, а затем выборочно ретранслировать события. Почему он избирательный, просто посмотрите на его реализацию:
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
Его метод contextPrepared пуст и не использует внутренний InitialMulticaster для отправки событий. Следовательно, даже если есть внешний ApplicationListener, заинтересованный в этом событии, нет возможности его отслеживать.
Итак, поскольку есть перенаправленные события, кто прослушивает эти события, объясняется в конструкторе этого класса:
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
Слушатели, установленные ранее во время создания экземпляра SpringApplication, добавляются один за другим в список ApplicationListeners, соответствующий initialMulticaster. Таким образом, когда initialMulticaster вызывает метод multicastEvent, будут запущены соответствующие методы, определенные в этих прослушивателях.
3.2.3 Создание контекста Spring
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);
}
// WEB应用的上下文类型
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
// 非WEB应用的上下文类型
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
AnnotationConfigEmbeddedWebApplicationContext — очень важный класс, который будет подробно проанализирован позже.
Мышление: В проекте ssm несколько контекстов, и в Spring Boot несколько контекстов, почему?
3.2.4 Предварительная обработка контекста Spring
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 将环境和上下文关联起来
context.setEnvironment(environment);
// 为上下文配置Bean生成器以及资源加载器(如果它们非空)
postProcessApplicationContext(context);
// 调用初始化器
applyInitializers(context);
// 触发Spring Boot启动过程的contextPrepared事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 添加两个Spring Boot中的特殊单例Beans - springApplicationArguments以及springBootBanner
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// 加载sources - 对于DemoApplication而言,这里的sources集合只包含了它一个class对象
Set<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加载动作 - 构造BeanDefinitionLoader并完成Bean定义的加载
load(context, sources.toArray(new Object[sources.size()]));
// 触发Spring Boot启动过程的contextLoaded事件
listeners.contextLoaded(context);
}
ключевой шаг:
Настройте генератор компонентов и загрузчик ресурсов (если они не пустые):
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
context.getBeanFactory().registerSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
if (this.resourceLoader != null) {
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context)
.setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context)
.setClassLoader(this.resourceLoader.getClassLoader());
}
}
}
вызов инициализатора
protected void applyInitializers(ConfigurableApplicationContext context) {
// Initializers是经过排序的
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
Здесь я, наконец, использую инициализаторы, установленные при создании экземпляра SpringApplication, просматриваю их по очереди и вызываю метод инициализации.
3.2.5 Весеннее обновление контекста 🚩
private void refreshContext(ConfigurableApplicationContext context) {
// 由于这里需要调用父类一系列的refresh操作,涉及到了很多核心操作,因此耗时会比较长,本文不做具体展开
refresh(context);
// 注册一个关闭容器时的钩子函数
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
// 调用父类的refresh方法完成容器刷新的基础操作
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext)applicationContext).refresh();
}
Реализация по умолчанию функции ловушки, которая регистрируется при закрытии контейнера, находится в классе AbstractApplicationContext:
public void registerShutdownHook() {
if(this.shutdownHook == null) {
this.shutdownHook = new Thread() {
public void run() {
synchronized(AbstractApplicationContext.this.startupShutdownMonitor) {
AbstractApplicationContext.this.doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
Если пользовательский shutdownHook не указан, будет сгенерирован и добавлен в среду выполнения вариант по умолчанию. Поведение по умолчанию — вызвать его метод doClose, чтобы выполнить некоторую очистку после уничтожения контейнера.
3.2.6 Постобработка контекста Spring
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
callRunners(context, args);
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
Так называемая пост-операция заключается в вызове зарегистрированных бегунов по очереди после обновления контейнера. Бегуны могут быть классами реализации двух интерфейсов:
- org.springframework.boot.ApplicationRunner
- org.springframework.boot.CommandLineRunner
Интерфейсы CommandLineRunner и ApplicationRunner являются последним обратным вызовом после успешного запуска контейнера (аналогично самозапуску при загрузке).
В чем разница между этими двумя интерфейсами?
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}
public interface CommandLineRunner {
void run(String... args) throws Exception;
}
На самом деле разницы никакой, разве что типы параметров, принимаемые методом run в интерфейсе, разные. Один из них — это инкапсулированный тип ApplicationArguments, а другой — это прямой тип массива переменной длины String. Таким образом, вы можете выбрать соответствующую реализацию интерфейса в соответствии с вашими потребностями.