spring注解

🔺以下整理的学习笔记来自雷丰阳老师的spring讲解视频👏

@Bean注解

先创建一个实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
    private Integer age;
}

配置一个Bean

// 告诉spring这是一个配置
@Configuration
public class BeanConfig {
    @Bean
    public Person person() {
        return new Person("张山", 25);
    }
}

在启动类中获取自定义的bean

public static void main(String[] args) {
        // 获取自己定义的Bean
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);

        // 获取Person在ioc容器中的名字
        String[] names = applicationContext.getBeanNamesForType(Person.class);
        Arrays.stream(names).forEach(System.out::println);  SpringApplication.run(SpringannotationstudyApplication.class, args);
    }

控制台打印
在这里插入图片描述

@ComponentScan注解

指定扫描哪些包下的注解

// 告诉spring这是一个配置
@Configuration
// includeFilters指定扫描哪些注解;
// excludeFilters忽略排除哪些注解
@ComponentScan(value = "com.yang", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
},useDefaultFilters = false)
public class BeanConfig {
    @Bean
    public Person person() {
        return new Person("张山", 25);
    }
}

写一个测试

@Test
public void Test(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
}

结果
在这里插入图片描述

@Scope注解

写一个配置类

@Configuration
public class BeanConfig2 {
    /**
     * @Scope注解默认是单实例的
     * 下面是可以取得值:
     * request
     * session
     * prototype 多实例的
     * singleton 单实例的(默认的)
     * @return
     */
    @Scope(value = "prototype")
    /**@Bean默认是单实例的*/
    @Bean("person2")
    public Person person(){
        return new Person("李四",55);
    }
}

写一个测试类

@Test
public void Test2(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig2.class);
    String[] names = applicationContext.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println(name);
    }
    Object person2 = applicationContext.getBean("person2");
    Object person3 = applicationContext.getBean("person2");
    // 判断是不是相等
    System.out.println(person2 == person3);
}

结果为false

@lazy注解(使Bean懒加载)

单实例的Bean默认在容器启动时创建,现在我们想容器创建的时候不创建Bean对象

正常的加载:

@Configuration
public class BeanConfig2 {
    /**@Lazy可以让容器创建时不创建这个Bean的对象*/
    @Bean("person2")
    public Person person(){
    	System.out.println("给容器中添加Person----");
        return new Person("李四",55);
    }
}
@Test
    public void Test2(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig2.class);
        System.out.println("ioc容器创建完成------");
    }

测试结果:
在这里插入图片描述
懒加载:

@Configuration
public class BeanConfig2 {
    /**@Lazy可以让容器创建时不创建这个Bean的对象*/
    @Bean("person2")
    public Person person(){
    	System.out.println("给容器中添加Person----");
        return new Person("李四",55);
    }
}
@Test
    public void Test2(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig2.class);
        System.out.println("ioc容器创建完成------");
    }

测试结果:
在这里插入图片描述
首次用到Bean:

@Test
public void Test2(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig2.class);
    Object person2 = applicationContext.getBean("person2");

测试结果:
在这里插入图片描述
以后每次获取也不创建了,因为是单实例的:

@Test
    public void Test2(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig2.class);
        System.out.println("ioc容器创建完成------");
        
        Object person2 = applicationContext.getBean("person2");
        // 获取第二次
        Object person3 = applicationContext.getBean("person2");
        System.out.println(person2 == person3);
    }

test result:
在这里插入图片描述

工厂Bean

创建一个Spring定义的FactoryBean

public class PersonFactoryBean implements FactoryBean {
    // 返回一个Person对象,这个对象会添加到容器中
    @Override
    public Object getObject() throws Exception {
        System.out.println("PersonFactoryBean调用了getObject方法---");
        return new Person();
    }

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

