spring注解详解

1. spring容器注册Bean的方式
1. @Bean注解,注册第三方组件
2. @ComponentScan扫描组件,扫描2中所描述的组件
3. @Import
4. FactoryBean(工厂bean)
5. xml配置
2. 组件的标注
1. @Component
		泛指各种组件,就是说当我们的类不属于各种归类的时候
2. @Controller
		用于标注控制层,相当于struts中的action层
3. @Service
		用于标注服务层,主要用来进行业务的逻辑处理
4. @Repository
		用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件
5. @Configuration
		用于标注配置文件
	
其底层都是@Component实现
3. @ComponentScan组件扫描
1. ComponentScan使用
@ComponentScan(value = "com.wang.spring",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class,Service.class})})

2. FilterType枚举
		ANNOTATION 根据注解匹配
		ASSIGNABLE_TYPE 根据给定的类型
		ASPECTJ 根据表达式匹配
		REGEX 正则表达式
		CUSTOM 自定义匹配规则,需要实现TypeFilter接口重写match方法		
// 自定义匹配规则实现接口
@FunctionalInterface
public interface TypeFilter {

	/**
	 * metadataReader 当前正在扫描到得类信息
	 * metadataReaderFactory 其他任何类信息
	 */
	boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException;

}
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.filter.TypeFilter;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 可单独重复指定和ComponentScans中包含多个ComponentScan效果相同
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

   /**
    * 用于扫描带注释的基本包,可为多个包名数组
    */
   @AliasFor("basePackages")
   String[] value() default {};

   /**
    * 同value功能一致,两者互斥,只能使用其中之一
    */
   @AliasFor("value")
   String[] basePackages() default {};


   Class<?>[] basePackageClasses() default {};

   Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

   Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

   ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

   String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

   /**
    * 表示是否自动检测被注释标注的类
    */
   boolean useDefaultFilters() default true;

   /**
    * 指定哪些类型的组件能被扫描
    * 注意:使用时需要将useDefaultFiltes自动检测设置为flase,否则不生效
    * @see #resourcePattern()
    * @see #useDefaultFilters()
    */
   Filter[] includeFilters() default {};

   /**
    * 指定哪些类型的组件不被扫描
    */
   Filter[] excludeFilters() default {};

   /**
    * Specify whether scanned beans should be registered for lazy initialization.
    * <p>Default is {@code false}; switch this to {@code true} when desired.
    * @since 4.1
    */
   boolean lazyInit() default false;


   /**
    * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
    * include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
    */
   @Retention(RetentionPolicy.RUNTIME)
   @Target({})
   @interface Filter {

      FilterType type() default FilterType.ANNOTATION;

      @AliasFor("classes")
      Class<?>[] value() default {};

      @AliasFor("value")
      Class<?>[] classes() default {};
       
      String[] pattern() default {};

   }
}
4. 组件注册
4.1 @Scope指定bean的作用域
1. scope取值有四种:
	singleton 单例(默认)
		IOC容器初始化时就会创建该对象实例放入容器内,每次getBean获取的都是同一个对象实例
	prototype 多实例
		每次getBean获取时才会创建对象实例,且每次得到不同实例
	request 同一个请求创建一个实例
	session 同一个session创建一个实例
	
2. 示例:
	@Scope("prototype")
    @Bean
    public Person getPerson() {
        return new Person("张三");
    }
3. scope注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

	/**
	 * 默认值,同scopeName作用一样
	 */
	@AliasFor("scopeName")
	String value() default "";

	/**
	 * 指定要为带注释的组件/bean使用的范围的名称。
	 */
	@AliasFor("value")
	String scopeName() default "";

	/**
	 * 指定是否应该将组件配置为范围代理
	 * 如果是,代理应该是基于接口的还是基于子类的。
	 * <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
	 * that no scoped proxy should be created unless a different default
	 * has been configured at the component-scan instruction level.
	 * <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
	 * @see ScopedProxyMode
	 */
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}
4.2 @Lazy懒加载
1. 懒加载:容器启动时不创建对象实例,第一次getBean获取Bean时才创建对象实例
2. 示例:
	@Lazy
    @Bean
    public Person getPerson() {
        return new Person("张三");
    }
3. @Lazy注解:
    @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR,
     			ElementType.PARAMETER, ElementType.FIELD})
    // 可多次使用
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Lazy {

        /**
         * 是否进行延迟初始化
         */
        boolean value() default true;

    }
