9、IOC 之基于注释的容器配置

9、IOC 之基于注释的容器配置

XML 设置的替代方法是通过基于注释的配置提供,该配置依赖于字节码元数据来连接组件,而不是尖括号声明。开发人员不是使用 XML 来描述 Bean 连接,而是通过在相关类、方法或字段声明上使用注释将配置移动到组件类本身中。如示例中所述:AutoWiredAndNotationBeanPostProcessor,结合注释使用BeanPostProcessor是扩展Spring IoC容器的常用方法。例如,Spring 2.0 引入了使用@Required注释强制实施所需属性的可能性。Spring 2.5 使得遵循相同的通用方法来驱动 Spring 的依赖注入成为可能。从本质上讲,@Autowired注释提供了与自动装配中所述相同的功能,但具有更细粒度的控制和更广泛的适用性。Spring 2.5 还添加了对 JSR-250 注释的支持,例如@PostConstruct@PreDestroy。Spring 3.0 添加了对javax中包含的 JSR-330(Java 的依赖注入)注释的支持,该注释包含在 javax.inject 包中,例如@Inject@Named。有关这些注释的详细信息,请参阅相关部分

注解注入在 XML 注入之前执行。因此,XML 配置将覆盖通过这两种方法连接的属性的批注。

与往常一样,您可以将后处理器注册为单独的bean定义,但它们也可以通过在基于xml的Spring配置中包含以下标记来隐式注册(注意包含了 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 开启注解支持 -->
    <context:annotation-config/>

</beans>

注意!!!

spring4之后使用注解开发,必须保证导入了aop包!

在这里插入图片描述

<context:annotation-config/>元素隐式注册以下后处理器:

<context:annotation-config/>只在定义它的应用程序上下文中查找bean上的注释。这意味着,如果将<context:annotation-config/>放入DispatcherServletWebApplicationContext中,则它仅检查控制器中的@Autowired bean,而不检查服务中的 bean。有关更多信息,请参阅 DispatcherServlet

9.1、@Required 【已弃用】

@Required注解适用于 Bean 属性 setter 方法,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

此注释指出,必须在配置时通过 Bean 定义中的显式属性值或通过自动装配来填充受影响的 Bean 属性。如果尚未填充受影响的 Bean 属性,容器将引发异常。这允许立即和显式的失败,避免以后出现NullPointerException等异常。我们仍然建议将断言放入 Bean 类本身(例如,放入 init 方法中)。这样做会强制执行这些必需的引用和值,即使是在容器外部使用类也是如此。

RequiredAnnotationBeanPostProcessor 必须注册为一个 Bean,以支持 @Required注释。

@Required 注释和 RequiredAnnotationBeanPostProcessor 在Spring Framework 5.1中已正式弃用,支持使用构造函数注入实现所需的设置(或自定义实现 InitializingBean.afterPropertiesSet() 或自定义 @PostConstruct 方法以及 Bean属性 setter方法)。

9.2、@Autowired

JSR 330 的 @Inject注解可以代替本节示例中的 Spring 的 @Autowired注解。有关更多详情,请参阅此处

① 将@Autowired应用于构造函数

public class ExceptionController {

    private ExceptionService exceptionService;

    @Autowired
    public ExceptionController(ExceptionService exceptionService) {
        this.exceptionService = exceptionService;
    }

}

从 Spring Framework 4.3 开始,如果目标Bean只定义了一个构造函数,则不再需要在这样的构造函数上使用@Autowired注释。但是,如果有多个构造函数可用,并且没有主/默认构造函数,则必须用@Autowired注释至少一个构造函数,以便指示容器知道使用哪一个进行Bean的实例化。

② 将@Autowired应用于传统的setter方法

public class ExceptionController {

    private ExceptionService exceptionService;

    @Autowired
    public void setExceptionService(ExceptionService exceptionService) {
        this.exceptionService = exceptionService;
    }

}

③ 还可以将@Autowired应用于具有任意名称和多个参数的方法

public class ExceptionController {

    private ExceptionService exceptionService;

    private OtherService otherService;

    @Autowired
    public void prepare(ExceptionService exceptionService,
            OtherService otherService) {
        this.exceptionService = exceptionService;
        this.otherService = otherService;
    }

}

④ 也可以将@Autowired应用到字段中,甚至可以与构造函数混合使用

public class ExceptionController {

    private ExceptionService exceptionService;

    @Autowired
    private OtherService otherService;

    @Autowired
    public ExceptionController(ExceptionService exceptionService) {
        this.exceptionService = exceptionService;
    }

}

⑤ 还可以指示Spring从ApplicationContext中提供特定类型的所有Bean,方法是将@Autowired注释添加到需要该类型数组的字段或方法

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

}

同样适用于类型化集合:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

}

如果希望按特定顺序对数组或列表中的项进行排序,您的目标 Bean 可以实现 org.springframework.core.Ordered接口或使用 @Order或标准 @Priority注释。否则,它们的顺序遵循容器中相应目标bean定义的注册顺序。

可以在目标类级别和 @Bean方法上声明@Order注释,可能针对单个 Bean 定义(如果多个定义使用同一个 Bean 类)@Order值可能会影响注入点的优先级,但请注意,它们不会影响单例启动顺序,这是由依赖关系和 @DependsOn声明确定的正交问题。

请注意,标准 javax.annotation.Priority注释在 @Bean级别不可用,因为它不能在方法上声明。它的语义可以通过@Order值与@Primary在每个类型的单个 Bean的组合来建模。

即使是类型化的 Map实例也可以自动连接,只要预期的键类型是 String。映射值包含预期类型的所有Bean,键包含相应的Bean名称。如以下示例所示:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

}

默认情况下,当给定注入点没有匹配的候选 Bean 可用时,自动装配将失败。对于已声明的数组、集合或映射,至少需要一个匹配的元素。

默认行为是将带批注的方法和字段视为指定所需的依赖项。也可以更改此行为,如以下示例所示,通过将不满足的注入点标记为非必需(即,通过将 @Autowiredrequired属性设置为false ) 来使框架能够跳过不满足的注入点:

public class ExceptionController {

    private ExceptionService exceptionService;

    @Autowired(required = false)
    public ExceptionController(ExceptionService exceptionService) {
        this.exceptionService = exceptionService;
    }

}

如果非必需的方法的依赖项(或其依赖项之一,如果有多个参数)不可用,则根本不会调用该方法。在这种情况下,一个非必需的字段根本不会被填充,保留其默认值。

注入的构造函数和工厂方法参数是一种特殊情况,因为@Autowired 中的 required 属性由于Spring的构造函数解析算法可能会处理多个构造函数而具有某种不同的含义。默认情况下,构造函数和工厂方法参数是有效的,但在单构造函数场景中有一些特殊的规则,例如,如果没有匹配的 Bean 可用,则多元素注入点(数组、集合、映射)解析为空实例。这允许一种通用的实现模式,其中所有依赖项都可以在唯一的多参数构造函数中声明 - 例如,声明为一个没有 @Autowired 注释的公共构造函数。

任何给定 Bean 类的只有一个构造函数可以声明 @Autowired,并将 required属性设置为 true,表示当用作Spring Bean时要自动装配的构造函数。因此,如果 required属性保留为其默认值 true,则只有单个构造函数可以使用 @Autowired进行注释。如果多个构造函数声明注释,则它们都必须声明 required=false,才能被视为自动装配的候选项(类似于 XML中的 autowire=constructor)。将选择通过匹配 Spring容器中的 Bean可以满足的依赖项最多的构造函数。如果无法满足任何候选项,则将使用主/默认构造函数(如果存在)。同样,如果一个类声明了多个构造函数,但没有一个构造函数用 @Autowired注释,则将使用主/默认构造函数(如果存在)。如果一个类一开始就只声明一个构造函数,则将始终使用它,即使没有注释也是如此。请注意,带批注的构造函数不必是公共的(为什么?反射还管你是不是公共的, 直接 setAccessible(true) 取消 Java语言访问检查)。

建议使用 @Autowiredrequired属性,而不是 setter方法上使用的已弃用的 @Required注释。将 required属性设置为 false表示自动装配不需要该属性,如果无法自动装配,则忽略该属性。另一方面,已弃用的 @Required更强大,因为它强制通过容器支持的任何方式设置属性,如果没有定义值,则会引发相应的异常。

或者,您可以通过Java 8的 java.util.Optional表示特定依赖项的非必需性质。如下所示:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        // ...
    }
}

从 Spring Framework 5.0开始,还可以使用 @Nullable 注释(在任何包中任何类型 - 例如 javax.annotation.Nullable,来自JSR-305)或仅利用Kotlin内置的空安全支持:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        // ...
    }
}

@Autowired还可以用于众所周知的可解析依赖项接口:BeanFactoryApplicationContextEnvironmentResourceLoaderApplicationEventPublisherMessageSource 。这些接口及其扩展接口(如 ConfigurableApplicationContextResourcePatternResolver)是自动解析的,无需特殊设置。下面的示例自动装配一个ApplicationContext对象:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

}

@Autowired@Inject@Value@Resource 注释由 Spring BeanPostProcessor实现处理。这意味着你无法在自己的 BeanPostProcessorBeanFactoryPostProcessor 类型(如果有)中应用这些注释。这些类型必须使用 XML 或 Spring @Bean方法显式地装配。

9.3、使用@Primary微调基于注释的自动装配

由于按类型自动装配可能会导致多个候选项,因此通常需要对选择过程进行更多控制。实现这一目标的一种方法是使用Spring的@Primary注释。 @Primary表示当多个 Bean 是要自动装配到单值依赖项的候选项时,应优先考虑特定的 Bean。如果候选者中只存在一个主Bean,则它将成为自动装配值。

◆ 例子:将firstMovieCatalog定义为主MovieCatalog的配置如下:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

}

使用上述配置,以下MovieRecommender将自动装配firstMovieCatalog

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

}

相应 XML 的 bean 定义如下:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true"/>

    <bean class="example.SimpleMovieCatalog"/>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

9.4、使用限定符微调基于注释的自动装配

@Primary 是一种有效的方法,可在确定一个主要候选项时对多个实例使用按类型自动装配。当需要对选择过程进行更多控制时,可以使用Spring的 @Qualifier 注释。可以将限定符值与特定参数相关联,从而缩小类型匹配集,以便为每个参数选择特定的 Bean。在最简单的情况下,这可以是一个普通的描述性值,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Qualifier("main") 
    //两个注解配合使用;实现 byName 注入【如果MovieCatalog的实现类有多个,这里指定id=main的那个】
    private MovieCatalog movieCatalog;

}

还可以在单个构造函数参数或方法参数上指定 @Qualifier 注释,如下例所示:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

}

以下 XML 配置示例显示了相应的 Bean 定义:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <!-- 具有 main 限定符值的Bean与构造函数参数装配,该参数具有相同的值 -->
        <qualifier value="main"/> 
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- 具有 action 限定符值的Bean与构造函数参数装配,该参数具有相同的值 -->
        <qualifier value="action"/> 
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

对于回退匹配,Bean 名称被视为默认限定符值。因此,可以使用idmain而不是嵌套的限定符元素来定义 Bean,从而获得相同的匹配结果。但是,尽管可以使用此约定按名称引用特定的 Bean,但@Autowired从根本上讲是关于具有可选语义限定符的类型驱动注入。这意味着,即使具有 Bean 名称回退,限定符值在类型匹配集中也总是具有狭义语义。它们在语义上不表达对唯一 Bean id 的引用。好的限定符值是mainEMEApersistent ,表示独立于 Bean id 的特定组件的特征,在匿名 Bean 定义(如上例中的定义)的情况下,可以自动生成这些特征。

限定符也适用于类型化集合,如前所述,例如 Set<MovieCatalog>。在这种情况下,根据声明的限定符,所有匹配的 Bean 都将作为集合注入。这意味着限定符不必是唯一的。相反,它们构成了过滤标准。例如,可以定义多个具有相同限定符值 “action” 的MovieCatalog bean,所有这些 Bean 都通过 @Qualifier("action") 注入到Set<MovieCatalog>集中。

让限定符值在类型匹配候选项中针对目标 Bean 名称进行选择,不需要在注入点使用 @Qualifier注释。如果没有其他解析指示符(如限定符或主标记),对于非唯一依赖关系情况,Spring 会将注入点名称(即字段名或参数名)与目标 Bean 名称进行匹配,并选择同名候选项(如果有)。

9.5、使用泛型作为自动装配限定符

除了@Qualifier注释之外,还可以使用 Java 泛型类型作为限定的隐式形式。例如,假设具有以下配置:

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}

假设前面的 Bean 实现了一个泛型接口(即Store<String>Store<Integer>),则可以用@Autowire注释Store接口,泛型用作限定符,如以下示例所示:

@Autowired
private Store<String> s1; // <String> qualifier, 注入 stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, 注入 integerStore bean

通用限定符也适用于自动关联列表、Map实例和数组。以下示例自动装配通用List

// 注入所有存储bean,只要它们具有<Integer>泛型
// Store<String> beans 不会出现在此列表中
@Autowired
private List<Store<Integer>> s;

9.6、使用 CustomAutowireConfigurer

CustomAutowireConfigurer 是一个允许你注册自己的自定义限定符注释类型的BeanFactoryPostProcessor,即使它们没有使用Spring的@Qualifier注释进行注释。下面的示例演示如何使用CustomAutowireConfigurer

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

AutowireCandidateResolver通过以下方式确定自动装配候选项:

  • 每个 Bean 定义的autowire-candidate
  • <beans/>元素上可用的任何default-autowire-candidates候选模式
  • @Qualifier注释的存在以及注册到CustomAutowireConfigurer上注册的任何自定义注释

当多个 Bean 有资格作为自动装配候选项时, “primary”的确定如下:如果候选项中正好有一个 Bean 定义具有primary属性设置为true ,则选择它。

9.7、@Resource 注入

Spring 还支持在字段或 Bean 属性设置器方法上使用 JSR-250 的@Resource注释 (javax.annotation.Resource) 进行注入。这是 J2EE 中的常见模式:例如,在 JSF 管理的 Bean 和 JAX-WS 端点中。Spring支持Spring管理对象的这种模式。

