Springboot装配方式

1.手动装配

  • 使用模式注解 @Component 等(Spring2.5+)
  • 使用模块装配 @EnableXXX 与 @Import (Spring3.1+)
  • 导入实现了ImportSelector接口的bean
  • 导入ImportBeanDefinitionRegistrar 的实现类

2. 使用模式注解 @Component 等(Spring2.5+)

  • 定义: 一种用于声明在应用中扮演"组件" 角色的注解
  • 比如: @Component、@Service、@Configuration等
  • 装配: <context:component-scan>或@ComponentScan

模式注解是一种用于声明在应用中扮演"组件"角色的注解。如 Spring Framework 中的 @Repository 标注在任何类上 ,用于扮演仓储角色的模式注解。

@Component 作为一种由 Spring 容器托管的通用模式组件,任何被 @Component 标准的组件均为组件扫描的候选对象。类 似地,凡是被 @Component 元标注(meta-annotated)的注解,如 @Service ,当任何组件标注它时,也被视作组件扫 描的候选对象

Spring Framework 注解 场景说明 起始版本
@Repository 数据仓储模式注解 2.0
@Component 通用组件模式注解 2.5
@Service 服务模式注解 2.5
@Controller Web 控制器模式注解 2.5
@Configuration 配置类模式注解 3.0

2.1 装配方式

2.1.1 <context:component-scan> 方式

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
               
     <!-- 激活注解驱动特性 -->
     <context:annotation-config/>
     
     <!-- 找寻被 @Component 或者其派生 Annotation 标记的类,将它们注册为 Spring Bean -->
     <context:component-scan base-package="com.evan"/>
</beans>

2.1.2 @ComponentScan方式

java 复制代码
@ComponentScan(basePackages="com.evan")
public class SpringConfiguration{
    //....
}

3 Spring @Enable模式装配

Spring Framework 3.1 开始支持"@Enable 模块驱动"。所谓"模块"是指具备相同领域的功能组件集合, 组合所形成一个独立 的单元。比如 Web MVC 模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管 理扩展)模块、Async(异步处 理)模块等。

3.1 @Enable 注解模块举例

框架实现 @Enable 注解模块 激活模块
Spring Framework @EnableWebMvc Web MVC 模块
@EnableTransactionManagement 事务管理模块
@EnableCaching Caching 模块
@EnableMBeanExport JMX 模块
@EnableAsync 异步处理模块
@EnableWebFlux Web Flux 模块
@EnableAspectJAutoProxy AspectJ 代理模块
Spring Boot @EnableAutoConfiguration 自动装配模块
@EnableManagementContext Actuator 管理模块
@EnableConfigurationProperties 配置属性绑定模块
@EnableOAuth2Sso OAuth2 单点登录模块
Spring Cloud @EnableEurekaServer Eureka服务器模块
@EnableConfigServer 配置服务器模块
@EnableFeignClients Feign客户端模块
@EnableZuulProxy 服务网关 Zuul 模块
@EnableCircuitBreaker 服务熔断模块

3.2 实现方式

3.2.1 注解驱动方式

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
    //...
}
java 复制代码
// 此处添加了@Configuration注解

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport{
    //...
}

通过@Configuration 将bean加载到Spring 容器中

3.2.2 接口编程方式

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
    //...
}
java 复制代码
//此处没有添加@Configuration注解,但是继承了AdviceModeImportSelector
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {

    /**
     * {@inheritDoc}
     *
     * @return {@link ProxyCachingConfiguration} or {@code
     * AspectJCacheConfiguration} for
     * {@code PROXY} and {@code ASPECTJ} values of {@link
     * EnableCaching#mode()}, respectively
     */

    public String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return new String[]{AutoProxyRegistrar.class.getName(), ProxyCachingConfiguration.class.getName()};
            case ASPECTJ:
                return new String[]{AnnotationConfigUtils.CACHE_ASPECT_CONFIGURATION_CLASS_NAME};
            default:
                return null;
        }
    }
}

此方式没有添加@Configuration注解,但继承了AdviceModeImportSelector。AdviceModeImportSelector 实现了ImportSelector,通过importSelect的方式把Bean加载到Spring容器中。这种方式灵活性比较高。

3.3 自定义@Enable 模块

java 复制代码
package com.evan.importselect;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}
java 复制代码
package com.evan.importselect;

import org.springframework.context.annotation.Bean;

public class HelloWorldConfiguration {

