SpringBoot自动装配与自定义starter

SpringBoot的核心思想是约定优于配置,它简化了之前使用SpringMVC时候的大量配置xml,使得开发者能够快速的创建一个Web项目。那么SpringBoot是如何做到的呢?

@SpringBootApplication

当我们创建一个SpringBoot项目完成后,会有一个启动类,直接就可以运行web项目了。所以我们首先从这个启动类的注解上出发,看看SpringBoot是如何实现的。

@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 {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
  //...
}

可以看到@SpringBootApplication主要是三个注解的复合注解。

@SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

这个注解最简单,它是对@Configuration的封装,@Configuration我们最熟悉不过了,这里就不做分析了。

@ComponentScan

这个注解的作用主要是扫描定义的包下的所有的包含@Controller@Service@Component@Repository等注解的类,把他们注册到Spring的容器中。具体是如何扫描,如何加载注解信息、如何生成Bean以及如何注册到Spring容器中,这里的逻辑相对来说比较复杂,不是本文的重点,不具体分析了。

@EnableAutoConfiguration

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

可以看到EnableAutoConfiguration注解中,主要依赖两个注解,AutoConfigurationPackageImport(AutoConfigurationImportSelector.class),这两个注解的作用都是根据条件动态的加载BeanSpring容器中,下面具体分析。

@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

这里主要是@Import(AutoConfigurationPackages.Registrar.class)注解,Import注解一定很熟悉了,主要是将import的类注入Spring容器中,下面具体分析AutoConfigurationPackages.Registrar

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    //重写这个方法,根据AnnotationMetadata将bean注册到spring容器中
    //这里的AnnotationMetadata就是@SpringBootApplication注解的元数据
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      //new PackageImport(metadata).getPackageName()返回的是SpringBootApplication注解对应的包名
      //也就是启动类所在的包名,所以,SpringBoot项目的启动类和包名是有一定的要求的,这也是SpringBoot约定大约配置
      //的一种体现
            register(registry, new PackageImport(metadata).getPackageName());
        }

        @Override
        public Set determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }

    }

AutoConfigurationImportSelector

下面要分析的这个类就是整个自动装配最关键的类了。查看源码可以知道,AutoConfigurationImportSelector实现了ImportSelector接口,ImportSelector接口中的selectImports方法会根据返回的String[]数组,然后Spring根据数组中的类的全路径类名把响应的类注入到Spring容器中。接着我们来看一下返回了哪些类。

@Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
    //加载元数据,这里面主要是一些Condition条件,目的是为了根据条件判断是否需要注入某个类
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
    //加载所有自动装载的元素
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
    //根据注解获取注解的属性
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //使用SpringFactoryLoader加载classpath下所有的META-INF/spring.factories中,key是           
    //org.springframework.boot.autoconfigure.EnableAutoConfiguration的值
        List configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //删除重复的类
        configurations = removeDuplicates(configurations);
    //删除被排除的类
        Set exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
    //根据上面loadMetadata方法加载的condition条件信息,过滤掉不符合条件的类
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

注意:这里getCandidateConfigurations方法是重点,SpringBoot中依赖的所有的starter都是基于此实现自动装配的。这里用到了SPI

SPI全称为Service Provier Interface,是一种服务发现机制。SPI的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。

SpringBoot的各种starter依赖都是基于此实现的,每个maven依赖的starter的包下都会有一个META-INF/spring.factories配置文件,里面都会有org.springframework.boot.autoconfigure.EnableAutoConfiguration键和对应的需要自动加载的类的全限定名。

实现一个Starter

根据上面的分析,下面简单实现一个starter

IDEA创建一个简单的Maven项目,然后创建相应的包名和类。

image.png

简单说明一下这个starter中的作用

  • FormatTemplate类提供一个模版方法doFormat,可以将传入的泛型对象输出一个字符串
public class FormatTemplate {
    private FormatProcessor formatProcessor;

    public FormatTemplate(FormatProcessor formatProcessor) {
        this.formatProcessor = formatProcessor;
    }
    public String doFormat(T data) {
        return formatProcessor.format(data);
    }
}
  • FormatProcessor是一个接口,提供了一个format的方法,它有两个实现,StringFormatProcessor直接返回传入对象的toStringJsonFormatProcessor根据用fastjson将传入的对象转成json字符串。
public class JsonFormatProcessor implements FormatProcessor {
    @Override
    public  String format(T data) {
        return JSON.toJSONString(data);
    }
}
public class StringFormatProcessor implements FormatProcessor {
    @Override
    public  String format(T data) {
        return data.toString();
    }
}
  • FormatAutoConfiguration利用@Configuration分别将JsonFormatProcessor和StringFormatProcessor注入到spring容器中,这里用了@Condition条件注解,只有当项目中引用了fastjson的时候,才会注入JsonFormatProcessor
@Configuration
public class FormatAutoConfiguration {

    @Bean
    @Primary
    @ConditionalOnClass(name = "com.alibaba.fastjson.JSON")
    public FormatProcessor jsonFormat(){
        return new JsonFormatProcessor();
    }

    @Bean
    @ConditionalOnMissingClass("com.alibaba.fastjson.JSON")
    public FormatProcessor stringFormat(){
        return new StringFormatProcessor();
    }

}
  • TemplateAutoConfiguration引用FormatAutoConfiguration并注入了一个FormatTemplate
@Configuration
@Import(FormatAutoConfiguration.class)
public class TemplateAutoConfiguration {

    @Bean
    public FormatTemplate formatTemplate(FormatProcessor formatProcessor) {
        return new FormatTemplate(formatProcessor);
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(TemplateAutoConfiguration.class);
        FormatTemplate bean = context.getBean(FormatTemplate.class);
        FormatProcessor formatProcessor = context.getBean(FormatProcessor.class);
        System.out.printf(bean.doFormat("aaa"));
        System.out.println(formatProcessor.format("bbb"));
    }
}
  • resources/META-INF下的spring.factories中定义了要自动装配的类的全路径,即TemplateAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.cross.springbootdemo.autoconfiguration.TemplateAutoConfiguration

编写好后,将项目进行打包,然后在其他项目中,就可以引入了,使用的时候直接可以用@Autowired引入FormatTemplate了。

@RestController
public class TestController {

    @Autowired
    private FormatTemplate formatTemplate;

    @GetMapping(value = "test")
    public String test() {
        User user = new User();
        user.setName("crossyf---");
        user.setAge(18);
        return formatTemplate.doFormat(user);
    }
}

一个简单的starter就完成了。

你可能感兴趣的:(SpringBoot自动装配与自定义starter)