SpringBoot

Spring Boot - 1. 自动配置

谈及SpringBoot之前,先来聊一聊**微服务,**那么什么是微服务呢?

​ 当然,关于 微服务 ,它并不存在公认的单一定义;

​ **微服务**是一种专注于构建基于能力(业务功能)、可独立部署的小型服务的架构风格。

​ 关于微服务的更细致分析,大家可以去看一下 Martin Fowler 关于微服务的论文

​ 论文的 原文地址:http://martinfowler.com/articles/microservices.html

*整体和微服务*

  • 微服务的划分方法更倾向于围绕业务功能对服务结构进行划分、拆解
  • 使用微服务所构建的各个应用的目标都是尽可能实现**“高内聚和低耦合”**
    • 他们拥有自己的领域逻辑
    • 更多的是 接收请求、处理逻辑、返回响应
    • 这些应用通过使用简单的REST风格的协议来进行编排
    • 而不不是诸如:WS-Choreography 、BEPL 或者集中式工具进行编排。

快速构建微服器原型

​ 在 Spring Boot之前, 我们通常使用 Spring MVC 来构建一个微服务, 并使用 JPA 来连接到数据库

具体的操作 应该有如下:

  • 确定要是用 SpringMVC 、JPA 、ORM框架;

  • 设置Spring 上下文并将所有不同的层装配在一起;

  • 使用Spring MVC 设置 Web层 (包括 SpringMVC 的配置) :

    • 为DispatcherServlet、处理程序、解析器、视图解析器等配置 相应的bean
  • 数据层配置 orm 框架 :

    • SessionFactory、数据源等配置相应的bean
  • 确定如何存储应用程序并实现此目标;

  • 确定如何进行单元测试;

  • 确定事务管理策略,并实现;

  • 确定如何实施安全性,并实现;

  • 设置日志记录框架;

  • 确定如何在生产环境中监视应用程序,并实现;

  • 确定并实现度量管理系统,一提供有关应用程序的统计数据;

  • 确定如何将应用程序部署到Web或者应用程序服务器,并实现。

    步骤何其多!!!

    那么恰巧, SpringBoot 正致力于解决这些问题; 开箱即用

  • 使用基于Spring 的项目 快速构建微服务
  • 采用固定的设置,基于常见用法 做出默认假设,并提供配置选项来处理偏离默认设置的情况;
  • 开箱既提供一系列非功能性特性
  • 不生成代码,并避免大量的使用XML配置

SpringBoot 自动装配 原理解析

​ 当你创建一个SpringBoot项目时,它自动创建一个类 Appliation ,我通常管他就叫做 启动类 ,SpringBoot 自动装配的关键在于他的启动类,下面就来仔细的看一下在这个启动类中都有哪些内容:

一、 启动类 Application 通过 main( )方法,启动程序

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

​ 可以看到 Application 类中就只有一个主函数 main( )方法、也用到了一个 org.springframework.boot.SpringApplication类的 静态方法 run( );

​ 既然,找到了些许苗头,不妨让我们点开看看 这个静态的run( ) 方法的究竟;

二、类 SpringApplication 的静态 run( ) 方法

org.springframework.boot.SpringApplication

	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
  • 通过查看run方法,可以获得以下信息:

    • run 是静态方法,可用于运行{@link SpringApplication} 指定源使用默认设置。
    • 参数primarySource 是 加载的主源
      • 接收一个Spring容器配置类 (也就是通过java代码,对Spring容器进行配置)
    • 参数args 是 应用程序参数
      • 通常是从主函数 main( )方法中将命令行参数args传递给SpringApplication类
        • 作用就是: 在使用 java命令启动该应用时,可以通过命令行的参数对Spring应用做一些配置
    • 返回值 是 运行的这的ApplicationContext {@link ApplicationContext}
  • SpringBootApplication类课用于从java main方法中 引导和启动 SpringBoot应用程序,通常执行以下步骤

    • 创建 Spring ApplicationContext 实例对象
    • 启用相关功能,接收命令行参数,并将他们公布为Spring 属性
    • 根据配置加载所有的 Spring bean

​ 那么翻到这里 你是不是 也没有找到 自动装配 的原理呢 ,是的 我也没有, 那不妨换一个方向!!!

​ 在 启动类 Application 的上方 还有一个标注着的注解 @SpringBootApplication 那就让我们一起来看看 这个注解 有起到了什么样的作用了吧.

三、注解 @SpringBootApplication (开启了Spring组件扫描、Spring Boot 自动配置)

org.springframework.boot.autoconfigure.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 {
}

