SpringBoot源码

SpringBoot核心前置内容

1.Spring注解编程的发展过程

image.png

1.1 Spring 1.x

2004年3月24日,Spring1.0 正式发布,提供了IoC,AOP及XML配置的方式。

在Spring1.x版本中提供的是纯XML配置的方式,也就是在该版本中必须要提供xml的配置文件,在该文件中通过 <bean> 标签来配置需要被IoC容器管理的Bean。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.bobo.demo01.UserService" />
</beans>

调试代码

public static void main(String[] args) {ApplicationContext ac = new FileSystemXmlApplicationContext("classpath:applicationContext01.xml");System.out.println("ac.getBean(UserService.class) = " + ac.getBean(UserService.class));
}

输出结果

image.png

在Spring1.2版本的时候提供了@Transaction (org.springframework.transaction.annotation )注解。简化了事务的操作.

image.png

1.2 Spring 2.x

在2006年10月3日 Spring2.0问世了,在2.x版本中,比较重要的特点是增加了很多注解

Spring 2.5之前

在2.5版本之前新增的有 @Required @Repository @Aspect,同时也扩展了XML的配置能力,提供了第三方的扩展标签,比如 <dubbo>

@Required

如果你在某个java类的某个set方法上使用了该注释,那么该set方法对应的属性在xml配置文件中必须被设置,否则就会报错!!!

public class UserService {private String userName;public String getUserName() {return userName;}@Requiredpublic void setUserName(String userName) {this.userName = userName;}
}

如果在xml文件中不设置对应的属性就会给出错误的提示。

image.png

设置好属性后就没有了错误提示了

image.png

源码中可以看到 @Required从2.0开始提供

image.png

@Repository

@Repository 对应数据访问层Bean.这个注解在Spring2.0版本就提供的有哦,大家可能没有想到。

image.png

@Aspect

@Aspect是AOP相关的一个注解,用来标识配置类。

Spring2.5 之后

在2007年11月19日,Spring更新到了2.5版本,新增了很多常用注解,大大的简化配置操作。

注解说明
@Autowired依赖注入
@Qualifier配置@Autowired注解使用
@Component声明组件
@Service声明业务层组件
@Controller声明控制层组件
@RequestMapping声明请求对应的处理方法

在这些注解的作用下,可以不用在xml文件中去注册没有bean,这时只需要指定扫码路径,然后在对应的Bean头部添加相关的注解即可,这大大的简化了配置及维护工作。案例如下:

在配置文件中只需要配置扫码路径即可:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.bobo" /></beans>

持久层代码:

@Repository
public class UserDao {public void query(){System.out.println("dao query ..." );}
}

业务逻辑层代码

@Service
public class UserService {@Autowiredprivate UserDao dao;public void query(){dao.query();}
}

控制层代码:

@Controller
public class UserController {@Autowiredprivate UserService service;public void query(){service.query();}
}

测试代码

public class Demo02Main {public static void main(String[] args) {ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext02.xml");UserController acBean = ac.getBean(UserController.class);acBean.query();}
}

虽然在Spring的2.5版本提供了很多的注解,也大大的简化了开发,但是任然没有摆脱XML配置驱动。

1.3 Spring 3.x

在2009年12月16日发布了Spring3.0版本,这是一个注解编程发展的里程碑版本,在该版本中全面拥抱Java5。提供了 @Configuration注解,目的就是去xml化。同时通过 @ImportResource来实现Java配置类和XML配置的混合使用来实现平稳过渡。

/*** @Configuration 标注的Java类 相当于 application.xml 配置文件*/
@Configuration
public class JavaConfig {/*** @Bean 注解 标注的方法就相当于 <bean></bean> 标签也是 Spring3.0 提供的注解* @return*/@Beanpublic UserService userService(){return new UserService();}
}

在Spring3.1 版之前配置扫描路径还只能在 XML 配置文件中通过 component-scan 标签来实现,在3.1之前还不能够完全实现去XML配置,在3.1 版本到来的时候,提供了一个 @ComponentScan注解,该注解的作用是替换掉 component-scan标签,是注解编程很大的进步,也是Spring实现无配置话的坚实基础。

@ComponentScan

@ComponentScan的作用是指定扫码路径,用来替代在XML中的 <component-scan>标签,默认的扫码路径是当前注解标注的类所在的包及其子包。

定义UserService

@Service
public class UserService {
}

创建对于的Java配置类

@Configuration
@ComponentScan
public class JavaConfig {public static void main(String[] args) {ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);System.out.println("ac.getBean(UserService.class) = " + ac.getBean(UserService.class));}
}

输出的结果

image.png

当然也可以指定特定的扫描路径

@Configuration
// 指定特定的扫描路径
@ComponentScan(value = {"com.bobo.demo04"})
public class JavaConfig {public static void main(String[] args) {ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);System.out.println("ac.getBean(UserService.class) = " + ac.getBean(UserService.class));}
}
@Import

@Import注解只能用在类上,作用是快速的将实例导入到Spring的IoC容器中,将实例导入到IoC容器中的方式有很多种,比如 @Bean注解,@Import注解可以用于导入第三方包。具体的使用方式有三种。
用在类上,也能将类导入spring容器

静态导入

静态导入的方式是直接将需要导入到IoC容器中的对象类型直接添加进去即可。

image.png

这种方式的好处是简单,直接,但是缺点是如果要导入的比较多,则不太方便,而且也不灵活。
在这里插入图片描述

ImportSelector

@Import注解中也可以添加一个实现了 ImportSelector接口的类型,这时不会将该类型导入IOC容器中,而是会调用 ImportSelector接口中定义的 selectImports方法,将该方法的返回的字符串数组的类型添加到容器中

定义两个业务类

public class Cache {
}
public class Logger {
}

定义ImportSelector接口的实现,方法返回的是需要添加到ICC容器中的对象对应的类型的全类路径的字符串数组,可以根据不同的业务需求而导入不同的类型,会更加的灵活些

public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{Logger.class.getName(),Cache.class.getName()};}
}

导入测试案例

@Configuration
@Import(MyImportSelector.class)
public class JavaConfig {public static void main(String[] args) {ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);for (String beanDefinitionName : ac.getBeanDefinitionNames()) {System.out.println("beanDefinitionName = " + beanDefinitionName);}}
}

输出结果:

image.png

ImportBeanDefinitionRegistrar

除了上面所介绍的ImportSelector方式灵活导入以外还提供了 ImportBeanDefinitionRegistrar 接口,也可以实现,相比 ImportSelector 接口的方式,ImportBeanDefinitionRegistrar 的方式是直接在定义的方法中提供了 BeanDefinitionRegistry ,自己在方法中实现注册

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 将需要注册的对象封装为 RootBeanDefinition 对象RootBeanDefinition cache = new RootBeanDefinition(Cache.class);registry.registerBeanDefinition("cache",cache);RootBeanDefinition logger = new RootBeanDefinition(Logger.class);registry.registerBeanDefinition("logger",logger);}
}

测试代码

@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class JavaConfig {public static void main(String[] args) {ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);for (String beanDefinitionName : ac.getBeanDefinitionNames()) {System.out.println("beanDefinitionName = " + beanDefinitionName);}}
}

输出结果

image.png

@EnableXXX

@Enable模块驱动,其实是在系统中先开发好各个功能独立的模块,比如 Web MVC 模块, AspectJ代理模块,Caching模块等。

image.png

案例说明,先定义好功能模块

/*** 定义一个Java配置类*/
@Configuration
public class HelloWorldConfiguration {@Beanpublic String helloWorld(){return "Hello World";}
}

然后定义@Enable注解

/*** 定义@Enable注解* 在该注解中通过 @Import 注解导入自定义的模块,使之生效。*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}

测试代码

@Configuration
// 加载 自定义 模块
@EnableHelloWorld
public class JavaMian {public static void main(String[] args) {ApplicationContext ac = new AnnotationConfigApplicationContext(JavaMian.class);String helloWorld = ac.getBean("helloWorld", String.class);System.out.println("helloWorld = " + helloWorld);}
}

效果

image.png

1.4 Spring 4.x

2013年11月1 日更新的Spring 4.0 ,完全支持Java8.这是一个注解完善的时代,提供的核心注解是@Conditional条件注解。@Conditional 注解的作用是按照一定的条件进行判断,满足条件就给容器注册Bean实例。

@Conditional的定义为:

// 该注解可以在 类和方法中使用
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {/*** 注解中添加的类型必须是 实现了 Condition 接口的类型*/Class<? extends Condition>[] value();}

Condition是个接口,需要实现matches方法,返回true则注入bean,false则不注入。

案例讲解:

/*** 定义一个 Condition 接口的是实现*/
public class MyCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {@verridepublic boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata) {// 根据我们特定的业务需求来决定是否注入对应的对象try {boolean flage = context.getRegistry().containsBeanDefinition("userService");if (flage) {Class.forName("com.bobo.test.test666");return flage;}} catch (ClassNotFoundException e) {e.printStackTrace();}return false;// 默认返回false}
}

创建Java配置类