    /** 是否是单例
     * true:这个Bean是单例的,在容器中保存一份
     * false:多实例,每次获取都会创建一个新的Bean
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}

将PersonFactoryBean 注册为Bean

@Configuration
public class BeanConfig2 {
	// 看似在这里装载的PersonFactoryBean  bean其实在①处Bean工厂调用getObject创建的对象
    @Bean
    public PersonFactoryBean personFactoryBean(){
        return new PersonFactoryBean();
     }
}

测试:

@Test
public void Test2(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig2.class);
    // ①
    Object personFactoryBean = applicationContext.getBean("personFactoryBean");
    System.out.println("bean的类型为"+personFactoryBean.getClass());
}

test result:
在这里插入图片描述
如果想要获取Bean本身,在按照id获取时加一个前缀 & 就可以获取到了

@Test
public void Test2(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig2.class);
    // ①
    Object personFactoryBean = applicationContext.getBean("&personFactoryBean");
    System.out.println("bean的类型为"+personFactoryBean.getClass());
}

test result:
在这里插入图片描述

自定义Bean的出初始和销毁

bean的生命周期就是
bean创建----初始化----销毁的过程
创建一个car类

public class Car {
	// 容器调用无参构造创建对象
    public Car() {
        System.out.println("car的无参构造");
    }
    public void init(){
        System.out.println("初始化Car");
    }
    public void destroy(){
        System.out.println("销毁Car");
    }
}

为car写一个配置类

@Configuration
public class CarBeanConfig {
    @Bean
    public Car car(){
        return new Car();
    }
}

测试:

@Test
    public void Test3(){
    	// 创建ioc容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(CarBeanConfig.class);
        System.out.println("容器创建完成---");
    }

test result:
在这里插入图片描述

指定初始化和销毁的方法:

initMethod、destoryMethod

@Configuration
public class CarBeanConfig {
    @Bean(initMethod = "init",destroyMethod = "destroy")
    public Car car(){
        return new Car();
    }
}

在启动测试:

@Test
    public void Test3(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(CarBeanConfig.class);
        System.out.println("容器创建完成---");
    }

test result:
在这里插入图片描述
容器关闭:

@Test
    public void Test3(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(CarBeanConfig.class);
        System.out.println("容器创建完成---");
        applicationContext.close();
    }

在这里插入图片描述

以上是单实例的Bean 在容器启动时创建对象
多实例的Bean在获取时创建对象
以下是多实例的自定义初始化:

@Configuration
public class CarBeanConfig {
	// 修改为多实例的Bean
    @Scope("prototype")
    @Bean(initMethod = "init",destroyMethod = "destroy")
    public Car car(){
        return new Car();
    }
}

创建容器看是否创建对象:

@Test
    public void Test3(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(CarBeanConfig.class);
        System.out.println("容器创建完成---");
        // applicationContext.close();
        // applicationContext.getBean("car");
    }

result:
没有创建对象
在这里插入图片描述
获取Bean:

@Test
public void Test3(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(CarBeanConfig.class);
    System.out.println("容器创建完成---");
    applicationContext.getBean("car");
}

result:
在这里插入图片描述
多实例 容器不会管理这个Bean 容器不会调用这个销毁的方法

另一中方法初始化和销毁Bean

通过实现 InitializingBean, DisposableBean两个接口

@Component
public class Cat implements InitializingBean, DisposableBean {
    public Cat() {
        System.out.println("Cat的无参构造");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet---------bean创建完成,属性赋值后调用");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("bean销毁方法");
    }
}

换种方式创建Bean、添加@Component 把cat当成一个组件去扫描它
借用了CarBeanConfig 类

@ComponentScan("com.yang.entitys")
@Configuration
public class CarBeanConfig {
    // @Scope("prototype")
    @Bean(initMethod = "init",destroyMethod = "destroy")
    public Car car(){
        return new Car();
    }
}

测试:

@Test
    public void Test4(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(CarBeanConfig.class);
        System.out.println("容器创建完成---");
        applicationContext.getBean("cat");
    }

test result:
在这里插入图片描述

@Conditional注解 可以根据配置条件选择性加载Bean

下面有两个Bean,现在想要如果是windows系统 把father添加到容器中如果是linux系统 把son添加到容器中

@Configuration
public class BeanConfig3 {
    /**如果是windows系统 把father添加到容器中
     * 如果是linux系统 把son添加到容器中*/
    @Conditional({WindowsCondition.class})
    @Bean("father")
    public Person person1(){
        return new Person("father",50);
    }
    @Conditional({LinuxCondition.class})
    @Bean("son")
    public Person person2(){
        return new Person("son",20);
    }
}

