嘘~ 正在从服务器偷取页面 . . .

SpringBoot AutoConfig 自动装配


SpringBoot 自动装配

什么是SpringBoot

SpringBoot 自动装配。

Springboot主要思想是遵循”约定优于配置”的设计,使用注解对一些常规的配置项做默认配置,减少使用XML配置,让你的项目结构快速搭建运行起来。

Springboot针对主流常用的框架技术将其封装成starter组件,现在引入框架只要引入一个starter,你就可以使用这个框架,只需少量的配置甚至是不需要任何配置就可以实现快速集成。可以让开发者关注业务,减少技术开发成本。

自动装配原理

Spring Boot中的自动装配常用技术:

1、Spring 模式注解装配。 如@AutoConfiguration@Configration

2、Spring @Enable模块装配。通过@EnableXxx注解灵活开启某些模块组件,如@EnableAutoConfiguration开启默认组件的自动化装配。

3、Spring 条件装配装。通过@ConditionalOnXxx注解进行条件判断是否加载相关组件。

4、Spring 工厂加载机制。通过读取META-INF/spring.factories加载对应的自动配置类。

模式注解装配

模式注解

stereotype annotation 一般翻译为模式注解,样板注解。

什么叫模式注解?

比如Spring中常见的模式注解有@Component@Service@Repository@Controller ,后面3个是@Component的派生注解,凡是被@Component标注的类都会被Spring扫描并纳入到IOC容器中。那么后面三个注解就有@Component相同的作用。

@Component 定义:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
    String value() default "";
}

再看看其中一个派生注解@Controller的定义:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

可以发现 @Controller注解是被@Component 修饰的,确实是从@Component派生出来的模式注解,在开发中我们使用过很多次,肯定是能够被扫描到IOC容器中的。

还有@RestController, @RequestMapping@ResponseBody等之类的。

模式注解,可以理解为就是这个注解专门用来处理解决某个事情的。让我联想到设计模式的单一职责原则。注解的本质不也是一个类吗?只是这个类比较特殊,可以对其他元素进行注解修饰。

模式注解装配

那么在SpringBoot中,也有不少派生注解,如@SpringBootApplication@Configuration@AutoConfiguration 注解。比如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 {

}

正好被@SpringBootConfiguration 修饰,正好可以看看:

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

正好被@Configuration 修饰,正好可以看看:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

发现了,竟然也是被@Component修饰,而且还很有层次:

└─----- @Component
      └─----- @Configuration
            └─----- @SpringBootConfiguration
                  └─----- @SpringBootApplication

这一层一层的像不像楼梯一样可爱? 🤣

这些都是派出来的注解。

@Enable模块驱动

什么是模块驱动?

找个例子怎么判断呢?在逐渐转向面向注解开发之后,模块驱动典型特征是@Enable开头的注解

比如Spring 中 开启web模块的@EnableWebMvc,开启事物管理的@EnableTransactionManagement,开启缓存模块的@EnableCaching,开启异步执行的@EnableAsync,开启响应编程的@EnableWebFlux等。

比如SpringBoot里的自动装配@EnableAutoConfiguration,开启属性绑定的@EnableConfigurationProperties,

模块驱动其实就是功能组件开发模块化过程,需要使用的时候通过一个注解开启模块的使用,不使用的时候不加载模块相关组件和配置。

模块驱动可以简化组件使用时的配置步骤,实现按需装配,典型的可插拔式使用,同时屏蔽组件集合装配的细节。

Srping实现@Enable模块驱动的方法大致分为两类: 注解驱动接口编程

实现方式一:注解驱动

注解驱动的实现@Enable模块驱动,可以参考现有的@EnableWebMvcEnableScheduling

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented
public @interface EnableScheduling {
}

比普通的注解多了一个@Import({SchedulingConfiguration.class}),使用@Import注解导入了一个SchedulingConfiguration的配置类。

@Configuration(
    proxyBeanMethods = false
)
@Role(2)
public class SchedulingConfiguration {
    public SchedulingConfiguration() {
    }

    @Bean(
        name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"}
    )
    @Role(2)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
        return new ScheduledAnnotationBeanPostProcessor();
    }
}

可以看到里面使用@Bean的方式注入了一个Bean。

有人就要说了这个玩意怎么就能加入到容器呢?

public class ScheduledAnnotationBeanPostProcessor implements ScheduledTaskHolder, 
    MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor, Ordered, 
    EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, 
    ApplicationContextAware, SmartInitializingSingleton, 
    ApplicationListener<ContextRefreshedEvent>, DisposableBean {

}

