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应用程序的配置和初始化变得更加简单、自动化,并支持模块化的开发方式。

相关推荐
0zxm2 小时前
06 - Django 视图view
网络·后端·python·django
m0_748257182 小时前
Spring Boot FileUpLoad and Interceptor(文件上传和拦截器,Web入门知识)
前端·spring boot·后端
小_太_阳2 小时前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
智慧老师3 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
搬码后生仔4 小时前
asp.net core webapi项目中 在生产环境中 进不去swagger
chrome·后端·asp.net
凡人的AI工具箱4 小时前
每天40分玩转Django:Django国际化
数据库·人工智能·后端·python·django·sqlite
Lx3525 小时前
Pandas数据重命名:列名与索引为标题
后端·python·pandas
小池先生5 小时前
springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失
java·spring boot·后端
百罹鸟5 小时前
【vue高频面试题—场景篇】:实现一个实时更新的倒计时组件,如何确保倒计时在页面切换时能够正常暂停和恢复?
vue.js·后端·面试
小蜗牛慢慢爬行6 小时前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate