基于xml的Spring应用-1

基于xml的Spring应用

Spring的get方法

方法定义返回值和参数
Object getBean (String beanName)根据beanName从容器中获取Bean实例,要求容器中Bean唯一
返回值为Object,需要强转
T getBean (Class type)根据Class类型从容器中获取Bean实例,要求容器中Bean类型唯一
返回值为Class类型实例,无需强转
T getBean (String beanName,Class type)根据beanName从容器中获得Bean实例,返回值为Class类型实例,无需强转
//根据beanName获取容器中的Bean实例,需要手动强转
UserService userservice = (UserService) applicationContext.getBean( "userService");
//根据Bean类型去容器中匹配对应的Bean实例,如存在多个匹配Bean则报错
UserService userService2 = applicationContext.getBean(UserService.class);
//根据beanName获取容器中的Bean实例,指定Bean的Type类型
UserService userService3 = applicationContext.getBean ( "userService",UserService.class) ;

Spring配置非自定义Bean

在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方jar包中的,那么,这些Bean要想让Spring进行管理,也需要对其进行配置,配置非自定义的Bean需要考虑如下两个问题:

  1. 配置的Bean的实例化方式是什么?是通过无参构造函数实例化还是有参构造函数、静态工厂方法或实例工厂方法实例化?
  2. 被配置的Bean是否需要注入必要的属性?如果是,则需要在配置文件中为该Bean设置属性值或通过其他方式进行注入。
示例1:配置Druid数据源交由Spring管理——存在构造函数
导入坐标
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>1.0.28</version>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.38</version>
</dependency>
找到对应jar报的class的位置,然后通过set的方式注入bean
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
	<property name="keepAlive" value="true"></property>
	<property name="name" value="com.mysql.jdbc.Driver"></property>
</bean>
2)配置Connection交由Spring管理——不存在构造函数的
Connection的产生是通过DriverManager的静态方法getConnection获取的,所以我们要用静态工厂方式配置
Class.forName("com.mysql.jdbc.Drive")
Connection connection = DriverManager.getConnection( url:"",user: "",password:"");


<bean class="java.lang.class" factory-method="forName">
	<constructor-arg name="className" value="com.mysql.jdbc.Driver" />
</bean>
<bean id="connection" class="java.sql.DriverManager" factory-method="getConnection"scope="prototype">
	<constructor-arg name="url" value="jdbc:mysql:///mybatis"/>
	<constructor-arg name="user" value="root"/>
	<constructor-arg name="password" value="root"/>
</bean>
3)配置日期对象交由Spring实现——工厂Bean实现
指定产生一个日期格式的对象,源码如下
String currentTimeStr = "2023-08-27 07:20:00";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat ("yyvy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse (currentTimeStr);
Spring配置的方式产生
<bean id="dateFormat" class="java.text.SimpleDateFormat">
	<constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
</bean>
<bean id="date" class="java.util.Date" factory-bean="dateFormat" factory-method="parse">
	<constructor-arg name="source" value="2023-08-27 07:20:00"></constructor-arg>
</bean>
4)配置MyBatis的SqlSessionFactory交由Spring管理
// 配置静态
InputStream in = Resources.getResourceAsStream("mybaits-config.xml");
// 无参构造
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 实例工厂
SqlSessionFactory sqlSessionFactory = builder.build(in);

<bean id="resources" class="org.apache.ibatis.io.Resources" factory-method="getResourceAsStream">
	<constructor-arg name="resource" value="mybaits-config.xml" ></constructor-arg>
</bean>
<bean class="org.apache.ibatis.session.SqlSessionFactoryBuilder" id="factoryBuilder">
</bean>
<bean class="org.apache.ibatis.session.SqlSessionFactory" id="sessionFactory" factory-bean="factoryBuilder" factory-method="build">
	<constructor-arg name="inputStream" ref="resources"></constructor-arg>
</bean>

Bean实例化的基本流程

在Spring框架中,<bean>标签对应着一个BeanDefinition对象。这个对象中封装了该bean的信息,包括bean的类名、构造函数、属性、依赖关系等等。这个过程是在Spring容器初始化的时候进行的。

在初始化过程中,Spring会将所有的BeanDefinition对象存储到一个名为beanDefinitionMap的Map集合中。然后,Spring会遍历beanDefinitionMap集合,使用反射创建Bean实例对象。这个实例对象创建完毕后,Spring会将这个对象存储到一个名为singletonObjects的Map集合中

当调用getBean方法时,Spring会从singletonObjects集合中查找对应的Bean实例对象,如果存在则直接返回。如果不存在,则会根据BeanDefinition中封装的信息创建一个新的Bean实例对象,并存储到singletonObjects集合中。最终,getBean方法会返回这个Bean实例对象

值得注意的是,singletonObjects集合中存储的都是单例Bean,也就是说,如果一个Bean被配置为单例,那么无论调用多少次getBean方法,都会返回同一个实例对象。而如果一个Bean被配置为非单例,那么每次调用getBean方法都会创建一个新的实例对象返回。这个区别是由BeanDefinition中的scope属性控制的

image-20230504214732920

Bean实例化的流程

  1. 加载xml配置文件,解析获取每个<bean>的信息,封装成BeanDefinition对象
  2. 将BeanDefinition对象存储在beanDefinitionMap中,以便后续使用
  3. ApplicationContext底层遍历beanDefinitionMap,使用反射创建Bean实例对象,这个过程中需要考虑Bean实例化的方式(无参构造、有参构造、工厂方法等)和需要注入的属性
  4. 创建好的Bean实例对象被存储到singletonObjects中,以便后续获取
  5. 当执行getBean方法时,从singletonObjects中匹配Bean实例对象并返回

在整个流程中,Bean实例化的过程是由Spring容器进行管理和调用的,开发者无需手动创建Bean实例对象。

Spring的后处理器

当Spring容器初始化时,它会加载所有的Bean定义,并将它们存储在一个Map中。这些Bean定义可以在XML配置文件中声明,也可以通过Java配置进行编程式声明

BeanFactoryPostProcessor是一个特殊类型的Bean后处理器。它允许我们在容器实例化任何其他Bean之前对Bean定义进行更改或者添加新的Bean定义。可以用它来实现自定义逻辑,例如动态修改Bean定义、动态注册Bean定义等等。

BeanFactoryPostProcessor的典型用途是为某个BeanFactory进行自定义配置,例如设置属性值或者更改BeanDefinition属性。在BeanFactoryPostProcessor执行过程中,可以修改beanDefinitionMap中的BeanDefinition,包括Bean的作用域、依赖关系等等。而且,BeanFactoryPostProcessor也可以添加新的BeanDefinition,以及删除不需要的BeanDefinition。

BeanPostProcessor是另一种类型的Bean后处理器,它允许在Bean实例化时进行拦截。BeanPostProcessor通常用于执行一些自定义逻辑,例如修改Bean属性或者调用Bean方法。例如,Spring AOP就是基于BeanPostProcessor来实现的。

总体来说,后处理器是Spring框架提供的非常重要的扩展点。通过使用后处理器,我们可以修改Bean的定义和行为,以适应特定的业务需求。

BeanFactoryPostProcessor

BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的一个子接口,它在BeanFactoryPostProcessor的基础上提供了一个额外的方法postProcessBeanDefinitionRegistry,用于在BeanDefinition被注册到BeanFactory之前,对BeanDefinition进行更复杂的修改或者注册。可以使用该接口实现自定义的BeanDefinition注册逻辑,例如通过读取配置文件、数据库、网络等方式动态地注册BeanDefinition

这段代码主要实现了动态修改和注册Bean的功能。具体来说,这段代码通过以下几个部分实现:

  1. 定义了一个XML配置文件(beans.xml),其中定义了两个Bean:userService和userDao。

    <bean id="userService" class="com.congyun.service.impl.UserServiceImpl" autowire="byName">
    </bean>
    <bean id="userDao" class="com.congyun.dao.impl.UserDaoImpl" scope="prototype">
    </bean>
    
  2. 定义了一个后处理器类(myBeanProcesser),该类实现了BeanFactoryPostProcessor接口,重写了postProcessBeanFactory方法。在该方法中,通过修改Bean定义信息和注册新的Bean实例实现动态修改和并注册了一个新的Bean实例personDao的效果

    public class myBeanProcesser implements BeanFactoryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
            // 修改configurableListableBeanFactory
            BeanDefinition date = configurableListableBeanFactory.getBeanDefinition("userService");
            date.setBeanClassName("com.congyun.dao.impl.UserDaoImpl");
    
            // 通过代码注册Bean
            BeanDefinition PersonDao = new RootBeanDefinition();
            PersonDao.setBeanClassName("com.congyun.dao.impl.PersonDaoImpl");
            DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
            defaultListableBeanFactory.registerBeanDefinition("personDao",PersonDao);
        }
    }
    
  3. 在测试方法中,创建了一个ApplicationContext对象,该对象读取了classpath下的beans.xml配置文件,并根据配置文件中的信息创建相应的Bean实例。同时,也获取了通过后处理器修改和注册后的Bean实例

    @Test
    public void ListTest() throws Exception{
        ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
        Object userService = app.getBean("userService");
        PersonDao personDao = (PersonDao) app.getBean("personDao");
        System.out.println(personDao);
        System.out.println(userService);
    }
    
  4. 综上,这段代码的主要作用是实现了动态修改和注册Bean的功能,并通过测试方法验证了修改和注册的效果。

BeanFactoryPostProcessor是一个接口规范,它定义了一个方法postProcessBeanFactory(),用于在BeanFactory加载Bean定义信息后、Bean实例化前被执行。实现了该接口的类只要交由Spring容器管理,那么Spring就会回调该接口的方法,在该方法中可以对BeanDefinition进行注册和修改等操作。这个接口在Spring中的作用非常重要,可以实现很多高级的功能,比如动态注入Bean等

示例

首先,在测试类中创建了一个ApplicationContext对象,该对象会读取classpath下的beans.xml配置文件,并根据配置文件中的信息创建相应的Bean实例。这个步骤是Spring IoC容器启动的过程,根据XML配置文件中的信息实例化相应的Bean。

ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");

然后,在测试类中配置了一个实现了BeanDefinitionRegistryPostProcessor接口的testBeanFactoryPostProcessor类,并将其声明为一个Bean,用于注册新的BeanDefinition信息。BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子接口,它允许我们在Spring容器创建Bean之前修改Bean的定义信息,从而达到动态注册Bean的目的。

<bean class="com.congyun.processer.testBeanFactoryPostProcessor"></bean>

接下来,实现BeanDefinitionRegistryPostProcessor接口的testBeanFactoryPostProcessor类中重写了postProcessBeanDefinitionRegistry方法,在该方法中注册了一个新的BeanDefinition,其Bean的实现类为PersonDaoImpl。

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
    // 先执行-子类方法
    // 注册新的Bean
    System.out.println("testBeanFactoryPostProcessor的postProcessBeanDefinitionRegistry方法");
    BeanDefinition PersonDao = new RootBeanDefinition("com.congyun.dao.impl.PersonDaoImpl");
    beanDefinitionRegistry.registerBeanDefinition("personDao",PersonDao);
}

最后,在测试方法中通过ApplicationContext对象获取该新注册的Bean实例并输出。

PersonDao personDao = (PersonDao) app.getBean("personDao");
System.out.println(personDao);

在这个过程中,Spring容器在初始化时会调用所有实现了BeanFactoryPostProcessor接口的类,并按照特定的顺序调用它们的postProcessBeanFactory方法。而在BeanFactoryPostProcessor中,又有BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法在postProcessBeanFactory方法之前被调用,因此实现了BeanDefinitionRegistryPostProcessor接口的类可以在BeanFactoryPostProcessor之前注册Bean定义信息,实现动态注册Bean的效果。

综上所述,这段代码演示了如何使用实现了BeanDefinitionRegistryPostProcessor接口的类实现动态注册Bean的操作,提供了更多的灵活性。

这里还有通过注解配置Bean的,但是我现在还不太了解注解,之后学习完注解以后补上

BeanPostProcessor

BeanPostProcessor是一个在Bean实例化和依赖注入之后,初始化方法执行之前和之后提供扩展点的接口,它允许开发者在Bean的初始化过程中进行自定义操作。BeanPostProcessor接口定义了两个回调方法:

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
**@Nullable**是一个注解,它可以用于表示一个元素(如方法参数、方法返回值、域、局部变量等)可以为 null。在 Java 中,null 值表示缺失或未初始化的值。当一个元素被标注为 `@Nullable` 时,这意味着它的值可以为 null,使用该元素的代码需要注意 null 值的情况,以避免出现 NullPointerException 异常。

在 Spring 框架中,`@Nullable` 注解通常用于方法参数或方法返回值上,用于提供额外的类型安全性和可读性。例如,当一个方法参数被标注为 `@Nullable` 时,调用该方法的代码就需要考虑到该参数值可能为 null 的情况,并且在代码中进行相应的处理,以避免可能的异常情况。