@Resource 接受一个name属性。默认情况下,Spring 将该值解释为要注入的 Bean 名称。换句话说,它遵循按名称语义,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果未显式指定 name,则默认名称派生自字段名称或 setter 方法。如果是字段,则采用字段名称。对于 setter 方法,它采用 Bean 属性名称。下面的示例将把名为movieFinder 的 Bean 注入到其 setter 方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

注释提供的名称被 CommonAnnotationBeanPostProcessor 感知的 ApplicationContext解析为 Bean 名称。如果您显式地配置 Spring的 SimpleJndiBeanFactory,则可以通过 JNDI 解析名称。但是,官方建议依靠默认行为,并使用 Spring的 JNDI 查找功能来保留间接寻址级别。

在没有指定显式名称的 @Resource 独占情况下,类似于@Autowired@Resource找到一个主要类型匹配,而不是一个特定的命名bean,并解析众所周知的可解析依赖项::BeanFactoryApplicationContextResourceLoaderApplicationEventPublisherMessageSource接口。

小白对上面这段话的理解就是: @Resource 先 byName 再 byType

因此,在以下示例中,customerPreferenceDao字段首先查找名为"customerPreferenceDao" 的 Bean,然后回退到customerPreferenceDao类型的主类型匹配项:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource // context 字段是基于已知的可解析依赖类型注入的: ApplicationContext
    private ApplicationContext context; 

    public MovieRecommender() {
    }

}

9.8、使用 @Value

@Value 通常用于注入外部化属性:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

使用以下配置:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

和下面的 application.properties文件:

catalog.name=MovieCatalog

在这种情况下,catalog参数和字段将等于值MovieCatalog

Spring 提供了一个默认的嵌入式值解析器。它将尝试解析属性值,如果无法解析,则属性名称(例如 ${catalog.name})将作为值注入。如果希望对不存在的值保持严格控制,则应声明一个PropertySourcesPlaceholderConfigurer Bean,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

当使用JavaConfig配置 PropertySourcesPlaceholderConfigurer 时,@Bean方法必须是 static 的。

用上述配置可确保在无法解析任何${}占位符时确保 Spring 初始化失败。也可以使用 setPlaceholderPrefixsetPlaceholderSuffix 之类的方法或 setValueSeparator 等方法自定义占位符。

Spring Boot 默认情况配置一个 PropertySourcesPlaceholderConfigurer Bean,该 Bean 将从 application.propertiesapplication.yml文件中获取属性。

Spring提供的内置转换器支持允许自动处理简单的类型转换(例如转换为Integerint)。多个逗号分隔的值可以自动转换为String数组,而无需额外的工作。

可以提供如下默认值:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

Spring BeanPostProcessor 在幕后使用 ConversionService 来处理将 @Value 中的String 值转换为目标类型的过程。如果要为你自己的自定义类型提供转换支持,则可以提供自己的 ConversionService Bean 实例,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = 
            new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

@Value 包含一个 SpEL 表达式 时,该值将在运行时动态计算,如下示例所示:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(
        @Value("#{systemProperties['user.catalog'] + 'Catalog' }") 
        String catalog) {
        this.catalog = catalog;
    }
}

SpEL还支持使用更复杂的数据结构:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
        @Value("#{{'Thriller': 100, 'Comedy': 300}}") 
        Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

9.9、使用 @PostConstruct@PreDestroy

CommonAnnotationBeanPostProcessor 不仅可以识别 @Resource 注释,还可以识别 JSR-250 生命周期注释:javax.annotation.PostConstructjavax.annotation.PreDestroy。在 Spring 2.5 中引入的对这些注释的支持提供了初始化回调和销毁回调中描述的生命周期回调机制的替代方法。如果 CommonAnnotationBeanPostProcessor 在 Spring ApplicationContext中注册的 ,则携带这些注释之一的方法将与对应的Spring生命周期接口方法或显式声明的回调方法在生命周期中的同一点被调用。在以下示例中,缓存在初始化时预先填充,并在销毁时清除:

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // 初始化时填充电影缓存...
    }

    @PreDestroy
    public void clearMovieCache() {
        // 摧毁时清除电影缓存...
    }
}

有关组合各种生命周期机制的效果的详细信息,请参阅组合生命周期机制

@Resource一样,@PostConstruct@PreDestroy注释类型是从 JDK 6到 8的标准 Java库的一部分。但是,整个 javax.annotation 包在 JDK 9 中与核心 Java 模块分离,并最终在 JDK 11 中删除。如果需要,现在需要通过 Maven Central 获取 javax.annotation-api工件,只需像任何其他库一样将其添加到应用程序的类路径中即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

纯纯的小白

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值