[Java] Spring Boot Auto Configure(Spring Boot自动装配)的原理

前言


Spring Boot能帮助我们Java开发者快速开发基于Spring框架的应用,除了其作为依赖管理好帮手的一众Spring-Boot-Starter之外,其自动装配(Auto Configure)特性也起到了非常重要的作用。那么Spring Boot是如何实现自动装配的呢?本文将结合源码去讲解其原理。

版本信息


  • spring-boot-autoconfigure-2.7.5.jar
  • spring-context-5.3.23.jar
  • dubbo-3.1.1.jar
  • dubbo-spring-boot-autoconfigure-3.1.1.jar

前置知识


Spring框架里最重要的模块莫过于IoC容器,流畅阅读本文的前提是读者有IoC容器的基本概念。
我们先来梳理一些概念,Spring IoC容器里的所有组件(Component,构建完整应用的基本单元)都被称为Bean。Spring IoC容器不是神,当想要帮助其使用者实例化某个具体的Bean时,需要一些额外的信息才能做到这些额外的信息被称为Meta-data(元数据或元信息),例如多个构造器选择哪个、构造器参数信息、是否需要Autowire处理等等信息,在Spring框架里,其被抽象为BeanDefinition接口,大概长下面这个样子。
BeanDefinition结构
就像如果我们要解析JSON字符串需要调JSON解析库把其转成JSON对象再从里获取你需要的信息(数据)一样,创建实例所用的这些个BeanDefinition信息是不会自己跑到Spring IoC容器里的,这需要Spring IoC容器自己去收集,收集BeanDefinition信息是Spring IoC容器的几大主要工作之一。那么Spring框架是如何收集这些信息的呢?

Spring框架如何收集BeanDefinition信息?

既然要收集信息,就不得不提信息载体,信息载体是承载信息(数据)的东西,但凡一个物体能表示1bit的信息都能被视为信息的载体,可见信息的载体有很多种,就例如各种格式的文本文件、序列化后的二进制文件、Java内存里的(Java注解、类名、方法名、参数名、参数类型)等等都是信息的载体。那么相信大家已经猜到了,Spring收集BeanDefinition信息的方式大家至少可以有这么几种:

  • XML配置:Spring所谓的XML config方式,通过XML文本文件存储信息。
  • Java配置:Spring所谓的Java config方式,利用反射API收集注解、类名、方法名、参数名、参数类型携带的信息。

Java配置又可以细分成几种:

  • 如@Component、@Service、@Controller之类的通常是Spring通过反射API和缺省信息去收集BeanDefinition信息。
  • 如@Configuration之类的则是由开发者自主装配Bean。Spring不需要费太大力气便能利用广大开发者写的Bean方法去实例化某个Bean。

除开上面两个,还有如ImportSelector接口、ImportBeanDefinitionRegistrar接口这两种方式:

  • ImportBeanDefinitionRegistrar接口:模块开发者直接向IoC容器里注册BeanDefinition,如dubbo里就用了这种方式。
  • ImportSelector接口:递归地注册ImportSelector#SselectImports方法所发现的所有ImportSelector、ImportBeanDefinitionRegistrar、@Configuration类相关的BeanDefinition。

虽然再细分ImportSelector还能分出DeferredImportSelector,不过这不在本文的讨论范畴之内,咱们先忽略它。

Spring Boot 自动装配的原理


Spring Boot自动装配本质就是向IoC容器提供BeanDefinition的过程。其用到了我们上述的四种Java Config中的后三种。

  • ImportSelector接口
  • ImportBeanDefinitionRegistrar接口
  • @Configuration类

Spring框架的Context核心模块提供了这些功能

上面提到的分三类的处理方式并不是Spring Boot提供的,而是Spring Context提供的。
你可以在下面方法里看到其处理的逻辑:

Spring-Context-5.3.23.jar文件的
org.springframework.context.annotation.ConfigurationClassParser类的
processImports方法
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {
	/* 省略 */
	for (SourceClass candidate : importCandidates) {
		if (candidate.isAssignable(ImportSelector.class)) { 
			// Candidate class is an ImportSelector -> delegate to it to determine imports
			/* 省略 */ 
		} else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
			// Candidate class is an ImportBeanDefinitionRegistrar ->
			// delegate to it to register additional bean definitions
			/* 省略 */
		} else {
			// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
			// process it as an @Configuration class
			/* 省略 */
		}
	}
	/* 省略 */
}

Spring Boot则利用了Spring Context提供的功能来实现自动装配。

@EnableAutoConfiguration:Spring Boot开启自动装配特性的注解

想要Spring Boot开启自动装配特性,我们是需要在程序入口类使用@EnableAutoConfiguration注解来开启自动装配功能,也许大家没怎么见过这个注解,不过一般使用了组合注解@SpringBootApplication的话就是等于开启了@EnableAutoConfiguration的,@SpringBootApplication注解的定义如下:

SpringBootApplication注解

@EnableAutoConfiguration本身也是一个组合注解,定义如下:

EnableAutoConfiguration注解
@Import(AutoConfigurationImportSelector.class)则是其最重要的部分,不难看出其核心是一个我们上面提到过的ImportSelector,@Import注解是向IoC容器里引入一个Bean,这里就引入了AutoConfigurationImportSelector类。

Spring Boot提供的AutoConfigurationImportSelector类干了什么?

相信大家都对spring.factories文件或多或少都有耳闻。对,你猜的没错,简单来AutoConfigurationImportSelector类主要干了两件事

  1. 利用SpringFactoriesLoader类从ClassPath上所有的META-INF/spring.factories文件读取上述三种自动配置类的信息(类名)。

SpringFactoriesLoader源码

  1. 利用ImportCandidates类从ClassPath上所有的META-INF/spring/org.springframework.boot.autoconfiguration.AutoConfiguration.imports文件里读取上述三种自动配置类的信息(类名)。

ImportCandidates源码

AutoConfigurationImportSelector类种利用这两个类的源码如下,第二种方式是较新的,而spring.factories文件的方式则是较老的一种实现。目前在笔者的spring boot 2.7.5版本里是两者都支持的(也就是提供了向下兼容性)。可以看到下方源码中,先调用了SpringFactoriesLoader,再调用了ImportCandidates。
AutoConfigurationImportSelector源码

AutoConfigurationImportSelector支持的两类文件长什么样?


在上一节我们提到了AutoConfigurationImportSelector类支持两类文件来做扩展,分别是:

  • 较老的META-INF/spring.factories文件
  • 较新的META-INF/spring/org.springframework.boot.autoconfiguration.AutoConfiguration.imports文件

1. META-INF/spring.factories文件

第一个是较老的META-INF/spring.factories文件,其本质是个Properties格式的文本文件。内容就是KEY=VALUE。
就比如说我们看一下dubbo-spring-boot-autoconfigure-3.1.1.jar里的这个文件就长这样:

dubbo的spring.factories文件

2. META-INF/spring/org.springframework.boot.autoconfiguration.AutoConfiguration.imports文件

第二个是比较新的META-INF/spring/org.springframework.boot.autoconfiguration.AutoConfiguration.imports,其就是个普通的换行符分割文本文件
我们看一下spring-boot-autoconfigure-2.7.5.jar里的这个文件长这样:

新文件

文件内容

两种文件的内容普遍都是@Configuration类的全名(包名+类名),这里的数据本质是字符串,读到内存里还是需要利用反射API转换成Class<?>实例再开始使用的,就是由ConfigurationClassParser$DeferredImportSelectorHandler的processGroupImports方法来实现的。可以看到内部调用了我们先前提到的processImports方法。
processGroupImports

反射API具体使用的地方就是上面代码里的调用asSourceClass方法了:

asSourceClass方法

结语


通过源码分析,我们可以看到Spring Boot利用了Spring Context提供的加载机制,其先利用@Import把自身最关键的组件AutoConfigurationImportSelector引入到容器里,并且引入ImportSelector是会递归引入AutoConfigurationImportSelector在两种文件里找到的所有的类,也是这种动态地加载所有这两类文件的机制是Spring Boot自动装配实现扩展性的核心。使得其他项目、组件的开发者可以利用这个扩展机制(如编写自己的spring.factories文件、@Configuration类等)去支持或叫实现自动装配。但不难看出Spring Boot虽然实现了自动装配,但实际的装配工作(@Configuration类、ImportSelector、ImportBeanDefinitionRegistrar的编码工作)还是需要由对应的项目开发者去实现的,比如dubbo就是自己实现的相应的spring boot自动装配类。

Java Spring Boot可以使用Spring Security来实现账号密码登录和注册。Spring Security是一个框架,用于在Java应用程序中提供身份验证和授权服务。以下是一个简单的示例。 首先,需要在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 接下来,需要创建一个用户实体类,包含用户名和密码字段: ``` @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String username; @Column(nullable = false) private String password; // getters and setters } ``` 然后,需要创建一个实现UserDetailsService接口的类,用于从数据库中获取用户信息: ``` @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found"); } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>()); } } ``` 在这个类中,通过UserRepository从数据库中获取User对象,然后将其转换为Spring Security的UserDetails对象。 最后,在Spring Boot的配置文件中配置Spring Security: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/register").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/") .permitAll() .and() .logout() .logoutUrl("/logout") .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); } } ``` 在这个配置类中,首先注入UserDetailsService,然后配置HttpSecurity,指定哪些URL需要身份验证,哪些URL不需要身份验证。然后,配置登录页面、登录成功后跳转的页面和退出登录的URL。 最后,在configureGlobal方法中,使用UserDetailsService和PasswordEncoder配置AuthenticationManagerBuilder。 这样,就实现了使用Spring Security实现账号密码登录和注册。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值