Spring Java Config——组件注册相关注解

目录

组件注册


@Order 注解 : 主要用来控制配置类的加载顺序
@Order 及 Order接口


给容器中注册组件

  • 包扫描+组件标注注解(@Component、@Service、@Controller、@Repository,主要是自己写的类
  • @Bean[导入的第三方包里面的组件]
  • @Import[快速给容器中导入一个组件]
    • Import(类名.class),容器中就会自动注册这个组件,id默认是组件的全名
    • ImportSelector:返回需要导入的组件的全类名的数组
    • ImportBeanDefinitionRegistrar:手动注册bean
  • 使用Spring提供的FactoryBean(工厂bean)
    • 默认获取到的是工厂bean调用getObject创建的对象
    • 要获取到bean本身,需要给id前面加个&标识
  • @Conditional({Condition}) :按照一定的条件判断,满足条件给容器中注册bean
  • @Scope
    • prototype:多例的 ioc容器启动并不会去调用方法创建对象在容器中,而是每次获取时才会调用方法创建对象
    • singleton:单例的(默认值) ioc容器启动会调用方法创建对象放到ioc容器中,以后每次获取就是从容器中拿

一、@Configuration

跳转到目录

  • @Configuration : 把一个类标记为spring的配置类,相当于之前的applicationContext.xml文件

1、看看之前通过applicationContext.xml配置文件来创建类的实例

public class SomeBean {}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 创建SomeBean的实例,交给IoC来管理 -->
    <bean id="somebean" class="com.zy._01_hello.SomeBean"/>
</beans>

测试方法

public class SomeBeanTest {
    /*
        Spring XML Config
     */
    @Test
    public void test(){
        // 加载配置文件
        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        // 从Spring容器中获取SomeBean的实例
        SomeBean someBean = ctx.getBean("somebean", SomeBean.class);
        System.out.println(someBean);
    }
}
  • 这种方式是通过Spring XML Config的方式来将交给Spring容器处理; 但是后来发现有很多这样的xml不容易管理,形成了配置类; 于是就有了后来的SpringBoot 在SpringBoot中几乎看不到配置文件了,取而代之的是Spring Java Config的配置类的形式!

2、创建配置类

// 把一个类标记为spring的配置类; (类名Config可以简单理解为XML中的 beans)
@Configuration
public class Config {
	// <bean id="somebean" class="com.zy._01_hello.SomeBean"/>
    @Bean // 给容器中注册一个Bean, 类型为返回值类型, id默认是方法名
    public SomeBean somebean(){
        return new SomeBean();
    }
}

测试方法

public class SomeBeanTest {
    /*
        Spring Java Config
     */
    @Test
    public void test1(){
        ApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
        SomeBean someBean = ctx.getBean(SomeBean.class);
        System.out.println(someBean);
    }
}
  • 这里要通过AnnotationConfigApplicationContext(config.class)来加载配置类,然后拿到配置类中SomeBean的实例即可(将组件的创建交给Spring容器处理, 也就是将组件注册到容器中)!

二、@Bean

跳转到目录

  • @Bean相当于在配置文件中写的<bean id="" class="" />, 将一个类的创建实例交给Spring IoC来处理;

配置文件中写的bean

<bean id="" class="" name="" init-method="" destory-method="" scope="">
    <property name="" value=""/>
    <property name="" ref=""/>
</bean>

配置类中写的bean

@Configuration
public class Config {
    @Bean
    public SomeBean someBean1() {
        return new SomeBean();
    }

    @Bean
    public SomeBean someBean2() {
        return new SomeBean();
    }

    @Bean(name = {"sb", "sbb"})
    public SomeBean someBean3() {
        return new SomeBean();
    }
}

1、在配置类中@Bean的含义

  • 被@Bean标注的方法的名字 —> bean的id
  • 方法的返回值类型 —> bean的class类型
  • 除了默认使用方法的名字作为id外, 还可以通过@Bean(name={"xxx1", "xxx2"})来指定多个id名; 然后通过name来获取其对象
@Test
public void test2() {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
    SomeBean someBean1 = ctx.getBean("someBean1", SomeBean.class); // bean的id去找
    System.out.println(someBean1);

    SomeBean someBean2 = ctx.getBean("someBean2", SomeBean.class);
    System.out.println(someBean2);

    SomeBean someBean3 = ctx.getBean("sb", SomeBean.class);
    System.out.println(someBean3);

    SomeBean someBean4 = ctx.getBean("sbb", SomeBean.class);
    System.out.println(someBean4);
}

2、配置initMethod、destroyMethod的方法

构造(对象创建)
  • 单例: 在容器启动(加载配置类/加载配置文件)的时候创建对象;
    • 容器启动先创建对象, 然后调用init(初始化方法);
  • 多例: 在每次获取bean(getBean())的时候创建对象;
    • 容器创建完成之后, 才创建对象完成init(初始化);
  • 方式一: 可以在@Bean中的属性initMethod, destroyMethod来指定初始化,销毁方法
@Bean(name="sb", initMethod = "init", destroyMethod = "destory")
  • 方式二: @PostConstruct,@PreDestroy来指定初始化,销毁方法
public class SomeBean {

    // 方式二: 配置init,destory  @PostConstruct,@PreDestroy

    @PostConstruct
    public void init() {
        System.out.println("SomeBean.init");
    }

    @PreDestroy
    public void destory() {
        System.out.println("SomeBean.destory");
    }
}

测试方法

public class SomeBeanTest {
    /*
        Spring Java Config
     */
    @Test
    void test(){
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
        SomeBean someBean = ctx.getBean("sb", SomeBean.class);
        System.out.println(someBean);
        ctx.close(); // 非Spring Test,容器不会正常关闭,调用close方法才可以
    }
}

3、@Bean依赖注入的方式

相当于之前在applicationContext.xml中各个bean之间通过<property标签进行进行赋值, ref等

public class OtherBean {
}

@Setter
@Getter
public class SomeBean {
    private OtherBean otherBean;
}
  • 方式一: 通过类似内部bean的方式
@Configuration
public class Config {
    /*
        方式一: 相当于内部Bean的形式
        <bean id="" class="">
            <property name="otherBean">
                <bean class="" />  内部bean的方式
            </property>
        </bean>
        这种方式用的很少!
     */
    @Bean
    public SomeBean someBean() {
        SomeBean sb = new SomeBean();
        sb.setOtherBean(new OtherBean());
        return sb;
    }
}
  • 方式二: 通过调用需要注入的Bean的方式名()即可

配置类

@Configuration
public class Config {

	@Bean
    public OtherBean otherBean() {
        return new OtherBean();
    }

    @Bean
    public SomeBean someBean(OtherBean otherBean) {
        SomeBean sb = new SomeBean();
        sb.setOtherBean(otherBean);
        return sb;
    }

	// 上面的简化写法
    @Bean
    public SomeBean someBean() {
        SomeBean sb = new SomeBean();
        sb.setOtherBean(otherBean());
        return sb;
    }
}
  • 方式三: 需要依赖的Bean, 放入到参数列表中,会自动注入;
    • 有多个OtherBean的实例时,可以使用 @Qualifier("bean的id")来指定
    • 有多个OtherBean时,在某个bean上添加 @Primary, 会优先注入该bean
    • 有多个OtherBean时,在参数列表中通过形参名称来指定对应的bean
@Bean
//@Primary
public OtherBean otherBean() {
    return new OtherBean("ob1");
}

@Bean
public OtherBean otherBean2() {
    return new OtherBean("ob2");
}

@Bean
// public SomeBean someBean(@Qualifier("otherBean") OtherBean ob) {
public SomeBean someBean(OtherBean otherBean) {
    SomeBean sb = new SomeBean();
    sb.setOtherBean(otherBean);
    return sb;
}

三、@ComponentScan

跳转到目录

  • 可以完成Spring组件的自动扫描(默认情况下,会去扫描被标注的类的对应的包(及其子包中)的所有的类; (@Configuration/ @Component/ @Service等注解)

配置类Config

@Configuration
// 可以完成Spring组件的自动扫描(默认情况下,会去扫描被标注的类的对应的包(及其子包中)的所有的类
//@ComponentScan(basePackages = "com.zy._04_componentscan") // 也可以自己指定扫描的范围
@ComponentScan
public class Config {
}

OtherBean和SomeBean类

@Component //设置该类作为Spring管理的组件
public class OtherBean {
}

@Component // 相当于之前在xml中写的bean标签
public class SomeBean {
	// 将Spring通过@Component创建好的OtherBean的实例,注入到下面的属性中;
	// 该实例是代理对象
    @Autowired 
    private OtherBean otherBean;
}

测试方法

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Config.class)
public class SomeBeanTest {

    @Autowired
    private SomeBean someBean;

    @Test
    public void test(){
        System.out.println(someBean.getOtherBean());
    }
}
  • 组件注册时的过滤条件
    • @ComponentScan value:指定要扫描的包
    • excludeFilters=Filter[]:指定扫描包的时候按照什么规则排除哪些组件
    • includeFilters=Filter[]:指定扫描包的时候要包含哪些组件,需将useDefaultFilters置false
    • FilterType.ANNOTATION:按照注解
    • FilterType.ASSIGNABLE_TYPE:按照指定的类型
    • FilterType.ASPECTJ: 切入点表达式
    • FilterType.REGEX:使用正则指定
    • FilterType.CUSTOM:使用自定义规则

      自定义规则: 需要继承TypeFilter类, 来实现其接口; 接口方法中可以获取当前类注解信息AnnotationMetadata/正在扫描的类信息ClassMetadata;通过类信息可以判断哪些bean组件要被加载到Spring容器

// 配置类 == 配置文件
@Configuration  // 告诉Spring这是一个配置类
//@ComponentScan(value = "com.zy")    // 不写默认扫描当前类所在包(及其子包)下的所有类(指定要扫描的包)

//@ComponentScan(value = "com.zy", excludeFilters = {
//        // excludeFilters: FilterType是过滤条件(这里是根据注解来过滤); 将Controller,Service的bean过滤掉(不扫描它们)
//        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
//})
@ComponentScan(value = "com.zy", includeFilters = {
        // excludeFilters: FilterType是过滤条件(这里是根据注解来过滤); 将Controller,Service的bean过滤掉(不扫描它们)
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class})
}, useDefaultFilters = false)