需要写两个类作为条件

/**
 * 判断是否是windows系统
 *
 * @author : li
 * @date : 2021-06-29 22:05
 */
@SuppressWarnings("all")
public class WindowsCondition implements Condition {
    /**
     * ConditionContext 判断条件能使用的上下文 (环境)
     * AnnotatedTypeMetadata 注释信息
     */

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 能获取到ioc使用的beanFactory,能获取到的ioc使用的beanFactory就是创建对象 以及装配的工厂
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        // 获取到类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        // 获取到bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        // 获取当前环境信息
        Environment environment = conditionContext.getEnvironment();
        String property = environment.getProperty("os.name");
        // 判断是否是windows
        if (property.contains("Windows")){
            return true;
        }
        return false;
    }
}

/**
 * 判断是否是linux系统
 *
 * @author : li
 * @date : 2021-06-29 22:08
 */
@SuppressWarnings("all")
public class LinuxCondition implements Condition {
    /**
     * ConditionContext 判断条件能使用的上下文 (环境)
     * AnnotatedTypeMetadata 注释信息
     */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 能获取到ioc使用的beanFactory,能获取到的ioc使用的beanFactory就是创建对象 以及装配的工厂
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        // 获取到类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        // 获取到bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        // 获取当前环境信息
        Environment environment = conditionContext.getEnvironment();
        String property = environment.getProperty("os.name");
        // 判断是否是windows
        if (property.contains("Linux")){
            return true;
        }
        return false;
    }
}

test:

@Test
    public void test5(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig3.class);
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        String property = environment.getProperty("os.name");
        System.out.println("操作系统"+property);
        String[] beanNamesForType = applicationContext.getBeanNamesForType(Person.class);
        for (String s : beanNamesForType) {
            System.out.println(s);
        }
        Map<String, Person> personMap = applicationContext.getBeansOfType(Person.class);
        /*personMap.forEach((key,value) -> {
            System.out.println(key+" : "+value);
        });*/
        System.out.println(personMap);
    }

test result:
在这里插入图片描述

@Import

加入我们要导入一个自己写的类或者第三方的类,可以类上面加上@Import注解
自己写的:

public class Color {
}

配置类:

@Configuration
@Import({Color.class})
// @Import({Color.class,Red.class})
public class BeanConfig4 {
}

test:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig4.class);

    public void getName(AnnotationConfigApplicationContext applicationContext){
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            System.out.println(name);
        }
    }
    
    @Test
    public void testImport(){
        getName(applicationContext);
    }

test result:
在这里插入图片描述

ImportSelector 接口

自定义一个类实现Import Selector接口

/**
 * importselector
 * 自定义逻辑返回需要导入的组件
 * @author : li
 * @date : 2021-07-03 21:49
 */

