- Предупреждение: Статья очень длинная, рекомендуется сначала отметить ее, а потом прочитать, возможно, это будет последний раз, когда вы пишите такую длинную статью.
- Описание: Есть 4 подраздела о базовых знаниях Spring, а именно: контейнер IOC, JavaConfig, мониторинг событий, подробное объяснение SpringFactoriesLoader, они занимают большую часть содержания этой статьи, хотя между ними может быть не так много связи, но эти знания Очень важно понимать основные принципы Spring Boot.Если вы знакомы с фреймворком Spring, вы можете пропустить эти 4 раздела. Именно потому, что эта серия статей состоит из этих, казалось бы, несвязанных знаний, она называется «Список знаний».
В экосистеме Spring за последние два-три года самое интересное — это фреймворк Spring Boot. Возможно, оригинальный дизайн этого фреймворка виден из названия: для быстрого запуска приложений Spring. Таким образом, приложение Spring Boot — это, по сути, приложение, основанное на платформе Spring.Это передовой продукт концепции Spring «соглашение важнее конфигурации».Он может помочь разработчикам быстрее и эффективнее создавать приложения на основе экосистемы Spring.
Так в чем же магия Spring Boot?Автоматическая конфигурация,Зависит от запуска,Actuator,Интерфейс командной строки (CLI)Это четыре наиболее важные основные функции Spring Boot. Среди них CLI является дополнительной функцией Spring Boot. Несмотря на то, что он мощный, он также представляет набор нетрадиционных моделей разработки. Поэтому в этой серии статей основное внимание уделяется другим три черты.. Как следует из названия статьи, эта статья является первой частью этой серии, которая откроет для вас двери Spring Boot, сосредоточив внимание на анализе процесса его запуска и принципа реализации автоматической конфигурации. Чтобы освоить эту часть основного содержания и получить некоторые базовые знания о среде Spring, вы сможете делать больше с меньшими затратами.
1. Знакомство с модулем: изучение контейнера Spring IoC
Если вы виделиSpringApplication.run()
Исходный код метода, долгий процесс запуска Spring Boot точно сведет с ума. Глядя на суть через явление, SpringApplication просто расширяет процесс запуска типичного приложения Spring. Поэтому доскональное понимание контейнера Spring обязательно открыть дверь Spring Boot.ключом.
1.1, контейнер Spring IoC
Контейнер Spring IoC можно сравнить с рестораном: приходя в ресторан, вы обычно прямо здороваетесь с официантом: заказывайте! Что касается ингредиентов блюда? Как сделать овощи из сырья? Может быть, вам все равно. То же самое верно и для контейнера IoC. Вам нужно только сказать ему, что ему нужен бин, и он выдаст вам соответствующий экземпляр. Что касается того, зависит ли бин от других компонентов, и как завершить его инициализацию, вы не вообще не нужно заботиться.
Как ресторан, если вы хотите приготовить блюда, вам нужно знать ингредиенты и рецепты блюд.Точно так же, если контейнер IoC хочет управлять различными бизнес-объектами и зависимостями между ними, он должен записывать и управлять этой информацией в каким-то образом.BeanDefinition
Объект берет на себя эту ответственность: каждый бин в контейнере будет иметь соответствующий экземпляр BeanDefinition, который отвечает за сохранение всей необходимой информации объекта бина, включая тип класса объекта бина, является ли он абстрактным классом, конструктором и параметры, другие свойства и т.д. Когда клиент запрашивает соответствующий объект из контейнера, контейнер будет использовать эту информацию, чтобы вернуть клиенту полностью доступный экземпляр компонента.
Ингредиенты готовы (смотрите в BeanDefinition на ингредиенты), приступим к приготовлению, подождите, еще нужен рецепт,BeanDefinitionRegistry
иBeanFactory
Именно по этому рецепту BeanDefinitionRegistry абстрагирует логику регистрации бинов, а BeanFactory абстрагирует логику управления бинами, а классы реализации каждого BeanFactory конкретно отвечают за регистрацию и управление бинами. Отношения между ними следующие:
DefaultListableBeanFactory
В качестве более общей реализации BeanFactory он также реализует интерфейс BeanDefinitionRegistry, поэтому он берет на себя работу по управлению регистрацией Bean. Из рисунка также видно, что интерфейс BeanFactory в основном включает в себя методы для управления bean-компонентами, такими как getBean, containsBean, getType и getAliases, тогда как интерфейс BeanDefinitionRegistry включает методы для регистрации и управления BeanDefinitions, такие как registerBeanDefinition, removeBeanDefinition и getBeanDefinition.
Ниже приведен простой код для имитации работы нижнего уровня BeanFactory:
// 默认容器实现
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
// 根据业务对象构造相应的BeanDefinition
AbstractBeanDefinition definition = new RootBeanDefinition(Business.class,true);
// 将bean定义注册到容器中
beanRegistry.registerBeanDefinition("beanName",definition);
// 如果有多个bean,还可以指定各个bean之间的依赖关系
// ........
// 然后可以从容器中获取这个bean的实例
// 注意:这里的beanRegistry其实实现了BeanFactory接口,所以可以强转,
// 单纯的BeanDefinitionRegistry是无法强制转换到BeanFactory类型的
BeanFactory container = (BeanFactory)beanRegistry;
Business business = (Business)container.getBean("beanName");
Этот код предназначен только для иллюстрации общего рабочего процесса в нижней части BeanFactory.Реальная ситуация будет более сложной.Например, зависимости между bean-компонентами могут быть определены во внешних файлах конфигурации (XML/Properties) или в аннотациях. Весь рабочий процесс контейнера Spring IoC можно условно разделить на два этапа:
①, этап запуска контейнера
Когда контейнер запускается, он каким-то образом загружаетсяConfiguration MetaData
. В дополнение к относительно простому кодовому методу в большинстве случаев контейнер должен полагаться на определенные классы инструментов, такие как:BeanDefinitionReader
, BeanDefinitionReader загрузитConfiguration MetaData
Выполните синтаксический анализ и анализ и соберите проанализированную информацию в соответствующий BeanDefinition и, наконец, зарегистрируйте эти BeanDefinition, сохраняющие определение компонента, в соответствующий BeanDefinitionRegistry, чтобы запуск контейнера был завершен. Этот этап в основном завершает некоторую подготовительную работу и больше фокусируется на сборе информации об управлении объектом bean-компонента.Конечно, на этом этапе также выполняется некоторая проверка или вспомогательная работа.
Давайте рассмотрим простой пример. Раньше все bean-компоненты определялись в XML-файлах конфигурации. Следующий код имитирует, как BeanFactory загружает определения bean-компонентов и зависимости из файла конфигурации:
// 通常为BeanDefinitionRegistry的实现类,这里以DeFaultListabeBeanFactory为例
BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory();
// XmlBeanDefinitionReader实现了BeanDefinitionReader接口,用于解析XML文件
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(beanRegistry);
// 加载配置文件
beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
// 从容器中获取bean实例
BeanFactory container = (BeanFactory)beanRegistry;
Business business = (Business)container.getBean("beanName");
②, этап создания экземпляра компонента
После первого этапа все определения bean-компонентов регистрируются в BeanDefinitionRegistry через BeanDefinition.Когда запрос запрашивает объект с помощью метода контейнера getBean или когда контейнеру зависимостей необходимо неявно вызвать getBean, действие второго этапа: запрошенный объект был создан ранее. Если нет, запрошенный объект создается в соответствии с информацией, предоставленной зарегистрированным BeanDefinition, и в него внедряются зависимости. Когда объект собран, контейнер немедленно возвращает его методу запроса.
BeanFactory — это просто реализация контейнера Spring IoC.Если нет специальной спецификации, он использует стратегию ленивой инициализации: только при доступе к объекту в контейнере объект инициализируется и вводится зависимость. В практических сценариях мы больше используем другой тип контейнера:ApplicationContext
, который построен поверх BeanFactory и относится к контейнеру более высокого уровня.Помимо всех возможностей BeanFactory, он также обеспечивает поддержку мониторинга событий и интернационализации. Все bean-компоненты, которыми он управляет, инициализируются, и при запуске контейнера внедряются зависимости.
1.2, Механизм расширения пружинного контейнера
Контейнер IoC отвечает за управление жизненным циклом всех bean-компонентов в контейнере, и на разных этапах жизненного цикла bean-компонента Spring предоставляет различные точки расширения для изменения судьбы bean-компонента. На этапе запуска контейнераBeanFactoryPostProcessor
Позволяет нам выполнять некоторые дополнительные операции с информацией, сохраненной BeanDefinition, зарегистрированной в контейнере, до того, как контейнер создаст экземпляр соответствующего объекта, например, изменить некоторые свойства определения компонента или добавить другую информацию.
Если вы хотите настроить класс расширения, вам обычно нужно реализоватьorg.springframework.beans.factory.config.BeanFactoryPostProcessor
интерфейс, в то же время, поскольку в контейнере может быть несколько BeanFactoryPostProcessors, может также потребоваться его реализацияorg.springframework.core.Ordered
Интерфейс для обеспечения того, чтобы BeanFactoryPostProcessor выполнялся по порядку. Spring предоставляет несколько реализаций BeanFactoryPostProcessor, начнем сPropertyPlaceholderConfigurer
чтобы объяснить его общий рабочий процесс.
В конфигурационном файле XML проекта Spring часто видно, что значения многих элементов конфигурации используют заполнители, а значения, представленные заполнителями, настраиваются отдельно в отдельном файле свойств, так что разрозненные данные в могут быть разные XML-файлы. Конфигурация управляется централизованно, а также удобно для эксплуатации и обслуживания настраивать разные значения в соответствии с разными средами. Эту очень полезную функцию реализует PropertyPlaceholderConfigurer.
Согласно предыдущей статье, когда BeanFactory загружает всю информацию о конфигурации на первом этапе, свойства объектов, сохраненных в BeanFactory, все еще существуют в виде заполнителей, таких как${jdbc.mysql.url}
. Когда PropertyPlaceholderConfigurer применяется как BeanFactoryPostProcessor, он заменяет значение свойства, представленное заполнителем в соответствующем BeanDefinition, значением в файле конфигурации свойств. Когда необходимо создать экземпляр компонента, значение свойства в определении компонента заменяется значением, которое мы настроили. Конечно, его реализация немного сложнее, чем описанная выше, здесь объясняется только его общий принцип работы, для более подробной реализации обратитесь к его исходному коду.
Точно так же естьBeanPostProcessor
, который существует на этапе создания экземпляра объекта. Подобно BeanFactoryPostProcessor, он будет обрабатывать все подходящие и созданные объекты в контейнере. Для простого сравнения BeanFactoryPostProcessor обрабатывает определение bean-компонентов, а BeanPostProcessor обрабатывает объекты после создания экземпляра bean-компонента. BeanPostProcessor определяет два интерфейса:
public interface BeanPostProcessor {
// 前置处理
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
// 后置处理
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
Для того, чтобы понять сроки выполнения этих двух методов, вкратце разберемся со всем жизненным циклом бина:
Процесс создания экземпляра компонента (из: Spring show)postProcessBeforeInitialization()
метод сpostProcessAfterInitialization()
Они соответствуют методам, которые должны выполняться на двух этапах предварительной обработки и постобработки на рисунке. Оба метода передают ссылку на экземпляр объекта bean-компонента, что обеспечивает большое удобство для процесса создания экземпляра объекта контейнера расширения, где с переданным экземпляром может быть выполнена практически любая операция. Реализация аннотаций, АОП и других функций широко используетсяBeanPostProcessor
Например, если у вас есть пользовательская аннотация, вы можете полностью реализовать интерфейс BeanPostProcessor, в котором вы можете судить о том, есть ли эта аннотация на голове объекта бина, если есть, то вы можете выполнять любую операцию над этим экземпляром бина. , Думаете, это очень просто?
Рассмотрим более распространенный пример: в Spring часто можно увидеть различные интерфейсы Aware, функция которых заключается в внедрении зависимостей, указанных в определении интерфейса Aware, в текущий экземпляр после завершения инстанцирования объекта. вроде самый обычныйApplicationContextAware
Интерфейс, классы, реализующие этот интерфейс, могут получить объект ApplicationContext. Когда процесс создания экземпляра каждого объекта в контейнере переходит к этапу предварительной обработки BeanPostProcessor, контейнер обнаружит ApplicationContextAwareProcessor, ранее зарегистрированный в контейнере, а затем вызовет его метод postProcessBeforeInitialization() для проверки и установки зависимостей, связанных с Aware. Взгляните на код, он очень простой:
// 代码来自:org.springframework.context.support.ApplicationContextAwareProcessor
// 其postProcessBeforeInitialization方法调用了invokeAwareInterfaces方法
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
// ......
}
Наконец, чтобы подвести итог, в этом разделе вместе с вами рассматривается часть основного содержимого контейнера Spring.Из-за нехватки места я не могу писать больше, но понимания этой части достаточно, чтобы вы могли легко понять принцип запуска Spring Boot. Если в последующем процессе обучения вы столкнетесь с какими-то неясными знаниями, а затем вернетесь к основным знаниям Spring, могут возникнуть неожиданные эффекты. Может быть, китайских материалов для Spring Boot очень мало, но китайских материалов и книг для Spring слишком много, и всегда есть что-то, что может вас вдохновить.
2. Прочная основа: JavaConfig и общая аннотация
2.1, JavaConfig
мы знаемbean
Это очень важная концепция в Spring IOC.Контейнер Spring отвечает за управление жизненным циклом bean-компонентов. Вначале Spring использовал файлы конфигурации XML для описания определения bean-компонентов и их зависимостей, но с развитием Spring все больше и больше людей выражали неудовлетворенность этим методом, потому что все бизнес-классы проекта Spring настраиваются в файлах XML в в виде bean-компонентов, что приводит к большому количеству XML-файлов, что делает проект сложным и трудным в управлении.
Позже, на основе чистой среды внедрения зависимостей аннотаций Java.Guice
Born, его производительность значительно выше, чем у Spring с использованием XML, и некоторые люди даже думают, чтоGuice
Может полностью заменить Spring(Guice
Это просто облегченная структура IOC, и она далека от замены Spring). Именно это ощущение кризиса побудило Spring и сообщество запустить и продолжать улучшатьJavaConfig
Подпроект, описывающий отношения привязки зависимостей между bean-компонентами на основе кода Java и аннотаций Annotation. Например, вот определение bean-компонента с использованием XML-конфигурации:
<bean id="bookService" class="cn.moondev.service.BookServiceImpl"></bean>
Конфигурация на основе JavaConfig выглядит так:
@Configuration
public class MoonBookConfiguration {
// 任何标志了@Bean的方法,其返回值将作为一个bean注册到Spring的IOC容器中
// 方法名默认成为该bean定义的id
@Bean
public BookService bookService() {
return new BookServiceImpl();
}
}
Если между двумя bean-компонентами существует зависимость, в конфигурации XML она должна выглядеть следующим образом:
<bean id="bookService" class="cn.moondev.service.BookServiceImpl">
<property name="dependencyService" ref="dependencyService"/>
</bean>
<bean id="otherService" class="cn.moondev.service.OtherServiceImpl">
<property name="dependencyService" ref="dependencyService"/>
</bean>
<bean id="dependencyService" class="DependencyServiceImpl"/>
А в JavaConfig это так:
@Configuration
public class MoonBookConfiguration {
// 如果一个bean依赖另一个bean,则直接调用对应JavaConfig类中依赖bean的创建方法即可
// 这里直接调用dependencyService()
@Bean
public BookService bookService() {
return new BookServiceImpl(dependencyService());
}
@Bean
public OtherService otherService() {
return new OtherServiceImpl(dependencyService());
}
@Bean
public DependencyService dependencyService() {
return new DependencyServiceImpl();
}
}
Вы можете заметить, что в этом примере есть два bean-компонента, которые зависят от dependencyService, а это означает, что когда bookService инициализируется, он будет вызыватьсяdependencyService()
, также вызываемый при инициализации otherServicedependencyService()
, тогда возникает вопрос? На данный момент есть ли в контейнере IOC один или два экземпляра dependencyService? Этот вопрос оставлен для размышления каждого и не будет повторяться здесь.
2.2, @components могут
@ComponentScan
Аннотация соответствует конфигурации XML в виде<context:component-scan>
элемент, указывающий, что сканирование компонентов включено, Spring автоматически просканирует все bean-компоненты, настроенные с помощью аннотаций, а затем зарегистрирует их в IOC-контейнере. мы можем пройтиbasePackages
и другие атрибуты для указания@ComponentScan
Область автоматического сканирования, если не указана, по умолчанию из декларации@ComponentScan
классаpackage
сканировать. Из-за этого классы запуска SpringBoot по умолчаниюsrc/main/java
Вниз.
2.3. @импорт
@Import
Аннотации используются для импорта классов конфигурации, например:
@Configuration
public class MoonBookConfiguration {
@Bean
public BookService bookService() {
return new BookServiceImpl();
}
}
Теперь есть еще один класс конфигурации, например:MoonUserConfiguration
, в этом классе конфигурации есть bean-компонент, который зависит отMoonBookConfiguration
bookService в , как мне объединить эти два bean-компонента? с помощью@Import
Просто:
@Configuration
// 可以同时导入多个配置类,比如:@Import({A.class,B.class})
@Import(MoonBookConfiguration.class)
public class MoonUserConfiguration {
@Bean
public UserService userService(BookService bookService) {
return new BookServiceImpl(bookService);
}
}
Следует отметить, что до 4.2,@Import
Аннотация поддерживает только импорт классов конфигурации, но после версии 4.2 она поддерживает импорт обычных классов и регистрацию этого класса в качестве определения bean-компонента в контейнере IOC.
2.4. @условный
@Conditional
Примечание. Указывает, что фасоль инициализируется или некоторая конфигурация включена после удовлетворения определенного состояния. Обычно используется@Component
,@Service
,@Configuration
над классом, указанным в аннотации, или@Bean
способ маркировки. если@Configuration
класс отмечен@Conditional
, то все идентификаторы в этом классе@Bean
метод и@Import
Связанные классы, импортированные аннотацией, будут подчиняться этим условиям.
Вы можете легко написать свои собственные условные классы в Spring, все, что вам нужно сделать, это реализоватьCondition
интерфейс и переопределить егоmatches()
метод. Например, следующий простой условный класс выражает толькоClasspath
существуют вJdbcTemplate
Класс вступает в силу только тогда, когда:
public class JdbcTemplateCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
try {
conditionContext.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
return true;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return false;
}
}
Когда вы объявляете bean-компоненты в Java, вы можете использовать этот пользовательский условный класс:
@Conditional(JdbcTemplateCondition.class)
@Service
public MyService service() {
......
}
В этом примере только когдаJdbcTemplateCondition
Бин MyService создается только при выполнении условий класса. Другими словами, условие создания компонента MyService:classpath
содержитJdbcTemplate
, иначе объявление bean-компонента будет проигнорировано.
Spring Boot
определяет много интересных условий и применяет их к классам конфигурации, которые составляютSpring Boot
Основа автоматической настройки.Spring Boot
Способ использования условной конфигурации заключается в определении нескольких специальных условных аннотаций и применении их к классу конфигурации. перечислено нижеSpring Boot
Некоторые из предоставленных условных аннотаций:
Условная аннотация | Условия действия конфигурации |
---|---|
@ConditionalOnBean | Конкретный bean-компонент настроен |
@ConditionalOnMissingBean | Конкретные bean-компоненты не настроены |
@ConditionalOnClass | В пути к классам есть указанный класс |
@ConditionalOnMissingClass | Класс не указан в classpath |
@ConditionalOnExpression | Учитывая результаты вычисления выражений на языке Spring Expression, True |
@ConditionalOnJava | Версия Java, соответствующая определенному индексу или диапазону значений |
@ConditionalOnProperty | Указанное свойство конфигурации должно иметь явное значение. |
@ConditionalOnResource | В пути к классам есть указанный ресурс |
@ConditionalOnWebApplication | Это веб-приложение |
@ConditionalOnNotWebApplication | Это не веб-приложение |
2.5, @ConfigurationProperties и @EnableConfigurationProperties
Когда значения некоторых свойств необходимо настроить, мы обычноapplication.properties
Создайте новый элемент конфигурации в файле и используйте его в bean-компоненте.@Value
Аннотация для получения значения конфигурации, например приведенный ниже код для настройки источника данных.
// jdbc config
jdbc.mysql.url=jdbc:mysql://localhost:3306/sampledb
jdbc.mysql.username=root
jdbc.mysql.password=123456
......
// 配置数据源
@Configuration
public class HikariDataSourceConfiguration {
@Value("jdbc.mysql.url")
public String url;
@Value("jdbc.mysql.username")
public String user;
@Value("jdbc.mysql.password")
public String password;
@Bean
public HikariDataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(url);
hikariConfig.setUsername(user);
hikariConfig.setPassword(password);
// 省略部分代码
return new HikariDataSource(hikariConfig);
}
}
использовать@Value
Атрибуты, вводимые аннотациями, обычно относительно просты, если одна и та же конфигурация используется в нескольких местах, ее также неудобно поддерживать (подумайте, есть ли десятки мест, использующих определенную конфигурацию, и теперь вы хотите изменить имя, как меняешь?) Для более сложных конфигураций Spring Boot предоставляет более элегантную реализацию, т.@ConfigurationProperties
аннотация. Мы можем переписать приведенный выше код следующим образом:
@Component
// 还可以通过@PropertySource("classpath:jdbc.properties")来指定配置文件
@ConfigurationProperties("jdbc.mysql")
// 前缀=jdbc.mysql,会在配置文件中寻找jdbc.mysql.*的配置项
pulic class JdbcConfig {
public String url;
public String username;
public String password;
}
@Configuration
public class HikariDataSourceConfiguration {
@AutoWired
public JdbcConfig config;
@Bean
public HikariDataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(config.url);
hikariConfig.setUsername(config.username);
hikariConfig.setPassword(config.password);
// 省略部分代码
return new HikariDataSource(hikariConfig);
}
}
@ConfigurationProperties
Для более сложных конфигураций также удобно обращаться со следующими файлами конфигурации:
#App
app.menus[0].title=Home
app.menus[0].name=Home
app.menus[0].path=/
app.menus[1].title=Login
app.menus[1].name=Login
app.menus[1].path=/login
app.compiler.timeout=5
app.compiler.output-folder=/temp/
app.error=/error/
Следующие классы конфигурации могут быть определены для получения этих свойств.
@Component
@ConfigurationProperties("app")
public class AppProperties {
public String error;
public List<Menu> menus = new ArrayList<>();
public Compiler compiler = new Compiler();
public static class Menu {
public String name;
public String path;
public String title;
}
public static class Compiler {
public String timeout;
public String outputFolder;
}
}
@EnableConfigurationProperties
В аннотации указано, что@ConfigurationProperties
Встроенная поддержка соответствующего класса Properties будет внедрена в IOC-контейнер как bean-компонент по умолчанию, то есть добавлять соответствующий класс Properties не требуется.@Component
аннотация.
В-третьих, режьте железо, как грязь: Spring FactoryLoader подробно
JVM предоставляет 3 загрузчика классов:BootstrapClassLoader
,ExtClassLoader
,AppClassLoader
Загрузите основную библиотеку классов Java, библиотеку классов расширения и путь к классам приложения соответственно (CLASSPATH
) в библиотеке классов. JVM загружает классы через родительскую модель делегирования, и мы также можем использовать наследование.java.lang.classloader
Реализуйте свой собственный загрузчик классов.
Какова модель родительского делегирования? Когда загрузчик класса получает задачу загрузки класса, он сначала передает ее своему родительскому загрузчику для завершения, поэтому окончательная задача загрузки будет передана самому верхнему BootstrapClassLoader, только когда родительский загрузчик не может выполнить задачу загрузки, он попытается Загружайте сами.
Одним из преимуществ использования родительской модели делегирования является гарантия того, что один и тот же объект в конечном итоге будет получен с использованием разных загрузчиков классов, что позволяет гарантировать безопасность типов базовой библиотеки Java.Например, загрузка пакета rt.jarjava.lang.Object
Класс, независимо от того, какой загрузчик загружает класс, в конечном итоге делегируется для загрузки загрузчику BootstrapClassLoader верхнего уровня, так что любой загрузчик класса может, наконец, получить тот же объект Object. Взглянув на исходный код ClassLoader, вы получите более интуитивное представление о модели родительского делегирования:
protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
// 首先,检查该类是否已经被加载,如果从JVM缓存中找到该类,则直接返回
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 遵循双亲委派的模型,首先会通过递归从父加载器开始找,
// 直到父类加载器是BootstrapClassLoader为止
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {}
if (c == null) {
// 如果还找不到,尝试通过findClass方法去寻找
// findClass是留给开发者自己实现的,也就是说
// 自定义类加载器时,重写此方法即可
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
Но модель родительского делегирования не решает всех проблем с загрузчиками классов, например, Java предоставляет множество интерфейсов поставщиков услуг (Service Provider Interface
, SPI), позволяя третьим сторонам предоставлять реализации для этих интерфейсов. Общие SPI включают JDBC, JNDI, JAXP и т. д. Интерфейсы этих SPI предоставляются основной библиотекой классов, но реализуются третьей стороной, поэтому возникает проблема: интерфейс SPI является частью базовой библиотеки Java и загружается с помощью BootstrapClassLoader; SPI Реализованные классы Java обычно загружаются с помощью AppClassLoader. BootstrapClassLoader не может найти
класс реализации SPI, потому что он загружает только основную библиотеку Java. Он также не может проксировать AppClassLoader, потому что это самый верхний загрузчик классов. Тем не менее, модель родительского делегирования не решает эту проблему.
загрузчик класса контекста потока (ContextClassLoader
) только что решил проблему. Из названия может быть неправильно понято как новый загрузчик классов, на самом деле это просто переменная класса Thread, доступ к которой может получитьsetContextClassLoader(ClassLoader cl)
иgetContextClassLoader()
установить и получить объект. Если ничего не задано, загрузчиком классов потока приложения Java по умолчанию является AppClassLoader. Когда основная библиотека классов использует интерфейс SPI, переданный загрузчик классов использует загрузчик класса контекста потока для успешной загрузки класса, реализованного SPI. Загрузчик класса контекста потока используется во многих реализациях SPI. Но в JDBC вы можете увидеть более прямую реализацию, например, управление драйверами JDBC.java.sql.Driver
серединаloadInitialDrivers()
можно напрямую увидеть, как JDK загружает драйвер:
for (String aDriver : driversList) {
try {
// 直接使用AppClassLoader
Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
На самом деле, чтобы объяснить загрузчик класса контекста потока, главное, чтобы все виделиThread.currentThread().getClassLoader()
иThread.currentThread().getContextClassLoader()
Это не будет перепутано, когда придет время.За исключением того, что ClassLoader, полученный во многих базовых платформах, может быть другим, они одинаковы в большинстве других бизнес-сценариев.Вам нужно только знать, для решения какой проблемы он существует.Вот и все.
Помимо загрузки классов, у загрузчика классов также есть очень важная функция — загрузка ресурсов, он может читать любой файл ресурсов из jar-пакета, например,ClassLoader.getResources(String name)
Метод используется для чтения файла ресурсов в пакете jar.Код выглядит следующим образом:
public Enumeration<URL> getResources(String name) throws IOException {
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
if (parent != null) {
tmp[0] = parent.getResources(name);
} else {
tmp[0] = getBootstrapResources(name);
}
tmp[1] = findResources(name);
return new CompoundEnumeration<>(tmp);
}
Вы чувствуете себя немного знакомым? Да, его логика фактически такая же, как и у загрузки класса. Сначала определите, пуст ли загрузчик родительского класса. Если он не пуст, поручите загрузчику родительского класса выполнить задачу поиска ресурсов, пока BootstrapClassLoader, и, наконец, ваша очередь узнать. Различные загрузчики классов отвечают за сканирование пакетов jar по разным путям, точно так же, как загрузка классов, и, наконец, сканируют все пакеты jar, чтобы найти подходящие файлы ресурсов.
загрузчик классовfindResources(name)
Метод будет проходить через все пакеты jar, за загрузку которых он отвечает, и найдет файл ресурсов с именем name в пакете jar. Ресурсом здесь может быть любой файл, даже файл .class. Например, следующий пример используется для поиска Файл Array.class:
// 寻找Array.class文件
public static void main(String[] args) throws Exception{
// Array.class的完整路径
String name = "java/sql/Array.class";
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
System.out.println(url.toString());
}
}
После запуска можно получить следующие результаты:
$JAVA_HOME/jre/lib/rt.jar!/java/sql/Array.class
В соответствии с URL-адресом файла ресурсов может быть создан соответствующий файл для чтения содержимого ресурса.
Увидев это, вы можете почувствовать себя очень странно, вы не пытаетесь подробно объяснитьSpringFactoriesLoader
? Что значит говорить о куче ClassLoaders? Взгляните на его исходный код, и вы узнаете:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式为:key=value1,value2,value3
// 从所有的jar包中找到META-INF/spring.factories文件
// 然后从文件中解析出key=factoryClass类名称的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 取得资源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 遍历所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根据资源文件URL解析properties文件
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 组装数据,并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
Обладая предыдущими знаниями о ClassLoader, а затем понимая этот код, вы чувствуете себя внезапно просветленным: отCLASSPATH
Искать все банки подMETA-INF/spring.factories
Конфигурационный файл, то будет проанализирован файл свойств, найдена и возвращена конфигурация с указанным именем. Следует отметить, что на самом деле он будет не только искать по пути ClassPath, но и сканировать пакеты Jar по всем путям, но этот файл будет только в пакете jar по пути Classpath. Давайте просто посмотримspring.factories
Содержимое файла:
// 来自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
// EnableAutoConfiguration后文会讲到,它用于开启Spring Boot自动配置功能
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
воплощать в жизньloadFactoryNames(EnableAutoConfiguration.class, classLoader)
После этого соответствующий набор@Configuration
своего рода,
Мы можем создать экземпляры этих классов с помощью отражения и внедрить их в контейнер IOC.Наконец, в контейнере будет ряд аннотаций.@Configuration
Класс конфигурации в форме JavaConfig.
ЭтоSpringFactoriesLoader
, который по сути является частной схемой расширения фреймворка Spring, похожей на SPI, многие основные функции Spring Boot на основе Spring основаны на этом, надеюсь, вы понимаете.
В-четвертых, еще одно оружие: механизм мониторинга событий контейнера Spring.
В прошлом механизм мониторинга событий в основном использовался для программирования с графическим интерфейсом, например:нажмитекнопка в текстовом полевходитьТакие операции, как контент, называются событиями, и когда событие инициируется, приложение делает определенный ответ, чтобы указать, что приложение прослушивает событие.На стороне сервера механизм мониторинга событий больше используется для асинхронного уведомления, мониторинга и исключения. умение обращаться. Java предоставляет два основных класса для реализации механизма мониторинга событий: пользовательские типы событий расширяются отjava.util.EventObject
, прослушиватель событий распространяется отjava.util.EventListener
. Давайте рассмотрим простой пример: просто отслеживайте время выполнения метода.
Сначала определите тип события.Обычной практикой является расширение EventObject.Когда происходит событие, соответствующее состояние обычно инкапсулируется в этот класс:
public class MethodMonitorEvent extends EventObject {
// 时间戳,用于记录方法开始执行的时间
public long timestamp;
public MethodMonitorEvent(Object source) {
super(source);
}
}
После публикации события соответствующий слушатель может обработать тип события. Мы можем опубликовать событие начала до того, как метод начнет выполняться, и опубликовать конечное событие после завершения выполнения метода. Соответственно, прослушиватель событий должен предоставить метод для Полученные события обрабатываются в обоих случаях:
// 1、定义事件监听接口
public interface MethodMonitorEventListener extends EventListener {
// 处理方法执行之前发布的事件
public void onMethodBegin(MethodMonitorEvent event);
// 处理方法结束时发布的事件
public void onMethodEnd(MethodMonitorEvent event);
}
// 2、事件监听接口的实现:如何处理
public class AbstractMethodMonitorEventListener implements MethodMonitorEventListener {
@Override
public void onMethodBegin(MethodMonitorEvent event) {
// 记录方法开始执行时的时间
event.timestamp = System.currentTimeMillis();
}
@Override
public void onMethodEnd(MethodMonitorEvent event) {
// 计算方法耗时
long duration = System.currentTimeMillis() - event.timestamp;
System.out.println("耗时:" + duration);
}
}
Интерфейс прослушивателя событий фактически предоставляет соответствующие определения методов обработки для различных релизов событий.Самое главное, что его метод получает только параметр MethodMonitorEvent, указывающий, что этот класс прослушивателя отвечает только за события, соответствующие слушателю и обработке. Имея события и слушатели, все, что осталось, — это опубликовать событие, а затем заставить соответствующий слушатель прослушивать и обрабатывать его. Обычно у нас есть издатель событий, который сам выступает в качестве источника событий и в нужное время публикует соответствующее событие в соответствующем прослушивателе событий:
public class MethodMonitorEventPublisher {
private List<MethodMonitorEventListener> listeners = new ArrayList<MethodMonitorEventListener>();
public void methodMonitor() {
MethodMonitorEvent eventObject = new MethodMonitorEvent(this);
publishEvent("begin",eventObject);
// 模拟方法执行:休眠5秒钟
TimeUnit.SECONDS.sleep(5);
publishEvent("end",eventObject);
}
private void publishEvent(String status,MethodMonitorEvent event) {
// 避免在事件处理期间,监听器被移除,这里为了安全做一个复制操作
List<MethodMonitorEventListener> copyListeners = ➥ new ArrayList<MethodMonitorEventListener>(listeners);
for (MethodMonitorEventListener listener : copyListeners) {
if ("begin".equals(status)) {
listener.onMethodBegin(event);
} else {
listener.onMethodEnd(event);
}
}
}
public static void main(String[] args) {
MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher();
publisher.addEventListener(new AbstractMethodMonitorEventListener());
publisher.methodMonitor();
}
// 省略实现
public void addEventListener(MethodMonitorEventListener listener) {}
public void removeEventListener(MethodMonitorEventListener listener) {}
public void removeAllListeners() {}
Для издателей событий (источников событий) обычно нужно сосредоточиться на двух вещах:
- Публикуйте события в нужное время. Метод methodMonitor() в этом примере является источником публикации событий. Он публикует события MethodMonitorEvent в два момента времени до и после выполнения метода. События, опубликованные в каждый момент времени, будут переданы соответствующему прослушивателю для обработки. В конкретной реализации следует отметить, что публикация событий выполняется последовательно, чтобы не влиять на производительность обработки, логика обработки прослушивателя событий должна быть максимально простой.
- Управление прослушивателями событий. Класс издателя предоставляет методы регистрации и удаления прослушивателей событий, чтобы клиент мог решить, зарегистрировать ли нового прослушивателя или удалить прослушиватель в соответствии с реальной ситуацией. Если здесь не указан метод удаления, на зарегистрированный экземпляр прослушивателя всегда будет ссылаться MethodMonitorEventPublisher, даже если он был заброшен, он все равно будет в списке прослушивателей издателя, что приведет к скрытой утечке памяти.
Механизм мониторинга событий в контейнере Spring
Все типы событий внутри контейнера ApplicationContext Spring наследуются отorg.springframework.context.AppliationEvent
, все слушатели в контейнере реализуютorg.springframework.context.ApplicationListener
интерфейс и регистрируется в контейнере как bean-компонент. После публикации событий ApplicationEvent и его подтипов в контейнере ApplicationListener, зарегистрированный в контейнере, будет обрабатывать эти события.
Вы должны были догадаться, что произошло.
ApplicationEvent наследуется от EventObject, и Spring предоставляет некоторые реализации по умолчанию, такие как:ContextClosedEvent
Представляет тип события, которое контейнер публикует перед завершением работы.ContextRefreshedEvent
Указывает тип события, которое публикует контейнер при его инициализации или обновлении...
Контейнер внутренне использует ApplicationListener в качестве определения интерфейса прослушивателя событий, который наследуется от EventListener. Когда контейнер ApplicationContext запускается, он автоматически идентифицирует и загружает bean-компоненты типа EventListener.Как только событие будет опубликовано в контейнере, он уведомит эти EventListeners, зарегистрированные в контейнере.
Интерфейс ApplicationContext наследует интерфейс ApplicationEventPublisher, который предоставляетvoid publishEvent(ApplicationEvent event)
В определении метода нетрудно заметить, что контейнер ApplicationContext играет роль издателя событий. Посмотрите, если интересноAbstractApplicationContext.publishEvent(ApplicationEvent event)
Исходный код метода: ApplicationContext делегирует публикацию событий и управление слушателямиApplicationEventMulticaster
Класс реализации интерфейса. При запуске контейнера проверяется, есть ли в контейнере экземпляр объекта ApplicationEventMulticaster с именем applicationEventMulticaster. Если есть, используйте его предоставленную реализацию, если нет, инициализируйте SimpleApplicationEventMulticaster как реализацию по умолчанию.
Наконец, если нашему бизнесу необходимо публиковать события внутри контейнера, нам нужно только внедрить в него зависимость ApplicationEventPublisher: реализовать интерфейс ApplicationEventPublisherAware или интерфейс ApplicationContextAware (пожалуйста, просмотрите содержимое интерфейса Aware выше).
5. Superb: демистификация принципа автоматической настройки
Классы запуска типичного приложения Spring Boot обычно расположены вsrc/main/java
по корневому пути, напримерMoonApplication
своего рода:
@SpringBootApplication
public class MoonApplication {
public static void main(String[] args) {
SpringApplication.run(MoonApplication.class, args);
}
}
в@SpringBootApplication
Включите сканирование компонентов и автоконфигурацию, аSpringApplication.run
отвечает за запуск приложения начальной загрузки.@SpringBootApplication
представляет собой соединениеAnnotation
, который объединяет три полезных аннотации:
@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 {
// ......
}
@SpringBootConfiguration
это@Configuration
, который является аннотацией среды Spring, указывающей, что класс являетсяJavaConfig
Класс конфигурации. и@ComponentScan
Включите сканирование компонентов, подробно описанное в предыдущей статье, здесь мы сосредоточимся на@EnableAutoConfiguration
.
@EnableAutoConfiguration
В аннотации указано, что функция автоконфигурации Spring Boot включена, Spring Boot угадывает нужные вам bean-компоненты на основе зависимостей приложения, пользовательских bean-компонентов, наличия класса в пути к классам и т. д., а затем регистрирует их в IOC. контейнер. Тот@EnableAutoConfiguration
Как вы определили свои потребности? Сначала посмотрите на его определение:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// ......
}
Ваше внимание должно быть сосредоточено на@Import(EnableAutoConfigurationImportSelector.class)
вверх, как я уже говорил,@Import
Аннотация используется для импорта класса и регистрации этого класса в контейнере в качестве определения компонента, здесь он поместитEnableAutoConfigurationImportSelector
Внедренный в контейнер как bean-компонент, и этот класс загрузит все подходящие конфигурации @Configuration в контейнер, взгляните на его код:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 省略了大部分代码,保留一句核心代码
// 注意:SpringBoot最近版本中,这句代码被封装在一个单独的方法中
// SpringFactoriesLoader相关知识请参考前文
List<String> factories = new ArrayList<String>(new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));
}
Этот класс просканирует все пакеты jar и внедрит все подходящие классы конфигурации @Configuration в контейнер.META-INF/spring.factories
содержимое файла:
// 来自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
// 配置的key = EnableAutoConfiguration,与代码中一致
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
.....
отDataSourceAutoConfiguration
В качестве примера посмотрите, как Spring Boot автоматически настраивается:
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {
}
Скажи отдельно:
-
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
: эта конфигурация активна только в том случае, если класс DataSource или EmbeddedDatabaseType существует в пути к классам, в противном случае эта конфигурация будет игнорироваться. -
@EnableConfigurationProperties(DataSourceProperties.class)
: Конфигурация класса DataSource по умолчанию вводится в IOC судна, свойства DataSource определяются как:
// 提供对datasource配置信息的支持,所有的配置前缀为:spring.datasource
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
private ClassLoader classLoader;
private Environment environment;
private String name = "testdb";
......
}
-
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
: импортировать другую дополнительную конфигурацию, просто начните сDataSourcePoolMetadataProvidersConfiguration
Например.
@Configuration
public class DataSourcePoolMetadataProvidersConfiguration {
@Configuration
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
static class TomcatDataSourcePoolMetadataProviderConfiguration {
@Bean
public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {
.....
}
}
......
}
DataSourcePoolMetadataProvidersConfiguration — это класс конфигурации для провайдеров пула соединений с базой данных, то есть он существует в пути к классам.org.apache.tomcat.jdbc.pool.DataSource.class
, затем используйте пул соединений tomcat-jdbc, если он существует в пути к классам.HikariDataSource.class
Затем используйте пул соединений Hikari.
Это только описывает верхушку айсберга DataSourceAutoConfiguration, но этого достаточно, чтобы проиллюстрировать, как Spring Boot использует условную конфигурацию для достижения автоматической конфигурации. Подводя итог,@EnableAutoConfiguration
Класс EnableAutoConfigurationImportSelector импортируется, и этот классselectImports()
Через Spring FactoriesLoader можно получить большое количество классов конфигурации, и каждый класс конфигурации принимает решения на основе условной конфигурации для достижения автоматической конфигурации.
Весь процесс ясен, но упущена одна большая проблема:EnableAutoConfigurationImportSelector.selectImports()
Когда оно было исполнено? На самом деле этот метод будет выполняться в процессе запуска контейнера:AbstractApplicationContext.refresh()
, более подробная информация описана в следующем подразделе.
Шесть, загрузка при запуске: секрет запуска приложения Spring Boot
6.1 Инициализация приложения Spring
Весь процесс запуска SpringBoot делится на два этапа: инициализация объекта SpringApplication и выполнение метода запуска объекта. Глядя на процесс инициализации SpringApplication, метод initialize(Object[] sources) вызывается в конструкторе SpringApplication Код выглядит следующим образом:
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();
}
Самое главное в процессе инициализации найти его через Spring FactoriesLoaderspring.factories
настроено в файлеApplicationContextInitializer
иApplicationListener
Имена классов реализации двух интерфейсов, чтобы соответствующие экземпляры можно было построить позже.ApplicationContextInitializer
Основная цель состоит в том, чтобыConfigurableApplicationContext
Перед обновлением выполните дополнительные настройки или обработку экземпляра ConfigurableApplicationContext. ConfigurableApplicationContext наследуется от ApplicationContext, который в основном предоставляет возможность устанавливать ApplicationContext.
Реализовать ApplicationContextInitializer очень просто, потому что у него всего один метод, но в большинстве случаев нам не нужно настраивать ApplicationContextInitializer, даже фреймворк Spring Boot, он по умолчанию регистрирует только две реализации, в конце концов, контейнер Spring очень зрелый и стабильный, вам не нужно его менять.
иApplicationListener
О его назначении говорить нечего.Это фреймворковая реализация механизма мониторинга событий Java фреймворком Spring.Конкретное содержание подробно объяснено в предыдущем разделе механизма мониторинга событий Spring. Главное здесь в том, что если вы хотите добавить прослушиватель в приложение Spring Boot, как вы это сделаете?
Spring Boot предоставляет два способа добавления пользовательских слушателей:
- пройти через
SpringApplication.addListeners(ApplicationListener<?>... listeners)
илиSpringApplication.setListeners(Collection<? extends ApplicationListener<?>> listeners)
Два способа добавить один или несколько пользовательских слушателей - Поскольку процесс инициализации SpringApplication был
spring.factories
получено вApplicationListener
Класс реализации, затем мы прямо в нашем собственном пакете jarMETA-INF/spring.factories
Добавьте конфигурацию в файл:
org.springframework.context.ApplicationListener=\
cn.moondev.listeners.xxxxListener\
Об инициализации SpringApplication мы так много говорили.
6.2 Процесс запуска Spring Boot
Весь процесс запуска приложения Spring Boot инкапсулирован в методе SpringApplication.run, Весь процесс действительно слишком долгий и слишком долгий, но по сути, он сделал много расширений на основе запуска контейнера Spring. к этой идее Посмотрите на исходный код:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
// ①
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// ②
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
// ③
Banner printedBanner = printBanner(environment);
// ④
context = createApplicationContext();
// ⑤
analyzers = new FailureAnalyzers(context);
// ⑥
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
// ⑦
refreshContext(context);
// ⑧
afterRefresh(context, applicationArguments);
// ⑨
listeners.finished(context, null);
stopWatch.stop();
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
① Найдите и загрузите всеSpringApplicationRunListeners
, уведомите все SpringApplicationRunListeners, вызвав метод start(): приложение запущено. SpringApplicationRunListeners по сути является издателем событий. Он публикует различные типы событий приложения (ApplicationEvent) в разные моменты времени при запуске приложения SpringBoot. Если какие-либо прослушиватели событий (ApplicationListener) заинтересованы в этих событиях, они могут их получать и обрабатывать. Помните, что во время процесса инициализации SpringApplication загрузил серию ApplicationListeners? В этом процессе запуска не найден код для публикации событий, но он уже реализован в SpringApplicationRunListeners.
Чтобы кратко проанализировать процесс его реализации, сначала взгляните на исходный код SpringApplicationRunListener:
public interface SpringApplicationRunListener {
// 运行run方法时立即调用此方法,可以用户非常早期的初始化工作
void starting();
// Environment准备好后,并且ApplicationContext创建之前调用
void environmentPrepared(ConfigurableEnvironment environment);
// ApplicationContext创建好后立即调用
void contextPrepared(ConfigurableApplicationContext context);
// ApplicationContext加载完成,在refresh之前调用
void contextLoaded(ConfigurableApplicationContext context);
// 当run方法结束之前调用
void finished(ConfigurableApplicationContext context, Throwable exception);
}
SpringApplicationRunListener имеет только один класс реализации:EventPublishingRunListener
. Код в ① получит только экземпляр EventPublishingRunListener, давайте взглянем на содержимое метода start():
public void starting() {
// 发布一个ApplicationStartedEvent
this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
}
Следуя этой логике, вы можете написать в ②prepareEnvironment()
находится в исходном коде методаlisteners.environmentPrepared(environment);
То есть второй метод интерфейса SpringApplicationRunListener, как и следовало ожидать,environmentPrepared()
Опубликовано еще одно событиеApplicationEnvironmentPreparedEvent
. Что будет дальше, мне не нужно говорить больше.
② Создайте и настройте текущее приложение для использованияEnvironment
, Среда используется для описания текущей операционной среды приложения, которая абстрагирует два аспекта: профиль и свойства.Учащиеся с богатым опытом разработки не должны быть незнакомы с этими двумя вещами: различные среды (например, производственная среда, предварительная среда) могут использовать разные файлы конфигурации, а свойства можно получить из таких источников, как файлы конфигурации, переменные среды, параметры командной строки и т. д. Поэтому, когда Среда готова, ресурсы могут быть получены из Среды в любое время на протяжении всего приложения.
Подводя итог, две строки кода в ② в основном выполняют следующие задачи:
- Определите, существует ли Среда, и создайте ее, если она не существует (если это веб-проект, создайте ее
StandardServletEnvironment
, иначе создатьStandardEnvironment
) - Настроить среду: настроить профиль и свойства
- вызов SpringApplicationRunListener
environmentPrepared()
метод для уведомления прослушивателей событий о том, что среда приложения готова
3. Приложение SpringBoot при запуске выведет что-то вроде этого:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.6.RELEASE)
Если вы хотите превратить эту вещь в собственное граффити, вы можете изучить реализацию следующего Баннера, и эта задача остается за вами.
④ Создайте разные контейнеры ApplicationContext в зависимости от того, является ли это веб-проектом.
⑤, создайте сериюFailureAnalyzer
, процесс создания по-прежнему заключается в получении всех классов, реализующих интерфейс FailureAnalyzer, через SpringFactoriesLoader, а затем в создании соответствующего экземпляра. FailureAnalyzer используется для анализа сбоев и предоставления соответствующей диагностической информации.
⑥, Инициализируйте ApplicationContext, в основном для выполнения следующей работы:
- Установите подготовленную среду в ApplicationContext
- Перебрать все вызванные ApplicationContextInitializer
initialize()
метод для дальнейшей обработки уже созданного ApplicationContext - вызов SpringApplicationRunListener
contextPrepared()
способ уведомления всех слушателей: ApplicationContext готов - Загрузите все бобы в контейнер
- вызов SpringApplicationRunListener
contextLoaded()
метод для уведомления всех слушателей о том, что ApplicationContext был загружен
⑦, вызовите ApplicationContextrefresh()
метод, который завершает последний процесс, доступный для контейнера IoC. Из названия это понимается как контейнер обновления, так что же такое обновление? Это вмешательство в запуск контейнера и обращение к содержимому первого подраздела. Как его обновить? И посмотрите на следующий код:
// 摘自refresh()方法中一句代码
invokeBeanFactoryPostProcessors(beanFactory);
Взгляните на реализацию этого метода:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
......
}
получить всеBeanFactoryPostProcessor
для выполнения некоторых дополнительных операций над контейнером. BeanFactoryPostProcessor позволяет нам выполнять некоторые дополнительные операции с информацией, сохраненной BeanDefinition, зарегистрированной в контейнере, прежде чем контейнер создаст экземпляр соответствующего объекта. Здесь метод getBeanFactoryPostProcessors() может получить 3 процессора:
ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
Классов реализации BeanFactoryPostProcessor не так много, почему здесь только три? Потому что среди различных ApplicationContextInitializers и ApplicationListeners, полученных в процессе инициализации, только 3 выше делают что-то похожее на следующее:
public void initialize(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}
Затем вы можете войтиPostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
метод, в дополнение к обходу трех вышеупомянутых обработок BeanFactoryPostProcessor, этот метод также получит типBeanDefinitionRegistryPostProcessor
фасоль:org.springframework.context.annotation.internalConfigurationAnnotationProcessor
, соответствующий классConfigurationClassPostProcessor
.ConfigurationClassPostProcessor
Используется для разбора и обработки различных аннотаций, в том числе: @Configuration, @ComponentScan, @Import, @PropertySource, @ImportResource, @Bean. когда имеешь дело с@import
При аннотировании вызывается в этом разделеEnableAutoConfigurationImportSelector.selectImports()
для завершения функции автоматической настройки. Другие здесь обсуждаться не будут.Если вам интересно, вы можете обратиться к ссылке 6.
8. Узнайте, зарегистрированы ли CommandLineRunner и ApplicationRunner в текущем контексте, и если да, пройдите и выполните их.
9. Выполнить все методы Finished() SpringApplicationRunListener.
Это весь процесс запуска Spring Boot. Суть заключается в добавлении различных точек расширения на основе инициализации и запуска контейнера Spring. Эти точки расширения включают в себя: ApplicationContextInitializer, ApplicationListener и различные BeanFactoryPostProcessors и т. д. Вам не нужно уделять слишком много внимания деталям всего процесса или даже понимать его, вам просто нужно понять, когда и как работают эти точки расширения, и сделать их доступными для вас.
Весь процесс запуска действительно очень сложен, вы можете запросить некоторые главы и содержание в справочных материалах и посмотреть исходный код, я думаю, вы сможете понять это в конце концов. Одним словом, Spring — это ядро, если вы понимаете процесс запуска контейнера Spring, то процесс запуска Spring Boot не представляет проблемы.
использованная литература
[1] Автор Фуцян Ван, SpringBoot Reveals: Rapidly Constructing Microservices System, Machinery Industry Press, 2016 г.
[2] Ван Фуцян; Весна раскрыта; Издательство "Народная почта", 2009 г.
[3] Написано Крейгом Уоллсом, переведено Дин Сюэфэном, Spring Boot на практике, China Industry and Information Publishing Group, People's Posts and Telecommunications Press, 2016 г.
[4] Глубокое погружение в загрузчики классов Java : Woohoo. IBM.com/developer Я…
[5] Весенняя загрузочная битва: анализ принципа автоматической настройки : blog.CSDN.net/Ляо Кайлинь/…
[6]Весенняя загрузочная битва: анализ исходного кода загрузки bean-компонента Spring boot:blog.CSDN.net/Ляо Кайлинь/…