来实现一个Spring Starter吧!

来实现一个Spring Starter吧!

Tans 1,718 2022-04-09

SpringBoot自动装配机制

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器。

通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能。

image-20220409182607750

具体内容就是

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
fun.tans.mystarter.ThreadPoolAutoConfiguration 

具体如何实现?

@EnableAutoConfiguration注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) //主要是此注解
public @interface EnableAutoConfiguration {
}

@AutoConfigurationImportSelector

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

	private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

	private static final String[] NO_IMPORTS = {};

	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 ConfigurationClassFilter configurationClassFilter;

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
          //1. 这里通过metadata获取注解实体,调用代码片段2
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
	/**
	 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
	 * of the importing {@link Configuration @Configuration} class.
	 * @param annotationMetadata the annotation metadata of the configuration class
	 * @return the auto-configurations that should be imported
	 */
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
          
          //2. 调用代码片段3
		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) {
          //3. 调用代码片段4
		List<String> configurations = 
               SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "......");
		return configurations;
	}

 }

SpringFactoriesLoader

public final class SpringFactoriesLoader {
     //代码片段4
     public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) 	{
          ClassLoader classLoaderToUse = classLoader;
          if (classLoaderToUse == null) {
               classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
          }
          String factoryTypeName = factoryType.getName();
          //调用代码片段5
          return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
     }	

	//代码片段5
     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 {
                    //Important:开始处理配置文件.....
                    //FACTORIES_RESOURCE_LOCATION="META-INF/spring.factories"
                    Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
                    while (urls.hasMoreElements()) {
                    }

                    // 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);
               }
               return result;
     }
}

筛选加载

在AutoConfiguration插入指定的@Conditional

@Configuration
// 检查相关的类:RabbitTemplate 和 Channel是否存在
// 存在才会加载
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}

实现自己的Stater

1. 新建工程mystarter, 引入依赖

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
     <groupId>org.springframework.boot</groupI	d>
     <artifactId>spring-boot-configuration-processor</artifactId>
     <optional>true</optional>
</dependency>

注意,如果pom.xml存在如下spring-boot-maven-plugin模块,请删除,因为如果存在此插件,在打包的时候会生成BOOT-INF目录,导致我们的其他模块引入此模块出现错误!问题指南

image-20220409190946130

2. 编写AutoConfiguration配置类和Properties配置文件类

package fun.tans.mystarter;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @Describe: 类描述
 * @Author: tyf
 * @CreateTime: 2022/4/9
 **/
@ConfigurationProperties(prefix = "my")
public class MyProperties {

    private String Name;

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    //    private String
}

package fun.tans;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Describe: 类描述
 * @Author: tyf
 * @CreateTime: 2022/4/9
 **/
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class ThreadPoolAutoConfiguration {

    @Bean
     //注意此判定条件一定存在
    @ConditionalOnClass(ThreadPoolExecutor.class)
    public ThreadPoolExecutor myThreadPool(){
        return new ThreadPoolExecutor(10,10,10, TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(100));
    }
}

3.编写Spring.factories文件

在resource目录下新建META-INF/spring.factories文件,写入我们的需要被扫描配置的类.注意不能出现多余空格等,格式要正确。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
fun.tans.ThreadPoolAutoConfiguration

image-20220409191340779

4.使用Maven工具打包并打包在本地仓库

一般是打成jar包然后发布到远程仓库,我们测试的话可以使用maven install命令,来打包后直接自动放在本地仓库中。

package命令仅仅是打包。

image-20220409191523670

5.查看打包好的模块

如果打包成功,在maven仓库目录/repository会找到你的jar包:

image-20220409191937488

6.开启新的项目进行测试

新建模块或者项目来进行测试,这里我们新建项目test

  • 首先引入依赖(也就是我们刚刚打好的jar包)
<dependency>
     <groupId>fun.tans</groupId>
     <artifactId>mystarter</artifactId>
     <version>1.0.0</version>
</dependency>
  • 编写测试类,使用junit

image-20220409193606856