Spring学习笔记整理——注解

学习链接地址:http://www.iteye.com/blogs/subjects/spring3

第一部分 实现Bean依赖注入

注解实现Bean配置主要用来进行如依赖注入、生命周期回调方法定义等,不能消除XML文件中的Bean元数据定义,且基于XML配置中的依赖注入的数据将覆盖基于注解配置中的依赖注入的数据
Spring3的基于注解实现Bean依赖注入支持如下三种注解:

  • Spring自带依赖注入注解: Spring自带的一套依赖注入注解;
  • JSR-250注解:Java平台的公共注解,是Java EE 5规范之一,在JDK6中默认包含这些注解,从Spring2.5开始支持。
  • JSR-330注解:Java 依赖注入标准,Java EE 6规范之一,可能在加入到未来JDK版本,从Spring3开始支持;
  • JPA注解:用于注入持久化上下文和尸体管理器。

Spring自带依赖注入注解

@Required:依赖检查

基于@Required的依赖检查表示注解的setter方法必须,即必须通过在XML配置中配置setter注入,如果没有配置在容器启动时会抛出异常从而保证在运行时不会遇到空指针异常,@Required只能放置在setter方法上,且通过XML配置的setter注入,可以使用如下方式来指定:

@Requried
setter方法

public class TestBean {
    private String message;
    @Required
    public void setMessage(String message) {
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
}
<bean id="testBean" class="cn.javass.spring.chapter12.TestBean">
    <property name="message" ref="message"/>
</bean>
<bean id="message" class="java.lang.String">
    <constructor-arg index="0" value="hello"/>
</bean>
public class DependencyInjectWithAnnotationTest {
    private static String configLocation = "classpath:chapter12/dependecyInjectWithAnnotation.xml";
    private static ApplicationContext ctx = new ClassPathXmlApplicationContext("configLocation");
    //1、Spring自带依赖注入注解
     @Test
    public void testRequiredForXmlSetterInject() {
        TestBean testBean = ctx.getBean("testBean", TestBean.class);
        Assert.assertEquals("hello", testBean.getMessage());
    }
}
@Autowired:自动装配

基于@Autowired的自动装配,默认是根据类型注入,可以用于构造器、字段、方法注入,使用方式如下:

@Autowired(required=true)
构造器、字段、方法

@Autowired默认是根据参数类型进行自动装配,且必须有一个Bean候选者注入,如果允许出现0个Bean候选者需要设置属性“required=false”,“required”属性含义和@Required一样,只是@Required只适用于基于XML配置的setter注入方式。

  • 构造器注入:通过将@Autowired注解放在构造器上来完成构造器注入,默认构造器参数通过类型自动装配。通过在构造器上添加@ Autowired来完成根据参数类型完成构造器注入
public class TestBean11 {
    private String message;
    @Autowired //构造器注入
    private TestBean11(String message) {
        this.message = message;
    }
    //省略message的getter和setter
}


<bean id="testBean11" class="cn.javass.spring.chapter12.TestBean11"/>


@Test
public void testAutowiredForConstructor() {
    TestBean11 testBean11 = ctx.getBean("testBean11", TestBean11.class);
    Assert.assertEquals("hello", testBean11.getMessage());
}
  • 字段注入:通过将@Autowired注解放在构造器上来完成字段注入。
public class TestBean12 {
    @Autowired //字段注入
    private String message;
    //省略getter和setter
}

<bean id="testBean12" class="cn.javass.spring.chapter12.TestBean12"/>

@Test
public void testAutowiredForField() {
    TestBean12 testBean12 = ctx.getBean("testBean12", TestBean12.class);
    Assert.assertEquals("hello", testBean12.getMessage());
}
  • 方法参数注入:通过将@Autowired注解放在方法上来完成方法参数注入。
public class TestBean13 {
    private String message;
    @Autowired //setter方法注入
    public void setMessage(String message) {
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
}

public class TestBean14 {
    private String message;
    private List<String> list;
    @Autowired(required = true) //任意一个或多个参数方法注入
    private void initMessage(String message, ArrayList<String> list) {
        this.message = message;
        this.list = list;
    }
    //省略getter和setter
}

/**
* xml配置
*/
<bean id="testBean13" class="cn.javass.spring.chapter12.TestBean13"/>
<bean id="testBean14" class="cn.javass.spring.chapter12.TestBean14"/>
<bean id="list" class="java.util.ArrayList">
    <constructor-arg index="0">
        <list>
            <ref bean="message"/>
            <ref bean="message"/>
        </list>
   </constructor-arg>         
</bean>

/**
 * 测试类
*/
@Test
public void testAutowiredForMethod() {
    TestBean13 testBean13 = ctx.getBean("testBean13", TestBean13.class);
    Assert.assertEquals("hello", testBean13.getMessage());

    TestBean14 testBean14 = ctx.getBean("testBean14", TestBean14.class);
    Assert.assertEquals("hello", testBean14.getMessage());
    Assert.assertEquals(ctx.getBean("list", List.class), testBean14.getList());
}
@Value:注入SpEL表达式;

用于注入SpEL表达式,可以放置在字段方法或参数上,使用方式如下

@Value(value = “SpEL表达式”)
字段、方法、参数

//可以在类字段上使用该注解
@Value(value = "#{message}")
private String message;

//可以放置在带@Autowired注解的方法的参数上
@Autowired
public void initMessage(@Value(value = "#{message}#{message}") String message) {
    this.message = message;
}

//还可以放置在带@Autowired注解的构造器的参数上
@Autowired
private TestBean43(@Value(value = "#{message}#{message}") String message) {
    this.message = message;
}
@Qualifier:限定描述符,用于细粒度选择候选者;

@Qualifier限定描述符除了能根据名字进行注入,但能进行更细粒度的控制如何选择候选者,具体使用方式如下:

@Qualifier(value = “限定标识符”)
字段、方法、参数