    @Bean
    public String helloWorld() {
        return "hello world";
    }
}
java 复制代码
package com.evan.importselect;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class HelloWorldImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{HelloWorldConfiguration.class.getName()};
    }
}
java 复制代码
package com.evan.bootstrap;

import com.evan.importselect.EnableHelloWorld;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@EnableHelloWorld
public class HelloWorldImportSelector {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(HelloWorldImportSelector.class)
                .web(WebApplicationType.NONE)
                .run(args);

        String helloWorld = context.getBean("helloWorld", String.class);
        System.out.println("helloWorld Bean :" + helloWorld);


        context.close();
    }
}

4 Spring 条件装备

从 Spring Framework 3.1 开始,允许在 Bean 装配时增加前置条件判断

4.1 条件装备

Spring 注解 场景说明 起始版本
@Profile 配置化条件装配 3.1
@Conditional 编程条件装配 4.0

4.2 @Profile

java 复制代码
package com.evan.profile;

public interface CalculateService {
    Integer sun(Integer ... values);
}
java 复制代码
package com.evan.profile;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

@Profile("java7")
@Service
public class Java7CalculateService implements CalculateService {
    @Override
    public Integer sun(Integer... values) {

        System.out.println("java 7....");
        int sum = 0;
        for (int i = 0; i < values.length; i++) {
            sum += i;
        }
        return sum;
    }
}
java 复制代码
package com.evan.profile;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import java.util.stream.Stream;

@Profile("java8")
@Service
public class Java8CalculateService implements CalculateService {
    @Override
    public Integer sun(Integer... values) {
        System.out.println("java 8....");
        return Stream.of(values).reduce(0, Integer::sum);
    }
}
java 复制代码
package com.evan.bootstrap;

import com.evan.profile.CalculateService;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackages = "com.evan.profile")
public class ProfileBootstrap {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(ProfileBootstrap.class)
                .web(WebApplicationType.NONE)
                .profiles("java8")
                .run(args);

        CalculateService bean = context.getBean(CalculateService.class);
        System.out.println(bean.sun(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        context.close();
    }
}

4.3 @Conditional

4.3.1 Conditional

@Conditional:Spring4.0 介绍了一个新的注解@Conditional,它的逻辑语义可以作为"If...then...else..."来对bean的注册起作用。

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
 
	Class<? extends Condition>[] value();
 
}

SpringBoot 模块大量的使用@Conditional 注释,我们可以将Spring的@Conditional注解用于以下场景:

  • 可以作为类级别的注解直接或者间接的与@Component相关联,包括@Configuration类;
  • 可以作为元注解,用于自动编写构造性注解;
  • 作为方法级别的注解,作用在任何@Bean方法上。

4.3.2 Condition 接口

我们需要一个类实现Spring提供的Condition接口,它会匹配@Conditional所符合的方法,然后我们可以使用我们在@Conditional注解中定义的类来检查。

java 复制代码
public interface Condition {
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

Condition是一个函数式接口,其内部只有一个matches()方法,用来判断条件是否成立的,方法有2个入参:

  • context:条件上下文,ConditionContext接口类型,用来获取容器中的信息
  • metadata:用来获取被@Conditional标注的对象上的所有注解信息

4.3.3 Condition 使用

java 复制代码
//如果当前工程运行在Windows系统下,就注册Student
public class Student {}

//如果当前工程运行在Linux系统下,就注册Teacher
public class Teacher {}

// 如果是Mac OSX 系统,就注册Parent
public class Parent {}
java 复制代码
public class LinuxCondition implements Condition {

	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
			// 获取系统环境的属性
		  String systemName = context.getEnvironment().getProperty("os.name");
		  if(systemName.contains("Linux")){
			  return true;
		  }
		  return false;
	}
}

//自定义一个判断条件
public class WindowsCondition implements Condition {

	/*
	 * ConditionContext context: spring容器上下文环境
	 * AnnotatedTypeMetadata metadata :@Conditional修饰类型信息
	 */
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		    
		   String systemName = context.getEnvironment().getProperty("os.name");
		   if(systemName.contains("Windows")){
			   return true;
		   }
		return false;
	}

}

public class OsxCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String property = context.getEnvironment().getProperty("os.name");
        if(property.equals("Mac OS X")){
            return true;
        }
        return false;
    }
}
java 复制代码
@Configuration
public class AppConfig {

    @Conditional(OsxCondition.class)
    @Bean
    public Student student(){
        return new Student();
    }

    @Conditional(LinuxCondition.class)
    @Bean
    public Teacher teacher(){
        return new Teacher();
    }