实际上它是一个复合注解,除了元注解之外,进入到注解当中,你肯定也能和我一样的 看到 如下的几个注解,那么这些个注解都起到了什么作用呢??

  • 元注解:
    • @Target: 表示该注解的作用目标;
      • @Target(ElementType.TYPE)——接口、类、枚举、注解
        @Target(ElementType.FIELD)——字段、枚举的常量
        @Target(ElementType.METHOD)——方法
        @Target(ElementType.PARAMETER)——方法参数
        @Target(ElementType.CONSTRUCTOR) ——构造函数
        @Target(ElementType.LOCAL_VARIABLE)——局部变量
        @Target(ElementType.ANNOTATION_TYPE)——注解
        @Target(ElementType.PACKAGE)——包
    • @Retention : 就是指定Annotation的保留策略属性。
      • @Retention(RetentionPolicy.SOURCE)——注解只会存在源代码中,将会被编译器丢弃
      • 注释将由编译器记录在类文件中,但不必在运行时由 VM保留。这是默认的行为
      • @Retention(RetentionPolicy.CLASS)——注解将会被编译器记录到class文件中,但是在加载到 VM 的时候会被抛弃(同时,这也是默认的策略)
      • @Retention(RetentionPolicy.RUNTIME)——注解不单会被保留到class文件阶段,而且也在运行时被VM保留、因此可以通过反射方式读取它们。
  • 核心注解:
    • @ComponentScan
    • @SpringBootConfiguration
    • @EnableAutoConfiguration
1、注解 @ComponentScan 启动组件扫描
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};
 
    @AliasFor("value")
    String[] basePackages() default {};
 
    Class<?>[] basePackageClasses() default {};
 
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
 
    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
 
    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
 
    String resourcePattern() default "**/*.class";
    boolean useDefaultFilters() default true;
    ComponentScan.Filter[] includeFilters() default {};
    ComponentScan.Filter[] excludeFilters() default {};
    boolean lazyInit() default false;
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;
        @AliasFor("classes")
        Class<?>[] value() default {};
        @AliasFor("value")
        Class<?>[] classes() default {};
        String[] pattern() default {};
    }
}

在这其中:

  • 关于类和方法的Annotation在发生情况下是不出现在javadoc中。

    • 定义Annotation时,@Documented可有可无;若没有定义,则Annotation不会出现在javadoc中。
  • basePackages与value**(互为对方的别名): 作用一致;**

    • 用于指定包的路径,进行扫描
    • 同时,该属性为一个数组,因此可以指定多个包路径;
    • 并且,该属性不赋值时,默认扫描被改注解修饰类所在的包及其子包下的组件。
  • basePackageClasses: 用于指定某个类的包的路径进行扫描

  • nameGenerator: bean的名称的生成器

  • useDefaultFilters: 是否开启对@Component,@Repository,@Service,@Controller的类进行检测 (默认开启)

  • includeFilters: 包含的过滤条件

    • FilterType.ANNOTATION:按照注解过滤
    • FilterType.ASSIGNABLE_TYPE:按照给定的类型
    • FilterType.ASPECTJ:使用ASPECTJ表达式
    • FilterType.REGEX:正则
    • FilterType.CUSTOM:自定义规则
  • excludeFilters: 排除的过滤条件,用法和includeFilters一样

​ **这就很明显了,注解 @ComponentScan 启动了组件扫描,开发的组件以及bean定义 能被自动扫描发现,并注入到Spring应用程序上下文,例如控制层添加的@Controller 等一些注解 都是可以被扫描到的 **

2、注解 @SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

​ 从上述源码得出,@SpringBootConfiguration 注解内部有一个核心注解@Configuration,而此注解是Spring提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。

3、注解 @EnableAutoConfiguration 自动载入应用程序所需的所有Bean

org.springframework.boot.autoconfigure.EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
  • @Import :用于做导入 | 导入一个或者多个的组件或者类< 通常是 @Configuration注解 标注的配置类。>
  • AutoConfigurationImportSelector: 自动配置导入选择器

​ 通过@Import(AutoConfigurationImportSelector.class),借助AutoConfigurationImportSelector@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。接下来就要仔细的扒一扒 这个选择器了.

3.1 选择器 AutoConfigurationImportSelector

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

AutoConfigurationImportSelector 方法 selectImports 中, 根据导入配置类的注释元数据, 选择并返回要导入的配置(类)的名称。

  • 在方法中 通过 调用 本类中受保护的 getAutoConfigurationEntry 方法 获取 自动配置实体
    AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)
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);
}

AutoConfigurationEntry 方法 根据导入的 标注有 @Configuration 注解的配置类 的 注解元数据 获取相应的自动配置实体

  • 在方法中 通过本类中受保护的 getCandidateConfigurations 方法 获取配置项完全限定名 列表

List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
   Assert.notEmpty(configurations, "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;
}

