开发者

解读spring.factories文件配置详情

开发者 https://www.devze.com 2025-04-01 12:51 出处:网络 作者: 旷野历程
目录使用场景作用内部原理机制SPI机制Spring Factories 实现原理用法及配置spring.factories 各种配置的具体含义ApplicationContextInitializerApplicationListenerAutoConfigurationImportListenerAutoConfiguratio
目录
  • 使用场景
  • 作用
  • 内部原理机制
  • SPI机制
  • Spring Factories 实现原理
  • 用法及配置
    • spring.factories 各种配置的具体含义
      • ApplicationContextInitializer
      • ApplicationListener
      • AutoConfigurationImportListener
      • AutoConfigurationImportFilter
      • EnableAutoConfiguration
      • FailureAnalyzer
      • TemplateAvailabilityProvider
  • 总结

    使用场景

    在程序开发中,可能会出现包名不一样的情况(如:pom 依赖的很多的 jar),如何解决Spring Boot不能被默认路径扫描呢?

    • 方法一:在 Spring Boot Application 主类上使用 @Import 注解。
    • 方法二:使用 spring.factories 文件

    方法一比较简单,在此就不做过多介绍,主要谈谈 spring.factories 使用。

    作用

    spring.factories 的作用是使用外部 jar 时不用再写配置,会自动加入已经配置好的配置。

    内部原理机制

    spring.factories 这种机制实际上是仿照 Java 中的 SPI 扩展机制实现的。

    SPI机制

    SPI全称Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的接口,其意义在于为某个接口寻找服务的实现,主要应用在框架中用来寻找组件,提高扩展性。

    面向的对象设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及了具体的实现类,就违反了可插拔的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

    • Java 中的 SPI 机制:为某个接口寻找服务的实现的机制,有点类似 IOC 的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制很重要。
    • Spring Boot 中的 SPI 机制:在 META-INFO/spring.factories 文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的 SPI 机制就是 Spring Boot Starter 实现的基础。

    Spring Factories 实现原理

    spring.factories 实现是依赖 spring-core 包里的 SpringFactoriesLoader 类,这个类实现了检索 META-INF/spring.factories 文件,并获取指定接口的配置的功能。

    这个类中定义了两个对外的方法:

    • loadFactories:根据接口类获取其实现类的实例,这个方法返回的是对象列表
    • loadFactoryNames:根据接口获取其接口类的名称,这个方法返回的是类名的列表

    上面两个方法的关键都是从指定的 ClassLoader 中获取 spring.factories 文件,并解析得到类名列表,具体代码如下:

    public final class SpringFactoriesLoader {
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
        private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
    
        private SpringFactoriesLoader() {
        }
    
        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()) {
                log编程客栈ger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
            }
    
            List<T> result = new ArrayList(factoryImplementationNames.size());
            Iterator var5 = factoryImplementationNames.iterator(javascript);
    
            while(var5.hasNext()) {
                String factoryImplementationName = (String)var5.next();
                result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
            }
    
            AnnotationAwareOrderComparator.sort(result);
            return result;
        }
    
        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()).triEQNprKfm();
                            String[] var9 = StringUtils.cojavascriptmmaDelimitedListToStringArray((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);
                }
            }
        }
    
        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);
            }
        }
    }

    从上述代码中可以看到,在这个方法中会遍历整个 ClassLoader 中所有 jar 包下的 spring.factories 文件,文件之间不会相互影响配置,也不回被别人的配置覆盖。

    spring.factories 的是通过 Properties 解析得到的,所以在写文件中的内容都是按照下面这种方式配置的。

    用法及配置

    spring.factories 文件必须放在 resources 目录下的 META-INF 的目录下,否则不会生效。如果一个接口希望配置多个实现类,可以用","分割。

    spring.factories 各种配置的具体含义

    ApplicationContextInitializer

    • 该配置项用来配置实现了 ApplicationContextInitializer 接口的类,这些类用来实现上下文初始化。
    • 配置如下:
    org.springframework.context.ApplicationContextInitializer=\
    com.xh.config.MyApplicationContextInitializer
    public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
     
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            System.out.println("MyApplicationContextInitializer.initialize() " + applicationContext);
        }
     
    }

    ApplicationListener

    • 配置应用程序监听器,该监听器必须实现 ApplicationListener 接口。它可以用来监听 ApplicationEvent 事件。
    • 配置如下:
    org.springframework.context.ApplicationListener=\
    com.xh.factories.listener.EmailApplicationListener
    @Slf4j
    public class EmailApplicationListener implements ApplicationListener<EmailMessageEvent> {
    
        @Override
        public void onApplicationEvent(EmailMessageEvent event) {
            log.info("模拟发送邮件... ");
            log.info("EmailApplicationListener 接受到的消息:{}", event.getContent());
        }
    }

    AutoConfigurationImportListener

    • 该配置项用来配置自动配置导入监听器,监听器必须实现 AutoConfigurationImportListener 接口。
    • 该监听器可以监听 AutoConfigurationImportEvent 事件。
    • 配置如下:
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
    com.xh.config.MyAutoConfigurationImportListener
    public class MyAutoConfigurationImportListener implements AutoConfigurationImportListener {
     
        @Override
        public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
            System.out.println("MyAutoConfigurationImportListener.onAutoConfigurationImportEvent() " + event);
        }
     
    }

    AutoConfigurationImportFilter

    • 配置自动配置导入过滤器,过滤器必须实现 AutoConfigurationImportFilter 接口。
    • 该过滤器用来过滤那些自动配置类可用。
    • 配置如下:
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
    com.xh.config.MyConfigurationCondition
    public class MyConfigurationCondition implements AutoConfigurationImportFilter {
     
        @Override
        public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
            System.out.println("MyConfigurationCondition.match() autoConfigurationClasses=" +  Arrays.toString(autoConfigurationClasses) + ", autoConfigurationMetadata=" + autoConfigurationMetadata);
            return new boolean[0];
        }
     
    }

    EnableAutoConfiguration

    • 配置自动配置类。这些配置类需要添加 @Configuration 注解。
    • 配置如下:
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.xh.config.MyConfiguration
    @Configuration
    public class MyConfiguration {
     
        public MyConfiguration() {
            System.out.println("MyConfiguration()");
        }
     
    }

    FailureAnalyzer

    • 配置自定的错误分析类,该分析器需要实现 FailureAnalyzer 接口。
    • 配置如下:
    org.springframework.boot.diagjsnostics.FailureAnalyzer=\
    com.huangx.springboot.autoconfig.MyFailureAnalyzer
    /**
     * 自定义错误分析器
     */
    public class MyFailureAnalyzer implements FailureAnalyzer {
     
        @Override
        public FailureAnalysis analyze(Throwable failure) {
            System.out.println("MyFailureAnalyzer.analyze() failure=" + failure);
            return new FailureAnalysis("MyFailureAnalyzer execute", "test spring.factories", failure);
        }
     
    }

    TemplateAvailabilityProvider

    • 配置模板的可用性提供者,提供者需要实现 TemplateAvailabilityProvider 接口。
    • 配置如下:
    org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
    com.huangx.springboot.autoconfig.MyTemplateAvailabilityProvider
    /**
     * 验证指定的模板是否支持
     */
    public class MyTemplateAvailabilityProvider implements TemplateAvailabilityProvider {
     
        @Override
        public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader, ResourceLoader resourceLoader) {
            System.out.println("MyTemplateAvailabilityProvider.isTemplateAvailable() view=" + view + ", environment=" + environment + ", classLoader=" + classLoader + "resourceLoader=" + resourceLoader);
            return false;
        }
     
    }

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号