JavaEE 企业级分布式高级架构师(十一)Spring Boot学习笔记(3)

Spring Boot工作原理解析

自动配置源码解析

  • 使用 Spring Boot 开发较之以前的基于 xml 配置式的开发,要简捷方便快速的多。而这完全得益于 Spring Boot 的自动配置。下面就通过源码阅读方式来分析自动配置的运行原理。

解析@SpringBootApplication

  • 打开启动类的 @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 { //...略 }

元注解:前四个是专门(即只能)用于对注解进行注解的,称为元注解。

@SpringBootConfiguration
  • 查看 @SpringBootConfiguration 注解的源码可知,该注解与 @Configuration 注解功能相同,仅表示当前类为一个 JavaConfig 类,其就是为 Spring Boot 创建的一个注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}
@ComponentScan
  • @ComponentScan 用于指定当前应用所要扫描的包。注意,其仅仅是指定包,而并没有扫描这些包,更没有装配其中的类,这个真正扫描并装配这些类是 @EnableAutoConfiguration 完成的。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
	/**
	 * Alias for {@link #basePackages}.
	 * <p>Allows for more concise annotation declarations if no other attributes
	 * are needed &mdash; for example, {@code @ComponentScan("org.my.pkg")}
	 * instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
	 */
	@AliasFor("basePackages")
	String[] value() default {};
	// ...略

	Filter[] includeFilters() default {};

	Filter[] excludeFilters() default {};
	// ...略
}
  • 这个注解有三个重要属性:
    • basePackages:用于指定要扫描的组件包,若没有指定则扫描当前注解所标的类所在的包及其子孙包。
    • includeFilters:用于进一步缩小要扫描的基本包中的类,通过指定过滤器的方式进行缩小范围。
    • excludeFilters:用于过滤掉那些不适合做组件的类。
@EnableXxx
  • @EnableXxx 注解一般用于开启某一项功能,是为了简化配置代码的引入。其是组合注解,一般情况下 @EnableXxx 注解中都会组合一个@Import 注解,而该@Import 注解用于导入指定的类,而被导入的类一般为配置类。其导入配置类的方式常见的有三种:
  • 一、直接引入配置类:@Import 中指定的类一般为 Configuration 结尾,且该类上会注解@Configuration,表示当前类为 JavaConfig 类。例如:@EnableScheduling
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

	@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
		return new ScheduledAnnotationBeanPostProcessor();
	}

}
  • 二、根据条件选择配置类:@Import 中指定的类一般以 ConfigurationSelector 结尾,且该类实现了 ImportSelector 接口,表示当前类会根据条件选择不同的配置类导入。例如:@EnableCaching
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching { // ...略 }

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
	// ...略
	@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return getProxyImports();
			case ASPECTJ:
				return getAspectJImports();
			default:
				return null;
		}
	}
	// ...略
}

public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
	// ...略
}
  • 三、动态注册Bean:@Import 中指定的类一般以 Registrar 结尾,且该类实现了 ImportBeanDefinitionRegistrar 接口,用于表示在代码运行时若使用了到该配置类,则系统会自动将其导入。例如:@EnableAspectJAutoProxy
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
	boolean proxyTargetClass() default false;
	boolean exposeProxy() default false;
}

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
	 * {@code @Configuration} class.
	 */
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

}

解析@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};
}
  • 该注解用于完成自动配置,是 Spring Boot 的核心注解,是一个组合注解。所谓自动配置是指,将用户自定义的类及框架本身用到的类进行装配。其中最重要的注解有两个:
    • @AutoConfigurationPackage:用于导入并装配用户自定义类,即自动扫描包中的类。
    • @Import:用于导入并装配框架本身的类。
@Import
  • 该注解用于导入指定的类。其参数 AutoConfigurationImportSelector 类,该类用于导入自动配置的类。
public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered {
	// ...略
	/**
	 * Return the auto-configuration class names that should be considered. 
	 */
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
		// ....
		return configurations;
	} 
}

在这里插入图片描述

  • 这样我们就找到自动配置的核心文件 spring.factories:

在这里插入图片描述

  • Debug启动一个SpringBoot工程,断点跟踪一下:

在这里插入图片描述