4.3 @Conditional条件注册组件
1. @Conditional注解在类或方法上
	类上:满足条件,该类中的所有配置(例如:注解@Bean的所有方法)才能生效
	方法上:满足条件,该方法中的配置生效
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * 实现Condition接口的的子类,重写matches方法,返回true才会加载
	 * 
	 */
	Class<? extends Condition>[] value();

}
    
2. Condition接口详解
@FunctionalInterface
public interface Condition {

	/**
	 * context 上下文环境,可获得当前运行环境、bean工厂、bean定义注册信息、类加载器等
	 * metadata 注解信息
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

3. 示例:当matchs方法返回true时,才将Person组件注册到容器中
	// value为实现Condition接口的类
	@Conditional(value = {? implement Condition})
    @Bean
    public Person getPerson() {
        return new Person("张三");
    }
4.4 @Import快捷注册组件
1. 方便快捷的给容器中注册组件,只能注释在类上
2. @import导入的组件的id跟其他方式导入的区别:默认是类的全类名(包+类名),其他方式导入id为首字母小写的类名

3. 示例:
	// User类
	public class User {
	}
	
	@Configuration
	// 导入User组件,bean的id为User类所在包名+类名
	@Import({User.class})
	public class User {
	}
	
4. @Import注解详解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * 可传入ImportSelector接口的实现类,并注册其方法的返回值类数组
	 * 可传入ImportBeanDefinitionRegistrar接口的实现类,并将其方法中注册到BeanDefinitionRegistry的bean定义导入容器
	 */
	Class<?>[] value();
}
4.4.1 ImportSelector接口 + @Import批量注册组件
1. ImportSelector接口详解
public interface ImportSelector{

	/**
	 * 返回值要被注册到容器中的所有类的全类名数组
	 * importingClassMetadata 可获取当前扫描到的类上的所有注解
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

}
    
2. 自定义实现
public class MyImportSelector implement ImportSelector{

	// 返回值就是要被注册到容器中的所有类的全类名
	@Override
	String[] selectImports(AnnotationMetadata importingClassMetadata) {
		return new String[]{"com.wangl.example.User","com.wangl.example.Role"};
	}

}

3. 示例:
        // 导入User组件、导入MyImportSelector类中selectImports方法返回的所有类
        // MyImportSelector类本身不会被注册
        @Configuration
        @Import({User.class,MyImportSelector.class})
        public class User {
        }
4.4.2 ImportBeanDefinitionRegistrar + @Import批量注册组件
1. ImportBeanDefinitionRegistrar接口详解
public interface ImportBeanDefinitionRegistrar {

	/**
	 * 根据给定的数据注册bean定义
	 * importingClassMetadata 当前扫描到的类的所有注解信息
	 * registry bean定义信息
	 */
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

2. 示例:
        // 导入User组件、导入MyImportBeanDefinitionRegistrar类中registerBeanDefinitions方法注册到bean定义中的类
        // 自定义类MyImportBeanDefinitionRegistrar不会被注册
        @Configuration
        @Import({User.class,MyImportBeanDefinitionRegistrar.class})
        public class User {
        }
    
3. 自定义实现
public class MyImportBeanDefinitionRegistrar implement ImportBeanDefinitionRegistrar{
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
			// User类的bean定义信息
			RootBeanDifinition beanDifinition = new RootBeanDifinition(User.class);
			// 注册bean定义到容器中
			registry.registerBeanDefinition("bean的id",beanDifinition);
	}
}
4.5 FactoryBean接口注册bean
1. 实现接口,重写其方法。
public class User implements FactoryBean {

    /**
     * @Description 返回一个Role对象,这个对象会被注册到IOC容器中。而不是User对象
     * @Param []
     * @return
     */
    @Override
    public Object getObject() throws Exception {
        return new Role();
    }

    /**
     * @Description 是否为单实例和@Scope注解类似 - true为单实例
     * @Param []
     * @return
     */
    @Override
    public boolean isSingleton() {
        return true;
    }

