springboot自动装配组件详解

前言

平时我们使用springboot可以做到去除xml配置,并且使用外部组件时可以做到开箱即用,使用非常方便,本文就详细介绍下springboot自动装配的原理,以便理解springboot的核心
首先我们先以RedisTemplate这个类来做案例讲解,如果我们在springboot项目中需要使用redis那么流程应该是
1、依赖官方提供的redis包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、在项目中需要使用的地方直接注入RedisTemplate即可使用

@Autowired
private RedisTemplate redisTemplate;

public void get() {
    redisTemplate.opsForValue().get("redisKey");
}

依照spring的约定RedisTemplate应该并没有被我们注册到spring的ioc容器中,那么我们为什么可以ioc容器中拿到RedisTemplate呢?
在springboot注解式编程中,如果我们需要把一个类注册到spring ioc容器中,那么应该有几种方式
1、直接在RedisTemplate这个类中使用注解@Service,@Component等标记,然后让spring扫描
2、使用@Configurable加@Bean标签装配bean,并且让spring扫描

@Configurable
public class RedisConfiguration {
    @Bean
    private RedisTemplate redisTemplate() {
        return new RedisTemplate();
    }
}

但是以上两种方法都需要让spring扫描对应的包才能扫描到对应的bean,这样对于我们的使用非常不方便,因为我们必须要@ComponentScan很多个包路径,而且我们必须知道我们要注入的类的包路径才可以,所以针对以上缺点,spring提供了一种规定,只要满足spring的规定则spring就会去扫描对应的类

ImportSelector核心原理

接下来我们就引入spring自动装配的核心接口ImportSelector

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);

    @Nullable
    default Predicate<String> getExclusionFilter() {
        return null;
    }
}

该接口核心方法selectImports,主要作用就是当我们使用@Import注解注入一个ImportSelector的实现类时,selectImports返回的数组列表中的所有文件路径spring都会去扫描,如下所示:
1、自定义配置类,默认情况下spring不会把TestBean加入到ioc容器,因为TestBeanConfiguration所在的包路径不在spring扫描路径之内

@Configurable
public class TestBeanConfiguration {
    @Bean
    private TestBean testBean() {
    	// 随意创建的一个类
        return new TestBean();
    }
}

2、自定义ImportSelector实现类

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{
            RedisConfiguration.class.getName()
        };
    }
}

3、注解引入MyImportSelector

@SpringBootApplication
@Import({MyImportSelector.class}) // 导入MyImportSelector类
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

配置完上述代码之后spring即会去扫描RedisConfiguration,然后注册TestBean到ioc容器中

springboot中的自动装配原理

按照上述规定的理解,接下来我们就很容易理解springboot中自动装配的原理了
我们直接查看springboot中自动装配的核心源码
1、先查看SpringBootApplication注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication

2、核心注解@EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

我们已经可以看到我们的@Import注解了
3、核心类AutoConfigurationImportSelector
AutoConfigurationImportSelector实现于接口DeferredImportSelector,DeferredImportSelector继承于ImportSelector接口,那么我们就可以直接看selectImports方法了,即selectImports方法返回的数组都是spring会去帮我们扫描的

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
    private static final String[] NO_IMPORTS = new String[0];
    private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
    private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    private ConfigurableListableBeanFactory beanFactory;
    private Environment environment;
    private ClassLoader beanClassLoader;
    private ResourceLoader resourceLoader;
    private AutoConfigurationImportSelector.ConfigurationClassFilter configurationClassFilter;

    public AutoConfigurationImportSelector() {
    }
	// 核心方法
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
        	// 核心代码
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
}

4、核心方法getAutoConfigurationEntry

  protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            // 核心代码
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

5、核心方法getCandidateConfigurations

   protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   // 核心方法
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	String factoryTypeName = factoryType.getName();
	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

6、核心方法loadSpringFactories

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			// 核心方法
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