// @ComponentScan   value: 指定扫描的包
// excludeFilters = Filter[] : 指定扫描的时候按照什么规则排除哪些组件
// includeFilters = Filter[] : 指定扫描的时候只需要包含哪些组件
// FilterType.ANNOTATION: 按照注解作为过滤规则
// FilterType.ASSIGNABLE_TYPE: 按照给定的类型作为过滤规则
public class MainConfig {

    // 给容器中注册一个Bean, 类型为返回值类型, id默认是方法名
    @Bean("person")
    public Person person(){
        return new Person("lisi", 22);
    }
}

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MainConfig.class)
public class IoCTest {

    @Test
    public void test1(){
        ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);
        // 看容器中有哪些bean,返回这些bean的名称
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            System.out.println(name);
        }
    }
}

四、@Scope

跳转到目录

  • 用来表示bean的范围(是单例还是多例)
  • prototype: 多例的 : IoC容器启动 并不会去调用方法创建对象 放在容器中, 每次获取bean的时候才会调用方法创建对象;
  • singleton: 单例的 : IoC容器启动就会 调用方法创建对象 放到IoC容器中;
  • request: 同一次请求创建一个实例
  • session: 同一个session创建一个实例
	@Scope("prototype")
    //@Scope // 默认不写value就是singleton
    @Bean
    public Person person(){
        System.out.println("给容器中添加Person...");
        return new Person("张三", 22);
    }