  • 根据基于XML配置中的标签指定的名字进行注入,使用如下方式指定名称
<qualifier 
    type="org.springframework.beans.factory.annotation.Qualifier" 
    value="限定标识符"/>
public class TestBean31 {
    private DataSource dataSource;
    @Autowired
    //根据<qualifier>标签指定Bean限定标识符
    public void initDataSource(@Qualifier("mysqlDataSource") DataSource dataSource) {
        this.dataSource = dataSource;
    }
    public DataSource getDataSource() {
        return dataSource;
    }
}

//配置
<bean id="testBean31" class="cn.javass.spring.chapter12.TestBean31"/>

<bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
     <qualifier value="mysqlDataSource"/>
</bean>

//测试方法
@Test
public void testQualifierInject1() {
    TestBean31 testBean31 = ctx.getBean("testBean31", TestBean31.class);
    try {
        //使用<qualifier>指定的标识符只能被@Qualifier使用
        ctx.getBean("mysqlDataSource");
        Assert.fail();
    } catch (Exception e) {
        //找不到该Bean
        Assert.assertTrue(e instanceof NoSuchBeanDefinitionException);
    }
    Assert.assertEquals(ctx.getBean("mysqlDataSourceBean"), testBean31.getDataSource());
}
  • 缺省的根据Bean名字注入:最基本方式,是在Bean上没有指定< qualifier>标签时一种容错机制,即缺省情况下使用Bean标识符注入,但如果你指定了< qualifier>标签将不会发生容错。
public class TestBean32 {
    private DataSource dataSource;
    @Autowired
    @Qualifier(value = "mysqlDataSource2") //指定Bean限定标识符
    //@Qualifier(value = "mysqlDataSourceBean") 
    //是错误的注入,不会发生回退容错,因为你指定了<qualifier>
    public void initDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    public DataSource getDataSource() {
        return dataSource;
    }
}

<bean id="testBean32" class="cn.javass.spring.chapter12.TestBean32"/>
<bean id="oracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"/>

@Test
public void testQualifierInject2() {
    TestBean32 testBean32 = ctx.getBean("testBean32", TestBean32.class);
    Assert.assertEquals(ctx.getBean("oracleDataSource"), testBean32.getDataSource());
}
  • 扩展@Qualifier限定描述符注解:对@Qualifier的扩展来提供细粒度选择候选者;具体使用方式就是自定义一个注解并使用@Qualifier注解其即可使用。
/** 表示注入Mysql相关 */
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Mysql {
}

/** 表示注入Oracle相关 */
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Oracle {
}
public class TestBean33 {
    private DataSource mysqlDataSource;
    private DataSource oracleDataSource;
    @Autowired
    public void initDataSource(@Mysql DataSource mysqlDataSource, @Oracle DataSource oracleDataSource) {
        this.mysqlDataSource = mysqlDataSource;
        this.oracleDataSource = oracleDataSource;
    }
    public DataSource getMysqlDataSource() {
        return mysqlDataSource;
    }
    public DataSource getOracleDataSource() {
        return oracleDataSource;
    }
}
<bean id="testBean33" class="cn.javass.spring.chapter12.TestBean33"/>

<bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
     <qualifier value="mysqlDataSource"/>
     <qualifier type="cn.javass.spring.chapter12.qualifier.Mysql"/>
</bean>
<bean id="oracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <qualifier type="cn.javass.spring.chapter12.qualifier.Oracle"/>
</bean>
/**
* 测试方法
*/

@Test
public void testQualifierInject3() {
    TestBean33 testBean33 = ctx.getBean("testBean33", TestBean33.class);
    Assert.assertEquals(ctx.getBean("mysqlDataSourceBean"), 
    testBean33.getMysqlDataSoruce());
    Assert.assertEquals(ctx.getBean("oracleDataSource"), 
    testBean33.getOracleDataSoruce());
}

带参数的注解

public enum DataBase {
    ORACLE, MYSQL;
}
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface DataSourceType {
    String ip();      //指定ip,用于多数据源情况
    DataBase database();//指定数据库类型
}
public class TestBean34 {
    private DataSource mysqlDataSource;
    private DataSource oracleDataSource;
    @Autowired
    public void initDataSource(
            @DataSourceType(ip="localhost", database=DataBase.MYSQL) 
            DataSource mysqlDataSource,
            @DataSourceType(ip="localhost", database=DataBase.ORACLE) 
            DataSource oracleDataSource) {
        this.mysqlDataSource = mysqlDataSource;
        this.oracleDataSource = oracleDataSource;
    }
    //省略getter方法   
}
<bean id="testBean34" class="cn.javass.spring.chapter12.TestBean34"/>

<bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <qualifier value="mysqlDataSource"/>
    <qualifier type="cn.javass.spring.chapter12.qualifier.Mysql"/>
    <qualifier type="cn.javass.spring.chapter12.qualifier.DataSourceType">
        <attribute key="ip" value="localhost"/>
        <attribute key="database" value="MYSQL"/>
    </qualifier>
</bean>
<bean id="oracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <qualifier type="cn.javass.spring.chapter12.qualifier.Oracle"/>
    <qualifier type="cn.javass.spring.chapter12.qualifier.DataSourceType">
        <attribute key="ip" value="localhost"/>
        <attribute key="database" value="ORACLE"/>
    </qualifier>
</bean>
@Test
public void testQualifierInject3() {
    TestBean34 testBean34 = ctx.getBean("testBean34", TestBean34.class);
    Assert.assertEquals(ctx.getBean("mysqlDataSourceBean"), 
    testBean34.getMysqlDataSource());
    Assert.assertEquals(ctx.getBean("oracleDataSource"), 
    testBean34.getOracleDataSoruce());
}
  • 自定义注解限定描述符:完全不使用@Qualifier,而是自己定义一个独立的限定注解;
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomQualifier {
    String value();
}
public class TestBean35 {
    private DataSource dataSoruce;
    @Autowired
    public TestBean35(@CustomQualifier("oracleDataSource") DataSource dataSource) {
        this.dataSoruce = dataSource;
    }
    public DataSource getDataSoruce() {
        return dataSoruce;
    }
}
<bean id="testBean35" class="cn.javass.spring.chapter12.TestBean35"/>

<bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>cn.javass.spring.chapter12.qualifier.CustomQualifier</value>
        </set>
    </property>
</bean>
@Test
public void testQualifierInject5() {
    TestBean35 testBean35 = ctx.getBean("testBean35", TestBean35.class);
    Assert.assertEquals(ctx.getBean("oracleDataSource"), testBean35.getDataSource());
}

JSR-250注解

@Resource:自动装配

默认根据类型装配,如果指定name属性将根据名字装配,可以使用如下方式来指定:

@Resource(name = “标识符”)
字段或setter方法

public class TestBean41 {
    @Resource(name = "message")
    private String message;
    //省略getter和setter
}

<bean id="testBean41" class="cn.javass.spring.chapter12.TestBean41"/>

@Test
public void testResourceInject1() {
    TestBean41 testBean41 = ctx.getBean("testBean41", TestBean41.class);
    Assert.assertEquals("hello", testBean41.getMessage());
}

使用@Resource需要注意以下几点:

  • @Resource注解应该只用于setter方法注入,不能提供如@Autowired多参数方法注入;
  • @Resource在没有指定name属性的情况下首先将根据setter方法对于的字段名查找资源,如果找不到再根据类型查找;
@PostConstruct和PreDestroy

通过注解指定初始化和销毁方法定义;

@PostConstruct
public void init() {
    System.out.println("==========init");
}
@PreDestroy
public void destroy() {
    System.out.println("==========destroy");
}
@Test
public void resourceInjectTest1() {
    ((ClassPathXmlApplicationContext) ctx).registerShutdownHook();
    TestBean41 testBean41 = ctx.getBean("testBean41", TestBean41.class);
    Assert.assertEquals("hello", testBean41.getMessage());
}

JSR-330注解

  • @Inject:等价于默认的@Autowired,只是没有required属性;
  • @Named:指定Bean名字,对应于Spring自带@Qualifier中的缺省的根据Bean名字注入情况;
  • @Qualifier:只对应于Spring自带@Qualifier中的扩展@Qualifier限定描述符注解,即只能扩展使用,没有value属性。
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface JSR330Mysql {
}
public class TestBean51 {
    private DataSource mysqlDataSource;
    private DataSource oracleDataSource;
    @Inject
    public void initDataSoruce(
            @JSR330Mysql  DataSource mysqlDataSource,
            @Named("oracleDataSource") DataSource oracleDataSource) {
        this.mysqlDataSource = mysqlDataSource;
        this.oracleDataSource = oracleDataSource;
     }
    //省略getter  
}
<bean id="testBean51" class="cn.javass.spring.chapter12.TestBean51"/>

<bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
     <qualifier value="mysqlDataSource"/>
     <qualifier type="cn.javass.spring.chapter12.qualifier.Mysql"/>
     <qualifier type="cn.javass.spring.chapter12.qualifier.DataSourceType">
         <attribute key="ip" value="localhost"/>
         <attribute key="database" value="MYSQL"/>
     </qualifier>
     <qualifier type="cn.javass.spring.chapter12.qualifier.JSR330Mysql"/>
</bean>
@Test
public void testInject() {
    TestBean51 testBean51 = ctx.getBean("testBean51", TestBean51.class);
    Assert.assertEquals(ctx.getBean("mysqlDataSourceBean"), 
    testBean51.getMysqlDataSource());
    Assert.assertEquals(ctx.getBean("oracleDataSource"), 
    testBean51.getOracleDataSource());
}

JPA注解

用于注入EntityManagerFactory和EntityManager。

public class TestBean61 {
    @PersistenceContext(unitName = "entityManagerFactory")
    private EntityManager entityManager;

