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