• SpringBoot 自动装配原理 - 支付宝支付封装starter


    SpringBoot 自动装配原理

    Spring Boot的自动装配是通过@EnableAutoConfiguration注解来实现的,该注解包含了一系列的自动装配配置类,这些配置类会根据项目的依赖和配置,自动地配置应用程序上下文中的Bean。

    SpringBoot 应用的启动类上都有一个 @SpringBootApplication 注解,该注解包含 @EnableAutoConfiguration注解。

    @EnableAutoConfiguration注解包含两个重要注解:

    1. @AutoConfigurationPackage
      • 该注解是用于标记主配置类(通常是Spring Boot应用程序的入口类),以指示在进行自动配置时应该扫描的基本包。它会将该类所在的包及其子包纳入自动配置的扫描范围。
    2. @Import({AutoConfigurationImportSelector.class})
      • 该注解用于导入一个配置选择器,即AutoConfigurationImportSelector类。
      • AutoConfigurationImportSelector是Spring Boot自动配置的核心,它负责从类路径下的META-INF/spring.factories文件中加载自动配置类的候选列表,并根据条件选择合适的自动配置类导入到Spring容器中。
      • 通过@Import注解将AutoConfigurationImportSelector引入到主配置类中,以启用自动配置的机制。

    装配流程如下:

    1. 主配置类上的@EnableAutoConfiguration触发自动配置的启用。
    2. @EnableAutoConfiguration包含@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})
    3. @AutoConfigurationPackage标记了要扫描的基本包。
    4. @Import({AutoConfigurationImportSelector.class})导入了AutoConfigurationImportSelector,启动自动配置的核心。
    5. AutoConfigurationImportSelector根据条件加载META-INF/spring.factories文件中的自动配置类候选列表。
    6. 过滤掉不符合条件的自动配置类,移除重复的自动配置类,获取需要排除的自动配置类。
    7. 最终,将符合条件的自动配置类导入到Spring容器中。

    详细介绍

    AutoConfigurationImportSelector 实现了 DeferredImportSelector接口,用于延迟导入配置类的选择器。它允许在运行时决定要导入的配置类。通常,它用于实现一些自定义逻辑,以便根据运行时条件来选择性地导入配置。

    在这里插入图片描述

    DeferredImportSelector 定义了一个方法:

    String[] selectImports(AnnotationMetadata importingClassMetadata);
    
    • 1

    AutoConfigurationImportSelector 对这个方法的实现

        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());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    getAutoConfigurationEntry()

        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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    getCandidateConfigurations() :获取候选配置

        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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    image-20231121185632216

    loadFactoryNames() :

    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());
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20231121183914769

    查看 Spring Boot 自动装配源码可以看到上面的代码就是加载 META-INF/spring.factories 中键org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值

    在这里插入图片描述

    自定义 Spring Boot Starter

    支付宝沙箱支付为例

    新建一个项目,启动类和配置文件都删掉,创建META-INF/spring.factories

    image-20231121191042109

    1.读取配置文件

    @Data
    @ConfigurationProperties(prefix = "alipay")
    public class PayProperties {
        private String appId;
        private String appPrivateKey;
        private String alipayPublicKey;
        private String notifyUrl;
        private String gateway;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.注册 AlipayClient bean

    @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.核心代码编写

    AlipayAPI

    @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 "支付失败";
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    Order

    @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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4.注册 AlipayAPI bean

    @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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    5.编写 META-INF/spring.factories 文件

    Spring Boot 自动装配会加载这个config.AutoConfiguration 类,在这个类中注册的bean也会注入到 Spring 容器中

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.hzy.alipaystarter.config.AutoConfiguration
    
    • 1
    • 2

    6.项目结构

    config
    	- AutoConfiguration 自动装配配置类
    	- PayProperties 配置文件读取类
    core 
    	- api
    		- AlipayAPI 
    	- dtos
    		- Order 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    测试

    1.创建一个测试项目,引入自定义 starter 依赖
            <dependency>
                <groupId>com.hzygroupId>
                <artifactId>alipay-starterartifactId>
                <version>0.0.1-SNAPSHOTversion>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2.配置文件编写
    alipay:
        appId: 
        appPrivateKey: 
        alipayPublicKey: 
        notifyUrl: 
        gateway: https://openapi-sandbox.dl.alipaydev.com/gateway.do
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    3.编写测试代码
    @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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    image-20231121203521803

  • 相关阅读:
    【网络八股】TCP八股
    Vue一些你不知道的东西
    http和https分别是什么?区别是什么?
    PHP-FPM配置文件详解
    安全可信丨两个项目新入选可信边缘计算推进计划!
    【深度学习】——深度学习中基本的网络结构(2)
    Ubuntu本地快速搭建web小游戏网站,公网用户远程访问【内网穿透】
    k8s教程(13)-pod定向调度
    Android 学习之追踪应用的安装情况
    MYSQL(四)DQL-----对表中数据进行查询
  • 原文地址:https://blog.csdn.net/zxwyhzy/article/details/134540730