1、概述
自动配置的功能是其简化运用的关键技术,思想就是约定大于配置,意思就是一个工程约定必须要有事务功能,要有aop功能,要有mvc功能等,所以springboot在创建工程的时候自动把这些功能所需的类实例化并加入到spring容器了,这个就是约定大于配置,约定了必须要有这些功能。
2、springboot中的SPI机制
java原生的SPI,是一种服务发现机制( Service Provider Interface)。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。 这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI。
2.1、JDK中的SPI机制
public interface Log {
boolean support(String type);
void info();
}
在resources/META-INF/services目录创建文件,文件名称必须跟接口的完整限定名相同。这个接口文件中配置了该接口的所有实现类的完整限定名。然后jdk api 加载配置文件
//jdk api 加载配置文件配置实例
ServiceLoader<Log> all = ServiceLoader.load(Log.class);
2.2、springboot中的SPI机制
具体流程和上面差不多,在工程的resources下面创建META-INF文件夹,在文件夹下创建spring.factories文件,在文件配置内容如下:
com.ss.yc.spi.Log=\
com.ss.yc.spi.Log4j,\
com.ss.yc.spi.Logback,\
com.ss.yc.spi.Slf4j
配置的key就是接口完整限定名,value就是接口的各个实现类,用","号隔开。
@Test
public void test() {
List<String> strings = SpringFactoriesLoader
.loadFactoryNames(Log.class, ClassUtils.getDefaultClassLoader());
for (String string : strings) { System.out.println(string);
}
}
@Test
public void test1() {
List<String> strings = SpringFactoriesLoader
.loadFactories(Log.class, ClassUtils.getDefaultClassLoader());
for (String string : strings) { System.out.println(string);
}
}
2.3、我们以SpringFactoriesLoader.loadFactoryNames(Log.class, ClassUtils.getDefaultClassLoader());方法调用为例分析其源码
可以看到springboot spi是加载了整个工程的jar包和自己工程定义的spring.factories文件的。
其核心代码
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
public static Properties loadProperties(Resource resource)
throws IOException {
Properties props = new Properties();
//核心代码,把文件包装成properties对象
fillProperties(props, resource);
return props;
}
springboot中的SPI其实就是加载整个工程里面的spring.factories文件,然后把文件里面的内容建立一个key和value的映射关系,只是这个映射关系是一个类型和list的映射关系。
3、@EnableAutoConfiguration
@EnableAutoConfiguration注解是springboot自动配置的核心注解,就是因为有这个注解存在就会把例如事务,缓存,aop,mvc等功能自动导入到springboot工程中,Spring框架提供的各种名字为@Enable开头的Annotation定义,比如@EnableScheduling、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义。
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
@Import了一个类,这个AutoConfigurationImportSelector自动配置类
3.1、DeferredImportSelector
DeferredImportSelector该接口是ImportSelector接口的一个子接口,那么它是如何使用的呢?
//
public class DeferredImportSelectorDemo implements DeferredImportSelector{
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata)
{
System.out.println("=====DeferredImportSelectorDemo.selectImports");
return newString[]{DeferredBean.class.getName()};
}
//要返回一个实现了Group接口的类
@Override
public Class<?extendsGroup> getImportGroup(){
return DeferredImportSelectorGroupDemo.class;
}
private static class DeferredImportSelectorGroupDemo implements Group{
List<Entry> list=new ArrayList<>();
//收集需要实例化的类
@Override
public void process(AnnotationMetadata metadata,DeferredImportSelector selector){
System.out.println("=====DeferredImportSelectorGroupDemo.process");
String[] strings=selector.selectImports(metadata);
for(String string:strings){
list.add(newEntry(metadata,string));
}
}
//把收集到的类返回给spring容器
@Override
public Iterable<Entry> selectImports(){
System.out.println("=====DeferredImportSelectorGroupDemo.selectImports");
return list;
}
}
}
//要实例的bean
public class DeferredBean{}
该类必须是@Import导入进来
@Component
//Import虽然是实例化一个类,Import进来的类可以实现一些接口@Import({DeferredImportSelectorDemo.class})
public class ImportBean{}
这样就实现了一个类的实例化。
3.2、EnableAutoConfigurationImportSelector
而AutoConfigurationImportSelector类,这个类就是@EnableAutoConfiguration注解中@Import进来的类,可以看到该类正是实现了DeferredImportSelector接口的。该类其实就是收集spring.factories文件中以@EnableAutoConfiguration类型为key的所有的类,然后把这些类交给spring去实例化,而这些类就是我们说的aop、事务、缓存、mvc等功能的支持类,这就是自动配置的加载原理。
4、@Configuration
就是JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。
其中@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。
5、@ComponentScan
其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
DemoService.java
@Data
public class DemoService{
private String str1;
private String str2;
}
//指定项目在属性文件中配置的前缀为str,即可以在属性文件中通过 str.str1=springboot,就可以改变属性类字段 str1 的值了
@SuppressWarnings("ConfigurationProperties")
@ConfigurationProperties(prefix = "str")
public class DemoProperties {
public static final String DEFAULT_STR1 = "I know, you need me";
public static final String DEFAULT_STR2 = "but I also need you";
private String str1 = DEFAULT_STR1;
private String str2 = DEFAULT_STR2;
}
// 定义 java 配置类
@Configuration
//引入DemoService
@ConditionalOnClass({DemoService.class})
// 将 application.properties 的相关的属性字段与该类一一对应,并生成 Bean
@EnableConfigurationProperties(DemoProperties.class)
public class DemoAutoConfiguration {
// 注入属性类
@Autowired
private DemoProperties demoProperties;
@Bean
// 当容器没有这个 Bean 的时候才创建这个 Bean
@ConditionalOnMissingBean(DemoService.class)
public DemoService helloworldService() {
DemoService demoService= new DemoService();
demoService.setStr1(demoProperties.getStr1());
demoService.setStr2(demoProperties.getStr2());
return demoService;
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.demo.springboot.config.DemoAutoConfiguration
<!--引入自定义Starter-->
<dependency>
<groupId>com.lhf.springboot</groupId>
<artifactId>spring-boot-starter-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
#配置自定义的属性信息
str.str1=为什么我的眼里常含泪水
str.str2=那是因为我对你爱的深沉
@RestController
public class StringController {
@Autowired
private DemoService demoService; //引入自定义Starter中的DemoService
@RequestMapping("/")
public String addString(){
return demoService.getStr1()+ demoService.getStr2();
}
}