zoukankan      html  css  js  c++  java
  • SpringBoot的SPI机制

    Java中自带了所谓SPI机制,按照约定去META-INF/services目录里找各个接口的配置文件,找到接口的实现类,然后使用当前线程上线文类加载器定位到实现类加载器,通过其加载实现类,然后再反射newInstance得到实现类的实例。

    Spring里也有类似的SPI,思路根上面类似,从classpath下所有jar包的META-INF/spring.factories 配置文件中加载标识为EnableAutoConfiguration的配置类,然后将其中定义的bean注入到Spring容器。

    笔者认为,跟Java的SPI更多的是为了面向接口编程和克服双亲委派局限不同,Spring的这种SPI可能更多的是体现一种框架的可扩展性:在springboot工程中我们都知道,默认是会加载主类所在目录及其所有子目录下的自动注入bean的,比如主类在com.wangan,则com.wangan.controller, com.wangan.service等等都会加载并注入;但如果第三方开发的jar包、大概率情况下目录是跟工程目录不同的,比如wangan公司的合作伙伴lb公司开发了一个组件用的是com.lb.*,这个组件的类就没法自动的注入到spring,而通过上面讲的SPI机制就可以解决这个问题。

    按照上面的思路,我们从spring boot工程的主类开始分析一下相关的源代码:

    源代码走读

    @SpringBootApplication实际上由三个注解组成:

    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    																  @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    

    @SpringBootConfiguration查看代码就是个@Configuration,所以上面的3注解相当于就是@EnableAutoConfiguration@Configuration@ComponentScan

    先研究下@EnableAutoConfiguration

    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    	Class<?>[] exclude() default {};
    	String[] excludeName() default {};
    
    }
    

    @AutoConfigurationPackage里边是@Import(AutoConfigurationPackages.Registrar.class)

    @Import(AutoConfigurationImportSelector.class)

    "这里有点跳跃",从主类main方法怎么执行,然后走到注解这的,又是怎么执行到的AutoConfigurationImportSelector.getCandidateConfigurations()方法。

    这里涉及到springboot的自动装配原理,最终是springboot启动时,是将主类作为一个配置类,ConfigurationClassPostProcessor.processConfigBeanDefinitions,然后通过ConfigrationClassParser类parse()、getImports()解析到@Import注解、从而获取到AutoConfigurationImportSelector这个类的,最终会调用到getCandidateConfiguration()方法。 完整的调用可以看下面的线程栈:

    Thread [main] (Suspended)	
    owns: Object  (id=69)	
    SpringFactoriesLoader.loadSpringFactories(ClassLoader) line: 128	
    SpringFactoriesLoader.loadFactoryNames(Class<?>, ClassLoader) line: 122	
    AutoConfigurationImportSelector.getCandidateConfigurations(AnnotationMetadata, AnnotationAttributes) line: 171	
    AutoConfigurationImportSelector.getAutoConfigurationEntry(AutoConfigurationMetadata, AnnotationMetadata) line: 116	
    AutoConfigurationImportSelector$AutoConfigurationGroup.process(AnnotationMetadata, DeferredImportSelector) line: 396	
    ConfigurationClassParser$DeferredImportSelectorGrouping.getImports() line: 869	
    ConfigurationClassParser$DeferredImportSelectorGroupingHandler.processGroupImports() line: 798	
    ConfigurationClassParser$DeferredImportSelectorHandler.process() line: 770	
    ConfigurationClassParser.parse(Set<BeanDefinitionHolder>) line: 185	
    ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry) line: 315	
    ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry) line: 232	
    PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(Collection<BeanDefinitionRegistryPostProcessor>, BeanDefinitionRegistry) line: 275	
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List<BeanFactoryPostProcessor>) line: 95	
    AnnotationConfigApplicationContext(AbstractApplicationContext).invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory) line: 705	
    AnnotationConfigApplicationContext(AbstractApplicationContext).refresh() line: 531	
    SpringApplication.refresh(ApplicationContext) line: 744	
    SpringApplication.refreshContext(ConfigurableApplicationContext) line: 391	
    SpringApplication.run(String...) line: 312	
    SpringApplication.run(Class<?>[], String[]) line: 1215	
    SpringApplication.run(Class<?>, String...) line: 1204	
    TestRestfullApplication.main(String[]) line: 18	
    

    总之,我们来到了getCandidateConfigurations方法:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
    			getBeanClassLoader());
    	Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
    			+ "are using a custom packaging, make sure that file is correct.");
    	return configurations;
    }
    

    SpringFactoriesLoader的方法,

    loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())
    

    两个参数,前一个就是EnableAutoConfiguration.class, 后一个getBeanClassLoader()是spring bean的classLoader(这个跟整个工程deploy有关,最后通过gradle打出来的jar包解压出来,有个目录专门放的就是classLoader,应该是一个自定义的classLoader)

    ps:eclipse里边debug看是sun.misc.Launcher$AppClassLoader

    loadFactoryNames调的是loadSpringFactories,

    /*
    从loadSpringFactories返回的 Map<String, List<String>>里边,
    按照name=EnableAutoConfiguration获取list,如果没有值就返回个空的list。
    */
    loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    

    loadSpringFactories方法很有意思,从spring.factories文件加载里边的k-v,然后一个key可能会有多个逗号分隔的value,所以这里最后返回的是个LinkedMultiValueMap。加载的时候会建立一个cache,下次就不用重复从文件里加载了直接读cache:

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        //根据classLoader查cache这个map如果查到了说明factories已经加载过了,直接从缓存返回就行了
    	MultiValueMap<String, String> result = cache.get(classLoader);
    	if (result != null) {
    		return result;
    	}
    	//所以下面的逻辑就是怎么填充这个cache
    	try {
            //用自定义classloader从Resource加载,没有就用system ClassLoader
    		Enumeration<URL> urls = (classLoader != null ?
    				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
    				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    		result = new LinkedMultiValueMap<>();
    		while (urls.hasMoreElements()) { //遍历从spring.factories查到的k-v,文件里可能是一个key多个v,逗号分隔
    			URL url = urls.nextElement();
    			UrlResource resource = new UrlResource(url);
    			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    			for (Map.Entry<?, ?> entry : properties.entrySet()) {
    				String factoryClassName = ((String) entry.getKey()).trim();
    				for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
    					result.add(factoryClassName, factoryName.trim());
    				}
    			}
    		}
    		cache.put(classLoader, result);
    		return result;
    	}
    	catch (IOException ex) { //抛异常,说从META-INF/spring.factories加载不了
    		throw new IllegalArgumentException("Unable to load factories from location [" +
    				FACTORIES_RESOURCE_LOCATION + "]", ex);
    	}
    }
    

    好了,到此我们分析完了getCandidateConfigurations这条线的关键代码逻辑了,总结一下就是从spring.factories里边加载配置的那些key-values,然后找到里边key名字是是EnableAutoConfiguration的那些。

    嗯,现在知道哪些类要(作为springboot插件)自动装配了,但是什么时候装配的呢?

    getCandidateConfigurations所在的类AutoConfigurationImportSelector实现了DeferredImportSelector接口,然后在ConfigurationClassPostProcessor这条线中,解析parse我们的各个@Configuration注解的类的时候,会去processDeferredImportSelectors(),在这个方法里进行的bean的生成。

    从实验结果上来看是spring.factories配置了EnableAutoConfiguration类型的那些个类,无论用的注解是@Component、@Configuration还是@RibbonClients都给加载了,然后生成了spring bean注入到了上下文里。进一步实验可知,即使把@Component去掉也是可以注入的。

    从META-INF/spring.factories文件里读取配置的自动装配Bean,EnableAutoConfiguration=xxx,然后这些Bean实例化的也是在springboot启动的时候完成的。对应main线程的完整线程栈:

    Thread [main] (Suspended)	
    owns: ConcurrentHashMap<K,V>  (id=203)	
    owns: Object  (id=34)	
    BeanUtils.instantiateClass(Constructor<T>, Object...) line: 171	
    CglibSubclassingInstantiationStrategy(SimpleInstantiationStrategy).instantiate(RootBeanDefinition, String, BeanFactory) line: 87	
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).instantiateBean(String, RootBeanDefinition) line: 1294	
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBeanInstance(String, RootBeanDefinition, Object[]) line: 1196	
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 555	
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 515	
    DefaultListableBeanFactory(AbstractBeanFactory).lambda$doGetBean$0(String, RootBeanDefinition, Object[]) line: 320	
    1932470703.getObject() line: not available	
    DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory<?>) line: 222	
    DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 318	
    DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 199	
    DefaultListableBeanFactory.preInstantiateSingletons() line: 847	
    AnnotationConfigApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 877	
    AnnotationConfigApplicationContext(AbstractApplicationContext).refresh() line: 549	
    SpringApplication.refresh(ApplicationContext) line: 744	
    SpringApplication.refreshContext(ConfigurableApplicationContext) line: 391	
    SpringApplication.run(String...) line: 312	
    SpringApplication.run(Class<?>[], String[]) line: 1215	
    SpringApplication.run(Class<?>, String...) line: 1204	
    TestRestfullApplication.main(String[]) line: 18	
    

    最终是来到BeanUtils.instantiateClass(Constructor<T>, Object...)

    public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
    	Assert.notNull(ctor, "Constructor must not be null");
    	try {
    		ReflectionUtils.makeAccessible(ctor);
    		return (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
    				KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
    	}
    	catch (InstantiationException ex) {
    		throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
    	}
    	catch (IllegalAccessException ex) {
    		throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
    	}
    	catch (IllegalArgumentException ex) {
    		throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
    	}
    	catch (InvocationTargetException ex) {
    		throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
    	}
    }
    

    注意上面代码里ctor.newInstance(args)就是利用Java反射实例化Bean对象了。

    我们比较一下上面两个线程栈的信息可以发现都会走到AbstractApplicationContext.refresh()这个方法里,所不同的是接下在这个方法内又去调用两个不同的方法:加载spring.factories调用的是invokeBeanFactoryPostProcessors,实例化Bean调用的是finishBeanFactoryInitialization,所以有必要来看一下这个refresh()方法,有种说法是读懂了refresh()方法就掌握了Spring容器的启动逻辑。

    传说中的refresh()方法

    org.springframework.context.support包下的

    AbstractApplicationContext类的refresh()方法是整个spring框架启动逻辑的“大纲”,refresh里边调的15个方法体现了容器中Bean的整个创建过程。比如前面starter里边配置的Bean,SpringBoot如何读取的spring.factories文件拿到类名,然后创建Defination,到最后使用反射进行实例化Bean,都在refresh方法中有所体现。

    public void refresh() throws BeansException, IllegalStateException {
    	synchronized (this.startupShutdownMonitor) {
    		// Prepare this context for refreshing.
    		prepareRefresh();
    
    		// Tell the subclass to refresh the internal bean factory.
    		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    		// Prepare the bean factory for use in this context.
    		prepareBeanFactory(beanFactory);
    
    		try {
    			// Allows post-processing of the bean factory in context subclasses.
    			postProcessBeanFactory(beanFactory);
    
    			// Invoke factory processors registered as beans in the context.
    			invokeBeanFactoryPostProcessors(beanFactory);  // spring.factories文件里的配置是在这加载的
    
    			// Register bean processors that intercept bean creation.
    			registerBeanPostProcessors(beanFactory);
    
    			// Initialize message source for this context.
    			initMessageSource();
    
    			// Initialize event multicaster for this context.
    			initApplicationEventMulticaster();
    
    			// Initialize other special beans in specific context subclasses.
    			onRefresh();
    
    			// Check for listener beans and register them.
    			registerListeners();
    
    			// Instantiate all remaining (non-lazy-init) singletons.
    			finishBeanFactoryInitialization(beanFactory);  //对配置的bean进行反射实例化
    
    			// Last step: publish corresponding event.
    			finishRefresh();
    		}
    
    		catch (BeansException ex) {
    			if (logger.isWarnEnabled()) {
    				logger.warn("Exception encountered during context initialization - " +
    						"cancelling refresh attempt: " + ex);
    			}
    
    			// Destroy already created singletons to avoid dangling resources.
    			destroyBeans();
    
    			// Reset 'active' flag.
    			cancelRefresh(ex);
    
    			// Propagate exception to caller.
    			throw ex;
    		}
    
    		finally {
    			// Reset common introspection caches in Spring's core, since we
    			// might not ever need metadata for singleton beans anymore...
    			resetCommonCaches();
    		}
    	}
    }
    
    总结

    springboot是基于Spring框架开发的,利用引导主类作为总的配置入口和启动入口,使用它自己上边的注解,然后处理@Import注解、拿到里边的AutoConfigurationImportSelector、调用它的相应的方法来加载SPI插件的配置spring.factories,之后使用反射来实例化插件中的Bean供使用。

    参考

    https://www.cnblogs.com/niechen/p/9027804.html

  • 相关阅读:
    ssh速度慢
    ps -ef和ps aux的区别
    docker国内镜像加速
    pptpd的log整理
    docker入门2--生命周期
    docker入门1--简介、安装
    Cent7.2单用户模式
    shell中得到当下路径所有文件夹名称
    在centos 7下升级内核
    Mysql如何将一张表重复数据删除
  • 原文地址:https://www.cnblogs.com/lyhero11/p/15580683.html
Copyright © 2011-2022 走看看