Springboot的自动注入

一、开篇

 在平时的开发过程中用的最多的莫属springboot了,都知道springboot中有自动注入的功能,在面试过程中也会问到自动注入,你知道自动注入是怎么回事吗,springboot是如何做到自动注入的,自动注入背后的原理是什么,今天来分析下springboot的自动注入,希望这篇文章可以解除大家心中的疑惑。

二、详述

2.1、什么是自动注入

天天将自动注入,你真正明白自动注入是怎么回事吗?举个例子来说,我们要在springboot中使用mybatis,之前的做法是什么?

1、引入依赖;

2、在配置文件中配置配置类;

3、写mybatis的配置文件或注解;

在springboot中这个步骤就减少了,减少的是第二步,不用再写一堆配置类了,步骤简化为:

1、引入依赖;

2、写mybatis的配置文件或注解;

也就是说无需再搞配置类了,就比如之前的”SqlSessionFactoryBean“,现在不用配置了,springboot为我们做了这些工作,现在看springboot引入mybatis需要加入的依赖,


        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.1.3

        

        
        
            mysql
            mysql-connector-java
            8.0.26
        

我们加入mybatis和数据库的驱动依赖,因为mybatis要使用数据库连接,所以这里少不了mysql的数据库驱动。重点看mybatis的这个依赖和之前的是不一样的,这个是”mybatis-spring-boot-starter“,再看这个依赖中都有哪些jar


    
      org.springframework.boot
      spring-boot-starter
    
    
      org.springframework.boot
      spring-boot-starter-jdbc
    
    
      org.mybatis.spring.boot
      mybatis-spring-boot-autoconfigure
    
    
      org.mybatis
      mybatis
    
    
      org.mybatis
      mybatis-spring
    
  

除了常见的mybatis及mybatis-spring还有一个mybatis-spring-boot-autoconfigure,这个就是今天的主角。

2.2、springboot读取spring.facotries文件

2.2.1、SpringApplication构造方法

springboot的启动很简单,就是下面这样一行代码

SpringApplication.run(BootServer.class);

 要跟着这样一行代码走下去,追踪到了这样一句,

public static ConfigurableApplicationContext run(Class[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

可以看的会new一个SpringApplication的实例,然后再调用其run方法,先看下new方法做了什么,最终调用的是下面的构造方法

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
         //重要-设置初始化器
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
         //重要-设置监听器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

我在上面 做了注释,重点看注释部分的代码;

2.2.2、setInitializers()方法

该方法从方法名上看是要设置初始化器,其中getSpringFactoriesInstances(ApplicationContextInitializer.class)是重点。其方法定义如下

private  Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
        //SpringFactoriesLoader.loadFactoryNames是重点
		Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

看SpringFactoriesLoader.loadFactoryNames方法,

public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
         //loadSpringFactories(classLoader)方法是重点
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

把断点放在loadSpringFactroies方法内

从上面的debug结果可以看到使用AppClassLoader读取FACTORIES_RESOURCE_LOCATION“处的资源,AppClassLoader大家都很熟悉,就说应用类加载器,常量FACTORIES_RESOURCE_LOCATION“指的是

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

文件内容如下:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

可以看到是一些列的键值对,我们看下loadSpringFactories方法最后的返回值

Springboot的自动注入_第1张图片

这个返回值是,项目中所有jar下META-INF/spring.factories文件中的键值对组成的map。回到loadFactoryNames方法处 

Springboot的自动注入_第2张图片

 该方法需要的是key为”org.springframework.context.ApplicationContextInitializer“的value,该value的值有这样8个

Springboot的自动注入_第3张图片

 对应的配置文件内容: 

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

 这里只有5个, 前三个应该是系统配置

这样我们把setInitializers方法就分析完了,其主要就是从jar包中的META-INF/spring.factories文件中获取org.springframework.context.ApplicationContextInitializer对应的值。下面看setListeners方法

2.2.3、setListeners()方法

该方法和setInitializers方法是类似的

Springboot的自动注入_第4张图片

重点是其参数不一样,该方法的参数是ApplicationListener.class,也就是要找出org.springframework.context.ApplicationListener在spring.factories中的配置

Springboot的自动注入_第5张图片

 文件内容如下:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener

和断点配置一样, 前三个可能是系统配置

写到这里其实和自动注入没有关系,如果说有关系的话是,这里认识了一个关键的类”SpringFactoriesLoader“,该类的作用就是读取jar包中META-INF/spring.facotries文件的内容。在后边的自动注入中还会出现该类的影子。继续向前。

2.3、自动注入的原理

2.3.1、@SpringBootApplication注解

在启动springboot程序的时候在程序的入口都会有写上@SpringBootApplication的注解 

 

@SpringBootApplication
public class BootServer {
    public static void main(String[] args) {
        try {
            SpringApplication.run(BootServer.class);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 在该注解上还有@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解,今天重点看@EnableAutoConfiguration注解。

2.3.2、@EnableAutoConfiguration注解

 该注解便是自动注入的核心注解

Springboot的自动注入_第6张图片

  重点是该注解上的下面这句话

@Import(AutoConfigurationImportSelector.class)

看下AutoConfigurationImportSelector类,该类中有这样一个方法,和自动注入是相关的

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List 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;
	}

很属性的SpringFactoriesLoader类又出现了,还是很熟悉的loadFactoryNames方法,这次的方法参数是getSpringFactoriesLoaderFactoryClass()方法

protected Class getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

 所以SpringFactoriesLoader.loadFactoryNames是要从META-INF/spring.factories中获取key为”org.springframework.boot.autoconfigure.EnableAutoConfiguration“的value,这里可以看到有很多,从中还可以找到我自定义的和myatis的

Springboot的自动注入_第7张图片

 也就是说要把这些配置类加到spring的容器中。现在有个问题这些配置都会生效吗?

2.3.3、这些配置类都会生效吗?

上面说到自动配置会加载很多的配置类,但是这些类都会生效吗?答案是不会的,只会在特定情况下生效,以MybatisAutoConfiguration为例

Springboot的自动注入_第8张图片

可以看的该类上有很多注解:

  @ConditionalOnClass,当类路径中存在某个类标识该注解的类才会生效,也就是只有存在SqlSessionFactory、SqlSessionFactoryBean才会解析MybatisAutoConfiguration类。换句话说,要有mybatis、mybatis-spring的jar包。

  @ConditionaleOnSigleCanidate,需要一个单例bean

  @EnableConfigurationProperties  读取配置文件,也就是application.properites

  @AutoConfigureAfter  自动配置在某个类之后

现在我们知道了一个XXAutoConfiguration类是否会生效还要看其上面的注解是怎么定义的。


三、总结

  本文主要分析了springboot的自动注入原理,

  1、注解@SpringBootApplication中含有三个注解,其中@EnabelAutoConfiguration和自动配置有关;

  2、@EnableAutoConfiguration会读取所有jar下META-INF/spring.factories文件的内容,获取”org.springframework.boot.autoconfigure.EnableAutoConfiguration“的配置,把这些配置注入到容器;

  3、@EnableAutoConfiguration注入的类是否生效,需要看其上面的注解,主要配合@ConditionaleXXX注解使用;

本文参考: https://www.cnblogs.com/teach/p/16411991.html

 

 

你可能感兴趣的:(spring,boot,java,spring)