0
点赞
收藏
分享

微信扫一扫

深入了解SpringBoot的自动装配


1、自动装配简介:

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

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

 

深入了解SpringBoot的自动装配_java

 一旦在项目之中引入了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”文件。

深入了解SpringBoot的自动装配_spring boot_02

{
"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、此时,子项目中会出现配置的提示:

深入了解SpringBoot的自动装配_java_03

 自定义starter组件

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

深入了解SpringBoot的自动装配_java_04

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来负责排除以及扫描包的定义。

举报

相关推荐

0 条评论