最棒 Spring Boot 干货总结(超详细,建议收藏)
共 15612字,需浏览 32分钟
·
2020-10-14 09:25
点击上方“JAVA”,星标公众号
![](https://filescdn.proginn.com/49a1b9b9ab2615b2d53b64f9a5cfd9a4/8d8dff4fc8ba5b51538b4a502f24596c.webp)
说明:前面有4个小节关于Spring的基础知识
分别是:IOC容器、JavaConfig、事件监听、SpringFactoriesLoader详解
它们占据了本文的大部分内容:
虽然它们之间可能没有太多的联系,但这些知识对于理解Spring Boot的核心原理至关重要,如果你对Spring框架烂熟于心,完全可以跳过这4个小节。正是因为这个系列的文章是由这些看似不相关的知识点组成,因此取名知识清单。
在过去两三年的Spring生态圈,最让人兴奋的莫过于Spring Boot框架。或许从命名上就能看出这个框架的设计初衷:快速的启动Spring应用。因而Spring Boot应用本质上就是一个基于Spring框架的应用,它是Spring对“约定优先于配置”理念的最佳实践产物,它能够帮助开发者更快速高效地构建基于Spring生态圈的应用。
那Spring Boot有何魔法?
自动配置、起步依赖、Actuator、命令行界面(CLI) 是Spring Boot最重要的4大核心特性,其中CLI是Spring Boot的可选特性,虽然它功能强大,但也引入了一套不太常规的开发模型,因而这个系列的文章仅关注其它3种特性。如文章标题,本文是这个系列的第一部分,将为你打开Spring Boot的大门,重点为你剖析其启动流程以及自动配置实现原理。要掌握这部分核心内容,理解一些Spring框架的基础知识,将会让你事半功倍。
我把 Spring Boot 相关的文章整理成了 PDF,关注公众号 Java后端 并回复 666 下载。
一、抛砖引玉:探索Spring IoC容器
如果有看过SpringApplication.run()方法的源码,Spring Boot冗长无比的启动流程一定
会让你抓狂,透过现象看本质.
SpringApplication只是将一个典型的Spring应用的启动流程进行了扩展,因此,透彻理解, Spring容器是打开Spring Boot大门的一把钥匙。
1.1、Spring IoC容器
可以把Spring IoC容器比作一间餐馆,当你来到餐馆,通常会直接招呼服务员:点菜!至于菜的原料是什么?如何用原料把菜做出来?可能你根本就不关心。
IoC容器也是一样,你只需要告诉它需要某个bean,它就把对应的实例(instance)扔给你,至于这个bean是否依赖其他组件,怎样完成它的初始化,根本就不需要你关心。
作为餐馆,想要做出菜肴,得知道菜的原料和菜谱,同样地,IoC容器想要管理各个业务对象以及它们之间的依赖关系,需要通过某种途径来记录和管理这些信息。
BeanDefinition对象就承担了这个责任:容器中的每一个bean都会有一个对应的BeanDefinition实例,该实例负责保存bean对象的所有必要信息,包括bean对象的class类型、是否是抽象类、构造方法和参数、其它属性等等。
当客户端向容器请求相应对象时,容器就会通过这些信息为客户端返回一个完整可用的bean实例。
原材料已经准备好(把BeanDefinition看着原料),开始做菜吧,等等,你还需要一份菜谱,BeanDefinitionRegistry和BeanFactory就是这份菜谱,BeanDefinitionRegistry抽象出bean的注册逻辑,而BeanFactory则抽象出了bean的管理逻辑,而各个BeanFactory的实现类就具体承担了bean的注册以及管理工作。
它们之间的关系就如下图:
BeanFactory、BeanDefinitionRegistry关系图(来自:Spring揭秘)
DefaultListableBeanFactory作为一个比较通用的BeanFactory实现,它同时也实现了BeanDefinitionRegistry接口,因此它就承担了Bean的注册管理工作。从图中也可以看出,BeanFactory接口中主要包含getBean、containBean、getType、getAliases等管理bean的方法,而BeanDefinitionRegistry接口则包含registerBeanDefinition、removeBeanDefinition、getBeanDefinition等注册管理BeanDefinition的方法。
下面通过一段简单的代码来模拟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");
// 通常为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");
// 通常为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");
// 代码来自: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);
}
// ......
}
"bookService" class="cn.moondev.service.BookServiceImpl">bean>
@Configuration
public class
MoonBookConfiguration
{
// 任何标志了@Bean的方法,其返回值将作为一个bean注册到Spring的IOC容器中
// 方法名默认成为该bean定义的id
@Bean
public BookService bookService() {
return new BookServiceImpl();
}
}
"bookService" class="cn.moondev.service.BookServiceImpl">
<property name="dependencyService" ref="dependencyService"/>
bean>
"otherService" class="cn.moondev.service.OtherServiceImpl">
<property name="dependencyService" ref="dependencyService"/>
bean>
"dependencyService" class="DependencyServiceImpl"/>
@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();
}
}
@Configuration
public class MoonBookConfiguration{
@Bean
public BookService bookService() {
return new BookServiceImpl();
}
}
@Configuration
// 可以同时导入多个配置类,比如:@Import({A.class,B.class})
@Import(MoonBookConfiguration.class)
public class MoonUserConfiguration
{
@Bean
public UserService userService(BookService bookService) {
return new BookServiceImpl(bookService);
}
}
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;
}
}
@Conditional(JdbcTemplateCondition.class) @Service public MyService service() { ...... }
// 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);
}
}
@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);
}
}
#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
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;
}
}
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()
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);
}
// 寻找Array.class文件
public static void main(String[] args) throws Exception{
// Array.class的完整路径
String name = "java/sql/Array.class";
Enumeration 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
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 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;
}
// 来自 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\
public class MethodMonitorEvent extends EventObject {
// 时间戳,用于记录方法开始执行的时间
public long timest
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.timest
System.out.println("耗时:" + duration);
}
}
public class MethodMonitorEventPublisher {
private List listeners = new ArrayList();
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 copyListeners = ➥ new ArrayList(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() {}
2. 事件监听器的管理。publisher类中提供了事件监听器的注册与移除方法,这样客户端可以根据实际情况决定是否需要注册新的监听器或者移除某个监听器。
@SpringBootApplication
public class MoonApplication {
public static void main(String[] args) {
SpringApplication.run(MoonApplication.class, args);
}
}
@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 {
// ......
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// ......
}
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 省略了大部分代码,保留一句核心代码
// 注意:SpringBoot最近版本中,这句代码被封装在一个单独的方法中
// SpringFactoriesLoader相关知识请参考前文
List<String> factories = new ArrayList<String>(new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));
}
// 来自 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\
.....
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {
}
// 提供对datasource配置信息的支持,所有的配置前缀为:spring.datasource
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
private ClassLoader classLoader;
private Environment environment;
private String name = "testdb";
......
}
@Configuration
public class DataSourcePoolMetadataProvidersConfiguration {
@Configuration
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
static class TomcatDataSourcePoolMetadataProviderConfiguration {
@Bean
public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {
.....
}
}
......
}
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();
}
org.springframework.context.ApplicationListener=\ cn.moondev.listeners.xxxxListener\
关于SpringApplication的初始化,我们就说这么多。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);
}
}
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);
}
public void starting() {
// 发布一个ApplicationStartedEvent
this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
}
listeners.environmentPrepared(environment);
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.6.RELEASE)
// 摘自refresh()方法中一句代码
invokeBeanFactoryPostProcessors(beanFactory);
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
......
}
ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
public void initialize(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}
作者:CHEN川
更多精彩?
在公众号【程序员编程】对话框输入以下关键词 查看更多优质内容! 大数据 | Java | 1024 | 电子书 | 速查表 Python进阶 | 面试 | 手册 | 成神 | 思想 | 小程序 命令行 | 人工智能 | 软件测试 | Web前端 | Python 获取更多学习资料
视频 | 面试 | 技术 | 电子书