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)来做条件装配。
- 其使用prefix 和 name属性表明哪个属性应该被检查。如果prefix()不为空,则属性名称为prefix()+name(),否则属性名称为name()
- 默认情况下,匹配存在且不等于false的任何属性。
- 此外可以使用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.enabled
和 formatter.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
- 激活自动装配 - @EnableAutoConfiguration
- 实现自动装配 - XXXAutoConfiguration
- 配置自动装配实现 - 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应用程序。这个机制的优点:
- 模块化和可插拔性:Spring Factories机制允许不同的模块(如Spring Boot Starter)在应用程序中提供自己的配置和初始化逻辑,使得应用程序可以轻松地集成和使用这些模块。
- 自动化配置:通过Spring Factories机制,Spring Boot可以根据类路径中存在的依赖项自动配置应用程序。这种自动化配置减少了手动配置的需求,使得应用程序的构建更加便捷和高效。
- 扩展点:开发人员可以利用Spring Factories机制来定义自己的扩展点,使得其他模块可以注册并使用这些扩展点,从而实现更灵活和可扩展的应用程序架构。
总的来说,Spring Factories机制提供了一种灵活且可扩展的方式,使得Spring应用程序的配置和初始化变得更加简单、自动化,并支持模块化的开发方式。