@Test
public void test2(){
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig2.class);
    // 看容器中有哪些bean,返回这些bean的名称
//        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
//        for (String name : beanDefinitionNames) {
//            System.out.println(name);
//        }
    // 默认是单例的
    System.out.println("IoC容器创建完成...");
    Object person1 = ctx.getBean("person");
    Object person2 = ctx.getBean("person");
    System.out.println(person1 == person2);
}
  • @Lazy
    第一次使用Bean的时候创建,不使用则不创建(即使IoC容器启动了) ;
	//@Scope("prototype")
    @Scope // 默认是单实例
    @Lazy //第一次使用Bean的时候创建,不使用则不创建(即使IoC容器启动了)
    @Bean
    public Person person(){
        System.out.println("给容器中添加Person...");
        return new Person("张三", 22);
    }
  • @PostConstruct、@PreDestroy

初始化相关方法: @PostConstruct

等同于实现: InitializingBean接口
xml方式: <bean init-method=""/>

销毁方法:@PreDestory

等同于实现: DisposableBean接口
xml方式: <bean destory-method=""/>

注意:

上述的两个注解并不是 Spring 提供的,由 JSR(JavaEE规范)520 提供

五、@Condition

跳转到目录

  • @Conditional: 按照一定的条件进行判断,满足条件给容器中注册bean
    • @Conditional 放在配置类上, 当前配置类满足条件, 该配置类中所有@Bean才生效
    • @Conditional 放在@Bean方法上, 只对该方法生效

