深入了解SpringBoot的自动装配

本文详细介绍了Spring Boot自动配置的工作原理,包括如何创建自定义starter,配置属性,以及使用@Import注解的三种方式实现Bean注入。此外,还展示了如何实现配置属性的自动提示,并通过示例演示了如何在项目中使用和测试自动配置的组件。
摘要由CSDN通过智能技术生成

1、自动装配简介:

在早先使用Spring开发框架的时候,如果想要进行某些服务的整合,常规的做法就是引入服务有关的依赖库,而后配置一些xml文件,进行组件的定义。但是在SpringBoot里边比较有特点的形式就是会出现很多的starter。

项目的开发要求是不断进化的,而随着时间以及技术的推移,一个项目中除了基本的编程语言之外,还需要进行大量的应用服务整合。例如:在项目中会使用到mysql数据库进行持久化存储,同时会利用Redis实现分布式缓存,以及使用RabbitMQ实现异构系统整合服务,这些都需要maven/gradle构建工具引入相关的依赖库之后才可以整合到项目之中,为项目提供应有的服务支持。

 

 一旦在项目之中引入了starter定义操作,那么就会出现一系列自动配置处理类。

下面我们自定义实现自动配置类的属性

1、新建starter的子模块包(父模块引入web即可)

最终父pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <modules>
        <module>microboot-autoconfig-starter</module>
        <module>microboot-web</module>
    </modules>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.pshdhx.yootk</groupId>
    <artifactId>boot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>boot</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>

        <!-- 引入依赖库 -->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

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

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2、 新建实体类(即为我们以后的自动装配的配置)

package com.pshdhx.vo;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Data
//@Component 此时不靠这个加入到容器
@ConfigurationProperties(prefix = "com.pshdhx.dept") //表示该类应该成为一个组件,但是此时的配置的启用并不是通过@Component完成的。而是通过另外的注解实现的。@EnableConfigurationProperties({Dept.class})
public class Dept {
    private Long deptNo;
    private String deptName;
    private String loc;

}

此时如果使用@ConfigurationPropereits注解,无法将此类添加到容器之中,所以需要我们新建一个配置类。

3、新建一个配置类

package com.pshdhx.config;

import com.pshdhx.register.DefaultImportBeanDefinitionRegister;
import com.pshdhx.selector.DefaultImportSelector;
import com.pshdhx.vo.Dept;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import java.util.ArrayList;
import java.util.List;

/**
 * 自动装配类
 */
@Configuration
//@EnableConfigurationProperties({Dept.class})
//@Import({Dept.class})
//@Import({DefaultImportSelector.class})
@Import({DefaultImportBeanDefinitionRegister.class})
public class PshdhxAutoConfiguration {
    @Bean(name = "books")
    public List<String> getBooks(){
        List<String> list = new ArrayList<>();
        list.add("p1");
        list.add("p2");
        list.add("p3");
        list.add("p4");
        return list;
    }
}

建立好配置类之后,我们的自动配置类已经注册到Spring容器之中。

此刻使用import注解的时候主要是将Bean加入到Spring实例管理之中,需要注意的是,如果要想使用@Import本身却有三种不同的处理形式:
类导入,ImportSelector导入,ImportBeanDefinitionRegister导入;
1、方式一:指定Bean的导入;@EnableConfigurationProperties({Dept.class}) 也可以写成@import({Dept.class}),此时可以正常注入;
	对于此时的Dept来讲,并没实现Bean的定义,但是由于@Import的作用,所以实现了Bean的自动注册。
2、方式二:如果此时要注入的Bean有很多,那么就需要写入很多Bean的名称。最好的方式是ImportSeletor实现Bean的配置。
3、方式三:自定义配置Bean和注册Bean

import三种方式实现容器注入

1、指定Bean注入【和直接使用EnableConfigurationProperties效果一样】


/**
 * 自动装配类
 */
@Configuration
//@EnableConfigurationProperties({Dept.class})
@Import({Dept.class})
public class PshdhxAutoConfiguration {

2、使用ImportSelector注入

package com.pshdhx.selector;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class DefaultImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.pshdhx.vo.Dept"};
    }
}

package com.pshdhx.config;

import com.pshdhx.register.DefaultImportBeanDefinitionRegister;
import com.pshdhx.selector.DefaultImportSelector;
import com.pshdhx.vo.Dept;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import java.util.ArrayList;
import java.util.List;

/**
 * 自动装配类
 */
@Configuration
@Import({DefaultImportSelector.class})
public class PshdhxAutoConfiguration {
    @Bean(name = "books")
    public List<String> getBooks(){
        List<String> list = new ArrayList<>();
        list.add("p1");
        list.add("p2");
        list.add("p3");
        list.add("p4");
        return list;
    }
}

