从零开始SpringCloud Alibaba实战(67)——SpringBoot的starter原理

一、前言

springboot开发一个后端应用,我们只需引入依赖,添加简单的配置就能实现对其他框架的整合。他的奥妙就在于它各种各样的starter。

1.1 starter的作用
SpringBoot这些starter的作用就是根据我们配置,给我们初始化一些整合其他框架时需要初始化的一些bean,并加载到spring容器中。这样就减少了在SSM时代那些繁琐的是xml配置。
1.2 环境信息

springboot 2.4.10

二、原理

2.1 概述
那么springboot是如何实现通过,通过引入一些jar包的依赖就将这些jar包中定义的spring的bean加载到spring容器中的呢?答案就是spring的SPI机制。

2.2 什么是spi
SPI(Service Provider Interface)就是服务提供接口,它是实现服务解耦,插件自由插拔的一直机制。简单来说,就是通过一些类加载器,去加载classpath下指定目录文件,文件中定义的有需要加载的类的全权限定名,然后这些类会被识别并加载。平时开发中数据库连接驱动的加载和dubbo中都用到了SPI技术。正是使用了SPI技术,才实现了插件和服务的解耦和可插拔。

2.3 Spring的SPI
Spring的SPI使用由SpringFactoriesLoader.loadFactoryNames方法实现的,
它会加载所有依赖的jar包下classPath下META-INF/spring.factories文件,然后将解析properties文件,找到指定名称的配置后返回。
源码如下:

	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}
	
	/**
	 * spring.factories文件的格式为:key=value1,value2,value3
	 * 从所有的jar包中找到META-INF/spring.factories文件
	 * 然后从文件中解析出key=factoryClass类名称的所有value值
	 */
	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			// 取得资源文件的URL
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			// 遍历所有的URL
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				// 根据资源文件URL解析properties文件,得到对应的一组@Configuration类
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

2.4 Springboot中对spring SPI的使用
springBoot对spring spi的使用具体体现在@EnableAutoConfiguration上。 springboot的启动类注解上有一个@EnableAutoConfiguration。
@EnableAutoConfiguration的作用是帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot,并创建对应配置类的Bean,并把该Bean实体交给IoC容器进行管理。

@EnableAutoConfiguration上面@Import注解指定了导入类的方法。具体是AutoConfigurationImportSelector的selectImports方法。

AutoConfigurationImportSelector源码

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
	
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// 加载所有META-INF/spring.factories文件中定义的类名
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	} 
	
	// 加载
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				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;
	}

在getCandidateConfigurations方法中我们就能看到上面讲的SpringFactoriesLoader.loadFactoryNames方法

分析springBoot starter的源码

3.1 引入依赖

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

META-INF/spring.factories指定了要加载的配置类的权限定名

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure

DruidDataSourceAutoConfigure里就是我们平时很熟悉的springboot添加配置类的方式了。DruidDataSourceAutoConfigure会给我们初始化一个DruidDataSourceWrapper 的spring bean。DruidDataSourceWrapper 又继承了DruidDataSource ,也就是初始化了一个DruidDataSource 的spring bean到spring容器中。

/**
 * 配置类
 */
@Configuration
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class,
    DruidStatViewServletConfiguration.class,
    DruidWebStatFilterConfiguration.class,
    DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {

    private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
    /**
     * 初始换了一个DruidDataSourceWrapper bean
     */
    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        LOGGER.info("Init DruidDataSource");
        return new DruidDataSourceWrapper();
    }
}


DruidDataSourceWrapper 会读写springboot yml文件中spring.datasource.druid开头的配置,并把这值配置到初始化到DruidDataSource 的bean中。

@ConfigurationProperties("spring.datasource.druid")
class DruidDataSourceWrapper extends DruidDataSource implements InitializingBean {
    @Autowired
    private DataSourceProperties basicProperties;

    @Override
    public void afterPropertiesSet() throws Exception {
        //if not found prefix 'spring.datasource.druid' jdbc properties ,'spring.datasource' prefix jdbc properties will be used.
        if (super.getUsername() == null) {
            super.setUsername(basicProperties.determineUsername());
        }
        if (super.getPassword() == null) {
            super.setPassword(basicProperties.determinePassword());
        }
        if (super.getUrl() == null) {
            super.setUrl(basicProperties.determineUrl());
        }
        if(super.getDriverClassName() == null){
            super.setDriverClassName(basicProperties.getDriverClassName());
        }

    }

    @Autowired(required = false)
    public void addStatFilter(StatFilter statFilter) {
        super.filters.add(statFilter);
    }

    @Autowired(required = false)
    public void addConfigFilter(ConfigFilter configFilter) {
        super.filters.add(configFilter);
    }

    @Autowired(required = false)
    public void addEncodingConvertFilter(EncodingConvertFilter encodingConvertFilter) {
        super.filters.add(encodingConvertFilter);
    }

    @Autowired(required = false)
    public void addSlf4jLogFilter(Slf4jLogFilter slf4jLogFilter) {
        super.filters.add(slf4jLogFilter);
    }

    @Autowired(required = false)
    public void addLog4jFilter(Log4jFilter log4jFilter) {
        super.filters.add(log4jFilter);
    }

    @Autowired(required = false)
    public void addLog4j2Filter(Log4j2Filter log4j2Filter) {
        super.filters.add(log4j2Filter);
    }

    @Autowired(required = false)
    public void addCommonsLogFilter(CommonsLogFilter commonsLogFilter) {
        super.filters.add(commonsLogFilter);
    }

    @Autowired(required = false)
    public void addWallFilter(WallFilter wallFilter) {
        super.filters.add(wallFilter);
    }
}

在项目中的使用
在springboot的项目中我们需要用到Druid数据源的时候只需要引入druid-spring-boot-starter的依赖,然后在yml文件中添加spring.datasource.druid开头的相关配置即可

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值