    @PersistenceUnit(unitName = "entityManagerFactory")
    private EntityManagerFactory entityManagerFactory;

    public EntityManager getEntityManager() {
        return entityManager;
    }
    public EntityManagerFactory getEntityManagerFactory() {
        return entityManagerFactory;
    }
}
<import resource="classpath:chapter7/applicationContext-resources.xml"/>
<import resource="classpath:chapter8/applicationContext-jpa.xml"/>
<bean id="testBean61" class="cn.javass.spring.chapter12.TestBean61"/>
@Test
public void testJpaInject() {
    TestBean61 testBean61 = ctx.getBean("testBean61", TestBean61.class);
    Assert.assertNotNull(testBean61.getEntityManager());
    Assert.assertNotNull(testBean61.getEntityManagerFactory());
}

第二部分 实现Bean定义

概述

Spring提供通过扫描类路径中的特殊注解类来自动注册Bean定义,同注解驱动事务一样需要开启自动扫描并注册Bean定义支持

<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <aop:aspectj-autoproxy />
    <context:component-scan base-package="cn.javass.spring.chapter12"/> 
</beans>

使用< context:component-scan>标签来表示需要要自动注册Bean定义,而通过base-package属性指定扫描的类路径位置。< context:component-scan>标签将自动开启“注解实现Bean依赖注入”支持。此处我们还通过< aop:aspectj-autoproxy/>用于开启Spring对@AspectJ风格切面的支持。

Spring基于注解实现Bean定义支持如下三种注解:

  • Spring自带的@Component注解及扩展@Repository、@Service、@Controller
    这里写图片描述
  • JSR-250 1.1版本中中定义的@ManagedBean注解,是Java EE 6标准规范之一,不包括在JDK中,需要在应用服务器环境使用(如Jboss)
    这里写图片描述
  • JSR-330的@Named注解
    这里写图片描述

Spring自带的@Component注解及扩展

@Component

定义Spring管理Bean

@Component(“标识符”)
POJO类

@Component("component")
public class TestCompoment {
    @Autowired
    private ApplicationContext ctx;
    public ApplicationContext getCtx() {
        return ctx;
    }
}

@AspectJ风格的切面可以通过@Compenent注解标识其为Spring管理Bean,而@Aspect注解不能被Spring自动识别并注册为Bean,必须通过@Component注解来完成,示例如下

@Component
@Aspect
public class TestAspect {
    @Pointcut(value="execution(* *(..))")
    private void pointcut() {}
    @Before(value="pointcut()")
    public void before() {
        System.out.println("=======before");
    }
}
@Repository

@Component扩展,被@Repository注解的POJO类表示DAO层实现,从而见到该注解就想到DAO层实现,使用方式和@Component相同;

@Repository("testHibernateDao")
public class TestHibernateDaoImpl {

}
@Service

@Component扩展,被@Service注解的POJO类表示Service层实现,从而见到该注解就想到Service层实现,使用方式和@Component相同;

@Service("testService")
public class TestServiceImpl {
    @Autowired
    @Qualifier("testHibernateDao")
    private TestHibernateDaoImpl dao;
    public TestHibernateDaoImpl getDao() {
        return dao;
    }
}
@Controller

@Component扩展,被@Controller注解的类表示Web层实现,从而见到该注解就想到Web层实现,使用方式和@Component相同;

@Controller
public class TestAction {
    @Autowired
    private TestServiceImpl testService;