public class MyImportSelector implements ImportSelector {
    /**
     * 返回值就是导入到容器中的组件全类名数组
     * @param annotationMetadata 当前标注@Import注解的类的所有信息
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        Set<String> annotationTypes = annotationMetadata.getAnnotationTypes();
        System.out.println(annotationTypes);
        return new String[]{"com.yang.entitys.Blue","com.yang.entitys.Yellow"};
    }
}

然后结合@Import注解导入配置类

@Configuration
// Import导入组件  id是默认的全类名
@Import({Color.class, Red.class,MyImportSelector.class})
public class BeanConfig4 {
}

test:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig4.class);

    public void getName(AnnotationConfigApplicationContext applicationContext){
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            System.out.println(name);
        }
    }

    @Test
    public void testImport(){
        getName(applicationContext);
        // 获取Blue的信息
        Blue bean = applicationContext.getBean(Blue.class);
        System.out.println(bean);
    }

在这里插入图片描述

ImportBeanDefinitionRegistrar接口 手动注册bean到容器中

定义一个类实现ImportBeanDefinitionRegistrar接口

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * AnnotationMetadata  当前类的注解信息
     * BeanDefinitionRegistry  BeanDefinition注册类
     * 把所有添加到容器中的bean 调用
     * BeanDefinitionRegistry.registerBeanDefinition手动注册进来
     * @param importingClassMetadata
     * @param registry
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 查询是否包含某个Bean定义
        boolean blue = registry.containsBeanDefinition("com.yang.entitys.Blue");
        boolean yellow = registry.containsBeanDefinition("com.yang.entitys.Yellow");
        if (blue && yellow){
            // 获取到一个bean
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rainbow.class);
            // 自定义名字注册bean
            registry.registerBeanDefinition("rainbow",rootBeanDefinition);
        }
    }
}

配合import使用:


```java
@Configuration
// Import导入组件  id是默认的全类名
@Import({MyImportBeanDefinitionRegistrar.class})
public class BeanConfig4 {
}

test:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig4.class);

    public void getName(AnnotationConfigApplicationContext applicationContext){
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            System.out.println(name);
        }
    }

    @Test
    public void testImport(){
        getName(applicationContext);
        Blue bean = applicationContext.getBean(Blue.class);
        System.out.println(bean);
    }

test result:
在这里插入图片描述

属性@Value赋值

在实体类中用@Value赋值

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    /**
     * 使用@value赋值
     * 1.基本数值
     * 2.SPEL表达式 #{}
     * 3.${};取出配置文件【properties】中的值(在运行环境变量中的的值)
     */

    @Value("张三")
    private String name;
    
    @Value("#{20-2}")
    private Integer age;
    
    @Value("${person.nickName}")
    private String nickname;
    
}

在resources文件夹下创建properties配置文件

person.nickName=小李子

写个配置类获取实体类的bean

/**
 * property配置文件取值
 *
 * @author : li
 * @date : 2021-07-04 11:59
 * 使用@properSource读取外部配置文件中的K/V保存运行的环境变量中
 */
@PropertySource(value = {"classpath:/person.properties"})
@Configuration
public class MyConfigOfPropertyValue {
     @Bean
    public Person person(){
        return new Person();
    }
}

test:

@Test
    public void TestProperty(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfPropertyValue.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);
        // 获取当前环境
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        // 从环境中取配置文件中的K
        String property = environment.getProperty("person.nickName");
        System.out.println(property);
        applicationContext.close();
    }

test result:
在这里插入图片描述

@Autowired 按照类型装配
@Qualifier(“xxx”)指定 需要装配的组件的id
如果没有装配好则会报错,那么可以使用:@Autowired(required=false)
@Primary 在Bean上加上此注解,默认首选装配这个Bean
Spring还支持@Resource(JSR250)和 Inject(JSR330)注解

@Resources默认按照名字装配,也可以指定名字@Resources(“name=xxx”)

@Resources不支持@Primary也不支持required=false

@Inject

需要导入依赖

<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

它和@Autowired功能一样,不过没有required=false功能

三者区别:

@Autowired是spring的、@Resources和@inject是Java规范的

AOP切面

导入依赖:

<dependency>
   <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
</dependency>

定义一个逻辑类:
在业务逻辑运行时将日志进行打印

/**
 * 除法
 *
 * @author : li
 * @date : 2021-07-04 16:54
 */
@Slf4j
public class Division {
    public int div(int a,int b){
        log.info("接收参数[{}],[{}]方法开始执行计算",a,b);
        return a/b;
    }
}