3、使用配置和注册注入Bean

package com.pshdhx.register;

import com.pshdhx.vo.Dept;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class DefaultImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Dept.class);//配置Bean
        registry.registerBeanDefinition("deptInstance",rootBeanDefinition); //注册Bean
    }
}
package com.pshdhx.config;

import com.pshdhx.register.DefaultImportBeanDefinitionRegister;
import com.pshdhx.selector.DefaultImportSelector;
import com.pshdhx.vo.Dept;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import java.util.ArrayList;
import java.util.List;

/**
 * 自动装配类
 */
@Configuration
@Import({DefaultImportBeanDefinitionRegister.class})
public class PshdhxAutoConfiguration {
    @Bean(name = "books")
    public List<String> getBooks(){
        List<String> list = new ArrayList<>();
        list.add("p1");
        list.add("p2");
        list.add("p3");
        list.add("p4");
        return list;
    }
}

测试Bean是否已经成功注入

com:
  pshdhx:
    dept:
      deptNo: 123
      deptName: code
      loc: jinan
      
import com.pshdhx.App;
import com.pshdhx.config.PshdhxAutoConfiguration;
import com.pshdhx.vo.Dept;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;

@ExtendWith(SpringExtension.class) //使用Junit5测试工具
@WebAppConfiguration//启动web运行环境
@SpringBootTest(classes = App.class) //配置程序启动类
public class Test1 {
    /**
     * 实现Dept对象实例的注入
     */
    @Autowired
//    @Qualifier("com.pshdhx.dept-com.pshdhx.vo.Dept")
    private Dept dept;

    @Test
    public void TestAutoConfiguration(){
        System.out.println(this.dept);
    }

    @Test
    public void testBeanInfo(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PshdhxAutoConfiguration.class);
        String[] beanNames = context.getBeanDefinitionNames();
        for(String name : beanNames){
            System.out.println("name==》"+name+"类型======》"+context.getBean(name).getClass().getName());
        }
    }

}

控制台输出:
 

Dept(deptNo=123, deptName=code, loc=jinan)

2021-12-11 10:55:22.367  INFO 16504 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

Process finished with exit code 0

name==》org.springframework.context.annotation.internalConfigurationAnnotationProcessor类型======》org.springframework.context.annotation.ConfigurationClassPostProcessor

name==》org.springframework.context.annotation.internalAutowiredAnnotationProcessor类型======》org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor

name==》org.springframework.context.annotation.internalCommonAnnotationProcessor类型======》org.springframework.context.annotation.CommonAnnotationBeanPostProcessor

name==》org.springframework.context.event.internalEventListenerProcessor类型======》org.springframework.context.event.EventListenerMethodProcessor

name==》org.springframework.context.event.internalEventListenerFactory类型======》org.springframework.context.event.DefaultEventListenerFactory

name==》pshdhxAutoConfiguration类型======》com.pshdhx.config.PshdhxAutoConfiguration$$EnhancerBySpringCGLIB$$2e08ce18

name==》org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor类型======》org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor

name==》org.springframework.boot.context.internalConfigurationPropertiesBinderFactory类型======》org.springframework.boot.context.properties.ConfigurationPropertiesBinder$Factory

name==》org.springframework.boot.context.internalConfigurationPropertiesBinder类型======》org.springframework.boot.context.properties.ConfigurationPropertiesBinder

name==》org.springframework.boot.context.properties.BoundConfigurationProperties类型======》org.springframework.boot.context.properties.BoundConfigurationProperties

name==》org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter类型======》org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter$$Lambda$603/62309924

name==》com.pshdhx.dept-com.pshdhx.vo.Dept类型======》com.pshdhx.vo.Dept

下面我们实现starter模块的application.yml配置属性的自动提示

在一些使用系统Bean的时候,每当进行application.yml配置的时候都会提供有一系列的配置提示,实际上这些配置提示开发者也可以自己来实现,而不需要你自己做任何的繁琐处理,可以直接生效。

1、在父项目之中引入configuration-processor包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

2、编译子项目,会在其生成包中发现”spring-configuration-metadata.json”文件。

{
  "groups": [
    {
      "name": "com.pshdhx.dept",
      "type": "com.pshdhx.vo.Dept",
      "sourceType": "com.pshdhx.vo.Dept"
    }
  ],
  "properties": [
    {
      "name": "com.pshdhx.dept.dept-name",
      "type": "java.lang.String",
      "sourceType": "com.pshdhx.vo.Dept"
    },
    {
      "name": "com.pshdhx.dept.dept-no",
      "type": "java.lang.Long",
      "sourceType": "com.pshdhx.vo.Dept"
    },
    {
      "name": "com.pshdhx.dept.loc",
      "type": "java.lang.String",
      "sourceType": "com.pshdhx.vo.Dept"
    }
  ],
  "hints": []
}

 3、此时,子项目中会出现配置的提示:

 自定义starter组件