@Configuration
public class JavaConfig {@Bean// 条件注解,添加的类型必须是 实现了 Condition 接口的类型// MyCondition的 matches 方法返回true 则注入,返回false 则不注入@Conditional(MyCondition.class)public StudentService studentService(){return new StudentService();}public static void main(String[] args) {ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);for (String beanDefinitionName : ac.getBeanDefinitionNames()) {System.out.println("beanDefinitionName = " + beanDefinitionName);}}
}

测试:

image.png

但是将 matchs方法的返回结果设置为 true 则效果不同

image.png

所以@Conditional的作用就是提供了对象导入IoC容器的条件机制,这也是SpringBoot中的自动装配的核心关键。当然在4.x还提供一些其他的注解支持,比如 @EventListener,作为ApplicationListener接口编程的第二选择,@AliasFor解除注解派生的时候冲突限制。@CrossOrigin作为浏览器跨域资源的解决方案。

1.5 Spring 5.x

2017年9月28日,Spring来到了5.0版本。5.0同时也是SpringBoot2.0的底层。注解驱动的性能提升方面不是很明显。在Spring Boot应用场景中,大量使用@ComponentScan扫描,导致Spring模式的注解解析时间耗时增大,因此,5.0时代引入**@Indexed**,为Spring模式注解添加索引

当在项目中使用了 @Indexed之后,编译打包的时候会在项目中自动生成 META-INT/spring.components文件。当Spring应用上下文执行 ComponentScan扫描时,META-INT/spring.components将会被 CandidateComponentsIndexLoader 读取并加载,转换为 CandidateComponentsIndex对象,这样的话 @ComponentScan不在扫描指定的package,而是读取 CandidateComponentsIndex对象,从而达到提升性能的目的。

<dependency><groupId>org.springframework</groupId><artifactId>spring-context-indexer</artifactId>
</dependency>

使用@Indexed注解

image.png

image.png
核心条件注解@Conditional控制对象是否注入到容器中,@Import 注解中导入的类型
ImportSelector 接口,不会将该类型注入到容器中而是会将selectImports的返回的类型的全类路径的字符串的数据注入到容器中动态注入(类似于factoryBean)
ImportBeanDefinitionRegistrar 的方式是直接在定义的方法中提供了 BeanDefinitionRegistry ,自己在方法中实现注册。(自己注册了bean定义,接口类似于factoryBean)
@Indexed ,编译打包的时候会在项目中自动生成 META-INT/spring.components文件。当Spring应用上下文执行 ComponentScan扫描时,META-INT/spring.components将会被 CandidateComponentsIndexLoader 读取并加载,从而达到提升spring性能的目的。

2. 什么是SPI

为什么要讲SPI呢?因为在SpringBoot的自动装配中其实有使用到SPI机制,所以掌握了这部分对于SpringBoot的学习还是很有帮助的。

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。先通过一个很简单的例子来看下它是怎么用的。

案例介绍

先定义接口项目

在这里插入图片描述

然后创建一个扩展的实现,先导入上面接口项目的依赖

    <dependencies><dependency><groupId>com.bobo</groupId><artifactId>JavaSPIBase</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>

然后创建接口的实现

/*** SPI:MySQL对于 baseURL 的一种实现*/
public class MySqlConnection implements BaseData {@Overridepublic void baseURL() {System.out.println("mysql 的扩展实现....");}
}

然后在resources目录下创建 META-INF/services 目录,然后在目录中创建一个文件,名称必须是定义的接口的全类路径名称。然后在文件中写上接口的实现类的全类路径名称
在这里插入图片描述

在这里插入图片描述

同样的再创建一个案例

在这里插入图片描述

然后在测试的项目中测试

    public static void main(String[] args) {ServiceLoader<BaseData> providers = ServiceLoader.load(BaseData.class);Iterator<BaseData> iterator = providers.iterator();while(iterator.hasNext()){BaseData next = iterator.next();next.baseURL();}}

根据不同的导入,执行的逻辑会有不同

image.png

image.png

源码查看

ServiceLoader

首先来看下ServiceLoader的类结构

   // 配置文件的路径private static final String PREFIX = "META-INF/services/";// 加载的服务  类或者接口private final Class<S> service;// 类加载器private final ClassLoader loader;// 访问权限的上下文对象private final AccessControlContext acc;// 保存已经加载的服务类private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 内部类,真正加载服务类private LazyIterator lookupIterator;

load

load方法创建了一些属性,重要的是实例化了内部类,LazyIterator。

public final class ServiceLoader<S> implements Iterable<S>private ServiceLoader(Class<S> svc, ClassLoader cl) {//要加载的接口service = Objects.requireNonNull(svc, "Service interface cannot be null");//类加载器loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;//访问控制器acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload();}public void reload() {//先清空providers.clear();//实例化内部类 LazyIterator lookupIterator = new LazyIterator(service, loader);}
}

查找实现类和创建实现类的过程,都在LazyIterator完成。当调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法。

private class LazyIterator implements Iterator<S>{Class<S> service;ClassLoader loader;Enumeration<URL> configs = null;Iterator<String> pending = null;String nextName = null; private boolean hasNextService() {//第二次调用的时候,已经解析完成了,直接返回if (nextName != null) {return true;}if (configs == null) {//META-INF/services/ 加上接口的全限定类名,就是文件服务类的文件//META-INF/services/com.viewscenes.netsupervisor.spi.SPIServiceString fullName = PREFIX + service.getName();//将文件路径转成URL对象configs = loader.getResources(fullName);}while ((pending == null) || !pending.hasNext()) {//解析URL文件对象,读取内容,最后返回pending = parse(service, configs.nextElement());}//拿到第一个实现类的类名nextName = pending.next();return true;}
}

创建实例对象,当然,调用next方法的时候,实际调用到的是,lookupIterator.nextService。它通过反射的方式,创建实现类的实例并返回。

private class LazyIterator implements Iterator<S>{private S nextService() {//全限定类名String cn = nextName;nextName = null;//创建类的Class对象Class<?> c = Class.forName(cn, false, loader);//通过newInstance实例化S p = service.cast(c.newInstance());//放入集合,返回实例providers.put(cn, p);return p; }
}

看到这儿,已经很清楚了。获取到类的实例,自然就可以对它为所欲为了!

SpringBoot自动装配原理分析

自动装配源码分析

在前面的分析中,Spring Framework一直在致力于解决一个问题,就是如何让bean的管理变得更简单,如何让开发者尽可能的少关注一些基础化的bean的配置,从而实现自动装配。所以,所谓的自动装配,实际上就是如何自动将bean装载到Ioc容器中来

实际上在spring 3.x版本中,Enable模块驱动注解的出现,已经有了一定的自动装配的雏形,而真正能够实现这一机制,还是在spirng 4.x版本中,conditional条件注解的出现。看一下spring boot的自动装配是怎么一回事。

ImportSelector接口,调用 ImportSelector接口中定义的 selectImports方法,将该方法的返回的字符串数组的类型添加到容器中
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
configurations = this.getConfigurationClassFilter().filter(configurations);
在这里插入图片描述

自动装配的核心

本质上就是Spring容器的初始化
核心是自动配置类实现ImportSelect接口在selectImports方法中通过SPI机制,在自动配置包下,将META-INF下的spring.factory文件中把key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值获取到,并将value值转换为java类,注入Spring容器当中

自动装配的演示

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> 
spring:redis:host: 127.0.0.1 port: 6379
 @Autowiredprivate RedisTemplate<String,String>redisTemplate;

按照下面的顺序添加starter,然后添加配置,使用RedisTemplate就可以使用了? 那大家想没想过一个问题,为什么RedisTemplate可以被直接注入?它是什么时候加入到Ioc容器的呢? 这就是自动装配。自动装配可以使得classpath下依赖的包相关的bean,被自动装载到Spring Ioc容器中,怎么做到的呢?

深入分析EnableAutoConfiguration

EnableAutoConfiguration的主要作用其实就是帮助springboot应用把所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器中。

再回到EnableAutoConfiguration这个注解中,我们发现它的import是这样

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

但是从EnableAutoCOnfiguration上面的import注解来看,这里面并不是引入另外一个Configuration。而是一个ImportSelector。这个是什么东西呢?

AutoConfigurationImportSelector是什么?

Enable注解不仅仅可以像前面演示的案例一样很简单的实现多个Configuration的整合,还可以实现一些复杂的场景,比如可以根据上下文来激活不同类型的bean,@Import注解可以配置三种不同的class

  1. 第一种就是前面演示过的,基于普通bean或者带有@Configuration的bean进行诸如
  2. 实现ImportSelector接口进行动态注入

实现ImportBeanDefinitionRegistrar接口进行动态注入

CacheService

public class CacheService {
}

LoggerService

public class LoggerService {
}

EnableDefineService

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented 
@Inherited  --允许被继承
@Import({MyDefineImportSelector.class})
public @interface EnableDefineService {String[] packages() default "";
}

MyDefineImportSelector

public class MyDefineImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {//获得指定注解的详细信息。我们可以根据注解中配置的属性来返回不同的class,//从而可以达到动态开启不同功能的目的annotationMetadata.getAllAnnotationAttributes(EnableDefineService.class.getName(),true).forEach((k,v) -> {log.info(annotationMetadata.getClassName());log.info("k:{},v:{}",k,String.valueOf(v));});return new String[]{CacheService.class.getName()};}
}

EnableDemoTest

@SpringBootApplication
@EnableDefineService(name = "mashibing",value = "mashibing")
public class EnableDemoTest {public static void main(String[] args) {ConfigurableApplicationContext ca=SpringApplication.run(EnableDemoTest.class,args);System.out.println(ca.getBean(CacheService.class));System.out.println(ca.getBean(LoggerService.class));}
}

了解了selector的基本原理之后,后续再去分析AutoConfigurationImportSelector的原理就很简单了,它本质上也是对于bean的动态加载。

@EnableAutoConfiguration注解的实现原理

了解了ImportSelector和ImportBeanDefinitionRegistrar后,对于EnableAutoConfiguration的理解就容易一些了

它会通过import导入第三方提供的bean的配置类:AutoConfigurationImportSelector

@Import(AutoConfigurationImportSelector.class)

从名字来看,可以猜到它是基于ImportSelector来实现基于动态bean的加载功能。之前我们讲过Springboot @Enable*注解的工作原理ImportSelector接口selectImports返回的数组(类的全类名)都会被纳入到spring容器中。

那么可以猜想到这里的实现原理也一定是一样的,定位到AutoConfigurationImportSelector这个类中的selectImports方法

selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}
// 从配置文件(spring-autoconfigure-metadata.properties)中加载 AutoConfigurationMetadataAutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// 获取所有候选配置类EnableAutoConfigurationAutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}
//获取元注解中的属性AnnotationAttributes attributes = getAttributes(annotationMetadata);
//使用SpringFactoriesLoader 加载classpath路径下META-INF\spring.factories中,
//key= org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的valueList<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);
//去重configurations = removeDuplicates(configurations);
//应用exclusion属性Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);
//过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载configurations = filter(configurations, autoConfigurationMetadata);//广播事件
fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);
}

本质上来说,其实EnableAutoConfiguration会帮助springboot应用把所有符合@Configuration配置都加载到当前SpringBoot创建的IoC容器,而这里面借助了Spring框架提供的一个工具类SpringFactoriesLoader的支持。以及用到了Spring提供的条件注解@Conditional,选择性的针对需要加载的bean进行条件过滤

SpringFactoriesLoader

为了给大家补一下基础,我在这里简单分析一下SpringFactoriesLoader这个工具类的使用。它其实和java中的SPI机制的原理是一样的,不过它比SPI更好的点在于不会一次性加载所有的类,而是根据key进行加载。

首先,SpringFactoriesLoader的作用是从classpath/META-INF/spring.factories文件中,根据key来加载对应的类到spring IoC容器中。接下来带大家实践一下

创建外部项目jar

<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.13.RELEASE</version>
</dependency>

创建bean以及config

public class mashibingCore {public String study(){System.out.println("good good study, day day up");return "mashibingEdu.com";}
}
@Configuration
public class mashibingConfig {@Beanpublic mashibingCore mashibingCore(){return new mashibingCore();}
}

创建另外一个工程(spring-boot)

把前面的工程打包成jar,当前项目依赖该jar包

<dependency><groupId>com.mashibingedu.practice</groupId><artifactId>mashibing-Core</artifactId><version>1.0-SNAPSHOT</version>
</dependency>

通过下面代码获取依赖包中的属性

运行结果会报错,原因是mashibingCore并没有被Spring的IoC容器所加载,也就是没有被EnableAutoConfiguration导入

@SpringBootApplication
public class SpringBootStudyApplication {public static void main(String[] args) throws IOException {ConfigurableApplicationContext ac=SpringApplication.run(SpringBootStudyApplication.class, args);mashibingCore Myc=ac.getBean(mashibingCore.class);System.out.println(Myc.study());}
}

解决方案

在mashibing-Core项目resources下新建文件夹META-INF,在文件夹下面新建spring.factories文件,文件中配置,key为自定配置类EnableAutoConfiguration的全路径,value是配置类的全路径

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.mashibingedu.practice.mashibingConfig

重新打包,重新运行SpringBootStudyApplication这个类。

可以发现,我们编写的那个类,就被加载进来了。

Spring Boot中的条件过滤

在分析AutoConfigurationImportSelector的源码时,会先扫描spring-autoconfiguration-metadata.properties文件,最后在扫描spring.factories对应的类时,会结合前面的元数据进行过滤,为什么要过滤呢? 原因是很多的@Configuration其实是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以,通过这种条件过滤可以有效的减少@configuration类的数量从而降低SpringBoot的启动时间。