我们看核心代码classLoader.getResources(FACTORIES_RESOURCE_LOCATION),FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”
即扫描classpath下面类路径为META-INF/spring.factories的所有文件(包括jar包的)
然后轮询这些文件,返回这些文件中所有的key-value值,再提取出里面key为
org.springframework.boot.autoconfigure.EnableAutoConfiguration的所有数据,即最后返回的String列表是spring会帮我们扫描的配置类

根据上述源码流程,我们可以得到结果,只要是classpath下有文件夹META-INF/spring.factories的,里面定义的org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx列表,xxx列表都是spring会帮我们扫描的

如上我们的代码,我们只要定义META-INF/spring.factories,在里面定义org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.demo.config.TestBeanConfiguration, spring就会扫描到TestBeanConfiguration类,以上就是spring对应第三方扩展组件想加入到springboot自动装配提供的约定

spring官方组件VS第三方组件自动装配

还有一点我们可以介绍下,spring官方的组件和第三方组件是有些不同的

  • 第三方组件都是依据以上约定实现的自动装配,如mybatis-spring-boot-starter-2.0.1.jar
  • 官方组件的classpath下不一定会有META-INF/spring.factories文件,其实现方式是基于spring-boot-autoconfigure组件来实现的

spring-boot-autoconfigure组件中有定义META-INF/spring.factories文件,我们可以看到里面定义了非常多的配置类,包含我们上述讲到的redis的配置类RedisAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
......
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration
......

我们看下RedisAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

可以看到一个注解@ConditionalOnClass(RedisOperations.class),ConditionalOnClass标记的类的作用是当ConditionalOnClass标记的类存在时才会被spring装配,即RedisOperations类在classpath下存在RedisAutoConfiguration才会被装配,RedisOperations存在于官方组件spring-boot-starter-data-redis中,即当项目依赖于spring-boot-starter-data-redis组件时RedisAutoConfiguration会被装配。

即springboot官方组件的装配过程是在spring-boot-autoconfigure组件定义要装配的配置类,并且使用ConditionalOnClass注解标记,只有当对应的官方组件被依赖时才会被加载,这就是官网组件和第三方组件的差别

扩展

如何区分是spring的组件还是第三方组件

  • spring官方自动装配组件的格式如:spring-boot-starter-data-xxx
  • 第三方自动装配组件的格式如:xxx-spring-boot-starter

以上就是springboot自动装配的原理,依据此约定,我们也可以实现我们自己的开箱即用的starter组件了

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
SpringBoot自动装配原理是基于 Spring 框架的核心特性之一,即依赖注入(Dependency Injection)和控制反转(Inversion of Control)。通过自动装配SpringBoot 可以根据一定的规则,自动将需要的依赖对象注入到相应的组件中,简化了开发者的配置工作。 在 SpringBoot 中,自动装配主要通过以下几个步骤实现: 1. ComponentScan:SpringBoot 会根据指定的包路径进行组件扫描,找到所有被注解标记的组件,如 @Component、@Service、@Repository 等。 2. ConditionalOnClass/ConditionalOnMissingClass:SpringBoot 会根据类路径中是否存在指定的类来判断是否需要装配某个组件。当类存在时,装配该组件;当类不存在时,跳过该组件。 3. ConditionalOnBean/ConditionalOnMissingBean:SpringBoot 会根据容器中是否存在指定的 Bean 来判断是否需要装配某个组件。当 Bean 存在时,跳过该组件;当 Bean 不存在时,装配该组件。 4. EnableAutoConfiguration:SpringBoot 提供了一系列以 Enable 开头的注解,用于开启特定功能的自动配置。这些注解会在特定条件下加载一些默认的配置类,将默认的配置注入到容器中。 5. 自定义配置:除了 SpringBoot 提供的默认自动配置之外,开发者还可以通过自定义配置文件(application.properties 或 application.yml)来覆盖默认配置,实现个性化的自动装配。 总的来说,SpringBoot自动装配原理就是根据一系列的条件和规则,将需要的依赖对象自动注入到相应的组件中,简化了开发者的配置工作,提高了开发效率。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值