04-详解SpringBoot自动装配的原理,依赖属性配置的实现,源码分析
自动装配原理
依赖属性配置
提供Bean用来封装配置文件中对应属性的值
@Data
public class Cat {private String name;private Integer age;
}
@Data
public class Mouse {private String name;private Integer age;
}
cartoon:cat:name: "图多盖洛"age: 5mouse:name: "泰菲"age: 1
读取yml
文件中的数据,将业务功能Bean运行需要的数据抽取出来封装到CartoonProperties
对象中
- 缺点: 要想封装yml文件中的数据这个Bean必须由Spring管控,但其实如果我们没有导入业务功能Bean就没必要读取yml文件中的数据
Component
@ConfigurationProperties(prefix = "cartoon")
@Data // 需要给Cat和Mouse提供对应的getter和setter方法,才能把yml文件中的数据注入到Cat和Mouse对象中
public class CartoonProperties { private Cat cat;private Mouse mouse;
}
在业务Bean中根据需要读取CartoonProperties
对象中的数据,@EnableConfigurationProperties
开启属性类的配置绑定功能并强制把其注册到容器中
- 如果开发者在yml文件中配置了对应的属性的值就使用配置的值,如果没有配置就使用默认值
@Data
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse{private Cat cat;private Mouse mouse;private CartoonProperties cartoonProperties;// 自动注入,如果有参的构造方法只有一个@Autowired注解可以省略public CartoonCatAndMouse(CartoonProperties cartoonProperties){this.cartoonProperties = cartoonProperties;cat = new Cat();// 如果开发者在yml文件中配置了对应的属性就使用配置的值,如果没有配置就使用默认值cat.setName(cartoonProperties.getCat()!=null && StringUtils.hasText(cartoonProperties.getCat().getName()) ? cartoonProperties.getCat().getName() : "tom");cat.setAge(cartoonProperties.getCat()!=null && cartoonProperties.getCat().getAge()!=null ? cartoonProperties.getCat().getAge() : 3);mouse = new Mouse();mouse.setName(cartoonProperties.getMouse()!=null && StringUtils.hasText(cartoonProperties.getMouse().getName()) ? cartoonProperties.getMouse().getName() : "jerry");mouse.setAge(cartoonProperties.getMouse()!=null && cartoonProperties.getMouse().getAge()!=null ? cartoonProperties.getMouse().getAge() : 4);}public void play(){System.out.println(cat.getAge()+"岁的"+cat.getName()+"和"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");}
}
使用@Import
方式导入业务Bean,避免业务Bean强制加载,根据需要导入,降低Spring管控Bean的强度
- 缺点: 自动配置类我们也没必要强制加载成容器的Bean,应当是满足某种条件时才加载
@SpringBootApplication
@Import(CartoonCatAndMouse.class)
public class App {public static void main(String[] args) {ConfigurableApplicationContext ctx = SpringApplication.run(App.class);CartoonCatAndMouse bean = ctx.getBean(CartoonCatAndMouse.class);bean.play();System.out.println(ctx.getBean(Cat.class));}
}
自动配置源码分析
@SpringBootApplication底层相关的注解
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class, args);}
}
SpringBootApplication底层注解 | 注解的底层注解 |
---|---|
@SpringBootConfiguration | @Configuration,说明Spring Boot程序的启动类也是一个配置类 |
@EnableAutoConfiguration | @AutoConfigurationPackage --> @ Import(AutoConfigurationPackages.Registrar.class) @Import(AutoConfigurationImportsSelector.class) |
@ComponentScan | 指定扫描过滤的规则FilterType.CUSTOM和TypeExcludeFilter.class等, 默认扫描主程序所在包及其子包下的所有组件 |
@Import(AutoConfigurationPackages.Registrar.class)注解
: 设置启动类的包作为基础扫描包, 后续将该包及其子包下注解标识类注册成Bean添加到容器中
AutoConfigurationPackages.Registrar
是AutoConfigurationPackages(抽象类)
的静态内部类
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {// 记录所有要扫描的包,启动类所在的包及其子包String[] basePackages() default {};Class<?>[] basePackageClasses() default {};static classimplementsImportBeanDefinitionRegistrar,DeterminableImportsRegistrar{@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// metadata是元数据,可以获取启动类上的所有注解信息// new PackageImports(metadata).getPackageNames()获取到的就是启动类所在的包register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));}@Overridepublic Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImports(metadata));}}
}public static void register(BeanDefinitionRegistry registry, String... packageNames){// 判断容器中是否加载过AutoConfigurationPackagesif (registry.containsBeanDefinition(BEAN)){BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);beanDefinition.addBasePackages(packageNames);}else{// 注册一个叫com...AutoConfigurationPackages的Bean,将要扫描的包封装到BasePackagesBeanDefinition对象中registry.registerBeanDefinition(BEAN,new BasePackagesBeanDefinition(packageNames));}
}static final class BasePackagesBeanDefinition extends GenericBeanDefinition {private final Set<String> basePackages = new LinkedHashSet<>();BasePackagesBeanDefinition(String... basePackages) {setBeanClass(BasePackages.class);setRole(BeanDefinition.ROLE INFRASTRUCTURE);addBasePackages(basePackages);}
}
@Import(AutoConfigurationImportSelector.class)
: 指定工程启动时需要向容器中添加的所有自动配置类XxxAutoConfiguration
- 在
spring-boot-autoconfigure-xxx.jar
包里面的META-INF/spring.factories
目录下存放了工程启动时需要加载的所有类(含自动配置类)
// Spring中的Bean只要实现了XxxAware相关的接口并实现接口的setXxx方法,就可以在当前Bean中使用对应的对象
// Ordered表示加载的顺序,因为有些Bean加载的时候是要依赖其他Bean的,每个Bean都有对应的加载顺序
// DeferredImportSelector表示推迟的导入选择器
public class AutoConfigurationImportSelector implements DeferredImportSelector,BeanClassLoaderAware,ResourceLoaderAware,BeanFactoryAware,EnvironmentAware,Ordered { private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}// 使用ApplicationContext接口中的相关方法String[] beans = applicationContext.getBeanDefinitionNames();for (String bean : beans) {System.out.println(bean);}
}@Override
// selectImports方法的返回值是一个String类型数组,数组的元素就是我们要批量导入的组件
public String[] selectImports (AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;} AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata){// 判断元注解也就是我们的启动类是否是可用的if(!isEnabled(annotationMetadata)) {return EMPTY_ENTRY:} // 获取启动类上的所有注解及其属性,@EnableAutoConfiguration注解有exclude,excludeName两个属性可以按照Class对象和全类名排除不需要加载的类 AnnotationAttributes attributes = getAttributes(annotationMetadata);// 获取候选的配置,读取META-INF/spring.factories中的数据,将所有自动配置类的全类名添加到一个List集合中并返回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);
}// 调用loadFactoryNames方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesloader.loadFactoryNames(getSpringFactoriesloaderFactoryClass(),getBeanClassLoader());Assert.notEmpty(configurations, message: "No auto configuration classes found in META-INF/spring.factories, If you " + "are using a custom packaging, make sure that file is correct.");return configurations;
}// 调用loadSpringFactories方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable Classloader classloader){// 获取类加载器ClassLoader classLoaderToUse = classLoader;if (classLoaderToUse == nul1) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}// 获取字符串String factoryTypeName = factoryType.getName();return loadSpringFactories(classloaderToUse).getorDefault(factoryTypeName, Collections,emptylist());
}// 最终调用的方法
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) (
Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}result = new HashMap<>();
try {// 通过类加载器加载外部资源,spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面的META-INF/spring.factories目录下的自动配置类Enumeration<URL> urls = classLoader,getResources(FACTORIES_ESOURCE_OCATION);while (urls.hasMoreElements()){URL url= urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);}}
测试XxxAutoConfiguration
AopAutoConfiguration
自动配置类的生效条件
@Configuration(proxyBeanMethods = false)
// 判断是否存在一个配置文件有spring.aop的前缀属性,默认是存在的
@ConditionalOnProperty(prefix = "spring.aop",name = "auto",havingValue = "true",matchIfMissing = true
)
public class AopAutoConfiguration {public AopAutoConfiguration() {}...
}
RedisAutoConfiguration
自动配置类的生效条件及其绑定的属性配置类 RedisProperties
@Configuration(proxyBeanMethods = false)
// 要加载RedisAutoConfiguration必须有RedisOperations,这个类在spring-boot-starter-data-redis中
@ConditionaionClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({LettuceConnectionConfiguration,class, JedisConnectionConfiguration,class})
public class RedisAutoConfiguration {@Bean@ConditionalOnMissingBean(name = "redisTemplate")@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate<0bject, 0bject> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);return template}@Bean@ConditionalOnMissingBean@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}
}
RedisProperties
封装了yml文件中的spring.redis
属性的值
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {private int database = 0;private String url;private String host = "localhost";// .....
}
自动配置流程
第一步: 将开发过程使用的常用技术列表整理成一个技术集A
即所有的自动配置类XxAutoConfiguration
,工程启动时默认会全部加载到内存中
- 这些自动配置类虽然会全部加载到内存中,但不会全部生效, 只有满足实际条件的自动配置类及其内部的才会注册成容器中的Bean
- 每个自动配置类都对应一个属性配置类,用来封装配置文件中指定前缀的属性值,自动配置类需要用时会从
xxxProperties
对象中获取
第二步: 将这些常用技术需要设置的参数整理成一个设置集B
即所有的属性配置类xxxxProperties
用来封装yml
文件中对应属性的值
第三步: 开放设置集B
的配置覆盖接口,若开发者在yml
文件中配置了某属性的值,对应属性配置对象中的对应属性就可以获取到值,如果没有配置对应属性为默认值
- SpringBoot默认会在底层配好所有的组件, 但是如果用户自己配置了以用户的优先,如直接通过定义@Bean替换底层的组件或者去修改这个组件获取的配置文件值
第四步: 生效的自动配置类从对应属性配置对象中获取值然后为要创建的组件
赋值(约定大于配置),若获取的属性值是null就使用默认值,如果不是就使用获取到的值
第五步: 加载用户自定义的Bean和导入的其他坐标,检测每个自动配置类的加载条件是否满足, 最后初始化SpringBoot的基础环境
变更自动配置
@SpringBootApplication(excludeName = "org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration")// 排除加载的自动配置类
//@Import(CartoonCatAndMouse.class) // 根据条件装配这个自动配置类
public class App {public static void main(String[] args) {ConfigurableApplicationContext ctx = SpringApplication.run(App.class);CartoonCatAndMouse bean = ctx.getBean(CartoonCatAndMouse.class);bean.play();System.out.println(ctx.getBean(Cat.class));}
}
SpringBoot中自带的自动配置类有130个,后面的技术若想要实现自动配置功能需要手动在t工程中的resources/META-INF
目录下添加spring.factories
文件
- SpringBoot默认会扫描我们当前工程里面所有的
META-INF/factories
文件(每个jar包都是一个工程)
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.itheima.bean.CartoonCatAndMouse
在配置文件中排除加载的自动配置类
spring:autoconfigure:exclude:- org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration