SpringBoot自动装配原理(源码分析)—看不懂来揍我!

目录

背景

自动装配干啥的

@SpringBootApplication

SpringApplication注解源码

1、元注解

@Retention

@Taget

@Inherited

@Documented

@intertface

2、@SpringBootConfiguration

3、@EnableAutoConfiguration(重点)

1、@AutoConfigurationPackage

2、@Import(AutoConfigurationImportSelector.class)

3、重写selectImports()


背景

        在我平时写项目时,都是业务逻辑为主,代码是抄了又抄,最后发现代码写不少,但是真遇到比较细节的,比如为什么Bean没有装配进去之类的问题,就直接百度找解决办法了,最后还是想自己研究一下。

        如果在文章中有什么不对或者不理解的地方,直接私信或者留言,我看到就会及时回复,或者关注我的公众号:Java小白,私信我也可以哈。

自动装配干啥的

        其实就是系统在项目启动,或者项目依赖包方面,Spring做了整合,由之前的导入jar包,变为现在的直接引入pom就可以直接使用了。

        现在我们从@SpringBootApplication注解来一步步看一下

@SpringBootApplication

        在项目启动类里面标记了@SpringBootApplication表示这个类为主启动类,并且带有main方法,main方法中执行了SpringApplication.run()方法

//我们这里主要看这个注解干了什么
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        //run方法同样也很重要,详情请看我的博客
        SpringApplication.run(Application.class,args);
    }

}

SpringApplication注解源码

//1、前四个就是元注解,系统原生注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//2、可以简单理解为@Configuration的替代注解,用来自动自动找到配置
@SpringBootConfiguration
//3、自动注入(重点)
@EnableAutoConfiguration
//4、指定扫描或者不扫描哪些包
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
            ......
}

按照我上面代码块中注释的点,逐个分析一下:(如果想直接看自动注入,那就跳转到第三点)

1、元注解
元注解解释
注解说明
@Retention是注解类,实现声明类 Class,声明类别 Category,声明扩展 Extension
@Taget放在自定义注解的上边,表明该注解可以使用的范围
@Inherited允许子类继承父类的注解,在子类中可以获取使用父类注解
@Documented表明这个注释是由 Javadoc 记录的
@intertface用来自定义注释类型

@Retention

        该注解用于说明自定义注解的生命周期,在注解中有三个生命周期。

  • RetentionPolicy.RUNTIME:始终不会丢弃, 运行期也保留读注解,可以使用反射机制读取该注解的信息。 自定义的注解通常使用这种方式。
  • RetentionPolicy.CLASS:类加载时丢弃,默认使用这种方式。
  • RetentionPolicy.SOURCE:编译阶段丢弃,自定义注解在编译结束之后就不再有意义,所以它们不会写入字节码。@Overide、@SuppressWarnings 都属于这类注解。
@Taget

        该注解的作用是告诉 Java 将自定义的注解放在什么地方,比如类、方法、构造器、变量上等,它的值是一个枚举类型,有一些属性:

  • ElementType.CONSTRUCTOR:用于描述构造器。
  • ElementType.FIELD:用于描述成员变量、对象、属性( 包括enum实例 )。
  • ElementType.LOCAL_VARIABLE:用于描述局部变量。
  • ElementType.METHOD:用于描述方法。
  • ElementType.PACKAGE:用于描述包。
  • ElementType.PARAMETER:用于描述参数。
  • ElementType.TYPE:用于描述类、接口( 包括注解类型 )或 enum 声明。
@Inherited

        该注解是一个标记注解,表明被标注的类型是可以被继承的。如果一个使用了 @Inherited 修饰的 Annotation 类型被用于一个Class,则这个 Annotation 将被用于该 Class 的子类。

@Documented

        该注解表示是否将注解信息添加在 Java 文档中。

@intertface

        该注解用来声明一个注解, 其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型( 返回值类型只能是基本类型、Class、String、enum )。可以通过 default 来声明参数的默认值。

定义注解格式的代码:

