Spring框架之注解编程

10 篇文章 0 订阅

代码和配置

代码结构
在这里插入图片描述

maven依赖

<properties>
    <java.version>1.8</java.version>
    <spring.verson>5.3.9</spring.verson>
    <junit.version>5.7.2</junit.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.verson}</version>
    </dependency>

    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
            </configuration>
        </plugin>
    </plugins>
</build>

User.java 代码

package org.spring.ss.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Map;
import java.util.Properties;

@Data
@NoArgsConstructor
public class User {
    private String name;
    private Integer age;
    private String[] favorites;
    private Map<String, Object> map;
    private Properties props;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

UserDao.java代码

package org.spring.ss.dao;

import org.spring.ss.pojo.User;
import org.springframework.stereotype.Repository;

@Repository
public class UserDao {
    public User find() {
        return new User("赵小八", 30);
    }
}

UserService.java 代码

package org.spring.ss.service;

import org.spring.ss.dao.UserDao;
import org.spring.ss.pojo.User;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserService {
    @Resource
    public UserDao userDao;

    public User find() {
        return userDao.find();
    }
}

UserController.java代码

package org.spring.ss.controller;

import org.spring.ss.pojo.User;
import org.spring.ss.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    @Autowired
    public UserService userService;

    public void find() {
        User userBean = userService.find();
        System.out.println(userBean);
    }
}

UserTest.java 代码:

package org.spring.ss.pojo;

import org.junit.jupiter.api.Test;
import org.spring.ss.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserTest {
}

基于XML方式的实现

新建Spring的配置文件applicationContext.xml,并添加包扫描路径。

如果xmlns:context不存在,注意添加

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
    <!-- 添加扫描的路径 指定从哪些 package下加载被 @Component @Controller @Service @Repository 等标注的类型-->
    <!-- 指定一个大的范围 -->
    <!--    <context:component-scan base-package="org.spring.ss"/>-->
    <!-- 通过逗号分割一起指定 -->
    <!--    <context:component-scan base-package="org.spring.ss.controller,org.spring.ss.service,org.spring.ss.dao"/>-->
    <!-- 分开指定 -->
    <context:component-scan base-package="org.spring.ss.controller"/>
    <context:component-scan base-package="org.spring.ss.service"/>
    <context:component-scan base-package="org.spring.ss.dao"/>
    <!--
        base-package 的包路径还可以使用通配符配置:
        * 标示一层包的通配,com.*.dao可以包括范围如:com.aa.dao,com.bb.dao,com.cc.dao
        ** 标示不确定层包通配,com.**.dao可以表示的范围如:com.aa.dao,com.aa.aa1.dao,com.bb.dao,com.bb.bb1.dao
    -->
</beans>

UserTest.java里添加测试代码:

@Test
public void testBySpringXML() {
    // ioc 容器初始化
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    // ioc 通过bean 的 id 获取指定的bean
    UserController userController = applicationContext.getBean("userController", UserController.class);
    // 使用bean
    userController.find();
}

输出User(name=赵小八, age=30, favorites=null, map=null, props=null)

限制指定包可以使用的注解

修改applicationContext.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
    <!-- 添加扫描的路径 指定从哪些 package下加载被 @Component @Controller @Service @Repository 等标注的类型-->
    <!-- 指定一个大的范围 -->
    <!--    <context:component-scan base-package="org.spring.ss"/>-->
    <!-- 通过逗号分割一起指定 -->
    <!--    <context:component-scan base-package="org.spring.ss.controller,org.spring.ss.service,org.spring.ss.dao"/>-->
    <!-- 分开指定 -->
    <!--<context:component-scan base-package="org.spring.ss.controller"/>
    <context:component-scan base-package="org.spring.ss.service"/>
    <context:component-scan base-package="org.spring.ss.dao"/>-->

    <!--
        还可以使用通配符
        * 标示一层包的通配,com.*.dao可以包括范围如:com.aa.dao,com.bb.dao,com.cc.dao
        ** 标示不确定层包通配,com.**.dao可以表示的范围如:com.aa.dao,com.aa.aa1.dao,com.bb.dao,com.bb.bb1.dao
    -->

    <!--
    use-default-filters="false" 表示不适用默认的过滤器
        默认过滤器会识别 @Component @Controller @Service @Repository
    -->
    <context:component-scan base-package="org.spring.ss.controller" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <context:component-scan base-package="org.spring.ss.service,org.spring.ss.dao">
        <!-- 排除掉某个注解 -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
</beans>

这个时候尝试修改UserController上面的注解为@Component

package org.spring.ss.controller;

import org.spring.ss.pojo.User;
import org.spring.ss.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;

@Component
public class UserController {
    @Autowired
    public UserService userService;