@AutoConfigurationPackage
  • 再打开@AutoConfigurationPackage 的源码,其也包含一个@Import 注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}
  • 这个注解是要将 AutoConfigurationPackages 类的内部静态类 Registrar 导入。从前面的学习可知,其是一个动态注册 Bean。

在这里插入图片描述

application.yml的加载

  • application.yml 文件对于 Spring Boot 来说是核心配置文件,至关重要,那么该文件是如何加载到内存的呢?需要从启动类的 run()方法开始跟踪。

启动方法run()跟踪

@SpringBootApplication
public class PrimaryApplication {
    public static void main(String[] args) {
        SpringApplication.run(PrimaryApplication.class, args);
    }
}
// ↓↓↓↓↓
// SpringApplication
	public static ConfigurableApplicationContext run(Class<?> primarySource,
			String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
// ↓↓↓↓↓
	public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

准备运行环境

// SpringApplication
public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);
		// 准备运行环境
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
				applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(
				SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		prepareContext(context, environment, listeners, applicationArguments,
				printedBanner);
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass)
					.logStarted(getApplicationLog(), stopWatch);
		}
		listeners.started(context);
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

让监听器监听环境准备过程

// SpringApplication
private ConfigurableEnvironment prepareEnvironment(
		SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// Create and configure the environment
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// 让监听器监听环境准备过程
	listeners.environmentPrepared(environment);
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader())
				.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}
// ↓↓↓↓↓
// SpringApplicationRunListeners
	public void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

发布环境准备事件

// EventPublishingRunListener
	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
				this.application, this.args, environment));
	}
// ↓↓↓↓↓
// SimpleApplicationEventMulticaster
	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

触发监听器

// SimpleApplicationEventMulticaster
	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			Executor executor = getTaskExecutor();
			if (executor != null) {
				// 触发监听器
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}
// ↓↓↓↓↓
	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				doInvokeListener(listener, event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
			doInvokeListener(listener, event);
		}
	}

在这里插入图片描述

加载配置文件

  • ConfigFileApplicationListener 配置文件的监听器加载配置文件:

在这里插入图片描述

  • 继续跟踪 load 方法:可以查看到其要选择加载的配置文件的扩展名是 properties 或 yml

在这里插入图片描述

  • 其中,properties 属性文件和yaml文件默认支持的文件后缀名可以查看源码得知:

在这里插入图片描述

  • 接下来继续分析源码 loadForFileExtension 这个方法:

在这里插入图片描述

  • Debug 断点看一下:配置文件最终被加载封装成一 List<PropertySource<?>>

在这里插入图片描述

在这里插入图片描述

Spring Boot与Redis的整合

