Spring boot

1.自动配置原理

spring boot 程序运行入口

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
     SpringApplication.run(MainApplication.class, args);
     }
}     

spring boot是通过@SpringBootApplication实现自动配置。

@SpringBootApplication

@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 {...}

核心注解三个:
1、@ComponentScan :标识扫描包的位置
2、@SpringBootConfiguration:只是一个配置类。
3、@EnableAutoConfiguration自动装配的核心注解

@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}

@AutoConfigurationPackage

将我们包下自己定义的注解类注入到容器中

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {...}

核心为@Import(AutoConfigurationPackages.Registrar.class)
该注解原理为@Import注入组件的第三种方式。利用Registrar注入一批组件。

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}
		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}
	}

AnnotationMetadata:表示当前注解所在类的所有注解;

getPackageNames():可以获取注解所在类位于的包名,此处是我们程序启动类(MainApplication)所在包名。这也解释为什么我们默认的包路径是程序启动类所在的包,所以我们要把带有注解的类都写在该包下。;

register(registry,new PackageImports...):把(注解所在)类所在的包下的所有组件(所有包含注解的类:@Contorller、@Component等)注册(注入容器)。@Bean只是组件注入的一个方式,所有注解注释的类都是组件。

@Import(AutoConfigurationImportSelector.class)

选择性的将spring boot自带的配置类注入到容器中
该注解原理为@Import注入组件的第二种方式

AutoConfigurationImportSelector类间接实现了ImportSelect接口。核心代码:

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = 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()方法获取所有需要导入到容器的组件(配置类),这里的配置类来源于spring boot的jar包(后面会介绍)。

通过断点查看:这里的133个组件全部都是配置类。
image-20220717163304368
那么是如何获取到这些配置类的呢:
getCandidateConfigurations
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

利用工厂加载 loadSpringFactories:得到所有的组件。
loadSpringFactories核心代码如下:

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 {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

核心代码为try部分。FACTORIES_RESOURCE_LOCATION值为META-INF

​ 从META-INF/spring.factories位置来加载一个文件。
​ 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件。
​ spring-boot-autoconfigure-2.6.4.RELEASE.jar包里面也有META-INF/spring.factories。
在这里插入图片描述
下面就是spring boot 全部需要注入的配置类xxxxAutoConfiguration(文件里面写死的,spring boot一启动就会注入容器)。
在这里插入图片描述

按需配置

虽然,我们上面的所有配置类在自动配置启动的时候默认全部加载。xxxxAutoConfiguration。
但是,由于条件装配注解@Conditional的作用,最终会按照条件装配规则按需配置,即基本上不会全部装配。

RabbitAutoConfiguration配置类为例,只有容器中存在RabbitTemplate, Channel两个组件时才会装配。由于我们没有导入这两个类所在的包(依赖),所以不会装配。
在这里插入图片描述
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先。

@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {....}

上面是源码的一部分,@ConditionalOnMissingBean标识只有容器中没有时才会配置,所以,如果我们自己在配置类中@Bean注入了CharacterEncodingFilter类,spring boot就不会帮我们再次配置。

总结

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration;

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件;

  • 只要容器中有这些组件,相当于这些功能就有了;

  • 定制化配置(两种方法);

    • 用户直接在自己的配置类中@Bean替换底层的组件;

    • 用户去看这个组件是获取的配置文件什么值就去修改(常用,直接去自己的application.properties文件中设置相关值)。

xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties

下面是@EnableAutoConfiguration注解的一个流程图。
在这里插入图片描述

注入bean注解

组件:即通过注解修饰的类均为组件。

@Configuration❤

表示这是一个配置类,配置类里面使用@Bean标识在方法上给容器注入组件,容器内的组件默认也是单实例的。

@Configuration
public class MyConfig {
    // 组件默认名为方法名
    @Bean
    public User user01(){
        return new User("komorebi");
    }
    // 可以通过Bean修改组件名
    @Bean("tom")
    public Pet pet01(){
        return new Pet("tomcat");
    }
}

验证容器内的组件默认也是单实例的。以下代码输出为true

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        // 1、返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
        // 2、调用组件,如果有直接拿,没有创建新对象
        User user01 = run.getBean("user01", User.class);
        User user02 = run.getBean("user01", User.class);
        System.out.println("两次调用同一组件,是否为同一对象:"+(user01 == user02));
    }
}

proxyBeanMethods属性

spring boot 2和1在@Configuration是有很大区别的。版本2中多了一个属性:boolean proxyBeanMethods() default true; 默认为true,True表示当代理对象调用@Configuration配置类中@Bean标识的方法时,总会去容器中查看有无该组件,如果有则去拿,每次取出的都是同一个对象,保持单实例。如果没有才去创建对象。False表示不会去查看容器,直接创建对象返回,就不能保证单例,但是时间会加快。

