SpringBoot 自动装配
- [SpringBoot 自动装配原理](#SpringBoot 自动装配原理)
-
- 详细介绍
- [自定义 Spring Boot Starter](#自定义 Spring Boot Starter)
SpringBoot 自动装配原理
Spring Boot的自动装配是通过@EnableAutoConfiguration
注解来实现的,该注解包含了一系列的自动装配配置类,这些配置类会根据项目的依赖和配置,自动地配置应用程序上下文中的Bean。
SpringBoot 应用的启动类上都有一个 @SpringBootApplication
注解,该注解包含 @EnableAutoConfiguration
注解。
@EnableAutoConfiguration
注解包含两个重要注解:
@AutoConfigurationPackage
:- 该注解是用于标记主配置类(通常是Spring Boot应用程序的入口类),以指示在进行自动配置时应该扫描的基本包。它会将该类所在的包及其子包纳入自动配置的扫描范围。
@Import({AutoConfigurationImportSelector.class})
:- 该注解用于导入一个配置选择器,即
AutoConfigurationImportSelector
类。 AutoConfigurationImportSelector
是Spring Boot自动配置的核心,它负责从类路径下的META-INF/spring.factories
文件中加载自动配置类的候选列表,并根据条件选择合适的自动配置类导入到Spring容器中。- 通过
@Import
注解将AutoConfigurationImportSelector
引入到主配置类中,以启用自动配置的机制。
- 该注解用于导入一个配置选择器,即
装配流程如下:
- 主配置类上的
@EnableAutoConfiguration
触发自动配置的启用。 @EnableAutoConfiguration
包含@AutoConfigurationPackage
和@Import({AutoConfigurationImportSelector.class})
。@AutoConfigurationPackage
标记了要扫描的基本包。@Import({AutoConfigurationImportSelector.class})
导入了AutoConfigurationImportSelector
,启动自动配置的核心。AutoConfigurationImportSelector
根据条件加载META-INF/spring.factories
文件中的自动配置类候选列表。- 过滤掉不符合条件的自动配置类,移除重复的自动配置类,获取需要排除的自动配置类。
- 最终,将符合条件的自动配置类导入到Spring容器中。
详细介绍
AutoConfigurationImportSelector
实现了 DeferredImportSelector
接口,用于延迟导入配置类的选择器。它允许在运行时决定要导入的配置类。通常,它用于实现一些自定义逻辑,以便根据运行时条件来选择性地导入配置。
DeferredImportSelector
定义了一个方法:
java
String[] selectImports(AnnotationMetadata importingClassMetadata);
AutoConfigurationImportSelector
对这个方法的实现
java
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// isEnabled(annotationMetadata): 用于判断是否启用了自动配置
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// * getAutoConfigurationEntry(annotationMetadata) 获取自动配置的条目,其中包含了要导入的配置类的信息。
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
getAutoConfigurationEntry()
java
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// * 获取候选的自动配置类的全限定类名列表
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 移除重复的自动配置类
configurations = this.removeDuplicates(configurations);
// 获取需要排除的自动配置类的全限定类名列表
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 检查是否有重复排除的自动配置类,如果有则抛出异常
this.checkExcludedClasses(configurations, exclusions);
// 移除需要排除的自动配置类
configurations.removeAll(exclusions);
// 获取配置类的过滤器,并过滤掉不符合条件的自动配置类
configurations = this.getConfigurationClassFilter().filter(configurations);
// 触发自动配置导入事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回一个AutoConfigurationEntry对象,包含了最终要导入的自动配置类的信息。
return new AutoConfigurationEntry(configurations, exclusions);
}
}
getCandidateConfigurations()
:获取候选配置
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
/*
使用SpringFactoriesLoader加载META-INF/spring.factories文件中的配置。
this.getSpringFactoriesLoaderFactoryClass()返回工厂类的类名,通常是org.springframework.boot.autoconfigure.EnableAutoConfiguration。
这里加载的是自动配置的候选类的全限定类名。
相当于根据 key 获取 value
*/
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
// 使用Assert来确保最终得到的自动配置类列表不为空,如果为空,则抛出异常。
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
loadFactoryNames()
:
java
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
// * 调用loadSpringFactories方法加载META-INF/spring.factories文件中的配置。
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
查看 Spring Boot 自动装配源码可以看到上面的代码就是加载 META-INF/spring.factories
中键org.springframework.boot.autoconfigure.EnableAutoConfiguration
的值
自定义 Spring Boot Starter
以支付宝沙箱支付为例
新建一个项目,启动类和配置文件都删掉,创建META-INF/spring.factories
。
1.读取配置文件
java
@Data
@ConfigurationProperties(prefix = "alipay")
public class PayProperties {
private String appId;
private String appPrivateKey;
private String alipayPublicKey;
private String notifyUrl;
private String gateway;
}
2.注册 AlipayClient bean
java
@Configuration
@EnableConfigurationProperties(PayProperties.class)
public class AutoConfiguration {
@Bean
public AlipayClient getAlipayClient(PayProperties payProperties){
AlipayClient alipayClient = new DefaultAlipayClient(
payProperties.getGateway(),
payProperties.getAppId(),
payProperties.getAppPrivateKey(),
AlipayConstants.FORMAT_JSON,
AlipayConstants.CHARSET_UTF8,
payProperties.getAlipayPublicKey(),
AlipayConstants.SIGN_TYPE_RSA2);
return alipayClient;
}
}
3.核心代码编写
AlipayAPI
java
@AllArgsConstructor // 生成全部参数的构造函数
public class AlipayAPI {
private String notifyUrl;
private AlipayClient alipayClient;
public String pay(Order order){
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
// 支付宝页面跳转地址
request.setReturnUrl(notifyUrl);
// 异步通知的地址
request.setNotifyUrl(notifyUrl);
Map<String,String> map = new HashMap<>();
map.put("out_trade_no",order.getOrderId());
map.put("total_amount",order.getPrice());
map.put("subject",order.getSubject());
map.put("body",order.getBody());
map.put("product_code","FAST_INSTANT_TRADE_PAY");
// 设置业务参数
request.setBizContent(JSONObject.toJSONString(map));
// 发起支付请求
// 发起支付请求
AlipayTradePagePayResponse response = null;
try {
response = alipayClient.pageExecute(request);
} catch (AlipayApiException e) {
throw new RuntimeException(e);
}
// 获取响应结果
if (response.isSuccess()) {
System.out.println("调用成功");
System.out.println("支付宝支付链接:" + response.getBody());
return response.getBody();
} else {
System.out.println("调用失败");
System.out.println("错误信息:" + response.getMsg());
return "支付失败";
}
}
}
Order
java
@Data
public class Order {
// 订单id
private String orderId;
// 价格
private String price;
// 商品名称
private String subject;
// 商品描述
private String body;
// 支付场景
/**
* FAST_INSTANT_TRADE_PAY(即时到账):适用于即时交易场景,买家付款后,卖家立即收到款项。
* QUICK_MSECURITY_PAY(手机网页支付):适用于手机网页支付场景。
* FACE_TO_FACE_PAYMENT(当面付):适用于线下面对面付款场景,比如扫码支付。
* APP支付(APP支付场景):适用于在APP内的支付场景。
* WAP支付(手机网站支付场景):适用于手机网站支付场景。
* PRE_AUTH(预授权):适用于预先授权场景,买家授权预先冻结资金,商家在完成业务后调用支付宝解冻资金
*/
private String code;
}
4.注册 AlipayAPI bean
java
@Configuration
@EnableConfigurationProperties(PayProperties.class)
public class AutoConfiguration {
@Bean
public AlipayClient getAlipayClient(PayProperties payProperties){
AlipayClient alipayClient = new DefaultAlipayClient(
payProperties.getGateway(),
payProperties.getAppId(),
payProperties.getAppPrivateKey(),
AlipayConstants.FORMAT_JSON,
AlipayConstants.CHARSET_UTF8,
payProperties.getAlipayPublicKey(),
AlipayConstants.SIGN_TYPE_RSA2);
return alipayClient;
}
@Bean
public AlipayAPI getAlipayApi(PayProperties payProperties,AlipayClient alipayClient){
return new AlipayAPI(payProperties.getNotifyUrl(),alipayClient);
}
}
5.编写 META-INF/spring.factories 文件
Spring Boot 自动装配会加载这个config.AutoConfiguration
类,在这个类中注册的bean也会注入到 Spring 容器中
factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hzy.alipaystarter.config.AutoConfiguration
6.项目结构
txt
config
- AutoConfiguration 自动装配配置类
- PayProperties 配置文件读取类
core
- api
- AlipayAPI
- dtos
- Order
测试
1.创建一个测试项目,引入自定义 starter 依赖
xml
<dependency>
<groupId>com.hzy</groupId>
<artifactId>alipay-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
2.配置文件编写
yml
alipay:
appId:
appPrivateKey:
alipayPublicKey:
notifyUrl:
gateway: https://openapi-sandbox.dl.alipaydev.com/gateway.do
3.编写测试代码
java
@SpringBootTest
class TestApplicationTests {
@Autowired
private AlipayAPI alipayAPI;
@Test
void pay(){
Order order = new Order();
order.setOrderId(String.valueOf(System.currentTimeMillis()));
order.setSubject("xiaomi 12");
order.setPrice("456.89");
order.setBody("8 + 256");
order.setCode("FAST_INSTANT_TRADE_PAY");
// 一行代码实现支付宝支付
String pay = alipayAPI.pay(order);
System.out.println(pay);
}
}