    public void find() {
        User userBean = userService.find();
        System.out.println(userBean);
    }
}

重新运行测试用例testBySpringXML,可以看到下面的错误提示。
在这里插入图片描述

基于java类配置实现

添加SpringConfig.java文件,代码如下,以下代码实现了和上面xml代码配置一样的功能

package org.spring.ss;

import org.spring.ss.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;

/*
 * java配置类,相当于 applicationContext.xml
 * */
@Configuration
@ComponentScans({ // 配置扫描路径
        @ComponentScan("org.spring.ss.controller"),
        @ComponentScan("org.spring.ss.service"),
        @ComponentScan("org.spring.ss.dao"),
})
public class SpringConfig {
    /**
     * @return
     * @Bean 作用和我们在applicationContext.xml中添加的<bean> 效果一样</>
     * 默认的name是方法名称
     * 自定义的name 可以通过value属性或者name属性来指定
     */
    @Bean(name = {"aaa", "bbb"})
    public User getUser() {
        User user = new User();
        user.setName("王小六");
        return user;
    }
}

添加测试代码:

@Test
public void testBySpringConfig() {
    // ioc 容器初始化
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    // ioc 获取配置类中设置的 bean
    User userBean = (User) applicationContext.getBean("aaa");
    // 使用bean
    System.out.println(userBean);
    // 获取容器中被扫描标记的
    UserController userController = applicationContext.getBean("userController", UserController.class);
    // 使用bean
    userController.find();
}

输出

User(name=王小六, age=null, favorites=null, map=null, props=null)
User(name=赵小八, age=30, favorites=null, map=null, props=null)
限制指定包可以使用的注解

修改spring配置类SpringConfig.java

package org.spring.ss;

import org.spring.ss.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;

/*
 * java配置类,相当于 applicationContext.xml
 * */
@Configuration
/*
*  @ComponentScan 如果不去指定扫描的路基,默认是会扫描当前目录及其子目录下的所有的
 *                被@Componenet @Controller @Service @Repository标注的类型
* */
@ComponentScans({
        @ComponentScan(
                value = "org.spring.ss.controller",
                useDefaultFilters = false,
                includeFilters = {@ComponentScan.Filter(Controller.class)}),
        @ComponentScan(
                value = {"org.spring.ss.service","org.spring.ss.dao"},
                excludeFilters = {@ComponentScan.Filter(Controller.class)}),
})
public class SpringConfig {
    /**
     * @return
     * @Bean 作用和我们在applicationContext.xml中添加的<bean> 效果一样</>
     * 默认的name是方法名称
     * 自定义的name 可以通过value属性或者name属性来指定
     */
    @Bean(name = {"aaa", "bbb"})
    public User getUser() {
        User user = new User();
        user.setName("王小六");
        return user;
    }
}

这个时候尝试修改UserController上面的注解为@Component

package org.spring.ss.controller;

import org.spring.ss.pojo.User;
import org.spring.ss.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;

@Component
public class UserController {
    @Autowired
    public UserService userService;

    public void find() {
        User userBean = userService.find();
        System.out.println(userBean);
    }
}

重新运行测试用例testBySpringConfig,可以看到下面的错误提示。
在这里插入图片描述

@Autowired@Resource的区别

@Autowired:默认只能根据类型来查找,可以结合@Qualifier("abc")注解来实现通过name查找

@Resource:默认是根据类型来查找,但是提供的有typename属性类实现不同的查找方式,@Resource(name="abc") 或者@Resource(type=UserService.class)

@Value注解的使用

修改SpringConfig.java添加对org.spring.ss.pojo包的扫描。

package org.spring.ss;

import org.spring.ss.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;

/*
 * java配置类,相当于 applicationContext.xml
 * */
@Configuration
/*
*  @ComponentScan 如果不去指定扫描的路基,默认是会扫描当前目录及其子目录下的所有的
 *                被@Componenet @Controller @Service @Repository标注的类型
* */
@ComponentScans({
        @ComponentScan("org.spring.ss.pojo"),
        @ComponentScan(
                value = {"org.spring.ss.controller"},
                useDefaultFilters = false,
                includeFilters = {@ComponentScan.Filter(Controller.class)}),
        @ComponentScan(
                value = {"org.spring.ss.service","org.spring.ss.dao"},
                excludeFilters = {@ComponentScan.Filter(Controller.class)}),
})
public class SpringConfig {
    /**
     * @return
     * @Bean 作用和我们在applicationContext.xml中添加的<bean> 效果一样</>
     * 默认的name是方法名称
     * 自定义的name 可以通过value属性或者name属性来指定
     * @Primary 表示如果遇到同类型的bean, 优先返回
     */
    @Bean(name = {"aaa", "bbb"})
    @Primary
    public User getUser() {
        User user = new User();
        user.setName("王小六");
        return user;
    }
}