/**
	 * Return the class used by {@link SpringFactoriesLoader} to load configuration
	 * candidates.
	 * @return the factory class
	 */
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}
  • 默认情况下,getCandidateConfigurations方法 将使用 SpringFactoriesLoader 类中的getSpringFactoriesLoaderFactoryClass()来加载候选对象,并返回应该考虑的自动配置类名。

    • 那么这些候选对象是些什么东西呢??
      • 也就是 getSpringFactoriesLoaderFactoryClass()方法的 返回值 EnableAutoConfiguration.class 了(也就是 标注着@EnableAutoConfiguration的配置类了)
  • 通过 调用 Spring 框架的 工具类 SpringFactoriesLoader中的 静态方法 loadFactoryNames
    使用给定的类加载器从指定的位置 中加载给定类型的工厂实现的全限定类名。
    List loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			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();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
  • 通过三目 去获取一个资源
    • Enumeration urls = (classLoader != null ?
      classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
      ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  • 并将所获取的资源 通过遍历封装为 Properties对象
  • 这个获取资源的 路径就是 “META-INF/spring.factories” 这就是 自动装配的 核心的配置 工厂文件了
    • 在这个文件中 存在着 所有 可以自动配置组件的 配置类的完全限定名

四、META-INF/spring.factories 文件

​ 找到 spring-boot-autoconfigure-2.3.2.RELEASE.jar!/META-INF/spring.factories 去这里边 瞧个究竟 !!

spring.factories文件中的 完全限定名以及与之对应的 自动配置类

我们拿 Redis自动配置 的 自动配置类 RedisAutoConfiguration来进一步的分析,到底是如何做到自动的装配的

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

RedisAutoConfiguration 自动配置类的详情

1、注解 @Configuration(proxyBeanMethods = false)
  • 这不就明摆着 告诉你 他就是一个 配置类嘛 !!!
  • proxyBeanMethods = flase 不会被代理,
  • 如果为true会被 CGLIB 代理;
  • 如果只是普通配置类的话建议设置为 flase ,这样能提升性能;
2、 注解 @ConditionalOnClass(RedisOperations.class) 条件类
  • 几个常用的 条件注解
Conditions作用描述 (判断是否满足条件)
@ConditionalOnBean判断是否存在某个指定的bean
@ConditionalOnMissingBean判断是否不存在某个bean
@ConditionalOnClass当前类路径classpath上是否存在指定的类
@ConditionalOnMissingClass当前类路径classpath上是否不存在指定的类
@ConditionalOnResource当前classpath是否存在某个资源文件
@ConditionalOnProperty当前jvm是否包含某个系统属性为某个值
@ConditionalOnWebApplication当前spring context是否是web应用程序
  • 它们的作用是按照一定的条件进行判断,满足条件给容器注册bean。
  • 首先进入眼睛的,铁定是那些个报错出现红线的地方了, 那这是为什么呢???
    • 找不到他, 为什么找不到呢!!!
      • 因为, 在依赖管理 pom.xml文件中 我们并没有加入Redis 相关的 starter 启动器
    • 稍后, 待我们添加上 redis 对应的启动器之后,再次回来 看看他的状态, 暂时先看下一个注解
3、注解 @EnableConfigurationProperties(RedisProperties.class)
  • 该注解的注释解释是这样的 :

    • 为带有@ConfigurationProperties注解的Bean提供有效的支持。这个注解可以提供一种方便的方式来将带有@ConfigurationProperties注解的类注入为Spring容器的Bean。
  • 里面参数 RedisOperations.class

    @ConfigurationProperties(prefix = "spring.redis")
    public class RedisProperties {
    
    	private int database = 0;
    
    	private String url;
    
    	private String host = "localhost";
    
    	private String password;
    
    	private int port = 6379;
        
        //  setter / getter
        //  ...... 
    }
    
    • @ConfigurationProperties(prefix = “spring.redis”)
      • 注解 @ConfigurationProperties prefix 绑定着核心配置文件中 以 spring.redis 为前缀的配置项 ;
    • 在Boot 的 核心配置文件 application.yml/properties 中配置过 redis 是不是对类 RedisProperties 中的 属性特别的眼熟, 没错,他们是一一对应的, 在配置文件中可以配置的东西就是这一系列 Spring Boot 提供的 XXXProperties 类中的属性.

XXXProperties 与 核心配置文件 的关系.jpg

4、再看一下添加stater 启动器之后的情况;

自动配置与pom导入的依赖相关2.jpg

条件判断成立,

五、总结

SpringBoot 自动配置原理( 注解 @EnableAutoConfiguration ) 的工作原理:

  • 自动配置的关键

    • @EnableAutoConfiguration注解;

    • META-INF/spring.factories 中 包含与之对应的类的限定名;

    • 依赖管理 pom.xml 的 中的 starter 启动器;

  • 自动配置的入口 :

    • 启动类 Application 上注解 @SpringBootApplication 中包含的注解 @EnableAutoConfiguration

    • @EnableAutoConfiguration 通过 @Import 注解,借助 AutoConfigurationImportSelector 自动配置导入选择器,调用SpringFactoriesLoaderloadFactoryNames方法,从 classpath 中寻找所有的 META-INF/spring.factories 配置文件;

      • 借助 AutoConfigurationImportSelector 将所有复合条件的@Configuration 配置都加载到 IoC容器当中
      • 当然这所有的自动配置类, 并不是都生效的, 根据类上标注的 @ConditionalXXX 注解, 进行条件判断,条件成立之后,进行自动配置Bean注入SpringContext(IoC);

    通过以上的这些原理, 无论何时在 Spring Boot 项目中添加新依赖项(启动器),Spring Boot 自动配置 都会尝试根据依赖项自动装配bean

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值