这个名字很长的玩意ScheduledAnnotationBeanPostProcessor 实现了那么多接口,乍一看是很吓人啊~ 不过不要担心,里面有一堆熟悉spring的接口呀BeanNameAware,
BeanFactoryAwareApplicationContextAwareSmartInitializingSingleton。不说别的,就凭它实现了Spring的一系列接口,不能加入到容器,我吃了它。

基于注解驱动实现@Enable模块驱动,本质就是通过@Import注解来导入一个对应配置类,来实现将相应的模块组件注册到IOC容器中,那么这个模块对应的功能也就可以让程序使用。

注解驱动示例

1、先定义个Bean
package com.xiaocai.base.demo.auto;

/**
 * @author Xiaocai.Zhang
 */
@Data
public class HelloWorldService {

    private String name ;

    public HelloWorldService(String name){
        this.name = name;
    }

    public HelloWorldService(){
        this("helloWorld");
    }
}

这里两个构造方法,一个可以自定义名字,一个无参数的给默认名字。

2、 定义一个配置类

因为要使用@Import导入配置类,那就定义一个属于自己的配置类

package com.xiaocai.base.demo.auto;

@Configuration
public class HelloWorldConfiguration {

    @Bean
    public HelloWorldService helloWorldService(){
        return new HelloWorldService("XiaoCaiService");
    }
}
3、定义@Enable注解驱动

定义对应的@Enable注解驱动@EnableHelloWorld

/**
 * @author Xiaocai.Zhang
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {


}
4、测试验证
package com.xiaocai.base.demo.auto;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @author Zongyuan.Zhang
 */
@EnableHelloWorld
public class HelloWordTest {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(HelloWordTest.class);
        context.refresh();
        HelloWorldService helloWorld = context.getBean("helloWorldService", HelloWorldService.class);
        System.out.println(helloWorld.getName());
    }
}

输出

XiaoCaiService

结论:使用@EnableHelloWorld通过导入配置HelloWorldConfiguration成功注册HelloWorldService组件。

关于@Import注解

@Import注解是Spring 注册组件的其中一种方式。

一般如果是个普通的类,可以直接导入,导入的名字默认为全类名

/**
 * @author Xiaocai.Zhang
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldService.class)
public @interface EnableHelloWorld {


}

但是这种方式每次只能导入一个类,我要导入一堆组件怎么办? 当然了@Import注解本身就支持多个类导入,但是一个一个的写,如果导入比较多还是很麻烦,Spring 已经想好了。

实现方式二:接口编程

接口编程方式实现模块驱动,相对于注解驱动,该种方式稍微复杂一些,参考现有的@EnableCaching示例:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CachingConfigurationSelector.class})
public @interface EnableCaching {
    boolean proxyTargetClass() default false;

    AdviceMode mode() default AdviceMode.PROXY;

    int order() default 2147483647;
}

比普通的注解多了一个@Import({CachingConfigurationSelector.class}),使用@Import注解导入了一个CachingConfigurationSelector的类,虽然不知道这个类是干嘛的,但是可以向上找看看:

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {

}

// 父级接口
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector{

}

从代码可以看到,该类间接实现了ImportSelector接口。

ImportSelector接口又是干嘛的?

刚才留了一个问题@Import要导入一堆组件怎么办?这个两个都有个Import关键字会不会有关系呢?

public interface ImportSelector {

    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
     String[] selectImports(AnnotationMetadata importingClassMetadata);
}

ImportSelector接口包含一个selectImports()方法,方法返回类的全类名数组,即需要导入到IOC容器中组件的全类名数组,包含一个AnnotationMetadata类型入参,通过这个参数我们可以获取到使用ImportSelector的类的全部注解信息。

也就说是@Import({CachingConfigurationSelector.class}) 这个东东其实是CachingConfigurationSelector类借助父类AdviceModeImportSelector实现了ImportSelector接口的特征,从而导入了一个组件全类名的数组,向IOC容器注册组件。

接口编程实现@Enable模块驱动的本质是:通过@Import来导入ImportSelector接口的实现类,该实现类里可以定义需要注册到IOC容器中的组件,以此实现相应模块对应组件的注册。

多说一句:

@Imprt注解除了传一个@Configuration类外,同时也支持传ImportSelector或者ImportBeanDefinitionRegistrar的实现类。

接口编程示例

这次就简单粗暴点吧~

1、自定义组件

我这里定义三个简单的类:HelloZhang,HelloXiao,HelloCai,分别返回一个字符串组成一个名字:

/**
 * @author Xiaocai.Zhang
 */
@Data
public class HelloZhang {

    private String name ;

    public HelloZhang(String zhang) {
        this.name = zhang;
    }

    public HelloZhang(){
        this("Zhang");
    }
}

/**
 * @author Xiaocai.Zhang
 */
@Data
public class HelloXiao {
    private String name ;