添加Account.java

package org.spring.ss.pojo;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

@Data
@Component
public class Account {
    @Value("bobo") // 注入普通的字符串
    private String userName;

    @Value("#{systemProperties['os.name']}")
    private String systemPropertiesName; // 注入操作系统的信息

    @Value("#{T(java.lang.Math).random()*100}")
    private double randomNumber; // 注入表达式的结果

    // 注入其他Bean的属性, aaa 来自于 SpringConfig,是通过 java 配置类注入的
    @Value("#{aaa.name}")
    private String fromPersonName;

    @Value("classpath:test.txt")
    private Resource resourceFile;

    @Value("http://www.baidu.com")
    private Resource baiduFile;
}

添加测试方法

@Test
public void testBySpringConfig2() {
    // ioc 容器初始化
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    // ioc 通过bean 的 id 获取指定的bean
    Account account = applicationContext.getBean("account", Account.class);
    // 使用bean
    System.out.println(account);
}

输出Account(userName=bobo, systemPropertiesName=Windows 10, randomNumber=16.667755826649376, fromPersonName=王小六, resourceFile=class path resource [test.txt], baiduFile=URL [http://www.baidu.com])

@PropertySouce引入属性文件

添加属性文件
在这里插入图片描述
Java配置类中通过@PropertySouce注解来显示的引入属性文件
在这里插入图片描述
效果
在这里插入图片描述

@Lazy注解

@Lazy 注解可以提高系统加载速度,@Component 和 @Lazy 一起使用的时候,被修饰的类在启动的时候不会被初始化,只有通过 ApplicationContext 对象的 getBean 方法获取的时候,或者第一次被使用的时候才会初始化。

需要注意的是 bean 的作用域范围为 prototype 的时候,也不会在加载的时候初始化。

作用范围
  • 可以作用于在类上和 @Component 注解搭配使用
  • 也可以作用在方法上和 @Bean 注解搭配使用
  • 当作用在类上和 @Configuration 注解搭配使用的情况下,该类下面所有带有 @Bean 注解的对象都将受到同样的影响
属性功能
  • value 的默认值为 true
  • 如果为 true 并且在其他 Bean 没有对其依赖或者没有使用的情况下将不会初始化
  • 如果为 false,跟其他的 Bean 一样正常加载

生命周期注解@PostConstruct @PreDestory

Spring负责管理Bean的初始化和销毁,但同时也提供方式让我们在bean初始化之后、销毁之前执行特定业务。@PostConstruct 和 @PreDestroy 注解,主要实现Bean在初始化之后、销毁之前执行自定义业务。

实现Bean在初始化之后,销毁之前执行自定义业务,还有其他两种方式。

  1. Spring bean 通过实现 InitializingBean ,DisposableBean 接口实现初始化方法和销毁前操作
  2. Spring 的 init-method 和 destory-method

Spring会在初始化bean属性之后,调用一次拥有@PostConstruct注解的方法。该方法可以为任何访问级别,但不能为static。

@Component
public class DbInit {
 
    @Autowired
    private UserRepository userRepository;
    // 该示例首先初始化userRepository,然后调用postConstruct()方法。
    @PostConstruct
    private void postConstruct() {
        User admin = new User("admin", "admin password");
        User normalUser = new User("user", "user password");
        userRepository.save(admin, normalUser);
    }
}
@Component
public class UserRepository {
 
    private DbConnection dbConnection;
    // 该方法一般用于在bean销毁之前释放资源或执行其他清理任务,如关闭数据库连接。
    @PreDestroy
    public void preDestroy() {
        dbConnection.close();
    }
}

需要注意的是 @PostConstruct@PreDestroy 注解是Java EE的一部分。自Java 9开始 Java EE 已被标注不建议使用,在Java 11 中已经被移除,因此需要手动增加相应依赖:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

@DependsOn注解

@DependsOn指定实例化对象的先后顺序

@Component
@DependsOn({"user"}) // Person的实例化依赖于User对象的实例化,也就是User先于Person实例化
public class Person {

    public Person(){
        System.out.println("Person 构造方法执行了...");
    }
}

@Import注解

添加Cat.java

package org.spring.ss.pojo;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;

@Data
public class Cat {
    @Value("加菲猫")
    private String color;
    private String nick;
}

静态使用

@Import注解中将前面创建的Cat.java引入到IOC容器,这种方式的缺点是:无法灵活的指定引入的类型。
在这里插入图片描述

测试代码:

@Test
public void testBySpringConfig3() {
    // ioc 容器初始化
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    // ioc 通过bean 的 id 获取指定的bean
    Cat cat = applicationContext.getBean(Cat.class);
    // 使用bean
    System.out.println(cat);
}

结果:Cat(color=加菲猫, nick=null)

动态使用

动态可以通过一定的逻辑来指定加入 IOC 容器中的对象。
LoggerService.java

package org.spring.ss.service;

public class LoggerService {
}

CacheService .java

package org.spring.ss.service;

public class CacheService {
}

ImportSelector

通过实现接口 ImportSelector 的 selectImports 方法,来灵活控制引入的类型。

package org.spring.ss;

import org.spring.ss.service.CacheService;
import org.spring.ss.service.LoggerService;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class ImportSelectorImpl implements ImportSelector {
    /**
     *
     * @param annotationMetadata
     * @return
     *    IoC 要加载的类型的全路径的字符串数组
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // 在此处实现不同的业务逻辑控制
        return new String[]{LoggerService.class.getName(), CacheService.class.getName()};
    }
}

修改SpringConfig.java
在这里插入图片描述
测试代码

@Test
public void testBySpringConfigImport() {
    // ioc 容器初始化
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    // 获取 IOC 容器中所有 bean 的名字
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
}

在这里插入图片描述

ImportBeanDefinitionRegistrar

通过重写 ImportBeanDefinitionRegistrarregisterBeanDefinitions 方法,来灵活控制引入的类型。

package org.spring.ss;

import org.spring.ss.service.CacheService;
import org.spring.ss.service.LoggerService;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class ImportBeanDefinitionRegistrarImpl implements ImportBeanDefinitionRegistrar {

    /**
     *
     * @param annotationMetadata
     * @param beanDefinitionRegistry IoC容器中管理对象的一个注册器
     */
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // 需要将添加的对象包装为一个RootBeanDefinition对象
        RootBeanDefinition cache = new RootBeanDefinition(CacheService.class);
        beanDefinitionRegistry.registerBeanDefinition("cache",cache);

        RootBeanDefinition logger = new RootBeanDefinition(LoggerService.class);
        beanDefinitionRegistry.registerBeanDefinition("logger",logger);
    }
}

修改SpringConfig.java
在这里插入图片描述
执行测试用例
在这里插入图片描述

SpringBoot中的ConditionalXXX

@Conditional扩展注解作用(判断是否满足当前指定条件)
@ConditionalOnJava系统的Java版本是否符合要全
@ConditionalOnBean容器中存在指定的Bean
@ConditionalOnMissingBean容器中不存在指定的Bean
@ConditionalOnExpression满足SpEL表达式
@ConditionalOnClass系统中有指定的类
@ConditionalOnMissingClass系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource类路径下是否存在指定的资源文件
@ConditionalOnWebApplication当前是Web环境
@ConditionalOnNotWebApplication当前不是Web环境
@ConditionalOnJndiJNDI存在指定项

具体案例

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class ConditionalOnClass implements Condition {
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        try {
            Class<?> aClass = conditionContext.getClassLoader().loadClass("com.gupaoedu.test.Test1");
            return aClass==null?false:true;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return false;
    }
}
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;


public class ConditionalOnBean implements Condition {
    /**
     *  如果IoC容器中有Person对象就返回true 否在返回false
     *
     * @param conditionContext
     * @param annotatedTypeMetadata
     * @return
     *    true 表示IoC容器加载该类型
     *    false 表示IoC容器不加载该类型
     */
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        boolean flag = conditionContext.getRegistry().containsBeanDefinition("person");
        System.out.println(flag + " **** ");
        return flag;
    }
}

