springboot自动装配之spi机制解析

1.什么是spi

Java里有两种spi,一种是jdk自带的spi机制,也就是服务发现机制,但是这里的服务发现和我们分布式系统上的服务发现是不一样的,另一个就是springboot的spi机制,都是一种服务扩展机制。

2.spring的spi机制

我们面试的时候,会经常被问到springboot的工作原理,这时候会有人回答了,这个问题我知道,是使用的@SpringApplication注解,然后使用@EnableAutoConfiguration以及@ComponentScan自动装配,注解@EnableAutoConfiguration使用了@Import加载,最后使用了SpringFactoriesLoader反射出maven中META-INF下spring.factories。说的很对,的确是这样的。

3.SpringFactoriesLoader源码解析

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

 

    public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryType, "'factoryType' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
        }

        List<T> result = new ArrayList(factoryImplementationNames.size());
        Iterator var5 = factoryImplementationNames.iterator();

        while(var5.hasNext()) {
            String factoryImplementationName = (String)var5.next();
            result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
        }

        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

从上面的源码,我们可以看到首先调用了loadFactoryNames方法,获取META-INF/spring.factories下的全类名,使用的方法也就是加载了META-INF/spring.factorie下的类名。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

 然后可以看见,在loadFactories方法里调用了result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));这里根据全类名反射了这个类,可以看下这个方法的源码。

   private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
        try {
            Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
            if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
                throw new IllegalArgumentException("Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
            } else {
                return ReflectionUtils.accessibleConstructor(factoryImplementationClass, new Class[0]).newInstance();
            }
        } catch (Throwable var4) {
            throw new IllegalArgumentException("Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]", var4);
        }
    }

4.springboot如何加载SpringFactoriesLoader

我们继续看源码。在SpringApplication源码中可以看见,调用了getSpringFactoriesInstances方法

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			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, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

可以看见,在 getSpringFactoriesInstances方法中调用了SpringFactoriesLoader.loadFactoryNames获取了全类名,使用createSpringFactoriesInstances方法反射bean的实例,并在上述run方法中加载到Spring容器中。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

 

### Spring Boot 自动装配原理 Spring Boot 的自动装配功能是其核心特性之一,旨在简化传统的 Spring 应用程序配置流程。以下是对其工作原理的详细解析: #### 1. **@SpringBootApplication 注解的作用** @SpringBootApplication 是一个复合注解,由以下几个主要部分组成: - @SpringBootConfiguration:表明这是一个 Spring Boot 配置类。 - @EnableAutoConfiguration:启用自动配置机制,这是自动装配的核心所在[^2]。 - @ComponentScan:开启组件扫描,默认会扫描该注解所在的包及其子包内的所有组件。 通过在启动类上标注此注解,开发者无需额外配置即可完成大部分基础设置。 #### 2. **自动装配的核心 —— @EnableAutoConfiguration** @EnableAutoConfiguration 实际上是一个触发器,它通过将 AutoConfigurationImportSelector 类导入到容器中来实现自动化配置的功能[^4]。这个选择器负责收集所有的候选配置类,并决定哪些应该被加载进上下文中。 #### 3. **META-INF/spring.factories 文件的角色** Spring Boot 利用了 Java SPI(Service Provider Interface)机制,在 classpath 下寻找名为 META-INF/spring.factories 的资源文件。该文件定义了一系列扩展点与其对应的实现类映射关系。其中就包含了众多以 EnableAutoConfiguration 开头的关键条目[^3]。 例如: ```properties org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,... ``` 每当项目运行时,Spring Boot 就会读取这些信息并实例化相应的 Configuration Classes。 #### 4. **条件化装配 (Conditional Configuration)** 为了确保只有必要的依赖才会生效,Spring Boot 广泛采用了基于 Condition 的动态决策方式。这意味着即使存在某个特定的技术栈关联的 auto-configurator ,但如果环境不满足预设的前提条件,则不会激活相关联的服务初始化逻辑[^3]。 典型的应用场景包括但不限于检测是否存在某类 jar 包、属性值是否符合预期等等。常用的条件注解有如下几种形式: - @ConditionalOnClass @ConditionalOnMissingClass :分别用于验证指定类型是否可获得或缺失; - @ConditionalOnBean / @ConditionalOnMissingBean : 检验 IOC 容器内部是否有某种 bean 对象注册成功与否; - @ConditionalOnProperty : 根据 application.properties 或 yml 文件里的键名匹配结果判定开关状态; #### 5. **总结** 综上所述,Spring Boot 的自动装配主要是依靠 @EnableAutoConfiguration 注解读入一系列预先定义好的 configuration classes 。再配合灵活多样的 conditional rules 设计思路,从而达到按需定制的效果。这种设计理念不仅减少了人为干预的成本开销,同时也提高了整体系统的稳定性一致性水平。 --- ### 示例代码片段 以下展示了如何自定义一个简单的自动配置模块: ```java @Configuration @ConditionalOnClass(SomeThirdPartyLibrary.class) public class MyCustomAutoConfig { @Bean public SomeService someService(){ return new DefaultSomeServiceImpl(); } } ``` 在此基础上,还需记得更新 spring.factories 文件内容以便框架识别新增加的支持选项。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值