    /**
     * @Description 返回bean对象的类型
     * @Param []
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return Role.class;
    }
}
2. 注意事项
	// 注册的其实是Role类,调用了FactoryBean的getObject方法
	@Bean
    public User getUser() {
        return new User();
    }
3. 要从IOC容器中获取自定义的FactoryBean本身User的bean对象,其id前需要加”&“符号,源码是给其id前加了该前缀
	getBean("&user")
5. bean的生命周期
  1. doCreateBean(),创建对象

  2. populateBean(),填充属性

  3. initializeBean{

    ​ 3.1 beanPostProcessorBefore(),bean前置处理器

    ​ 3.2 init(),bean初始化

    ​ 3.3 beanPostProcessorAfter(),bean后置处理器

    }

  4. destroy(),对象销毁

5.1 对象创建
  1. 单实例:容器启动的时候创建对象
  2. 多实例:每次获取的时候创建对象
5.2 对象初始化

对象实例化、属性赋值完成后调用初始化方法

5.3 对象销毁
  1. 单实例:容器关闭的时候销毁
  2. 多实例:容器只创建不进行管理,也就不会自动调用销毁方法
5.4 初始化、销毁方法的实现方式
5.4.1 @Bean - 注解属性指定初始化、销毁
1. 示例
	// 返回值为哪个类,该类的实例对象添加初始化和销毁方法
	@Bean(value = "user",initMethod = "初始化方法名",destroyMethod = "销毁方法名")
    public User getUser() {
        return new User();
    }
5.4.2 InitializingBean接口初始化、DisposableBean接口销毁
1. 示例
// 注册User组件,哪个类实现接口,该类的实例对象添加初始化和销毁方法
@Component
public class User implements InitializingBean, DisposableBean {

    /**
     * @Description User对象的初始化方法
     * @Param []
     * @return
     */
    @Override
    public void afterPropertiesSet() throws Exception {
		
    }

    /**
     * @Description User对象的销毁方法
     * @Param []
     * @return
     */
    @Override
    public void destroy() throws Exception {

    }
}
5.4.3 @PostConstruct - 对象初始化
1. 执行时机 
		在对象创建完成且属性赋值完成后执行该注解标注的初始化方法,注解只能在方法上。标注在哪个类中的方法上,就为该类的实例对象添加初始化方法
2. @PostConstruct注解
		该注解是jdk实现,不是spring框架。PostConstruct的实现其实也是一个BeanPostProcessor,只不过这个BeanPostProcessor实现了PriorityOrdered接口, 所以他的优先顺序也会比普通的postProcessBeforeInitialization优先执行
3. 初始化方法执行的顺序 
		构造函数 => PostConstruct => postProcessBeforeInitialization =>
		InitializingBean => initMethod => postProcessAfterInitialization
4. 源码
        package javax.annotation;

        @Documented
        @Retention (RUNTIME)
        @Target(METHOD)
        public @interface PostConstruct {
        }
5.4.4 @PreDestroy - 对象销毁
1. 执行时机 
		在IOC容器关闭要清理对象之前执行,注解只能在方法上。标注在哪个类中的方法上,就为该类的实例对象添加销毁方法
2. @PreDestroy注解
		该注解是jdk实现,不是spring框架。
3. 销毁方法执行的顺序 
		
4. 源码
        package javax.annotation;
        @Documented
        @Retention (RUNTIME)
        @Target(METHOD)
        public @interface PreDestroy {
        }
5.4.5 BeanPostProcessor后置处理器

所有实现该接口的处理器,IOC容器会在每个bean的初始化前后遍历所有的实现,调用其方法

/**
 * 会在所有bean初始化前后调用其两个方法
 */
public interface BeanPostProcessor {

  /**
   * 在bean的初始化之前调用,即5.4.1中的init-mothod、5.4.2中的afterPropertiesSet方法之前调用、
   * @PostConstruct标注的方法之前调用
   */
   @Nullable
   default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }
	
  /**
   * 在bean的初始化之后调用,即5.4.1中的init-mothod、5.4.2中的afterPropertiesSet方法之后调用
   * @PostConstruct标注的方法之后调用
   */
   @Nullable
   default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }
}
6. 属性赋值
6.1 @Value - 属性赋值
1. 基本数值:字符串、int等
2. SpeL(spring表达式):#{20-15} = 给属性赋值5
3. 获取配置文件中值(运行在环境变量中的值):${对应配置文件key}
   		yml和application配置文件springboot启动容器时将其加入环境变量中,所以可以直接获取
   		其余如:properties文件中的值要获取,需要先将properties配置文件加载到spring环境变量中,详细请看6.2加载配置文件