1、定义Spring.factories配置文件,开启对外的自动配置;

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.pshdhx.config.com.pshdhx.config.PshdhxAutoConfiguration

2、将子项目引入到另外的子项目中(web子项目)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>boot</artifactId>
        <groupId>com.pshdhx.yootk</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>microboot-web</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.pshdhx.yootk</groupId>
            <artifactId>microboot-autoconfig-starter</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>


</project>

3、开启测试

com:
  pshdhx:
    dept:
      dept-no: 124
      dept-name: pansd
      loc: jinan

import com.pshdhx.App;
import com.pshdhx.vo.Dept;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;

import java.util.List;

@ExtendWith(SpringExtension.class) //使用Junit5测试工具
@WebAppConfiguration//启动web运行环境
@SpringBootTest(classes = App.class) //配置程序启动类
public class Test1 {
    @Autowired
    private Dept dept;
    @Autowired
    private List<String> books;

    @Test
    public void testConfig(){
        System.out.println(this.dept);
        System.out.println(this.books);
    }
}

结果输出:

2021-12-11 14:54:21.706  INFO 41432 --- [           main] Test1                                    : Started Test1 in 4.517 seconds (JVM running for 5.652)

Dept(deptNo=124, deptName=pansd, loc=jinan)

[p1, p2, p3, p4]

Springboot启动核心类

注解集合:
org.springframework.context.annotation.Configuration @interface
org.springframework.boot.autoconfigure.condition.ConditionalOnClass  @interface
org.springframework.boot.context.properties.EnableConfigurationProperties  @interface
org.springframework.context.annotation.Import  @interface
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean @interface
org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate @interface

不同的模块定义一些自动的启动类,而后这些启动类需要结合application.yml配置生效,实现最终的Bean注册,注册之后在容器里边便可以直接使用了。

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@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 {

Target注解决定MyAnnotation注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分
注解@Retention可以用来修饰注解,是注解的注解,称为元注解。
1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Documented 来进行标注,如果使用@Documented标注了,在生成javadoc的时候就会把@Documented注解给显示出来。==@API的样式,没有实际作用。
@Inherited //允许被继承
@SpringBootConfiguration:springboot启动代理模式配置,cglib而不是动态代理;
@EnableAutoConfiguration:启用springboot的自动装配。

@EnableAutoConfiguration

@Target({ElementType.TYPE})
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	@Inherited
	@AutoConfigurationPackage							// 主要是做扫描包配置的。里边有String[] basePackages() default {};Class<?>[] basePackageClasses() default {};
	@Import({AutoConfigurationImportSelector.class}) 	//
	public @interface EnableAutoConfiguration {
		//定义了一个环境所需要的属性名称;
		String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

		Class<?>[] exclude() default {};

		String[] excludeName() default {};
	}

AutoConfigurationPackage

@Target({ElementType.TYPE})
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	@Inherited
	@Import({Registrar.class})		//找到所需要的Bean,添加到扫描包中;
	public @interface AutoConfigurationPackage {
		String[] basePackages() default {};

		Class<?>[] basePackageClasses() default {};
	}

点击Register.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));
        }
    }

点击Register.class会发现是一个静态内部类,点击方法的register,回看到真正方法  

 public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
            beanDefinition.addBasePackages(packageNames);
        } else {
            registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames));
        }

    }

 BEAN对应的类型应该是:private static final String BEAN = AutoConfigurationPackages.class.getName();

public abstract class AutoConfigurationPackages {
    private static final Log logger = LogFactory.getLog(AutoConfigurationPackages.class);
    private static final String BEAN = AutoConfigurationPackages.class.getName();

 通过以上的分析,可以发现自动扫描的时候可以通过一系列的selector和registor。

	

看下@Import({AutoConfigurationImportSelector.class})
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
里边实现了多个接口,重点看:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
}


protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
			//通过annotation获取对应属性的内容
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
			//根据扫描得到的属性来进行配置类的加载
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
			//剔除掉所有重复的配置类信息
            configurations = this.removeDuplicates(configurations);
			//获取所有排除的配置
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
			//检查所有排除配置
            this.checkExcludedClasses(configurations, exclusions);
			//剔除掉所有排除配置
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
			//返回了具体的配置项
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }


    }
	

 最终所有的扫描配置和排除配置都是通过AutoConfigurationImportSelector来实现配置管理的。
那么也就是相当于开发者只需要在启动类中配置了一个@SpringBootApplication注解之后,会由具体的Selector来负责排除以及扫描包的定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值