文章目录
SpringBoot是什么
SpringBoot是一个脚手架
Spring 纯注解方式去定义bean
package cn.edu.guet.config;
import org.springframework.context.annotation.*;
/**
* @author pangjian
* @ClassName config
* @Description MyConfig就是一个配置类,你可以抽象理解它为xml定义bean方式里面的配置文件
* @date 2021/8/30 8:50
*/
@Configuration
public class MyConfig {
// 这个相当于xml定义bean方式里面的<bean id="" class=""/>
// 最后被容器扫描并解析变成一个bean定义对象(BeanDefinition对象)
// 如果是xml文件,最后也是读取<bean/> 标签去解析变成一个bean定义对象
@Bean
public MyBean myBean(){
return new MyBean();
}
}
- 比对这启动IOC容器的方式,就能知道纯注解的方式去定义bean了
public class App {
public static void main(String[] args) {
// ApplicationContext context = new ClassPathXmlApplicationContext("配置文件.xml");
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
MyBean myBean = context.getBean("myBean", MyBean.class);
System.out.println(myBean.toString());
}
}
SpringBoot自动装配
- 怎么叫自动
通过批量的机制去读取我们meta-inf/spring.factoryies下面的约定配置
- 装配什么
装配bean定义(beanDefinition),对象装配到IOC容器,并且对象也要装配到对应的变量
// 没有去通过@Bean配置bean了,也没有加@Component,为什么还能加载出来
@Configuration
@Import({MyBeanPostProcess.class, Boy.class, MyBean.class})
public class MyConfig {
}
public class App {
// 就是因为配置类用了@Import注解,把我们需要的组件进行了导入
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
MyBean myBean = context.getBean("myBean", MyBean.class);
System.out.println(myBean.toString());
}
}
- @Import
@lmport也是为我们容器中导入bean定义,Springboot自动装配要理解该注解。
SpringBoot大概实现就是使用@Import注解导入一个选择器,去扫描meta-info/spring.factory中org.springframework.boot.autoconfigure.EnableAutoConfiguration键对应的值(各种框架的配置类全限定名,如redis等),然后通过该值创建配置类(特殊的bean,就是上面纯注解方法定义bean的配置类),然后第三方框架的bean就会交给SpringIOC容器管理。
源码解读
- 从SpringBoot启动类去进行解读
// 我们先看看@SpringBootApplication这个复合注解
@SpringBootApplication
public class PethouseApplication {
public static void main(String[] args) {
SpringApplication.run(PethouseApplication.class, args);
}
}
- @SpringBootApplication
// 重点解读下面三个注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// 略
}
@SpringBootConfiguration
就是为了可以让我们在启动类里面纯注解配置Bean
@SpringBootApplication
public class PethouseApplication {
public static void main(String[] args) {
SpringApplication.run(PethouseApplication.class, args);
}
@Bean
public Admin getAdmin() {
return new Admin();
}
}
@ComponentScan
就是为了扫描启动类所在的包和子包下面的注解类,就是我们写的@Controller、@Service、@Component等修饰的类,扫描然后将beanDefinition装配到Ioc容器中
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
它在IOC容器导入了一个AutoConfigurationImportSelector.class的类的类定义,看看这个类有什么用,它是一个选择器,可以通过selectImports()批量导入全类名字符串放到一个数组里面。通过全类名就可以导入该组件。SpringBoot就是通过该方法,把spring.factory里面的109约定好的配置读取出来。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 获取配置的入口方法 getAutoConfigurationEntry()
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
}
- getAutoConfigurationEntry()
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取后选的配置,就是可以要或不要
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
- getCandidateConfigurations()
获取后选配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
return configurations;
}
- loadFactoryNames()
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 获取类全限定名org.springframework.boot.autoconfigure.EnableAutoConfiguration
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
- loadSpringFactories()
// Map<类全限定名org.springframework.boot.autoconfigure.EnableAutoConfiguration,109个约定配置的全限定名数组>
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 从缓存中读取配置
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
// 略
}
}
第一次打开spring.factories的时候(第一次就是容器初始化,监听器构建时候,是比bean装配要早)就会全部加载里面的全部配置(包括了Rabbitmq的等)到缓存中了,第二次我们直接拿就可以了,我们再回到获取后选配置方法。
- getAutoConfigurationEntry()
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取后选的配置,就是可以要或不要
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 进行去重,防止你自己写了一个同名的启动器
configurations = removeDuplicates(configurations);
// 从109个约定好的配置中排除你不需要的
// 你可以在@SpringBootApplication用它的excludeName属性去排除你不需要的配置,比如redis等
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 根据你pom.xml文件的依赖进行过滤,比如你pom没有rabbitmq的依赖,它就会进行一个自动排除。
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
由于spring-data jpa和mybatis是竞争关系,所以约定配置文件中并没有mybatis的启动器,就需要我们自动导入,所有一般SpringBoot整合mybatis时候导入一个启动器就完成整合了。