6.2 @PropertySource - 加载properties文件
1. 作用:将配置文件加载到环境变量(environment)中
2. 使用:@PropertySource(value={"classpath://application.properties","也可写文件绝对路径"})
		value支持两种形式读取配置文件:
			1. 项目的根路径:classpath:/com/myco/app.properties
			2. 文件绝对路径:file:/com/myco/app.properties
3. @PropertySource注解的value属性可传入多个配置文件,也可重复多个@PropertySource注解,还可使用
	@PropertySources注解传入多个@PropertySource,三种方式殊途同归
6.3 @Autowired - 自动装配
1. 装配方式
		1. 默认优先按照类型在容器中找对应组件
		2. 若该类型的组件有多个实现,优先获取该注解所标注的属性的名称作为id的组件
		3. 如果需要获取指定id的组件,可联合使用@Qualifier注解指定要获取的组件名称
2. @Autowired默认是一定要找到对应的组件,如果未找到直接报错(NoSuchBeanDefinitionException)
		1. @Autowired(required = false)指定未找到组件时,也可不装配,即组件为null
3. 标注位置
		构造器、@Bean标注方法的参数、set方法、类属性
4. 标注位置详解
		1. 【构造器】
			1.1 构造器参数包含该类的所有属性且都赋值,都会自动装配成功。
			1.2 构造器参数少于属性或有未进行构造赋值的参数,该参数都不会自动装配
			1.3 当只有一个有参构造时,可省略@Autowired注解,构造中进行赋值的参数也会装配成功
			1.4 多个有参构造时,不能省略。且需要按需在构造方法上标注注解
		2. 【set方法】
			2.1 实例化bean后,会调用set方法给对应属性赋值。且set方法的参数对象也是从容器中获取,所以
				其形参都需要注入到容器中
		3. 【@Bean标注方法的参数】
			3.1 @Bean标注的方法,其方法的形参默认都是从容器中获取,参数是否标注@Autowired注解都无所				 谓,所以其形参都需要注入到容器中
		
5. 具体实现:Autowired是由AutowiredAnnotationBeanPostProcessor类解析完成装配功能
6.3.1 @Qualifier - 自动装配指定组件
1. 当@Autowired标注的属性有多个实现,需要获取指定id的组件时,可联合使用@Qualifier注解指定要获取的组件名称
2. 明确指定后优先级最高,高于@Autowired的默认选择,也高于@Primary标注的首选
3. 示例:
		public class User {
			
			// 自动装配
			@Qualifier("role2")
			@Autowired
			private Role role;
		}
		
		// 注册Role组件,id为:role
		@Component
		public Class Role {
		
		}
		
		@Configuration
		public Class Config {
			// 注册Role组件,id为:role2
			@Bean("role2")
			public Role getRole() {
				return new Role();
			}
		}
6.3.2 @Primary - 自动装配首选组件
1. 当@Autowired标注的属性有多个实现,需要首选获取某个实现时,可使用@Primary注解
2. 示例:
		public class User {
			
			// 自动装配,会首选role2进行注册
			@Autowired
			private Role role;
		}
		
		// 注册Role组件,id为:role
		@Component
		public Class Role {
		
		}
		
		@Configuration
		public Class Config {
			// 注册首选Role组件,id为:role2
			@Primary
			@Bean("role2")
			public Role getRole() {
				return new Role();
			}
		}
6.4 @Resource
1. 所属java规范
		JSR250
2. 作用
		同@Autowired一样可实现自动装配功能,但不支持联合@Qualifier、@Qualifier使用。
3. 装配方法
		默认按照属性名称进行装配组件
		可指定组件名称进行装配:@Resource(name="role2")
6.5 @Inject
1. 所属java规范
		JSR330
2. 使用
		需要导入包:javax.inject包,才能使用
3. 作用
		同@Autowired一样可实现自动装配功能,可以支持联合@Qualifier、@Qualifier使用,但是不能指定属性是否可以为非必需的:required = false	
7. Aware接口
1. 作用
		自定义组件需要使用Spring容器底层实现的一些组件时,可使自定义组件实现spring提供的对应Aware接口
2. aware功能的实现
		XXAware接口 ========》 XXAwareProcessor解析实现