多环境下的解决方案之Profile

Profile本质就是Conditional的实现。
添加DataSource类:

package org.spring.ss.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class DataSource {
    private String username;

    private String password;

    private String url;
}

SpringConfig.java中添加环境配置。

@Bean
@Profile("pro") // 其实Profile注解本质上就是Conditional的一种实现
public DataSource proDataSource() {
    DataSource ds = new DataSource("root", "123", "192.168.11.190");
    return ds;
}

@Bean
@Profile("dev")
public DataSource devDataSource() {
    DataSource ds = new DataSource("admin", "456", "192.168.12.190");
    return ds;
}

添加测试代码:

public void testByProfile() {
   AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();

    ac.getEnvironment().setActiveProfiles("pro");
    ac.register(SpringConfig.class);
    ac.refresh();
    System.out.println(ac.getBean(DataSource.class));
}

输出DataSource(username=root, password=123, url=192.168.11.190)

Bean对象的作用域

作用域说明
prototype每次请求,都是一个新的Bean( java原型模式
singletonbean是单例的(Java单例模式
request在一次请求中,bean的声明周期和request同步
sessionbean的生命周期和session同步

默认的情况是 singleton

@Bean
@Scope("prototype")
public Person person(){
    return new Person();
}
@Bean
@Scope("singleton")
public Person person(){
    return new Person();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值