    @Conditional(WindowsCondition.class)
    @Bean
    public Parent parent(){
        return new Parent();
    }
}
java 复制代码
public class ConditionTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        String[] names = context.getBeanDefinitionNames();
        for(String name : names){
            System.out.println("name = " + name);
        }
    }
}

4.3.4 @Conditional衍生注解

从SpringBoot1.0版本开始@Conditional派生出了大量的子注解;用于Bean的按需加载。 主要包括六大类:

  • Class Conditions
  • Bean Conditions
  • Property Conditions
  • Resource Conditions
  • Web Application Conditions
  • SpEL Expression Conditions

4.3.4.1 Class Conditions

包含两个注解:@ConditionalOnClass 和 @ConditionalOnMissingClass

@ConditionalOnClass

@ConditionalOnClass注解用于判断其value值中的 Class类是否都可以使用类加载器加载到,如果都能,则符合条件装配。

java 复制代码
@Configuration
@ConditionalOnClass(WebClient.class)
@AutoConfigureAfter({ClientHttpConnectorAutoConfiguration.class })
public class WebClientAutoConfiguration {
    ....
}

这里表示,只有当WebClient.Class类和ClientHttpConnectorAutoConfiguration.class类都存在时,才会加载WebClientAutoConfiguration到Spring容器。

@ConditionalOnMissingClass

@ConditionalOnMissingClass注解用于判断其value值中的Class类是否都不可以使用类加载器加载到,如果都不能,则符合条件装配。

java 复制代码
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@Configuration
static class ClassProxyingConfiguration {
    ....
}

这里表示,只有当org.aspectj.weaver.Advice类不存在时,才会加载ClassProxyingConfiguration到Spring容器。

4.3.4.2 Bean Conditions

包含两个注解:@ConditionalOnBean 和 @ConditionalOnMissingBean。

SpringBoot官网的JavaDoc强烈建议开发人员仅在自动装配中使用Bean Conditions条件注解。因为开发人员需要特别小心BeanDefinition的添加顺序,因为这些条件是依赖与迄今为止哪些bean已经被处理来评估的!

@ConditionalOnBean

@ConditionalOnBean注解用于判断某些Bean是否都加载到了Spring容器BeanFactory中,如果是的,则符合条件装配。

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ConnectionFactory.class)
public class JmsAutoConfiguration {
    ....
}

这里表示,只有当ConnectionFactory类已经被加载到Spring容器BeanFactory中时,才会加载JmsAutoConfiguration类到Spring容器中。

@ConditionalOnMissingBean

@ConditionalOnBean注解用于判断某些Bean是否都没有加载到Spring容器BeanFactory中,如果是的,则符合条件装配。

java 复制代码
@Configuration
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public SomeService someService() {
        return new SomeService();
    }

}

这里表示,只有当SomeService类没有被加载到Spring容器BeanFactory中时,才会加载SomeService类到Spring容器中。

java 复制代码
@Configuration
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(type = "com.evan.mapper.UserMapper")	
    public SomeService someService() {
        return new SomeService();
    }

}

这里表示,只有当Bean类型为com.evan.mapper.UserMapper的类没有被加载到Spring容器BeanFactory中时,才会加载SomeService类到Spring容器中。

4.3.4.3 Property Conditions

ConditionalOnProperty

@ConditionalOnProperty注解依赖于Spring环境参数(Spring Environment property)来做条件装配。

  1. 其使用prefix 和 name属性表明哪个属性应该被检查。如果prefix()不为空,则属性名称为prefix()+name(),否则属性名称为name()
  2. 默认情况下,匹配存在且不等于false的任何属性。
  3. 此外可以使用havingValue 和 matchIfMissing 属性创建更高级的检查。
    • havingValue() --> 表示期望的配置属性值,并且禁止使用false
    • matchIfMissing() --> 用于判断当属性值不存在时是否匹配
java 复制代码
@Configuration
@ConditionalOnProperty(prefix = "formatter", name = "enabled", havingValue = "true")
public class ForMatterAutoConfiguration {
    ....
}

这里表示,当Spring Environment的属性 formatter.enabled 为"true"时,ForMatterAutoConfiguration才会被加载到Spring容器。

java 复制代码
@Configuration
@ConditionalOnProperty(prefix = "formatter", name = "enabled", havingValue = "true", matchIfMissing = true)
public class ForMatterAutoConfiguration {
    ....
}

这里表示,当属性 formatter.enabled 配置不存在时,同样视作匹配。