3. 种类(非全部)
		1. BeanNameAware#setBeanName  - bean名称
        2. BeanClassLoaderAware#setBeanClassLoader
        3. BeanFactoryAware#setBeanFactory  - bean工厂
        4. EnvironmentAware#setEnvironment
        5. EmbeddedValueResolverAware#setEmbeddedValueResolver- 字符串解析器(解析#{}、			${}等占位符,${}可获取到容器启动后环境变量中的值,#{}可执行spring提供表达式)
        6. ResourceLoaderAware#setResourceLoader  - 资源加载器
        7. ApplicationEventPublisherAware#setApplicationEventPublisher
        8. MessageSourceAware#setMessageSource  - 国际化
        9. ApplicationContextAware#setApplicationContext  - IOC容器
        10. ServletContextAware#setServletContext
        11. LoadTimeWeaverAware#loadTimeWeaverAware
        12. ImportAware#setImportMetadata
        13. ImportAware > BeanNameAware#setBeanName
7.1 接口的执行顺序及实现

1、其中 1~3 的执行顺序在 AbstractAutowireCapableBeanFactory 类中,该类继承的 AbstractBeanFactory#createBean 方法中,按照 doCreateBean > initializeBean > invokeAwareMethods 顺序调用,在 invokeAwareMethods 方法中,按照以下顺序处理 Aware 接口

private void invokeAwareMethods(final String beanName, final Object bean) {
    if (bean instanceof Aware) {
        if (bean instanceof BeanNameAware) {
            ((BeanNameAware) bean).setBeanName(beanName);
        }
        if (bean instanceof BeanClassLoaderAware) {
            ClassLoader bcl = getBeanClassLoader();
            if (bcl != null) {
                ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
            }
        }
        if (bean instanceof BeanFactoryAware) {
            ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
        }
    }
}

2、其中 3~8 的执行顺序在 ApplicationContextAwareProcessor 类中,该类实现的 BeanPostProcessor#postProcessBeforeInitialization 方法中,按照以下顺序处理的 Aware 接口

private void invokeAwareInterfaces(Object bean) {
    if (bean instanceof Aware) {
        if (bean instanceof EnvironmentAware) {
            ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
        }
        if (bean instanceof EmbeddedValueResolverAware) {
            ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
        }
        if (bean instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
        }
        if (bean instanceof ApplicationEventPublisherAware) {
            ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
        }
        if (bean instanceof MessageSourceAware) {
            ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
        }
        if (bean instanceof ApplicationContextAware) {
            ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
        }
    }
}

3、 9 在 ServletContextAwareProcessor 中处理,该类实现的 BeanPostProcessor#postProcessBeforeInitialization 方法中,按照以下顺序处理的 Aware 接口:

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    if (getServletContext() != null && bean instanceof ServletContextAware) {
        ((ServletContextAware) bean).setServletContext(getServletContext());
    }
    if (getServletConfig() != null && bean instanceof ServletConfigAware) {
        ((ServletConfigAware) bean).setServletConfig(getServletConfig());
    }
    return bean;
}

4、10 只有在使用 @EnableLoadTimeWeaving 或者存在 LoadTimeWeaver 实现的 Bean 时才会调用,顺序也很靠后。

11 只有被其他配置类 @Import(XX.class) 时才会调用,这个调用对 XX.class 中的所有 @Bean 来说顺序是第 1 的。

12 为了证明 11,注意 11 和 12 和前面所有输出不是同一个 Bean

8. @Profile - 组件生效环境
1. 指定组件可被注册到容器中的环境
2. 加了@Profile环境标识注解的bean,只有当指定的环境激活时才能被注册到容器中,默认是default环境
3. 当@Profile写在配置类上,只有当前环境是指定的环境时,整个配置类中的所有配置才能生效,否则都不生效
4. 没有指定生效环境的类或方法,在任何环境下都生效
5. 示例
		1. 可标注在数据源方法上,切换环境变更数据源链接
		2. @Profile("test")、@Profile("dev")、@Profile("prod"),可自定义,推荐有实际意义
6. 环境指定
		1. 通过IDE开发工具指定JVM环境VM options : -Dspring.profiles.active=test
		2. 通过applicationContext指定:
			AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
			// 获取容器环境并指定当前生效环境
            applicationContext.getEnvironment().setActiveProfiles("dev");
            // 手动注册配置文件
            applicationContext.register(MainConfig.class);
            // 手动刷新容器
            applicationContext.refresh();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值