    public HelloXiao(String xiao) {
        this.name = xiao;
    }

    public  HelloXiao(){
        this("xiao");
    }
}

/**
 * @author Xiaocai.Zhang
 */
@Data
public class HelloCai {
    private String name ;

    public  HelloCai(){
        this("Cai");
    }
    public HelloCai(String cai) {
        this.name = cai ;
    }
}
2、定义ImportSelector组件

自定义一个ImportSelector实现类MyImportSelector

/**
 * @author Xiaocai.Zhang
 */
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return  new String[]{
                HelloZhang.class.getName(),
                HelloXiao.class.getName(),
                HelloCai.class.getName()
        };
    }

    @Override
    public Predicate<String> getExclusionFilter() {
        return null;
    }
}
3、定义模块驱动
/**
 * @author Xiaocai.Zhang
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MyImportSelector.class})
public @interface EnableHello {
}
4、测试验证
/**
 * @author Zongyuan.Zhang
 */
@EnableHello
public class HelloTest {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(HelloTest.class);
        context.refresh();


        HelloZhang helloZhang = context.getBean("com.xiaocai.base.demo.auto.HelloZhang", HelloZhang.class);
        HelloXiao helloXiao = context.getBean("com.xiaocai.base.demo.auto.HelloXiao", HelloXiao.class);
        HelloCai helloCai = context.getBean("com.xiaocai.base.demo.auto.HelloCai", HelloCai.class);
        System.out.println(helloZhang.getName()+" "+helloXiao.getName()+" "+ helloCai.getName());
    }
}

输出:

Zhang xiao Cai

工厂加载机制

Spring 工厂加载机制的实现类为SpringFactoriesLoader

public final class SpringFactoriesLoader {

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

static final Map<ClassLoader, Map<String, List<String>>> cache 
                 = new ConcurrentReferenceHashMap();

}

该类的loadSpringFactories()方法会读取spring-boot-autoconfigure-2.4.0.RELEASE.jar里的META-INF/spring.factories这个配置文件,并将读到的配置加载到自己的静态变量cache中。

spring.factories 部分组件:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
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.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\

当启动类被@EnableAutoConfiguration标注后,上面所以配置在org.springframework.boot.autoconfigure.EnableAutoConfiguration中的所有类Spring都会去扫描,看是否可以纳入到IOC容器中进行管理。

比如我们查看org.springframework.boot.autoconfigure.aop.AopAutoConfiguration的源码:

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

}

可看到该类上标注了一些注解,其中@Configuration为模式注解,ConditionalOnClass为条件装配,@EnableConfigurationProperties为注解式模块装配。

工厂加载组件示例

前面已经写过的就不重复写了,原来的写好的一些组件加一些初始化打印:

(1)组件类HelloWorldService:

/**
 * @author Xiaocai.Zhang
 */
@Data
public class HelloWorldService {

    private String name ;

    public HelloWorldService(String name){
        this.name = name;
    }

    public HelloWorldService(){
        this("helloWorld");
    }
}

(2)组件配置类HelloWorldConfiguration:

/**
 * @author Xiaocai.Zhang
 */
@Configuration
public class HelloWorldConfiguration {
    public HelloWorldConfiguration(){
        System.out.println(" init HelloWorldConfiguration");
    }
    @Bean
    public HelloWorldService helloWorldService(){
        System.out.println(" init helloWorldService");

        return new HelloWorldService("XiaoCaiService");
    }
}

(2)模块驱动@EnableHelloWorld:

/**
 * @author Xiaocai.Zhang
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {


}
1、写个自己的自动配置类
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;

/**
 * @author Xiaocai.Zhang
 */
@Configuration
@EnableHelloWorld
@ConditionalOnProperty(prefix = "helloWorld",name = "enabled", havingValue = "true")
public class MyHelloWorldConfiguration {

    public MyHelloWorldConfiguration(){
        System.out.println(" init MyHelloWorldConfiguration");
    }
}
2、创建spring.factories文件

然后在resources目录下新建META-INF目录,并创建spring.factories文件:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xiaocai.base.demo.auto.MyHelloWorldConfiguration
3、添加配置

在配置文件application.properties中添加

helloWorld.enabled=true
4、测试一下
/**
 * @author Xiaocai.Zhang
 */
@EnableAutoConfiguration
public class MyHelloWorldCfgTest {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(MyHelloWorldCfgTest.class)
                .web(WebApplicationType.NONE)
                .run(args);
        HelloWorldService hello = context.getBean("helloWorldService", HelloWorldService.class);
        System.out.println("name : " + hello.getName());
        context.close();
    }

}


版权声明: 本博客所有文章除特別声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明来源 Small-Rose / 张小菜 !
评论
  目录