在这里插入图片描述

  • 在 spring.factories 中有一个 RedisAutoConfiguration 类,通过前面的分析我们知道,该类一定会被 Spring 容器自动装配。但是自动装配了就可以读取到 Spring Boot 配置文件中 Redis 相关的配置信息了?这个类与 Spring Boot 配置文件是怎么建立的联系?
  • 我们知道,若要使代码可以操作 Redis,就需要获取到 RedisTemplate,并通过 RedisTemplate 获取到 Redis 操作对象 RedisOperations。没有 RedisTemplate 对象,没有 RedisOperations 接口,是无法操作 Redis 的。
  • 查看 RedisAutoConfiguration 这个类的源码:加载了 Redis 配置、并创建了 RedisTemplate 对象。
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(
			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(
			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

MyBatis与Spring Boot的整合

  • 我们发现在 spring-boot-autoconfigure 中的 spring.factories 文件中并没有像 Redis 那样类似的 xxxAutoConfiguration 类的配置,因此我们只能去分析 mybatis-spring-boot-starter 依赖。而该依赖又依赖于 mybatis-spring-boot-autoconfigurigure。其 META-INF 中有 spring.factories 文件,打开这个文件我们找到了 Mybatis 的自动配置类。

在这里插入图片描述

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration { // ...略 }
  • 这个自动配置类依赖两个工厂 Bean:SqlSessionFactory 和 SqlSessionFactoryBean,还依赖 DataSource 对象,加载 MyBatis 配置 MybatisProperties。这个类中包含两个@Bean 方法用于创建相应的 Bean:

在这里插入图片描述

自定义Starter

  • 如果我们自己的某项功能代码也想将其应用到 Spring Boot 中,为 Spring Boot 项目提供相应支持,需要怎样实现呢?同样,我们需要定义自己的 Starter。

自定义 wrap-spring-boot-starter

  • starter 工程的命名规范:Spring 官方定义的 starter 通常命名遵循的格式为 spring-boot-starter-{name},例如 spring-boot-starter-web。Spring 官方建议,非官方 starter 命名应遵循 {name}-spring-boot-starter 的格式,例如,dubbo-spring-boot-starter。
  • 下面我们自定义一个自己的 Starter,实现的功能是:为用户提供的字符串添加前辍与后辍,而前辍与后辍定义在 yml 或 properties 配置文件中。例如,用户输入的字符串为 China,application.yml 配置文件中配置的前辍为$$$,后辍为+++,则最终生成的字符串为 $$$China+++。
  • 创建一个 Spring Boot 工程,命名为 wrap-spring-boot-starter。
  • 定义一个 Wrapper 类,核心功能在这个类中完成的。该类中的成员变量可以随意命名,但一般与在 Spring Boot 中使用的属性名同名。
@AllArgsConstructor
public class Wrapper {
    private String prefix;
    private String suffix;

    /**
     * 当前starter的核心功能实现方法
     */
    public String wrap(String word) {
        return prefix + word + suffix;
    }
}
  • 定义配置属性封装类:指定当前类用于封装来自于 Spring Boot 核心配置文件中的以 wrapper.service 开头的 prefix 与 surfix 属性值。
// 该类的对象是由系统自动创建,所以无需将其将给 Spring 容器管理
@Data
@ConfigurationProperties("wrapper.service")
public class WrapperProperties {
    /**
     * 对应配置文件中的wrapper.service.prefix属性
     */
    private String prefix;
    /**
     * 对应配置文件中的wrapper.service.suffix属性
     */
    private String suffix;
}
  • 定义自动配置类:这里再增加一个开关配置wrapper.service.enable,当 enable 属性值为 true 时,或没有设置 some.service.enable 属性时才进行组装,若 enable 为 false,则不进行组装。
@Configuration
// 自由当前路径下存在SomeService类时,才会启用当前的JavaConfig配置类
@ConditionalOnClass(Wrapper.class)
// 指定配置文件中指定属性的封装类
@EnableConfigurationProperties(WrapperProperties.class)
public class WrapperAutoConfiguration {
    @Resource
    private WrapperProperties properties;

    /**
     * 以下两个方法的顺序不能颠倒
     */
    @Bean
    @ConditionalOnProperty(name = "wrapper.service.enable", havingValue = "true", matchIfMissing = true)
    public Wrapper wrapper() {
        return new Wrapper(properties.getPrefix(), properties.getSuffix());
    }

    @Bean
    @ConditionalOnMissingBean
    public Wrapper defaultWrapper() {
        return new Wrapper("", "");
    }
}
  • 在 resources/META-INF 目录下创建一个名为 spring.factories 的文件。文件中内容:键是固定的,为 EnableAutoConfiguration 类的全限定性类名,而值则为我们自定义的自动配置类。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.yw.springboot.example.config.WrapperAutoConfiguration

starter测试 09-wrapper-starter

  • 创建一个 Spring Boot 工程,命名为 09-wrapper-starter,添加依赖:
<!--自定义Starter依赖-->
<dependency>
  <groupId>com.yw.springboot.example</groupId>
  <artifactId>wrap-spring-boot-starter</artifactId>
  <version>1.0</version>
</dependency>
  • 定义 application.yml 文件:自定义 Starter 中的属性在配置文件中也是有自动提示功能的。
wrapper:
  service:
    # 设置开关状态,默认true开启
    enable: true
    # 指定前缀
    prefix: $$$
    # 指定后缀
    suffix: +++
  • 定义控制器类:
@RestController
public class WrapController {
    /**
     * byType方式的自动注入
     */
    @Resource
    private Wrapper wrapper;

    @RequestMapping("/wrap/{param}")
    public String wrap(@PathVariable("param") String param) {
        return wrapper.wrap(param);
    }
}
  • 启动项目,测试接口:http://localhost:8080/wrap/test。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值