上一篇文章 《深度解析SpringBoot以及手写一个 starter》火了之后,由于篇幅的原因讲的不够深,我决定再加更一篇,两篇看下来相信对SpringBoot肯定能够理解了。
自动装配就是像事务、配置文件加载和解析、aop、缓存、数据源、springmvc等等,我们以前在项目中要加载这些需要配置xml或者写个@Configuration和很多个@Bean,这些都是需要手写的,现在不需要,只需要引入spring-boot-autoconfigure这个jar包即可,这个就是自动装配的魅力。以前要手动搞定的事,现在自动搞定。
本文的议题是run方法启动,SPI机制以及@EnableAutoConfiguration。
run方法启动我们从SpringApplication.run(SpringBootApp.class,args);方法点进去看下,具体的步骤写在下面源码上。
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); //SPI的方式获取SpringApplicationRunListener实例 SpringApplicationRunListeners listeners = getRunListeners(args); //调用SpringApplicationRunListener的starting()方法 listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //生成Environment对象 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); configureIgnoreBeanInfo(environment); //打印banner图 Banner printedBanner = printBanner(environment); //创建springboot的上下文对象AnnotationConfigServletWebServerApplicationContext context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); //初始化上下文对象 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); //spring容器启动的核心代码 refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { //调用SpringApplicationRunListener的running()方法 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; }
上面的源码主要干了两件事
完成Spring容器的启动,把需要扫描的类实例化启动Servlet容器,完成Servlet容器的启动
上面的核心代码就是refreshContext(context),这个context对象就是AnnotationConfigServletWebServerApplicationContext,打个断点启动一下
最终进到了AbstractApplicationContext类的refresh方法,这个就是spring容器启动的核心方法,所以springboot的底层调的还是spring的源码。
Servlet容器启动,refresh方法往下走,就有一个onRefresh方法
点到具体的实现类ServletWebServerApplicationContext,这里有个createWebServer方法
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create"); //这里获取TomcatServletWebServerFactory对象 ServletWebServerFactory factory = getWebServerFactory(); createWebServer.tag("factory", factory.getClass().toString()); //这里new了Tomcat对象,启动了tomcat容器 this.webServer = factory.getWebServer(getSelfInitializer()); createWebServer.end(); getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
点击factory.getWebServer(getSelfInitializer());进入到tomcat的实现类
这一段就是在启动tomcat容器并且把项目部署到tomcat中。至此run方法就介绍完了,结合着源码看就是启动Spring容器和Servlet容器。
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的meta-INF/services文
件夹查找文件,自动加载文件里所定义的类。 这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用
到了SPI机制。我们先通过一个很简单的例子来看下它是怎么用的。
简单来说,SPI是一种扩展机制,核心就是将服务配置化,在核心代码不用改动的前提下,通过加载配置文件中的服
务,然后根据传递的参数来决定到底走什么逻辑,走哪个服务的逻辑。这样就对扩展是开放的,对修改是关闭的。
先在包里建了几个类
分别看下它们的代码,定义接口
public interface Log { void debug(); }
定义Log4j实现类
public class Log4j implements Log { @Override public void debug() { System.out.println("-----Log4j"); } }
定义Logback实现类
public class Logback implements Log { @Override public void debug() { System.out.println("-----Logback"); } }
定义Slf4j实现类
public class Slf4j implements Log { @Override public void debug() { System.out.println("-----Slf4j"); } }
在resources文件下meta-INF文件下新建一个spring.factories
里面写的内容,这里的key是类型(可以是接口、注解、抽象类等等),value是实例类
再写一个测试类
public class SpiTest { @Test public void test() { Liststrings = SpringFactoriesLoader.loadFactoryNames(Log.class, ClassUtils.getDefaultClassLoader()); for (String string : strings) { System.out.println(string); } } @Test public void test1() { List logs = SpringFactoriesLoader.loadFactories(Log.class, ClassUtils.getDefaultClassLoader()); for (Log log : logs) { System.out.println(log); } } }
执行一下,没问题,全都获取出来了
下面我们来分析下SpringFactoriesLoader.loadFactoryNames源码,点进去看到factoryType.getName()是获取类型的名字,这个就是key。
再点进去看下
private static Map> loadSpringFactories(ClassLoader classLoader) { //先根据classLoader从缓存中拿,如果能拿到就返回 Map > result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { //FACTORIES_RESOURCE_LOCATION的值就是 meta-INF/spring.factories //获取的文件是所有jar包和自己工程里面的所有spring.factories文件 Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); //循环所有文件 while (urls.hasMoreElements()) { URL url = urls.nextElement(); //包装成UrlResource对象 UrlResource resource = new UrlResource(url); //核心代码,把文件包装成properties对象 Properties properties = PropertiesLoaderUtils.loadProperties(resource); //循环properties对象 for (Map.Entry, ?> entry : properties.entrySet()) { //拿到key String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { //把key对应的所有实例加入到list容器中 result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); //建立缓存 cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
我们看到 Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);这里FACTORIES_RESOURCE_LOCATION的值就是meta-INF/spring.factories,所以我们上面做演示的时候,配置是写在这个文件里面的,因为代码里就是从这里读的。
然后我们看到这里Enumeration是urls,为什么是urls,因为这里获取的是自己项目里的meta-INF/spring.factories文件以及引用jar包里的meta-INF/spring.factories文件。
我们在result上打个断点,发现加载了46个key value,可是我们在项目里只写了com.jackxu.zhengcheng.spi.Log这三个,剩下的就是在别的jar包里加载的key value。比如我们熟悉的EnableAutoConfiguration,一共有131个,这个就是在spring-boot-autoconfigure包里读取出来的。
最后SpringFactoriesLoader.loadFactoryNames返回的就是上面截图中com.jackxu.zhengcheng.spi.Log作为key返回的3个list,SpringFactoriesLoader.loadFactories就不分析了,只是调用了loadFactoryNames拿到了接口类型对应的所有类的名称,多了一步反射实例化而已。
在第一篇文章中介绍了@SpringBootApplication是一个组合注解,它包含了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个注解。这里来介绍@EnableAutoConfiguration注解。
@EnableAutoConfiguration注解上面又有一个核心注解@import,在第一篇文章中介绍过它的功能,可以引入三种类型的对象:
- 普通 Bean 或者带有 @Configuration 的配置文件实现 importSelector 接口进行动态注入实现 importBeanDefinitionRegistrar 接口进行动态注入
我们这里用的就是第二种,动态注入,下面来介绍一下它的写法。新建三个类。
这个就是实现DeferredimportSelector接口的固定写法,这么写了spring才能获取到需要实例化的类。
public class DeferredimportSelectorDemo implements DeferredimportSelector { //返回需要实例化的类,这个方法spirng是不会直接调的,这里是给下面process方法调的 @Override public String[] selectimports(Annotationmetadata importingClassmetadata) { System.out.println("==============DeferredimportSelectorDemo.selectimports==========="); //如果需要把类实例化,就需要把该类的完整限定名返回 return new String[]{DeferredBean.class.getName()}; } //需要返回一个实现了Group接口的类 @Override public Class extends Group> getimportGroup() { return DeferredimportSelectorGroupDemo.class; } //内部类,实现Group private static class DeferredimportSelectorGroupDemo implements Group { Listlist = new ArrayList<>(); //spring会调这个方法,收集需要实例化的类 @Override public void process(Annotationmetadata metadata, DeferredimportSelector selector) { System.out.println("==============DeferredimportSelectorGroupDemo.process==========="); String[] strings = selector.selectimports(metadata); for (String string : strings) { list.add(new Entry(metadata, string)); } } //spring会调这个方法,返回给spring容器进行实例化 @Override public Iterable selectimports() { System.out.println("==============DeferredimportSelectorGroupDemo.selectimports==========="); return list; } } }
需要实例的bean
public class DeferredBean { @PostConstruct public void init() { System.out.println("DeferredBean,我是通过import注解加载进来的"); } }
通过import导进来,spring会扫描收集import注解里面需要实例化的类。
@Component @import(DeferredimportSelectorDemo.class) public class importBean { }
启动执行一下,代码里打印的日志全显示出来了,证明spring确实是走的这一套逻辑。
下面看下AutoConfigurationimportSelector,确实也是这个套路,实现了DeferredimportSelector接口
返回了实现Group接口的类
实现了process方法,selectimports方法
那process方法里我们猜也能够猜到,它的功能是收集需要交给spring容器需要实例化的类,点进去
getAutoConfigurationEntry方法看下。
protected AutoConfigurationEntry getAutoConfigurationEntry(Annotationmetadata annotationmetadata) { if (!isEnabled(annotationmetadata)) { return EMPTY_ENTRY; } //获取@SpringBootApplication的配置属性 AnnotationAttributes attributes = getAttributes(annotationmetadata); //获取候选的所有类的名称 Listconfigurations = getCandidateConfigurations(annotationmetadata, attributes); configurations = removeDuplicates(configurations); //从注解配置属性中获取需要排除的类 Set exclusions = getExclusions(annotationmetadata, attributes); checkExcludedClasses(configurations, exclusions); //从候选的类中删除需要排除的类 configurations.removeAll(exclusions); //SPI的扩展,获取过滤器实例对候选的类过滤 configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationimportEvents(configurations, exclusions); //把候选的所有类包装成AutoConfigurationEntry对象 return new AutoConfigurationEntry(configurations, exclusions); }
那么怎么收集就是通过上面说的SPI的方式,点进getCandidateConfigurations方法看下,SpringFactoriesLoader.loadFactoryNames,获取了131个类型,和上面演示SPI的时候数量一样。
其实就是收集spring.factories文件中以@EnableAutoConfiguration类型为key的所有的类,然后把这些类交给spring去实例化,而这些类就是我们说的aop、事务、缓存、mvc等功能的支持类,从而达到自动装配。
后记最后在演示一下自动装配,写一个需要注入的类Person类
在meta-INF/spring.factories下加上org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.jackxu.zhengcheng.spi.Person这句话
启动一下,Person类就被注进来了
而DeferredBean是通过@import注解加载进来的,所以我们想把一个类加载到Spring容器的时候,又多了两种方式,看完这篇文章原理相信大家也都明白了,最后感谢收看,如果你喜欢请点一个赞!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)