4.3.4.4 Resource Conditions

@ConditionalOnResource

@ConditionalOnResource通过判断某些资源是否存在来做条件装配。

java 复制代码
@Configuration
@ConditionalOnResource(resources = {"classpath:META-INF/build-info.properties"})
public class ForMatterAutoConfiguration {
    ....
}

这里表示,只有当classpath:META-INF/build-info.properties文件资源存在时,ForMatterAutoConfiguration才会被加载到Spring容器。

4.3.4.5 Web Application Conditions

包含两个注解:@ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication。

@ConditionalOnWebApplication

@ConditionalOnWebApplication用于判断SpringBoot应用的类型是否为指定Web类型(ANY、SERVLET、REACTIVE)

java 复制代码
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
public class ForMatterAutoConfiguration {
    ....
}

这里表示,只有当SpringBoot应用类型为SERVLET应用类型时,ForMatterAutoConfiguration才会被加载到Spring容器。

@ConditionalOnNotWebApplication

@ConditionalOnNotWebApplication用于判断SpringBoot应用的类型是否为不为Web应用。

java 复制代码
@Configuration
@ConditionalOnNotWebApplication
public class ForMatterAutoConfiguration {
    ....
}

这里表示,只有当SpringBoot应用类型不是Web应用类型时,ForMatterAutoConfiguration才会被加载到Spring容器。

4.3.4.6 SpEL Expression Conditions

@ConditionalOnExpression通过SpEL Expression来做条件装配。
java 复制代码
@Configuration
@ConditionalOnExpression("${formatter.enabled:true} && ${formatter.enabled.dup:true}")
public class ForMatterAutoConfiguration {
    ....
}

这里表示,只有当属性formatter.enabledformatter.enabled.dup同时为true时,ForMatterAutoConfiguration才会被加载到Spring容器。

4.3.5 自定义@Conditional 注解

java 复制代码
package com.evan.condition;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.*;

/**
 * 系统变量条件判断
 */

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionOnSystemProperty {

    String name();

    String value();
}
java 复制代码
package com.evan.condition;


import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Map;

/**
 * 系统属性条件判断
 */
public class OnSystemPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionOnSystemProperty.class.getName());

        String propertyName = String.valueOf(attributes.get("name"));
        String propertyValue = String.valueOf(attributes.get("value"));
        String property = System.getProperty(propertyName);
        return propertyValue.equals(property);
    }
}
java 复制代码
package com.evan.bootstrap;

import com.evan.condition.ConditionOnSystemProperty;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

public class SystemPropertyConditionBootstrap {

    @Bean
    @ConditionOnSystemProperty(name = "user.name", value = "xuyatao")
    public String helloWorld() {
        return "hello evan";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(SystemPropertyConditionBootstrap.class)
                .web(WebApplicationType.NONE)
                .run(args);

        String helloWorld = context.getBean("helloWorld", String.class);
        System.out.println(helloWorld);
        context.close();
    }
}

5 自动装备

在 Spring Boot 场景下,基于约定大于配置的原则,实现 Spring 组件自动装配的目的。其中使用了

  • Spring 模式注解装配
  • Spring @Enable 模块装配
  • Spring 条件装配装配
  • Spring 工厂加载机制
    • 实现类: SpringFactoriesLoader
    • 配置资源: META-INF/spring.factories

5.1 Spring 工厂加载机制

Spring Factories是一种类似于Java SPI的机制,它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。

5.2 为什么要有Spring Factories

Spring Factories机制提供了一种解耦容器注入的方式,帮助外部包(独立于spring-boot项目)注册Bean到spring boot项目容器中。

问题:如果想要被Spring容器管理的Bean的路径不再Spring Boot 项目的扫描路径下,那该怎么办呢?
  • 普通解法1:在Spring Boot 项目中配置ComponentScan注解的扫描路径,添加需要被扫描的方法。
  • 普通解法2:通过在Spring Boot 项目中添加@EnableAutoConfiguration注解,并自定义@EnableXXXXConfiguration的注解,通过注解中的方法注入Bean。

Spring Factories机制解法:在外部包的META-INF/spring.factories文件中添加配置文件,Spring Boot项目会自动扫描这个配置文件,获取外部包的Bean的详细信息。基本思想如下图所示,从图中可以看到,Spring Boot项目中已经不包含外部包的相关逻辑,实现与外部包之间的解耦关系。

5.3 自定义SpringFactories

  1. 激活自动装配 - @EnableAutoConfiguration
  2. 实现自动装配 - XXXAutoConfiguration
  3. 配置自动装配实现 - META-INF/spring.factories