修改mashibing-Core

在META-INF/增加配置文件,spring-autoconfigure-metadata.properties。

com.mashibingedu.practice.mashibingConfig.ConditionalOnClass=com.mashibingedu.TestClass

格式:自动配置的类全名.条件=值

上面这段代码的意思就是,如果当前的classpath下存在TestClass,则会对mashibingConfig这个Configuration进行加载

演示过程(spring-boot)

  1. 沿用前面spring-boot工程的测试案例,直接运行main方法,发现原本能够被加载的mashibingCore,发现在ioc容器中找不到了。

    public static void main(String[] args) throws IOException {ConfigurableApplicationContext ac=SpringApplication.run(SpringBootStudyApplication.class, args);mashibingCore Myc=ac.getBean(mashibingCore.class);System.out.println(Myc.study());
    }
    
  2. 在当前工程中指定的包com.mashibingedu下创建一个TestClass以后,再运行上面这段代码,程序能够正常执行

手写Starter

我们通过手写Starter来加深对于自动装配的理解

1.创建一个Maven项目,quick-starter

定义相关的依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.1.6.RELEASE</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.56</version><!-- 可选 --><optional>true</optional>
</dependency>

2.定义Formate接口

定义的格式转换的接口,并且定义两个实现类

public interface FormatProcessor {/*** 定义一个格式化的方法* @param obj* @param <T>* @return*/<T> String formate(T obj);
}
public class JsonFormatProcessor implements FormatProcessor {@Overridepublic <T> String formate(T obj) {return "JsonFormatProcessor:" + JSON.toJSONString(obj);}
}
public class StringFormatProcessor implements FormatProcessor {@Overridepublic <T> String formate(T obj) {return "StringFormatProcessor:" + obj.toString();}
}

3.定义相关的配置类

首先定义格式化加载的Java配置类

@Configuration
public class FormatAutoConfiguration {@ConditionalOnMissingClass("com.alibaba.fastjson.JSON")@Bean@Primary // 优先加载public FormatProcessor stringFormatProcessor(){return new StringFormatProcessor();}@ConditionalOnClass(name="com.alibaba.fastjson.JSON")@Beanpublic FormatProcessor jsonFormatProcessor(){return new JsonFormatProcessor();}
}

定义一个模板工具类

public class HelloFormatTemplate {private FormatProcessor formatProcessor;public HelloFormatTemplate(FormatProcessor processor){this.formatProcessor = processor;}public <T> String doFormat(T obj){StringBuilder builder = new StringBuilder();builder.append("Execute format : ").append("<br>");builder.append("Object format result:" ).append(formatProcessor.formate(obj));return builder.toString();}
}

再就是整合到SpringBoot中去的Java配置类

@Configuration
@Import(FormatAutoConfiguration.class)
public class HelloAutoConfiguration {@Beanpublic HelloFormatTemplate helloFormatTemplate(FormatProcessor formatProcessor){return new HelloFormatTemplate(formatProcessor);}
}

4.创建spring.factories文件

在resources下创建META-INF目录,再在其下创建spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mashibingedu.autoconfiguration.HelloAutoConfiguration

install 打包,然后就可以在SpringBoot项目中依赖改项目来操作了。

5.测试

在SpringBoot中引入依赖

<dependency><groupId>org.example</groupId><artifactId>format-spring-boot-starter</artifactId><version>1.0-SNAPSHOT</version>
</dependency>

在controller中使用

@RestController
public class UserController {@Autowiredprivate HelloFormatTemplate helloFormatTemplate;@GetMapping("/format")public String format(){User user = new User();user.setName("BoBo");user.setAge(18);return helloFormatTemplate.doFormat(user);}
}

6.自定义Starter关联配置信息

有些情况下我们可以需要用户在使用的时候动态的传递相关的配置信息,比如Redis的Ip,端口等等,这些信息显然是不能直接写到代码中的,这时我们就可以通过SpringBoot的配置类来实现。

首先引入依赖支持

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><version>2.2.6.RELEASE</version><optional>true</optional>
</dependency>

然后创建对应的属性类

@ConfigurationProperties(prefix = HelloProperties.HELLO_FORMAT_PREFIX)
public class HelloProperties {public static final String HELLO_FORMAT_PREFIX="mashibing.hello.format";private String name;private Integer age;private Map<String,Object> info;public Map<String, Object> getInfo() {return info;}public void setInfo(Map<String, Object> info) {this.info = info;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}
}