如果不满足Conditional, 则不会加入到Spring容器

MacOSXCondition

// 判断是否Mac系统
public class MacOSXConditaion implements Condition {
    /**
     *
     * @param conditionContext :判断条件能使用的上下文(环境)
     * @param annotatedTypeMetadata : 注释信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 是否Mac系统

        //1. 能获取到IoC使用的beanfactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //2. 获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        //3. 获取到bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();

		// -----------------------------------------------------------
		//4. 获取当前环境信息(封住系统的的环境信息等,虚拟机等信息)
        Environment environment = conditionContext.getEnvironment();
        String property = environment.getProperty("os.name");
        if (property.contains("Mac OS X"))
            return true;

        return false;
    }
}

WindowsCondition

// 判断是否windows系统
public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        String property = environment.getProperty("os.name");
        if (property.contains("Windows"))
            return true;
        return false;
    }
}

配置类

// ComponentScan 这里省略扫描组件

/*
    @Conditional: 按照一定的条件进行判断,满足条件给容器中注册bean

    如果系统是windows, 给容器中注册("bill")
    如果系统是mac, 给容器中注册("linus")
 */
// 配置到这个方法上,只对这个方法作条件判断: 如果满足,则这个方法注册的bean才能生效
@Conditional({WindowsCondition.class})
@Bean("bill")
public Person person1(){
    return new Person("Bill Gates", 62);
}

@Conditional({MacOSXConditaion.class})
@Bean("linus")
public Person person2(){
    return new Person("linus", 48);
}

测试

@Test
public void test3(){

	ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig2.class);

    // 根据Person类型来获取容器中bean的名称
    String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);

    // 动态获取环境变量的值: Mac OS X
    Environment environment = ctx.getEnvironment();
    String property = environment.getProperty("os.name");
    System.out.println(property); // Max OS X

    for (String name : beanNamesForType) {
        System.out.println(name); // linus
    }

    Map<String, Person> persons = ctx.getBeansOfType(Person.class);
    System.out.println(persons); // 只会输出linus的相关信息
}

六、@Import

跳转到目录

  • 作用
    • 用于快速导入其他的配置类
    • 也可以导入一个需要注册的组件(类), id默认是全类名;

DataSource类

public class DataSource {
}

RedisTemplate类

public class RedisTemplate {
}

DataSourceConfig配置类

@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource(){
        return new DataSource();
    }
}

RedisConfig配置类

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(){
        return new RedisTemplate();
    }
}

AppConfig配置类

@Configuration
@Import({DataSourceConfig.class, RedisConfig.class})
public class AppConfig {
}

ImportTest测试类

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(classes = {DataSourceConfig.class, RedisConfig.class})
@ContextConfiguration(classes = AppConfig.class)
public class ImportTest {

    @Autowired
    private DataSource ds;

    @Autowired
    private RedisTemplate rt;

    @Test
    public void test() {
        System.out.println(ds);
        System.out.println(rt);
    }
}

1、ImportSelector接口

  • 返回需要导入的组件的全类名

需要导入的组件

public class Blue {
}

public class Red {
}

自定义MyImportSelector

