一.自动配置原理
探究自动配置原理,就是探究spring是如何在运行时将要依赖JAR包提供的配置类和bean对象注入到IOC容器当中。我们当前准备一个maven项目itheima-utils,这里面定义了bean对象以及配置类,用来模拟第三方提供的依赖,首先进行导入。如果我们要使用这个第三方依赖,就要首先将其引入进来。
<dependency>
<groupId>com.example</groupId>
<artifactId>itheima-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
我们在itheima-utils中可以发现,有一个配置类HeaderConfig,上面加上了一个注解@Configuration,表明当前类是一个配置类,里面有两个配置方法HeaderParser和HeaderGenerator,用来返回HeaderParser和HeaderGenerator的实例化对象,上面加上了@Bean注解,表明将这两个类作为IOC容器当中的bean对象。
package com.example;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HeaderConfig {
@Bean
public HeaderParser headerParser(){
return new HeaderParser();
}
@Bean
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}
package com.example;
public class HeaderParser {
public void parse(){
System.out.println("HeaderParser ... parse ...");
}
}
package com.example;
public class HeaderGenerator {
public void generate(){
System.out.println("HeaderGenerator ... generate ...");
}
}
我们还定义了一个普通类TokenParser,在该类上面加上了一个注解@Component。
package com.example;
import org.springframework.stereotype.Component;
@Component
public class TokenParser {
public void parse(){
System.out.println("TokenParser ... parse ...");
}
}
然后我们在springboot-web-config2中的pom文件中导入itheima-utils的依赖,然后在其测试类中编写测试代码,查看是否能够将IOC容器当中的bean对象引入进来。
package com.gjw.springbootwebconfig2;
import com.example.HeaderGenerator;
import com.example.HeaderParser;
import com.example.TokenParser;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
@SpringBootTest
public class AutoConfigTests {
@Autowired
private ApplicationContext applicationContext;
// 测试第三方依赖下面的bean对象
@Test
void tokenParserTest() {
System.out.println(applicationContext.getBean(TokenParser.class));
}
@Test
void headerParserTest() {
System.out.println(applicationContext.getBean(HeaderParser.class));
}
@Test
void headerGeneratorTest() {
System.out.println(applicationContext.getBean(HeaderGenerator.class));
}
}
通过applicationContext这个IOC容器对象将TokenParser等bean对象引入进来,我们发现报错了,没有找到TokenParser这个bean对象
HeaderParser这个bean对象也没有找到。
那么为什么没有找到呢?
我们在类上加上了@Compoment注解来声明bean对象,这个注解就一定会生效么?并不是,这个注解还要被spring组件扫描到才行,而我们前面提到,在springboot项目当中,@SpringBootApplication具有包扫描的作用,但是其扫描范围仅仅是当前启动类所在包及其子包,也就是说根据当前的目录结构,他只会扫描com.gjw这个包下的类中定义的bean对象以及com.gjw这个包下子包中的类中定义的bean对象。
因此是扫描不到itheima-utils这个第三方依赖中所定义的bean对象的,所以找不到该bean对象。那么如何设置其扫描范围,让其扫描得到呢?
二.@ComponentScan组件扫描
我们可以在当前要运行的springboot项目的启动类上加上一个注解@ComponentScan来配置要扫描的范围。原来我们的扫描范围只是com.gjw,这是默认的。现在指定了要扫描com.example这个包,那么原来默认的包就要显式的指定出来。
在@ComponentScan这个注解当中我们可以通过value属性或者basePackages这个属性来指定我们要扫描哪些包?而这个属性返回值是一个数组,我们就在数组当中指定我们要扫描到的包是哪些。
package com.gjw;
import ch.qos.logback.core.joran.event.SaxEvent;
import com.example.EnableHeaderConfig;
import com.example.HeaderConfig;
import com.example.MyImportSelector;
import com.example.TokenParser;
import org.dom4j.io.SAXReader;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
@ComponentScan({"com.gjw","com.example"}) // 方式一:采用@ComponentScan指定bean对象的扫描范围
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
这样再次运行测试方法,发现已经成功获取到了IOC容器当中的bean对象。但是如果要扫描的包特别多,那么使用起来将非常繁琐,进而影响性能。
三.@Import导入
使用@Import导入主要的形式有三种:
1.导入普通类
2.导入配置类
3.导入ImportSelector接口实现类
我们依次来看
首先我们导入普通类,当前com.itheima这个包下有一个普通类TokenParser,上面加上了@Component注解,那么可以使用@Import注解将其导入。在@Import注解解释中如下。 
在@Import注解当中我们可以使用value属性导入一个常规类。也可以导入一个被@Configuration标识的配置类,也可以导入ImportSelector这个接口的实现类,还可以导入ImportBeanDefinitionRegistrar这个接口的实现类。
1.导入普通类
首先导入普通类TokenParser
package com.gjw;
import ch.qos.logback.core.joran.event.SaxEvent;
import com.example.EnableHeaderConfig;
import com.example.HeaderConfig;
import com.example.MyImportSelector;
import com.example.TokenParser;
import org.dom4j.io.SAXReader;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
// @ComponentScan({"com.gjw","com.example"}) 方式一:采用@ComponentScan指定bean对象的扫描范围
/**
* 方式二:可以使用@Import注解
* 可以用于:1.普通类
* 2.配置类
* 3.ImportSelector接口的实现类
* 4.@EnableXXXX注解,封装@Import注解
*/
@Import({TokenParser.class}) // 普通类
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
发现TokenParser这个类的bean对象已经存在。
2.导入配置类
然后我们导入配置类,
package com.gjw;
import ch.qos.logback.core.joran.event.SaxEvent;
import com.example.EnableHeaderConfig;
import com.example.HeaderConfig;
import com.example.MyImportSelector;
import com.example.TokenParser;
import org.dom4j.io.SAXReader;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
// @ComponentScan({"com.gjw","com.example"}) 方式一:采用@ComponentScan指定bean对象的扫描范围
/**
* 方式二:可以使用@Import注解
* 可以用于:1.普通类
* 2.配置类
* 3.ImportSelector接口的实现类
* 4.@EnableXXXX注解,封装@Import注解
*/
@Import({TokenParser.class}) // 普通类
@Import({HeaderConfig.class}) // 配置类
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
打开测试类,在测试类当中运行测试方法,发现HeaderParser和HeaderGenerator的bean对象已经存在。
3.导入ImportSelector接口实现类
我们查看ImportSelector这个接口的源码。这个接口中有一个接口方法selectImports,返回值是一个String类型的数组,封装的是类名,封装的就是我们要将哪些类交给IOC容器管理,可以将这些类的全类名封装在数组中返回即可。
首先我们定义ImportSelector这个接口的实现类,
package com.example;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.example.HeaderConfig"};
}
}
既然selectImports这个方法中要的是全类名,那么我们可以将这些类定义到一个文件当中,要加载哪些类直接添加进去读取即可。读取之后封装到这个String数组中就行。这里我们指定将HeaderConfig这个类交给IOC容器管理。
然后我们使用@Import这个注解将其导入即可。
package com.gjw;
import ch.qos.logback.core.joran.event.SaxEvent;
import com.example.EnableHeaderConfig;
import com.example.HeaderConfig;
import com.example.MyImportSelector;
import com.example.TokenParser;
import org.dom4j.io.SAXReader;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
// @ComponentScan({"com.gjw","com.example"}) 方式一:采用@ComponentScan指定bean对象的扫描范围
/**
* 方式二:可以使用@Import注解
* 可以用于:1.普通类
* 2.配置类
* 3.ImportSelector接口的实现类
* 4.@EnableXXXX注解,封装@Import注解
*/
@Import({TokenParser.class}) // 普通类
@Import({HeaderConfig.class}) // 配置类
@Import(MyImportSelector.class) // MyImportSelector里面定义了要生成的bean对象
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
4.@EnableXxxx注解,封装@Import注解
但是如果基于上面的方式,以后我们在引入一个第三方依赖所提供的bean和配置类,每一次都要知道我们要引入哪些bean和配置类。但是我们知道吗?不知道,只有第三方依赖最清楚。常见的方案是第三方依赖会给我们提供一个注解,这个注解一般都是@Enable打头,注解中封装的就是@Import注解。在@Import后面再指定要导入哪些bean和配置类。
如itheima-utils依赖中就提供了@EnableHeaderConfig这个注解,
package com.example;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableHeaderConfig {
}
上面加了注解@Import,@Import中指定我们要导入哪些配置类。而在MyImportSelector.class这个类中就制定了我们要导入哪些配置类。现在我们要指定导入哪些bean,直接在启动类上加上@EnableHeaderConfig这个注解即可。
package com.gjw;
import ch.qos.logback.core.joran.event.SaxEvent;
import com.example.EnableHeaderConfig;
import com.example.HeaderConfig;
import com.example.MyImportSelector;
import com.example.TokenParser;
import org.dom4j.io.SAXReader;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
// @ComponentScan({"com.gjw","com.example"}) 方式一:采用@ComponentScan指定bean对象的扫描范围
/**
* 方式二:可以使用@Import注解
* 可以用于:1.普通类
* 2.配置类
* 3.ImportSelector接口的实现类
* 4.@EnableXXXX注解,封装@Import注解
*/
//@Import({TokenParser.class}) // 普通类
//@Import({HeaderConfig.class}) // 配置类
//@Import(MyImportSelector.class) // MyImportSelector里面定义了要生成的bean对象
@EnableHeaderConfig // @EnableHeaderConfig继承封装了MyImportSelector
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}