postProcessBeforeInitialization 方法在 Bean 的初始化方法被调用之前被执行,可以在此方法中对 Bean 进行一些操作,可以实现 BeanPostProcessor 接口,并重写 postProcessBeforeInitialization 方法,下面是一些例子:

  1. 为 Bean 设置默认属性值:假设有一个名为 User 的 Bean,我们可以通过实现 BeanPostProcessor 接口,在 postProcessBeforeInitialization 方法中判断 User 的某个属性是否为空,如果为空则设置默认值。

    public class UserDefaultValueProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof User) {
                User user = (User) bean;
                if (user.getAge() == null) {
                    user.setAge(18);
                }
                if (user.getName() == null) {
                    user.setName("Unknown");
                }
            }
            return bean;
        }
    }
    
  2. 实现日志记录:我们可以使用 BeanPostProcessor 在 Bean 初始化之前和之后记录日志

    public class LoggingBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            //记录初始化前的日志
            System.out.println("Initializing bean " + beanName + " : " + bean.getClass());
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            //记录初始化后的日志
            System.out.println("Bean " + beanName + " initialized successfully.");
            return bean;
        }
    }
    
  3. 实现安全检查:我们可以使用 BeanPostProcessor 在 Bean 初始化之前和之后执行安全检查

    public class SecurityCheckBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof UserService) {
                if (SecurityUtils.hasAccess("user:add")) {
                    return bean;
                } else {
                    throw new SecurityException("Access denied.");
                }
            }
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    }
    

postProcessAfterInitialization 方法在 Bean 的初始化方法被调用之后被执行,同样可以在此方法中对 Bean 进行一些操作,例如添加一些功能性的处理,当我们需要在Bean的初始化前后执行一些逻辑时,就可以使用Bean后处理器。下面是一些使用Bean后处理器可以实现的功能性处理例子:

  1. 属性值的加密和解密:我们可以使用Bean后处理器,在Bean的初始化前后对属性值进行加密和解密操作,增强数据安全性。
  2. 类型转换:Bean后处理器可以在Bean初始化之前对属性进行类型转换,避免在属性注入时出现类型错误。
  3. 自定义日志输出:我们可以在Bean后处理器中添加自定义的日志输出逻辑,记录Bean的初始化过程,便于排查错误。
  4. 检查Bean是否实现了指定接口:我们可以在Bean后处理器中检查Bean是否实现了指定的接口,如果没有实现则抛出异常或者进行其他处理。
  5. 对象实例的包装:在Bean后处理器中,我们可以对Bean进行包装,比如将Bean包装成一个代理对象,以增加Bean的功能。

这些例子只是说明了Bean后处理器可以实现的功能,实际使用时可能还有更多的应用场景。

下面给出一个例子,展示如何使用Bean后处理器对属性进行加密和解密。

public class EncryptionBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Encryptable) {
            Encryptable encryptable = (Encryptable) bean;
            String encryptedValue = encryptable.getEncryptedValue();
            String decryptedValue = decrypt(encryptedValue);
            encryptable.setDecryptedValue(decryptedValue);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Encryptable) {
            Encryptable encryptable = (Encryptable) bean;
            String decryptedValue = encryptable.getDecryptedValue();
            String encryptedValue = encrypt(decryptedValue);
            encryptable.setEncryptedValue(encryptedValue);
        }
        return bean;
    }

    private String decrypt(String encryptedValue) {
        // decryption logic
        return decryptedValue;
    }

    private String encrypt(String decryptedValue) {
        // encryption logic
        return encryptedValue;
    }
}

这两段代码是一个示例的Bean后处理器,用于对实现了Encryptable接口的Bean对象进行加密和解密操作。

postProcessBeforeInitialization方法在Bean的初始化方法被调用之前被执行,该方法首先判断当前传入的bean是否实现了Encryptable接口,如果是,则将其转换为Encryptable类型,并获取其加密后的值,进行解密操作,最后将解密后的值设置到Encryptable对象中,以便在初始化时能够获取到解密后的值

postProcessAfterInitialization方法在Bean的初始化方法被调用之后被执行,该方法同样判断当前传入的bean是否实现了Encryptable接口,如果是,则将其转换为Encryptable类型,并获取其解密后的值,进行加密操作,最后将加密后的值设置到Encryptable对象中,以保证在Bean对象被使用时能够获取到加密后的值

在 Bean 后处理器中, postProcessBeforeInitializationpostProcessAfterInitialization 这两个方法的返回值是经过处理后的 Bean 对象。如果返回 null,那么处理后的 Bean 对象就会变成 null。

postProcessBeforeInitialization 方法中,如果返回 null,那么原始的 Bean 对象将不会被处理,直接跳过后续的处理过程,直接返回原始的 Bean 对象