// 自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
    /**
     *
     * @param annotationMetadata 当前标注@Import注解类的所有注解信息
     * @return 返回要导入到容器中的组件的全类名
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.zy.beans.Blue","com.zy.beans.Red"};
    }
}

配置类

@Configuration
@Import(MyImportSelector.class)
public class MainConfig2 {
}

测试

@Test
public void testImport(){
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig2.class);
    printBeans((AnnotationConfigApplicationContext) ctx);
    Blue bean = ctx.getBean(Blue.class);
    System.out.println(bean); // 这样就可以成功在Spring 容器中获取到bean了; 底层扔是反射获取对象
}

private void printBeans(AnnotationConfigApplicationContext atx){
    String[] definitionNames = atx.getBeanDefinitionNames();
    for (String name : definitionNames) {
        System.out.println(name);
    }
}

2、ImportBeanDefinitionRegistrar接口

自定义类来实现该接口, 在接口方法中; 可以手动注册一个bean到Spring容器中

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     *
     * @param annotationMetadata 当前注解信息
     * @param beanDefinitionRegistry BeanDefinition注册类
     *  把所有需要添加到容器的bean; 调用BeanDefinitionRegistry.registerBeanDefinition手工注册进来
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // 简单写点逻辑(看容器中是否有Red和Blue的bean)
        boolean definition1 = beanDefinitionRegistry.containsBeanDefinition("red");
        boolean definition2 = beanDefinitionRegistry.containsBeanDefinition("blue");

        // 如果存在上面两个bean, 则将rainbow也注册到Spring容器
        if (definition1 && definition2)
            beanDefinitionRegistry.registerBeanDefinition("rainBow", new RootBeanDefinition(RainBow.class));
    }
}
@ComponentScan("com.baizhiedu.bean")
@Import(MyImportBeanDefinitionRegistrar.class)
public class AppConfig6 {

    @Bean
    public Red red() {
        return new Red();
    }

    @Bean
    public Blue blue() {
        return new Blue();
    }
}
@Test
public void test() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig6.class);
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for (String name : beanDefinitionNames) {
        System.out.println(name);
    }
}

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
appConfig6
customer
red
blue
rainBow

3、@ImportResource

  • @ImportResource来引入xml配置文件, 使xml和javaconfig共同使用
public class OtherBean {
}

@Setter
@Getter
public class SomeBean {
    private OtherBean otherBean;
}

配置类

@Configuration
@ImportResource("classpath:applicationContext.xml")
public class AppConfig {
    @Bean
    public SomeBean someBean(OtherBean otherBean){
        SomeBean sb = new SomeBean();
        sb.setOtherBean(otherBean);
        return sb;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 创建SomeBean的实例,交给IoC来管理 -->
    <bean id="otherBean" class="com.zy._01_hello.OtherBean"/>
</beans>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class AppTest {

    @Autowired
    private SomeBean someBean;

    @Test
    public void test() {
        System.out.println(someBean.getOtherBean());
    }
}

七、使用FactoryBean注册组件

跳转到目录

// 创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {

    // 返回一个Color对象,这个对象会添加到容器中
    @Override
    public Color getObject() throws Exception {
        System.out.println("ColorFactoryBean.getObject");
        return new Color();
    }

    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }

    // 是否是单例: true, 在容器中只会保留一份
    @Override
    public boolean isSingleton() {
        return true;
    }
}

配置类

@Configuration
public class MainConfig{
	// 实际返回的是getObject()方法返回的对象
	@Bean
    public ColorFactoryBean colorFactoryBean(){
        return new ColorFactoryBean();
    }
}

测试

@Test
public void testImport() {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig2.class);
    printBeans((AnnotationConfigApplicationContext) ctx);
    Blue bean = ctx.getBean(Blue.class);
    System.out.println(bean);

    // 工厂Bean获取的是调用getObject创建的对象; 
    // 因为Spring容器会帮我们对Bean进行加工, 然后返回代理对象
    Object bean2 = ctx.getBean("colorFactoryBean");
    Object bean3 = ctx.getBean("colorFactoryBean");
    System.out.println("bean的类型:" + bean2.getClass());
    System.out.println(bean2 == bean3);

    // 获取ColorFactorybean的本身
    Object bean4 = ctx.getBean("&colorFactoryBean");
    System.out.println(bean4.getClass());
}

private void printBeans(AnnotationConfigApplicationContext atx) {
    String[] definitionNames = atx.getBeanDefinitionNames();
    for (String name : definitionNames) {
        System.out.println(name);
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

white camel

感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值