public @interface 注解名 {
    ......
}
2、@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
            ......
}

        可以看到也是引用了@Configuration注解,相当于对@Configuration做了包装,所以称@SpringBootConfiguration可以是替代品,用了这个注解表示能够像xml配置文件,现在是用java配置文件。并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名,例如:

package com.zhanghao.config;
 
import java.util.HashMap;
import java.util.Map;
 
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
 
@SpringBootConfiguration
public class Config {
    @Bean
    public Map createMap(){
        Map map = new HashMap();
        map.put("username","zhanghao");
        map.put("age",27);
        return map;
    }
}

        别的地方想用直接获取这个Bean名字就可以了。

3、@EnableAutoConfiguration(重点)

        启用Spring应用程序上下文的自动配置,尝试猜测和配置可能需要的bean。自动配置类通常是根据类路径和您定义的bean来应用的。例如,如果您的类路径上有tomcat embedded.jar,那么你可能想要一个TomcatServletWebServerFactory,除非你写了自定义的ServletWebServerFactory的bean。

        所以我们直接使用一个@SpringBootApplication放在主启动类上就行了,因为用了@SpringBootApplication之后,上下文的自动配置会自动启用(因为它里面就包含了@EnableAutoConfiguration)。

        并且,这个注解是在@Configuration或者@SpringConfiguration注解加载完Bean之后才会加载(我不清楚到底是谁在控制注解加载顺序,但我猜肯定是框架默认的顺序)。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//1、注册包的
@AutoConfigurationPackage
//2、引入自动注入类(重点)
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	Class<?>[] exclude() default {};

	String[] excludeName() default {};

}

        元注解就不看了啊

1、@AutoConfigurationPackage

        注册程序包,如果未指定basePackages基包或basePackageClasses基包类,则会注册带注释类的包,简单而言,就是将主配置类(@SpringBootApplication标注的类)所在包以及子包里面的所有组件扫描并加载到spring的容器中,这也就是为什么我们在利用springboot进行开发的时候,无论是Controller还是Service的路径都是与主配置类同级或者次级的原因。

2、@Import(AutoConfigurationImportSelector.class)

        @Import这个注解就是把AutoConfigurationImportSelector.class这个类注册为Bean,没啥好说的了,相当于手动注入了。

        AutoConfigurationImportSelector.class这个类就有的说了:

public class AutoConfigurationImportSelector implements 
DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
                                ......
}

        直接就可以看到,这类实现了很多接口:

BeanClassLoaderAware:Bean加载接口,用来通知@Bean的加载

ResourceLoaderAware:Resource加载接口,用来通知@Resource的加载

BeanFactoryAware:Bean工厂的加载

EnvironmentAware:获取系统环境信息

Ordered:类排序接口(具体咋用不知道,没研究过这个)

DeferredImportSelector:

        {@ImportSelector}的变体,在处理完所有{@Configuration}bean后运行。实现还可以扩展{org.springframework.core.Ordered}接口,或者使用{@link.org.springframework.core.annotation.Order}注释来指示相对于其他{@link DeferredImportSelector DeferredImport Selector}的优先级<p> 实现还可以提供{@link getImportGroup()import group},它可以在不同的选择器之间提供额外的排序和过滤逻辑。

        这是源码中的注释,简单来说就是继承了ImportSelector接口,处理完@Configuration或者@SpringConfiguration之后才会加载,也可以搭配@Order注解对某个类进行优先级的加载。

        所以这个接口才是@EnableAutoConfiguration为什么会在@Configuration或者@SpringConfiguration之后才会加载的主要原因。

        那么实现了这个接口,它的主要方法selectImports()是如何重写的?

3、重写selectImports()

现在回到AutoConfigurationImportSelector类中看一下:

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

这里进入方法首先判断了这么一个东西,我还纳闷这个参数AnnotationMetadata 是什么东西。。。。。

if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}

随后我看了一下isEnabled方法:

protected boolean isEnabled(AnnotationMetadata metadata) {
		if (getClass() == AutoConfigurationImportSelector.class) {
			return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
		}
		return true;
	}

好像没有用到这个参数,最后还是判断了一下

EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY

//它的值是:

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

 enableautoconfiguration不就表示可以自动配置么,所以这个if判断就是判断一下是否开启了自动配置罢了,和那个Metadata参数没鸡毛关系。。。。。