然后再Java配置类中关联

@Configuration
@Import(FormatAutoConfiguration.class)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {@Beanpublic HelloFormatTemplate helloFormatTemplate(HelloProperties helloProperties,FormatProcessor formatProcessor){return new HelloFormatTemplate(helloProperties,formatProcessor);}
}

调整模板方法

public class HelloFormatTemplate {private FormatProcessor formatProcessor;private HelloProperties helloProperties;public HelloFormatTemplate(HelloProperties helloProperties,FormatProcessor processor){this.helloProperties = helloProperties;this.formatProcessor = processor;}public <T> String doFormat(T obj){StringBuilder builder = new StringBuilder();builder.append("Execute format : ").append("<br>");builder.append("HelloProperties:").append(formatProcessor.formate(helloProperties.getInfo())).append("<br>");builder.append("Object format result:" ).append(formatProcessor.formate(obj));return builder.toString();}
}

增加提示

在这个工程的META-INF/下创建一个additional-spring-configuration-metadata.json,这个是设置属性的提示类型

{"properties": [{"name": "mashibing.hello.format.name","type": "java.lang.String","description": "账号信息","defaultValue": "root"},{"name": "mashibing.hello.format.age","type": "java.lang.Integer","description": "年龄","defaultValue": 18}]
}
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes = getAttributes(annotationMetadata);// 加载当前系统下 META-INF/spring.factories 文件中声明的配置类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);}

SpringBoot初始化核心流程源码

SpringBoot中的监听机制详解

SpringBoot中的属性文件加载原理

SpringBoot中的Tomcat容器加载

SpringBoot中的Acuator监控

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/736048.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

八、词嵌入语言模型(Word Embedding)

词嵌入&#xff08;Word Embedding, WE&#xff09;&#xff0c;任务是把不可计算、非结构化的词转换为可以计算、结构化的向量&#xff0c;从而便于进行数学处理。 一个更官方一点的定义是&#xff1a;词嵌入是是指把一个维数为所有词的数量的高维空间&#xff08;one-hot形式…

小迪安全36WEB 攻防-通用漏洞XSS 跨站MXSSUXSSFlashXSSPDFXSS

#XSS跨站系列内容:1. XSS跨站-原理&分类&手法 XSS跨站-探针&利用&审计XSS跨站另类攻击手法利用 XSS跨站-防御修复&绕过策略 #知识点&#xff1a; 1、XSS 跨站-原理&攻击&分类等 2、XSS 跨站-MXSS&UXSS&FlashXss&PDFXSS 等 1、原…

HCS-华为云Stack-计算节点内部网络结构

HCS-华为云Stack-计算节点内部网络结构 图中表示的仅为计算节点是两网口的模式&#xff0c;如果是四网口模式&#xff0c;系统会再自动创建一个网桥出来 图中未画出存储平面和Internal Base平面&#xff0c;它们和tunnel bearing、External OM-样&#xff0c;都是通过trunk0的…

信息系统项目管理师006:车联网(1信息化发展—1.2现代化基础设施—1.2.3车联网)

文章目录 1.2.3 车联网1.体系框架2.链接方式3.场景应用 记忆要点总结 1.2.3 车联网 车联网是新一代网络通信技术与汽车、电子、道路交通运输等领域深度融合的新兴产业形态。智能网联汽车是搭载先进的车载传感器、控制器、执行器等装置&#xff0c;并融合现代通信与网络技术&…

Linux常用命令之top监测

(/≧▽≦)/~┴┴ 嗨~我叫小奥 ✨✨✨ &#x1f440;&#x1f440;&#x1f440; 个人博客&#xff1a;小奥的博客 &#x1f44d;&#x1f44d;&#x1f44d;&#xff1a;个人CSDN ⭐️⭐️⭐️&#xff1a;传送门 &#x1f379; 本人24应届生一枚&#xff0c;技术和水平有限&am…

for、while、do While、for in、forEach、map、reduce、every、some、filter的使用

for、while、do While、for in、forEach、map、reduce、every、some、filter的使用 for let arr [2, 4, 6, 56, 7, 88];//for for (let i 0; i < arr.length; i) {console.log(i : arr[i]) //0:2 1:4 2:6 3:56 4:7 5:88 }普通的for循环可以用数组的索引来访问或者修改…

SAP BTP Hyperscaler PostgreSQL都有哪些Performance监控 (一)

前言 SAP BTP云平台中&#xff0c;除了自身的HANA数据库作为首选以外&#xff0c;它还支持PostgreSQL的整套服务&#xff0c;并以PaaS的形式提供给客户。你可以按照实例为单位进行购买申请不同标准规格的PG实例&#xff0c;然后构建自己的业务逻辑。Hyperscaler是这套产品或方…

