карта разума
Статья была выбрана Github, добро пожаловать в Star:Github.com/yehongqin/lai...
предисловие
Что меня больше всего впечатлило в SpringBoot в начале, так это возможность запуска приложения через класс запуска. До SpringBoot запуск приложения хоть и не был проблематичным, но все же немного громоздким, его нужно было запаковать в военный пакет, и надо было настроить tomcat, и у tomcat был файл server.xml для настройки.
Однако в SpringBoot есть встроенный tomcat, который запускается через класс startup, а конфигурация тоже сосредоточена в application.yml, что не слишком удобно. Движимый любопытством, поэтому мне не терпится разобраться с процессом запуска класса запуска, так что давайте начнем.
1. Стартовый класс
Во-первых, мы рассмотрим наиболее распространенный тип начала письма.
@SpringBootApplication
public class SpringmvcApplication {
public static void main(String[] args) {
SpringApplication.run(SpringmvcApplication.class, args);
}
}
Декомпозиция класса запуска на самом деле состоит из двух частей:
- Аннотация @SpringBootApplication
- Метод main(), который вызывает метод SpringApplication.run().
2. @SpringBootApplication
Сначала посмотрите на исходный код аннотации @SpringBootApplication.
@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 {
}
Очевидно, что аннотация @SpringBootApplication состоит из трех аннотаций, а именно:
- @ComponentScan
- @EnableAutoConfiguration
- @SpringBootConfiguration
2.1 @ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
}
Роль этого комментарияСкажите Spring сканировать класс, в котором находится пакет, и загружать подходящие компоненты.(например, классы с @Component и @Repository и т. д.) или определения bean-компонентов.
Итак, есть атрибут basePackages, если он не прописан по умолчанию, то он будет сканироваться из пакета того класса, где объявлен @ComponentScan.
такКласс Startup лучше всего определяется под корневой пакетом, поскольку обычно мы не указываем базовые пакеты при использовании @SpringBootApplication.
2.2 @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
Это составная аннотация, она выглядит как множество аннотаций, на самом деле ключом является аннотация @Import, которая загружает класс AutoConfigurationImportSelector, а затем вызывает метод selectImports() этого класса. Загрузите класс конфигурации в соответствии с возвращенным массивом строк (имя класса класса конфигурации).
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//返回的String[]数组,是配置类Class的类名
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
//返回配置类的类名
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
Если мы продолжим щелкнувшись, мы можем найти последний герой за кулисами, класс SpringFactory Class, который загружает классы конфигурации в META-INF / Spring. Ifactorory через метод LoadsProingFactory ().
Здесь класс конфигурации загружается файлом spring.factories, что обеспечивает хорошую расширяемость.
Таким образом, функция аннотации @EnableAutoConfiguration фактически состоит в том, чтобы включить автоматическую настройку, и автоматическая настройка в основном зависит от этого метода загрузки.
2.3 @SpringBootConfiguration
@SpringBootConfiguration наследуется от @Configuration, и их функции также одинаковы., помечая текущий класс как класс конфигурации, И он будет включать один или несколько экземпляров метода, объявленного в текущем классе, помеченного аннотацией @Bean, в контейнер Spring, а имя экземпляра — это имя метода.
2.4 Резюме
Давайте нарисуем здесь рисунок, чтобы объяснить три аннотации, содержащиеся в аннотации @SpringBootApplication.
Три, класс SpringApplication
Далее поговорим о коде, выполняемом в основном методе, то есть статическом методе run() класса SpringApplication.
//启动类的main方法
public static void main(String[] args) {
SpringApplication.run(SpringmvcApplication.class, args);
}
//启动类调的run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
//调的是下面的,参数是数组的run方法
return run(new Class<?>[] { primarySource }, args);
}
//和上面的方法区别在于第一个参数是一个数组
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
//实际上new一个SpringApplication实例,调的是一个实例方法run()
return new SpringApplication(primarySources).run(args);
}
В приведенном выше исходном коде обнаружено, что последний вызов является не статическим методом, а методом экземпляра, для которого требуется новый экземпляр SpringApplication, и этот конструктор также имеет параметр primarySources. Итак, переходим непосредственно к конструктору.
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
//断言primarySources不能为null,如果为null,抛出异常提示
Assert.notNull(primarySources, "PrimarySources must not be null");
//启动类传入的Class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判断当前项目类型,有三种:NONE、SERVLET、REACTIVE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//设置ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//判断主类,初始化入口类
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;
}
Выше показано, что вы делаете для создания экземпляра SpringApplication, который представлен на рисунке ниже.
После создания экземпляра SpringApplication завершается инициализация класса SpringApplication, который включает прослушиватель, инициализатор, тип приложения проекта, коллекцию классов запуска и загрузчик классов. как показано на рисунке.
После получения экземпляра SpringApplication далее вызывается метод экземпляра RUN(). Продолжайте видеть.
public ConfigurableApplicationContext run(String... args) {
//创建计时器
StopWatch stopWatch = new StopWatch();
//开始计时
stopWatch.start();
//定义上下文对象
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//Headless模式设置
configureHeadlessProperty();
//加载SpringApplicationRunListeners监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//发送ApplicationStartingEvent事件
listeners.starting();
try {
//封装ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//配置环境模块
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//根据环境信息配置要忽略的bean信息
configureIgnoreBeanInfo(environment);
//打印Banner标志
Banner printedBanner = printBanner(environment);
//创建ApplicationContext应用上下文
context = createApplicationContext();
//加载SpringBootExceptionReporter
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//ApplicationContext基本属性配置
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文
refreshContext(context);
//刷新后的操作,由子类去扩展
afterRefresh(context, applicationArguments);
//计时结束
stopWatch.stop();
//打印日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//发送ApplicationStartedEvent事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕
listeners.started(context);
//查找容器中注册有CommandLineRunner或者ApplicationRunner的bean,遍历并执行run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//发送ApplicationFailedEvent事件,标志SpringBoot启动失败
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//发送ApplicationReadyEvent事件,标志SpringApplication已经正在运行,即已经成功启动,可以接收服务请求。
listeners.running(context);
}
catch (Throwable ex) {
//报告异常,但是不发送任何事件
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
Совмещая комментарии и исходный код, на самом деле очень понятно.Чтобы углубить впечатление, нарисуйте картинку, чтобы увидеть весь процесс.
Суммировать
Класс запуска Surface выглядит как аннотация @SpringBootApplication и метод run(). На самом деле это результат высокой инкапсуляции. Мы можем многое узнать из этого анализа. Например, файл spring.factories используется для завершения автоматической настройки и улучшения расширяемости. Используйте шаблон наблюдателя при запуске, уведомляйте в виде публикации событий, уменьшайте связанность, легко расширяйте и многое другое.
Тогда анализ класса запуска SpringBoot здесь, спасибо за чтение.
Ставьте лайки, если считаете это полезным, ваши лайки — самая большая мотивация для моего творчества.~
我是一个努力让大家记住的程序员。 Увидимся в следующий раз! ! !
Возможности ограничены, если есть какие-то ошибки или неуместности, просьба критиковать и исправлять их, учиться и обмениваться вместе!