java 复制代码
package com.evan.autoconfig;


import com.evan.condition.ConditionOnSystemProperty;
import com.evan.importselect.EnableHelloWorld;
import org.springframework.context.annotation.Configuration;

//实现自动装配 - XXXAutoConfiguration
@Configuration //Spring 模式注解
@EnableHelloWorld //Spring @Enable注解
@ConditionOnSystemProperty(name = "user.name",value = "xuyatao")
public class HelloWorldAutoConfiguration {
}
ini 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.evan.autoconfig.HelloWorldAutoConfiguration
java 复制代码
package com.evan.bootstrap;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

//激活自动装配 - @EnableAutoConfiguration
@EnableAutoConfiguration
public class EnableAutoConfigurationBootstrap {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationBootstrap.class)
                .web(WebApplicationType.NONE)
                .run(args);

        String helloWorld = context.getBean("helloWorld", String.class);
        System.out.println("helloWorld Bean :" + helloWorld);


        context.close();
    }
}

5.4 SpringFactories 实现原理

Spring Factories机制通过META-INF/spring.factories文件获取相关的实现类的配置信息,而SpringFactoriesLoader的功能就是读取META-INF/spring.factories文件中配置的接口实现类名称,然后在程序中读取这些配置文件并实例化。

5.4.1 SpringFactoriesLoader 源码

java 复制代码
public final class SpringFactoriesLoader {

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

   private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

   private SpringFactoriesLoader() {
   }
   //...
  }

5.4.2 SpringFactoriesLoader核心方法

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

5.4.3 loadFactories

java 复制代码
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
    Assert.notNull(factoryClass, "'factoryClass' must not be null");
   
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    // 调用loadFactoryNames获取接口的实现类
    List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
    if (logger.isTraceEnabled()) {
        logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
    }
    // 遍历 factoryNames 数组,创建实现类的对象
    List<T> result = new ArrayList<>(factoryNames.size());
    for (String factoryName : factoryNames) {
        result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
    }
    // 排序
    AnnotationAwareOrderComparator.sort(result);
    return result;
}

5.4.4 instantiateFactory

java 复制代码
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
    try {
        
        Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
        // 是否实现了指定接口
        if (!factoryClass.isAssignableFrom(instanceClass)) {
            throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
        }
        // 创建对象
        return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
    } catch (Throwable ex) {
        throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
    }
}

5.4.5 loadFactoryNames

在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。通过Properties解析所有接口的实现类名称。

java 复制代码
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    try {
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

5.5 Spring Factories机制的优点

Spring Framework中的Spring Factories机制提供了一种可扩展的方式来配置和初始化Spring应用程序。这个机制的优点:

  1. 模块化和可插拔性:Spring Factories机制允许不同的模块(如Spring Boot Starter)在应用程序中提供自己的配置和初始化逻辑,使得应用程序可以轻松地集成和使用这些模块。
  2. 自动化配置:通过Spring Factories机制,Spring Boot可以根据类路径中存在的依赖项自动配置应用程序。这种自动化配置减少了手动配置的需求,使得应用程序的构建更加便捷和高效。
  3. 扩展点:开发人员可以利用Spring Factories机制来定义自己的扩展点,使得其他模块可以注册并使用这些扩展点,从而实现更灵活和可扩展的应用程序架构。

总的来说,Spring Factories机制提供了一种灵活且可扩展的方式,使得Spring应用程序的配置和初始化变得更加简单、自动化,并支持模块化的开发方式。

相关推荐
爱勇宝13 分钟前
没有 Fn 键关触控板?我做了一个双击即用的 Windows 小工具
前端·后端·程序员
沙湖遇雨1 小时前
8.ByteBuf 的数据模型、生命周期和内存分配
后端
by组态1 小时前
Ricon组态系统API参考手册
前端·后端·物联网
再吃一根胡萝卜1 小时前
Agent 如何选择与生成 SQL —— Text2SQL 全链路详解
后端
苍何1 小时前
把操作流程直接录成 Skill,豆包太夯了!
后端
Go_error1 小时前
在 Go 服务层中如何优雅地管理事务?
后端·go
爱读源码的大都督2 小时前
DeepSeek面试官问:多租户 RAG 系统怎样实现细粒度权限控制?
后端·面试·架构
苍何2 小时前
我们终于成立了 AgentWork 开源社区,16.4 万字豆包工作蓝皮书同步开源(建议收藏)
后端