SpringBoot自定义Starter,分析加载过程

863 阅读4分钟

刚刚看视频,雷老师亲手写了一个Starter,我想着也自定义一个Starter,并看源码解析一下AutoConfiguration类何时运行,其中组件何时被加载到容器。

第一步,建立空工程,分别加入两个模块

第一个项目工程,也就是给外界引用的启动器,主要引用了自动配置包

pom.xml文件依赖如下:

<dependencies>
        <dependency>
            <groupId>com.yanxin</groupId>
            <artifactId>myhello-spring-boot-autoconfigurer</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

第二个项目工程,自动配置包 pom.xml文件依赖如下:

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

第二步,为自动配置包加入自动配置

先考虑这样一个组件,这个组件中的方法需要调用另一个组件的某些属性,我们想springboot运行的时候会自动配置这个组件。利用组件来绑定属性,我们可以先写出这个类:

@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

其中@ConfigurationProperties注解用来将配置文件的配置和类绑定起来。

我们需要的功能组件类如下:

public class HelloService {
    
    HelloProperties hello;

    public HelloProperties getHello() {
        return hello;
    }

    public void setHello(HelloProperties hello) {
        this.hello = hello;
    }

    public String sayHello(String name){
        String reply = hello.getPrefix() + name + hello.getSuffix();
        System.out.println(reply);
        return reply;
    }
}

我们给HelloProperties属性加上setter和getter方法就是为了可以给功能组件注入HelloProperties属性值。

自动配置类如下:

@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {

    @Autowired
    HelloProperties hello;

    @Bean
    public HelloService getService(){
        HelloService ser = new HelloService();
        ser.setHello(hello);
        return ser;
    }
}

自动配置类有@Configuration注解,可以自动加载该类到容器中(那么,springboot程序何时加载HelloAutoConfiguration类呢?)

/META-INF/spring.factories中配置好,以便读取自动配置类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.yanxin.myhellospringbootautoconfigurer.HelloAutoConfiguration

将其导入maven仓库中,分别对两个module进行maven,install。

第三步,新建一个web项目,引用自定义starter

新建test-starter module,pom.xml文件引入

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.yanxin</groupId>
			<artifactId>myhello-spring-boot-starter</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
        	</dependency>

在application.properties中配置

hello.prefix=zy
hello.suffix=yz

新建Controller类HelloController

@RestController
public class HelloController {

    @Autowired
    HelloService service;

    @GetMapping("/hello")
    public String getCon(String name){
        return service.sayHello(name);
    }
}

运行springboot项目,访问/hello,得到

可见返回的是zy--前缀,yz--后缀,zys--name。

至此自动配置类配置成功。那么,自动配置类是合适加载的呢?我们跟进源码。

自动配置原理

我们点开@SpringBootApplication注解,发现注解@SpringBootConfiguration@EnableAutoConfiguration,顾名思义,EnableAutoConfiguration负责开启自动配置,那到底是怎样开启的呢?我们继续点开:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {

我们注意到两个注解@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class}),点开@AutoConfigurationPackage知道这个类有两个属性:basePackages和basePackageClasses,且导入了组件Registrar.class

点开Registrar.class

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }

在此处断点看看到底registerBeanDefinitions方法干了什么

进入方法,

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(2);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }

    }

知道这似乎是将注解所在的包名信息注入到一个bean中,然后使得该报名下的配置默认扫描。

总结一下就是,@AutoConfigurationPackage就是使得自动配置包,使得默认扫描主配置类所在包。

下面再看看,导入的组件AutoConfigurationImportSelector.class 点开能看到

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

这个方法用于导入某些配置。我们点进getAutoConfigurationEntry()方法,

this.getCandidateConfigurations(annotationMetadata, attributes)就是通过注解信息获取配置类,点开看到,

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;
    }

再看loadFactoryNames方法,

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

再看loadSpringFactories方法,

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }

得知在加载自动配置类的地方再这里,

我们的自动配置类在这里被加入缓存result中

然后返回到上级

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList(names.size());
        Iterator var7 = names.iterator();

        while(var7.hasNext()) {
            String name = (String)var7.next();

            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            } catch (Throwable var12) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
            }
        }

        return instances;
    }

这一步获取5个ContextInitializer进行初始化容器。从而将/META-INF/spring.factories下的AutoConfigration类加载到容器中并进行了初始化。