@Configuration(proxyBeanMethods = True)

False的使用场景:对应轻量级模式。如果我们只是向容器注入组件,别人不会使用这些组件,我们就设置为False,加快运行速度。

True的使用场景:对用重量级模式。如果组件后面会被使用,则设置为True。例如组件依赖:如果user01组件内想要使用容器内的tom组件,就必须要设置为True,否则会新建tom所对应类的对象,和我们的预期就会不同。

@Bean
上面配置类使用了@Bean注解,下面介绍一些其他关于@Bean的知识点

@Bean标识的函数有参数时,参数也会去容器中找

@Bean
@ConditionalOnBean(MultipartResolver.class)  //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
     //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
     //SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
	// Detect if the user has created a MultipartResolver but named it incorrectly
	return resolver;
}

@Import(三种方式)

@Import只能写在类上
注意:Import不只是能导入@Bean组件,它可以导入所有注解(Controller、Component等)修饰的组件。

Import(),括号内可以填写三种类型类实现组件注入。
方式一 实体类.class

@Configuration配置类除了@Bean注入组件之外,还可以通过@Import实现向容器中注入组件的功能。该注解也是加在配置类上的。

​ 前提:注入的类必须定义无参构造函数,如果为有参,还需要填入参数,但是好像不支持。

@Import({User.class}) // 向容器中注入一个User类型的组件
// @Configuration表示这是一个配置类,配置类里面使用Bean标识在方法上给容器注入组件
@Configuration
public class MyConfig {
    // 组件默认名为方法名
    @Bean
    public User user01(){
        return new User("komorebi");
    }
    // 可以通过Bean修改组件名
    @Bean("tom")
    public Pet pet01(){
        return new Pet("tomcat");
    }
}

方式二: 实现了ImportSelector接口的类.class

上面使用@Import导入组件方法,我们只需要想要导入的组件名加入到Import中即可。

还有另外一种常用的方法,自定义逻辑返回要导入的组件(Spring Boot自动装配源码中大量使用了该方法)。

  • 通过实现接口ImportSelector来导入多个组件(这里导入blue、car两个组件)
通过重写selectImports返回要导入的组件
class MyImportSelector implements ImportSelector{
	// 返回值就是要导入的组件
    // AnnotationMetadata可以获取使用@Import标记的类上所有注释信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.komorebi.bean.Car","com.komorebi.bean.Blue"};
    }
    @Override
    public Predicate<String> getExclusionFilter() {
        return ImportSelector.super.getExclusionFilter();
    }
}

在Import中加入自定义的逻辑

@Import({User.class,MyImportSelector.class}) 
@Configuration
@ConditionalOnMissingBean(name="tom")
@EnableConfigurationProperties(Car.class)
public class MyConfig {
    // 组件默认名为方法名
    @Bean
    public User user01(){
        return new User("komorebi");
    }
    // 可以通过Bean修改组件名
    @Bean("tom")
    public Pet pet01(){
        return new Pet("tomcat");
    }
}

查看组件是否注入成功(发现blue、car成功注入)

在这里插入图片描述

方式三 :实现了ImportBeanDefinitionRegistrar接口的类.class

registry里面记录了所有注册(注入)的组件,所以可以通过registry获取所有组件信息
它可以支持我们自己写的代码封装成BeanDefinition对象;实现此接口的类会回调postProcessBeanDefinitionRegistry方法,注册到spring容器中。把bean注入到spring容器不止有 @Service @Component等注解方式;还可以实现此接口。

本质:将想要注入的类生成对应BeanDefinition对象,然后BeanDefinition注册登记,后续bean实例化时会根据BeanDefinition对象生成(参考bean的生命周期)

class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    // BeanDefinitionRegistry: BeanDefinition注册类。即Bean定义注册
    // 调用registerBeanDefinitions方法实现Bean注册,Bean注册=注入Bean,注入的Bean都会有注册,都可以查到。
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(Yellow.class);
        // 这里注册bean
        registry.registerBeanDefinition("yellow",beanDefinition);
    }
}

查看结果

在这里插入图片描述

下面@Import中的三个内容代表了三种不同的Import注入组件的方式

@Import({User.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class}) 
@Configuration
@ConditionalOnMissingBean(name="tom")//容器中没有tom组件才注入下面的两个组件
public class MyConfig {...}

@Component

@Configuration@Import都是使用在配置类上面,@Component则是使用在实体类上面,可以实现向容器中注入实体类类型的组件。

前提:注入的类必须定义无参构造函数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值