    public void list() {
        //调用业务逻辑层方法
    }
}
自定义扩展

Spring内置了三种通用的扩展注解@Repository、@Service、@Controller ,大多数情况下没必要定义自己的扩展,在此我们演示下如何扩展@Component注解来满足某些特殊规约的需要;
在此我们可能需要一个缓存层用于定义缓存Bean,因此我们需要自定义一个@Cache的注解来表示缓存类。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Cache{
    String value() default "";
}
@Cache("cache")
public class TestCache {

}

JSR-250中定义的@ManagedBean注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ManagedBean {
     String value() default "";
}
@javax.annotation.ManagedBean("managedBean")
public class TestManagedBean {
    @Resource
    private ApplicationContext ctx;
    public ApplicationContext getCtx() {
        return ctx;
    }
}
@Test
public void testManagedBean() {
    TestManagedBean testManagedBean = ctx.getBean("managedBean", TestManagedBean.class);
    Assert.assertNotNull(testManagedBean.getCtx());
}

JSR-330的@Named注解

@Named不仅可以用于依赖注入来指定注入的Bean的标识符,还可以用于定义Bean。即注解在类型上表示定义Bean,注解在非类型上(如字段)表示指定依赖注入的Bean标识符。

@Named("namedBean")
public class TestNamedBean {
    @Inject
    private ApplicationContext ctx;
    public ApplicationContext getCtx() {
        return ctx;
    }
}
@Test
public void testNamedBean() {
    TestNamedBean testNamedBean = ctx.getBean("namedBean", TestNamedBean.class);
    Assert.assertNotNull(testNamedBean.getCtx());
}

细粒度控制Bean定义扫描

<!-- 定义 -->
<context:component-scan 
        base-package=""
        resource-pattern="**/*.class"
        name-generator="org.springframework.context.annotation.AnnotationBeanNameGenerator"
        use-default-filters="true"
        annotation-config="true">
    <context:include-filter type="aspectj" expression=""/>
    <context:exclude-filter type="regex" expression=""/>
</context:component-scan>

提供更多的配置元数据

@Lazy

定义Bean将延迟初始化

@Component("component")
@Lazy(true)
public class TestCompoment {
……
}
@DependsOn

定义Bean初始化及销毁时的顺序

@Component("component")
@DependsOn({"managedBean"})
public class TestCompoment {
……
}
@Scope

定义Bean作用域,默认单例

@Component("component")
@Scope("singleton")
public class TestCompoment {
……
}
@Qualifier

指定限定描述符,对应于基于XML配置中的< qualifier>标签

@Component("component")
@Qualifier("component")
public class TestCompoment {
……
}
@Primary

自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常

@Component("component")
@Primary
public class TestCompoment {
……
}

基于Java类定义Bean配置元数据

基于Java类定义Bean配置元数据中的@Configuration注解的类等价于XML配置文件,@Bean注解的方法等价于XML配置文件中的Bean定义。

@Configuration

通过@Configuration注解的类将被作为配置类使用,表示在该类中将定义Bean配置元数据,且使用@Configuration注解的类本身也是一个Bean

import org.springframework.context.annotation.Configuration;
@Configuration("ctxConfig")
public class ApplicationContextConfig {
    //定义Bean配置元数据
}

@Bean

通过@Bean注解配置类中的相应方法,则该方法名默认就是Bean名,该方法返回值就是Bean对象,并定义了Spring IoC容器如何实例化、自动装配、初始化Bean逻辑

@Bean(name={}, 
      autowire=Autowire.NO, 
      initMethod="", 
      destroyMethod="")

@Bean 
public String message() {
    return new String("hello");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值