postProcessAfterInitialization 方法中,如果返回 null,那么处理后的 Bean 对象就会变成 null。在这种情况下,调用方将无法得到已处理的 Bean 对象,而是得到一个 null 对象

所以,一般情况下我们会在 Bean 后处理器的实现中返回处理后的 Bean 对象,这样可以确保处理的连续性和完整性,避免因为返回 null 导致的不必要问题

ps

原始的 Bean 对象指的是容器实例化的、尚未被修改或增强的 Bean 对象。在 Bean 后处理器的 postProcessBeforeInitialization() 方法中,传入的第一个参数 bean 就是原始的 Bean 对象。在该方法中,我们可以对该对象进行各种修改和增强操作,比如修改其属性、检查其实现的接口等等。而在 postProcessAfterInitialization() 方法中,同样需要返回一个修改或增强后的 Bean 对象,此时传入的 bean 参数就是经过前一个方法修改后的对象。

需要注意的是,在 postProcessBeforeInitialization() 方法中对原始的 Bean 对象进行修改,会影响后续 postProcessAfterInitialization() 方法中对 Bean 的处理。因此,在实现 Bean 后处理器时,应该谨慎地考虑对 Bean 对象的修改,确保不会破坏 Bean 的正确性。

postProcessAfterInitializationpostProcessBeforeInitialization方法是针对所有单例池中的bean对象的。当Spring容器创建一个单例bean时,它会首先调用postProcessBeforeInitialization方法,然后调用bean的初始化方法(如果有的话),最后再调用postProcessAfterInitialization方法。这些方法可以让开发人员在Bean被初始化前后添加额外的逻辑处理,以满足各种定制化需求。值得注意的是,这些方法只适用于单例bean。对于非单例bean,Spring只会在创建Bean实例时调用它们

Bean 对象的修改,确保不会破坏 Bean 的正确性。

postProcessAfterInitializationpostProcessBeforeInitialization方法是针对所有单例池中的bean对象的。当Spring容器创建一个单例bean时,它会首先调用postProcessBeforeInitialization方法,然后调用bean的初始化方法(如果有的话),最后再调用postProcessAfterInitialization方法。这些方法可以让开发人员在Bean被初始化前后添加额外的逻辑处理,以满足各种定制化需求。值得注意的是,这些方法只适用于单例bean。对于非单例bean,Spring只会在创建Bean实例时调用它们

image-20230505221849999

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Integration提供了基于MQTT的适配器,可以很方便地集成MQTT协议到Spring应用程序中。下面是基于spring-integration-mqtt在生产环境中使用的步骤: 1. 添加依赖 在项目的pom.xml文件中,添加spring-integration-mqtt依赖: ```xml <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mqtt</artifactId> <version>5.5.0</version> </dependency> ``` 2. 配置MQTT连接参数 在项目的配置文件中,添加MQTT连接参数: ```yaml spring: mqtt: url: tcp://localhost:1883 username: test password: test ``` 3. 配置MQTT消息通道 在Spring Integration中,可以使用`MessageChannel`来定义消息通道,下面是一个基本的MQTT消息通道配置: ```xml <int-mqtt:message-driven-channel-adapter id="mqttInboundAdapter" client-id="test" url="${spring.mqtt.url}" topics="test/topic" qos="1" channel="mqttInputChannel"> <int:poller fixed-delay="1000"/> </int-mqtt:message-driven-channel-adapter> <int:channel id="mqttInputChannel"/> ``` 4. 处理MQTT消息 在处理MQTT消息的地方,可以使用`@ServiceActivator`注解和`MessageHandler`接口来处理消息: ```java @Service public class MqttMessageHandler implements MessageHandler { @Override @ServiceActivator(inputChannel = "mqttInputChannel") public void handleMessage(Message<?> message) { String topic = message.getHeaders().get(MqttHeaders.TOPIC, String.class); String payload = message.getPayload().toString(); System.out.println("Received message - Topic: " + topic + ", Payload: " + payload); } } ``` 5. 发布MQTT消息 在需要发送MQTT消息的地方,可以使用`MessageChannel`来发送消息: ```java @Autowired private MessageChannel mqttOutputChannel; public void sendMessage() { mqttOutputChannel.send(MessageBuilder.withPayload("hello").setHeader(MqttHeaders.TOPIC, "test/topic").build()); } ``` 以上就是基于spring-integration-mqtt在生产环境中使用的步骤。需要注意的是,本文只是介绍了基本的MQTT使用方法,实际应用中还需要考虑消息质量、消息持久化等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值