【Python-Docx库】Word与Python的完美结合

今天给大家分享Python处理Word的第三方库&#xff1a;Python-Docx。 什么是Python-Docx&#xff1f; Python-Docx是用于创建和更新Microsoft Word&#xff08;.docx&#xff09;文件的Python库。 日常需要经常处理Word文档&#xff0c;用Python的免费第三方包&#xff1a;Pyt…

【Linux】Shell及Linux权限

Shell Shell的定义 Shell最简单的定义是&#xff1a;命令行解释器。 Shell的主要任务&#xff1a;1. 将使用者的命令翻译给核心进行处理。2.将核心的处理结果翻译给使用者 为什么要有Shell? 使用者和内核的关系就相当于两个完全陌生的外国人之间的关系&#xff0c;他们要进…

Linux:线程互斥与同步

目录 线程互斥 锁的初始化 加锁 解锁 锁的初始化 锁的原理 死锁 线程同步 方案一&#xff1a;条件变量 条件变量初始化 等待 唤醒 条件变量的代码示例 基于阻塞队列的生产消费模型 方案二&#xff1a;POSIX信号量 初始化信号量&#xff1a; 销毁信号量 等待信…

中国大学生计算机设计大赛--智慧物流挑战赛基础

文章目录 一、Ubuntu基础1.1 基本操作1.2 文本编辑 二、ROS基础介绍2.1 概念与特点2.2 基本结构2.3 创建工程2.4 节点和节点管理器2.5 启动文件 三、ROS通信机制3.1 话题3.2 服务3.3 动作3.4 参数服务器 四、ROS可视化工具4.1 rviz4.2 rqt4.3 tf 五、Python实现简单的ROS节点程…

01-分析同步通讯/异步通讯的特点及其应用

同步通讯/异步通讯 微服务间通讯有同步和异步两种方式 同步通讯: 类似打电话场景需要实时响应(时效性强可以立即得到结果方便使用),而且通话期间不能响应其他的电话(不支持多线操作)异步通讯: 类似发邮件场景不需要马上回复并且可以多线操作(适合高并发场景)但是时效性弱响应…

MQ高可用相关设置

文章目录 前言MQ如何保证消息不丢失RabbitMQRocketMQKafkaMQ MQ如何保证顺序消息RabbitMQRocketMQKafka MQ刷盘机制/集群同步RabbitMQRocketMQKafka 广播消息&集群消息RabbitMQRocketMQ MQ集群架构RabbitMQRocketMQKafka 消息重试RabbitMQRockeMqKafka 死信队列RocketMQKaf…

Claude3横空出世:颠覆GPT-4,Anthropic与亚马逊云科技共启AI新时代

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

车载电子电器架构 —— 汽车电子电气系统分解

车载电子电器架构 —— 汽车电子电气系统分解 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何…

【操作系统概念】第14章:系统保护

文章目录 0. 前言14.1 保护目标14.2 保护原则14.3 保护域14.3.1 域结构14.3.2 实例&#xff1a;UNIX14.3.3 实例&#xff1a;MUTICS 14.4 访问矩阵14.5 访问矩阵的实现14.5.1 全局表14.5.2 对象的访问列表14.5.3 域的能力(权限)列表14.5.4 锁-钥匙机制*14.5.5 比较* 14.6 访问控…

Github 2024-03-10php开源项目日报Top10

根据Github Trendings的统计,今日(2024-03-10统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10Blade项目1Laravel:表达力和优雅的 Web 应用程序框架 创建周期:4631 天开发语言:PHP, BladeStar数量:75969 个Fork数量:24281 次…

Vue脚手架

Vue脚手架 学习目标&#xff1a; 理解Node.js基本使用方法理解包资源管理器NPM的使用理解webpack的作用理解 vue-cli 脚手架 (重点)Element-UI 组件库 1.vue的格式&#xff1a;new Vue({//作用的视图el:"id选择器",//vue中的数据/*data:{key:value,key:value,...}…

Java 基于微信小程序的快递柜小程序

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

devops-Jenkins【内网环境部署及插件安装】

1、准备工作 外网Linux机器一台&#xff0c;内网Linux机器一台。硬件环境要求&#xff1a;至少1GB的可用内存空间&#xff0c;至少50GB的可用硬盘空间。软件环境需求&#xff1a;需要安装好Java8&#xff0c;Java的运行环境JRE1.8或者Java的开发工具包JDK1.8都可以。 2、外网安…