随后来到了下面的方法

	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);
	}

List<String> configurations = getCandidateConfigurations(annotationMetadata, 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;
	}

在断言表达式中我看到了:No auto configuration classes found in META-INF/spring.factories

表示断言上面的方法将会寻找这个配置文件

首先看一下,这个方法SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),          getBeanClassLoader())的参数:

        第一个getSpringFactoriesLoaderFactoryClass()是返回EnableAutoConfiguration用来加载候选配置的类,我理解就是加载配置文件里面记录的类;

        第二个getBeanClassLoader()是返回一个Bean加载器,类加载器有很多种,这个可以自己去了解一下,或者看我的博客文章,这里先不说了

        再进入方法loadFactoryNames()

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

这里就印证了上面那句话“回EnableAutoConfiguration用来加载候选配置的类” 

loadSpringFactories(classLoader)

         进入方法

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<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

 其中标红的全局变量看一下是什么?

public static final String FACTORIES_RESOURCE_LOCATION =

"META-INF/spring.factories";

到这里默认配置文件已经出来了 ,这时候你想去找一下这个文件在哪

去依赖包里面找一下

 可以看到有两个,下面那个才是自动配置的文件

进去可以看到

 EnableAutoConfiguration=a,b,c,d,e,f....

它们是以key=value键值对儿来存放的,值里面可以看到很多我们项目中用到的:jdbc,es,redis,datasourse,shiro,http等,

加载完这些值后,再继续看代码

 最终把加载出来的值通过IO方式加载到Properties中,返回至AutoConfigurationImportSelector中的AutoConfigurationEntry中。

        到这里自动装配就算完事儿了,那会有个疑问,这个selectImports()方法什么时候用的呢?后面文章再说!

        我还整理汇总了⼀些 Java ⾯试相关的⾼质量 PDF 资料和免费Idea账号

        微信公众号:Java小白

欢迎关注!!

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Spring Boot自动装配原理可以分为以下几个关键步骤: 1. 启动过程:在Spring Boot应用启动时,会执行SpringApplication类的run方法。该方法会创建Spring应用上下文,并触发自动装配过程。 2. 自动装配过程:自动装配过程主要依赖于条件注解和自动配置类。 - 条件注解:Spring Boot提供了一系列条件注解,如@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnProperty等。这些注解可以根据特定的条件来决定是否进行自动配置。 - 自动配置类:自动配置类使用@Configuration注解进行标识,通过@Bean注解来声明需要自动配置的Bean。自动配置类通常使用@Conditional注解来指定条件,只有当满足条件时才会生效。 3. 自动配置类的加载:Spring Boot会自动扫描项目中的META-INF/spring.factories文件,该文件中定义了需要自动配置的类。Spring Boot会根据这些类的条件进行加载和实例化。 4. Bean的创建:在自动配置类中使用@Bean注解声明的Bean会被Spring容器自动创建和管理。这些Bean的创建过程会经过Spring的各个处理器,如BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor等。 下面是一个简单的示例来说明Spring Boot自动装配原理: ```java @Configuration @ConditionalOnClass(MyService.class) public class MyAutoConfiguration { @Bean public MyService myService() { return new MyService(); } } ``` 在上述示例中,@ConditionalOnClass注解表示只有当MyService类在类路径中存在时,该自动配置类才会生效。当应用启动时,Spring Boot会扫描到该自动配置类,并根据条件判断是否需要进行自动配置。如果满足条件,Spring Boot会实例化并管理MyService的Bean。 通过自动装配Spring Boot可以根据项目的实际需求,自动配置和管理各种Bean,从而减少了开发人员的配置工作,提高了开发效率。 请注意,上述示例只是简单示意,并不涵盖所有细节。实际的自动装配过程还涉及到条件判断、依赖关系处理、配置属性绑定等复杂的逻辑。如果你想深入了解Spring Boot自动装配原理,建议阅读Spring Boot源码以及相关文档。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

派大星的无情铁锤

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

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

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

打赏作者

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

抵扣说明:

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

余额充值