栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

详解SpringBoot自动装配原理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

详解SpringBoot自动装配原理


文章目录
    • 一、从RedisAutoConfiguration源码分析自动装配
    • 二、@EnableConfigurationProperties注解
    • 三、@import注解
      • 3.1 类导入
      • 3.2 importSelector导入
      • 3.3 importBeanDefinitionRegistrar导入
    • 四、application.yml配置提示
    • 五、自定义Starter组件

一、从RedisAutoConfiguration源码分析自动装配

SpringBoot主张一种零配置的开发方式,特别是第三方框架的整合往往只需要在项目中引入相应的starter,就能做到开箱即用的效果。以整合Redis为例:

  1. 使用构建工具导入spring-boot-starter-redis依赖。
  2. 在application.yml中编写redis配置信息。
  3. 在项目直接注入RedisTemplate类的实例对象,依靠此对象完成对redis的操作。

以上过程中,我们并没有实例化RedisTemplate类型的对象,也就是说在项目导入spring-boot-starter-redis依赖后,会在Spring容器中自动注册一个RedisTemplate类的实例对象。

查看Redis自动配置类的源代码:

①注解 @Configuration 标注了本类是一个配置类。

②注解 @ConditionalOnClass 表示条件装配,当Spring容器中存在RedisOperations类实例时,该配置类才会生效。

⑤注解 @Bean 标注方法的返回值注册到Spring容器中。

⑥注解也是一个条件注解,当前Spring容器中没有name为redisTemplate的实例时,@Bean 才会生效(这样可以允许用户自己定义RedisTemplate 并注册到Spring容器,但是name一定要为redisTemplate)。

⑦注解 @ConditionalOnSingleCandidate 表示当Spring容器中有唯一一个 RedisConnectionFactory 实例对象或者有多个RedisConnectionFactory 实例对象,其中一个使用@Primary注解时(保证注入的唯一性,程序不会报错),才会让@Bean生效。这是因为 RedisTemplate 类对象必须依赖于 RedisConnectionFactory 进行构造,如果Spring容器中不存在RedisConnectionFactory类实例,则无法生成 RedisTemplate 类实例。

依靠这些注解,当Spring容器中没有 RedisTemplate 类实例时,自动创建一个 RedisTemplate 类实例并注册到Spring容器。

二、@EnableConfigurationProperties注解

@EnableConfigurationProperties 注解可以令 @ConfigurationProperties 注解的类生效,@ConfigurationProperties 注解的作用是批量注入有着相同前缀的属性(SpringBoot属性注入 ),@ConfigurationProperties 注解可以将application.yml文件中的属性注入到对象实例中,使用 @Component 注解可以将对象实例化并注入到Spring容器。

@EnableConfigurationProperties 注解也会令@ConfigurationProperties 注解标注的类实例化并注册到Spring容器(此处的作用相当于在类上同时写了@ConfigurationProperties和@Component)。

在spring-boot-starter模块中新建Member类,并设置好setter、getter方法(此处用lombok生成)。

package com.it.vo;

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

@Data
@ConfigurationProperties(prefix = "com.it")
public class Member {
    private Integer id;
    private String name;
    private double salary;
}

新建MemberAutoConfiguration 自动装配类。

package com.it.config;

import com.it.vo.Member;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(value = Member.class) // Bean注册
public class MemberAutoConfiguration { // 自动装配类

}

建立启动类,启动SpringBoot程序。

@Slf4j
@RestController
@SpringBootApplication
public class StartSpringApp {

	@Autowired
    private Member member;
    
    public static void main(String[] args) {
        SpringApplication.run(StartSpringApp.class, args);
    }

    @RequestMapping("/member")
    public Member getMember() {
        log.info("member: {}", member.toString());
        return member;
    }
}

访问:http://localhost:8080/member

以上使用了@Autowired 注解直接注入了Member实例,当Spring容器中有多个Member实例时,可以用@Qualifier 注解指定注入实例。

三、@import注解

翻看@EnableConfigurationProperties 注解源代码,发现其引用了@import注解。

@import注解的作用是将Bean加入到Spring容器中,其支持3种形式的导入。

  1. 类导入
  2. importSelector导入
  3. importBeanDefinitionRegistrar导入
3.1 类导入

修改MemberAutoConfiguration 类,将@EnableConfigurationProperties 注解替换为 @import注解后执行,程序依然正常。

package com.it.config;

import com.it.vo.Member;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.import;

@Configuration
@import(Member.class)
public class MemberAutoConfiguration {

}

也就是说在配置类上使用@import(Member.class) 相当于实例化一个Member对象并注册到Spring容器,这一点和@EnableConfigurationProperties 是一样的,都等价于在Member类上直接使用 @Component 注解。@EnableConfigurationProperties 注解源代码上的 @import 采用的就是这种形式。

打开@import 注解源代码可以发现,其value接收Class数组,也就是说写在@import注解中的类型都会被实例化并注册到Spring容器。

@import({TestA.class,TestB.class,TestC.class})  // 将TestA,TestB,TestC实例化并注册到Spring容器
3.2 importSelector导入

使用importSelector可以实现将Bean批量注册到Spring容器。

importSelector是一个接口,要完成Bean注册的功能需要自定义其实现子类并覆写 selectimports 方法。

新建两个测试VO类:

package com.it.vo;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UselessTypeA {
    public UselessTypeA() {
        log.info("UselessTypeA实例化...");
    }
}
package com.it.vo;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UselessTypeB {
    public UselessTypeB() {
        log.info("UselessTypeB实例化...");
    }
}