定义一个日志切面类(切面里的方法动态感知Division类里的div方法运行到哪一步):
通知方法:
前置通知(@Before):在div方法运行之前运行
返回通知(@AfterReturning):执行完之后返回结果
后置通知()@After:在div方法运行之后运行,无论成功与否都调用
异常通知(@AfterThrowing):有异常打印一场信息
环绕通知(@Around):环绕通知,手动推进目标方法运行(joinPoind.procced)
给切面类的方法标注何时何地运行(通知注解),并告诉Spring哪一个是切面类,给切面类加注解@Aspects

/**
 * 除法的日志切面
 *
 * @author : li
 * @date : 2021-07-04 16:57
 */
@Aspect
@Slf4j
public class LogAspects {

    /**
     * 抽取公共的切入点表达式
     * 1.本类引用  @Before("pointCut()")
     * 外部类引用  @After("com.yang.config.LogAspects.pointCut()")
     */
    @Pointcut("execution(public int com.yang.config.Division.*(..))")
    public void pointCut(){}

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint){
        Object[] pointArgs = joinPoint.getArgs();
        System.out.println(joinPoint.getSignature().getName()+"除法运行-----参数列表是{"+ Arrays.asList(pointArgs)+"}");
    }

    @AfterReturning(value = "pointCut()",returning = "result")
    public void logResult(Object result){
        System.out.println("除法返回结果-------{"+result+"}");
        MDC.clear();
    }

    @After("com.yang.config.LogAspects.pointCut()")
    public void logEnd(JoinPoint joinPoint){
        System.out.println(joinPoint.getSignature().getName()+"除法结束-----");
    }

    @AfterThrowing(value = "pointCut()",throwing = "exception")
    public void logException(JoinPoint joinPoint,Exception exception){
        System.out.println(joinPoint.getSignature().getName()+"除法异常-------返回结果{"+exception+"}");
    }

    @Around("pointCut()")
    public Object timeAround(ProceedingJoinPoint joinPoint){
        // 获取开始执行的时间
        long startTime = System.currentTimeMillis();
        // 定义返回对象,得到方法需要的参数
        Object o = null;

        try {
            o = joinPoint.proceed();
        } catch (Throwable throwable) {
            log.info("统计[{}]执行耗时环绕通知出错",joinPoint.getSignature().getName(),throwable);
        }
        log.info("方法[{}]执行时间[{}]",joinPoint.getSignature().getName(),System.currentTimeMillis() - startTime);
        return o;
    }
}

ps:JoinPoint joinPoint在参数表中一定写在第一位,不然报错

将切面类和业务逻辑类都加入到容器中:

/**
 * aop切面
 *
 * @author : li
 * @date : 2021-07-04 16:52
 */
@Configuration
public class MyConfigOfAop {
    /**
     * 将Division类加入到容器中
     * @return
     */
    @Bean
    public Division division(){
        return new Division();
    }

    /**
     * 将LogAspects类加入到容器中
     * @return
     */
    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}

添加开启切面的功能注解@EnableAspectJAutoProxy

/**
 * 切面配置类
 *
 * @author : li
 * @date : 2021-07-04 16:52
 */
@EnableAspectJAutoProxy
@Configuration
public class MyConfigOfAop {
    /**
     * 将Division类加入到容器中
     * @return
     */
    @Bean
    public Division division(){
        return new Division();
    }

    /**
     * 将LogAspects类加入到容器中
     * @return
     */
    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}

test:

@Test
    public void TestAop(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfAop.class);
        // 必须从容器中获取Bean,切面才生效
        Division division = applicationContext.getBean(Division.class);
        division.div(1,1);
    }

normal test:

@Test
public void TestAop(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfAop.class);
    // 必须从容器中获取Bean,切面才生效
    Division division = applicationContext.getBean(Division.class);
    division.div(10,5);
}

normal test result:
在这里插入图片描述
exception test result:
在这里插入图片描述
执行顺序:
①:前置通知(@Before):在div方法运行之前运行
②:返回通知(@AfterReturning):执行完之后返回结果
③:后置通知()@After:在div方法运行之后运行,无论成功与否都调用
②:异常通知(@AfterThrowing):有异常打印一场信息
返回通知和异常通知只能有一个

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值