【Spring】原来SpringBoot是这样玩的
菜瓜:我自己去调Mvc的源码差点没给Spring的逻辑秀死。。。难受
水稻:那今天咱们看一个简单易用的SpringBoot吧
菜瓜:可以,这个我熟悉
水稻:嘿嘿嘿。平时工作中用多了SpringBoot。咱们今天带着几个问题来看看它的操作吧
- 如何启动Spring容器
- 如何内嵌Tomcat容器
- 如何完成自动装配,就是0配置
菜瓜:???小问号,你是否有很多朋友
水稻:。。。看过来
- 启动类点进去
-
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ... listeners.starting(); try { ... // ①创建Spring上下文容器对象 - 默认Servlet容器 context = createApplicationContext(); ... // ②调用refresh方法 - 回到熟悉的容器启动流程 refreshContext(context); afterRefresh(context, applicationArguments); ... ... return context; }
- ① 创建上下文对象
-
protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { ... } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }
-
- ②启动容器
-
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { prepareRefresh(); ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { ... // ①springboot 内嵌tomcat容器 onRefresh(); ... } @Override protected void onRefresh() { super.onRefresh(); try { // ②创建Servlet容器 默认tomcat createWebServer(); } ... } private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); // ③看进去 回到mvc集成tomcat的场景 this.webServer = factory.getWebServer(getSelfInitializer()); } ... initPropertySources(); } @Override public WebServer getWebServer(ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); }
-
水稻:好了,第一步和第二步完成了
菜瓜:就这???
水稻:是不是极其简单,令人发指。重头戏是后面的自动装配
- 回到咱们启动类的注解上
-
... // 标记自身被扫描 @SpringBootConfiguration // 下一步 - 自动装配入口 @EnableAutoConfiguration // 扫描bean路径 - 约定是启动类所在的包:所以没事别把启动类挪走(都是泪) @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication -> @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration -> 重要 public class AutoConfigurationImportSelector ... { @Override public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { ... // 获取以EnableAutoConfiguration命名的/META-INF/Spring.factories文件中的value去重 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); // 启动的时候断点可以看到
this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } } } -
AutoConfigurationImportSelector 中的process是被ConfigurationClassPostProcessor通过processConfigBeanDefinitions方法调用(调用链如下) 1. this.processConfigBeanDefinitions(registry); 2. parser.parse(candidates); 3. this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName()); 4. sourceClass = this.doProcessConfigurationClass(configClass, sourceClass); 5. this.processImports(configClass, sourceClass, this.getImports(sourceClass), true); 6. this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector); 7. handler.processGroupImports(); 8. grouping.getImports().forEach... 9. this.group.process(...); -- 搜集到需要自动装配的类,封装成BeanDefinition后续实例化,实现自动装配功能 譬如引入WebMvcAutoConfiguration类 - webmvc功能自动集成 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
菜瓜:原来如此。你把调用链拎出来就简单了很多。自动装配就是通过SPI加载org.springframework.boot.autoconfigure包下的class,封装成BeanDefinition后交给容器加载
水稻:所以,所谓自动装配,就是通过一个工具包,将很多默认的配置给引入了进来。妈妈再也不用担心我记不住复杂的配置了
总结:SpringBoot只需要一行代码便能启动一个Java应用。完全解放开发者复杂的配置
- 内嵌Servlet容器,默认tomcat
- 启动SpringWeb容器
- 自动装配了简单web应用需要的工具和组建
是谁来自江河湖海,却囿于昼夜厨房与爱