新建 DefaultimportSelector 实现 importSelector 接口,selectimports 方法返回一个String[]数组,这个数组的内容是要注册的Bean信息(类全名),DefaultimportSelector 需要使用@import注解导入后才会生效,将放在selectimports方法的Bean注册到容器中。

package com.it.selector;

import org.springframework.context.annotation.importSelector;
import org.springframework.core.type.Annotationmetadata;

public class DefaultimportSelector implements importSelector {

    @Override
    public String[] selectimports(Annotationmetadata importingClassmetadata) {
        return new String[]{"com.it.vo.UselessTypeA","com.it.vo.UselessTypeB"}; // 要注册Bean的类全名
    }
}

修改 MemberAutoConfiguration 类,@import注解导入 DefaultimportSelector。

@Configuration
@import({Member.class, DefaultimportSelector.class})
public class MemberAutoConfiguration {

}

启动项目,观察UselessTypeA、UselessTypeB 确实已经实例化了。

如果想知道UselessTypeA、UselessTypeB实例对象是否被注册到Spring容器中,则可以编写接口,打印出容器的所有实例对象(也可以导入spring-boot-starter-actuator后访问beans端点查看)。

@RestController
@RequestMapping("/beans")
public class BeanInfoAction {

    @GetMapping("/get")
    public List getBeans() {
        List beans = new ArrayList<>();
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MemberAutoConfiguration.class);
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            beans.add("[" + name + "]" + context.getBean(name).getClass().getSimpleName());
        }
        return beans;
    }
}

启动项目,访问:http://localhost:8080/beans/get 发现UselessTypeA、UselessTypeB确实注册到了Spring容器中。

3.3 importBeanDefinitionRegistrar导入

相比于importSelector方式,使用importBeanDefinitionRegistrar导入可以指定注册Bean的name属性。

新建DefaultimportBeanDefinitionRegistrar 类并实现importBeanDefinitionRegistrar 接口。

public class DefaultimportBeanDefinitionRegistrar implements importBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(Annotationmetadata importingClassmetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition uselessBeanA = new RootBeanDefinition(UselessTypeA.class);
        RootBeanDefinition uselessBeanB = new RootBeanDefinition(UselessTypeB.class);
        registry.registerBeanDefinition("uselessBeanA", uselessBeanA);
        registry.registerBeanDefinition("uselessBeanB", uselessBeanB);
    }
}

DefaultimportBeanDefinitionRegistrar 类也需要 @import注解导入,修改MemberAutoConfiguration 类

@Configuration
@import({Member.class, DefaultimportBeanDefinitionRegistrar.class})
public class MemberAutoConfiguration {

}

重新启动容器,访问http://localhost:8080/beans/get 发现Bean名称被修改了。

四、application.yml配置提示

如果想要在application.yml中出现配置项的提示信息,项目需要导入spring-boot-configuration-processor依赖。


    org.springframework.boot
    spring-boot-configuration-processor

如果使用的是gradle构建工具,则需要禁止一系列的打包任务。

jar {enabled true} //允许当前模块打包为*.jar文件

// 不启用javaDoc任务
javadoc {enabled false}
javadocTask{enabled false}
// 不生成javadoc的*.jar文件
javadocJar{enabled false}
// 不执行springboot的打包任务
bootJar{enabled false}

dependencies { // 配置子模块依赖
    compile 'org.springframework.boot:spring-boot-starter-web'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}

将项目打包后,会在build -> classes -> java -> main -> meta-INF 文件夹下发现一个spring-configuration-metadata.json文件,这个文件就是用来描述application.yml中的提示信息。


重新进入application.yml文件,发现配置项的名称和类型都会正常提示。

五、自定义Starter组件

starter组件可以在被其他项目引用时提供注册好的Bean实例,spring-boot-starter-redis就是一个starter。

由于以上代码都编写在spring-boot-starter模块中,为了方便,这里将spring-boot-starter模块制作成starter,再新建一个starter-test模块负责引入spring-boot-starter模块。

修改MemberAutoConfiguration 类,注册一个List集合到容器中。

@Configuration
@import({Member.class, DefaultimportBeanDefinitionRegistrar.class})
public class MemberAutoConfiguration {

    @Bean(name = "messages")
    public List messages() {
        return List.of("hello", "spring-boot-starter","test array");
    }
}

再resources根路径下新建一个spring.factories文件,这个文件否则配置装配信息。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.it.config.MemberAutoConfiguration

以上配置指明,其他模块导入本模块后,可以依靠MemberAutoConfiguration类进行自动装配。

新建starter-test项目,导入spring-boot-starter模块作为依赖。


    com.it
    spring-boot-starter

gradle项目:

compile this.rootProject.project('spring-boot-starter')

在starter-test项目中编写application.yml文件,此时提示信息正常显示。

server:
  port: 8080
com:
  it:
    id: 10
    name: NicholasGUB
    salary: 0.13

编写主启动类启动程序,观察日志发现Bean注册正常。

创建测试Controller,查看Member属性和List集合是否正常注入。

@RestController
@RequestMapping("/test/*")
public class TestController {

    private final List messages;
    private final Member member;

    @Autowired
    public TestController(List messages, Member member) {
        this.messages = messages;
        this.member = member;
    }

    @GetMapping("/beans")
    public Object getBeans() {
        Map result = new HashMap<>();
        result.put("messages", messages);
        result.put("member", member);
        return result;
    }
}

访问:http://localhost:8080/test/beans 发现Bean全部注册正常,application.yml中的配置也生效。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/642681.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号