spring---- (2)注解开发

参考视频

https://www.bilibili.com/video/BV1yq4y1N78E?spm_id_from=333.337.search-card.all.click

注解驱动的意义

在这里插入图片描述

在这里插入图片描述

常用注解(重点)

xml启动注解驱动

在核心配置文件中添加如下配置

<!--启动注解驱动:指定扫描路径,也就是资源所在的包-->
<context:component-scan base-package="com.zs"/>

说明:递归扫描,读取java文件,spring注解,将有bean注解的资源加入IoC容器

⛳️IoC

bean定义(@Component 、@Controller、@Service、 @Repository)

类注解
设置该类为spring管理的bean

   <bean id="userService" class="com.zs.service.impl.UserServiceImpl"/>
@Service("userService")
public class UserServiceImpl  implements UserService {

说明:@Controller、@Service、@Repository是@Component的衍生注解,功能同@Component,分别表示表现层,业务层,数据层
value:定义bean的访问id

@Scope

类注解
设置创建对象的模式,singleton或不写就是单例模式,prototype是多例模式

   <bean id="userService" scope="prototype" class="com.zs.service.impl.UserServiceImpl"/>
@Service("userService")
@Scope("prototype")
public class UserServiceImpl  implements UserService {
bean生命周期(@PostConstruct、@PreDestroy)
   <bean id="userService" scope="prototype" init-method="init" destroy-method="destroy" class="com.zs.service.impl.UserServiceImpl"/>
@Service("userService")
@Scope("prototype")
public class UserServiceImpl  implements UserService {
	@PostConstruct
	public void init(){
		System.out.println("init...");
	}
	@PreDestroy
	public void destroy(){
		System.out.println("destroy...");
	}
🍎@Bean (加载第三方资源)

@Bean
方法注解
设置该方法的返回值作为spring管理的bean

说明:第三方bean无法在其原码上修改,使用@Bean引入,替换配置文件中静态工厂实例工厂创建bean,不区分方法是否为静态或非静态

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
package com.zs.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;


@Component //此处后面会替换成专门的配置文件注解
public class JDBCConfig {

    @Bean("dataSource")
    public DruidDataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/testdb");
        ds.setUsername("iplat62");
        ds.setPassword("iplat62");
        return ds;
    }
}

DI

非引用类型注入(@Value)

@Value
属性注解、方法注解
设置对应属性的值或方法进行传参

    <!--要注入的资源-->
    <bean id="userDao" class="com.zs.dao.impl.UserDaoImpl">
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </bean>
package com.zs.dao.impl;

import com.zs.dao.UserDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

@Repository("userDao")//别名不取默认为类名
public class UserDaoImpl implements UserDao {

    @Value("${username}")
    private String username;
    @Value("${password}")
    private String password;

    @Override
    public void save() {
        System.out.println(username + "\t" + password);
    }
}

说明: value值支持读取properties文件中的属性值,通过类属性将properties中数据传入类中
value值支持SpEL
@value注解如果添加在属性上,可省略set方法(set方法的目的就是为属性赋值)

🍎引用类型注入(@Autowired、@Qualifier、@Primary、@Resource、@Inject、@Named)

@Autowired、@Qualifier
属性,方法注解
设置对应属性的对象或对方法进行引用类型传参

先按类型装配,再按变量名装配,如果都匹配不了就报错

相关属性:

  • required: 定义该属性是否允许为null @Autowired(required=false)

@Primary
类注解
设置类对应的bean按类型装配时优先装配

说明:设置多个类是会导致优先级无效,只能设置一个类优先

@Autowired //此时类型相同时会根据属性名自动识别
private UserDao userDao;

@Repository("userDao")
public class UserDaoImpl implements UserDao{

@Repository("userDao2")
public class UserDaoImpl2 implements UserDao{

匹配不到指定的bean,使用@Qualifier指定加载哪个bean

@Autowired //匹配不到
@Qualifier("userDao1")//指定引用哪个
private UserDao userDao;


@Repository("userDao1")
public class UserDaoImpl implements UserDao{

@Repository("userDao2")
public class UserDaoImpl2 implements UserDao{

bean不起名,默认使用类名

@Autowired //匹配不到,bean的名称没写默认是类名小写驼峰
private UserDao userDao;

@Repository
public class UserDaoImpl implements UserDao{

@Repository
public class UserDaoImpl2 implements UserDao{

指定优先加载的bean,只能用一个

@Autowired //匹配不到,bean的名称没写默认是类名小写驼峰
private UserDao userDao;

@Repository
@Primary //优先加载该bean
public class UserDaoImpl implements UserDao{

@Repository
public class UserDaoImpl2 implements UserDao{

@Inject、@Named、@Resource
说明:

  • @Inject、@Named是 JSR330中的注解,与@Autowired、@Qualifier完全相同
  • @Resource 是 JSR250 中的注解,可以简化书写格式

@Resource 相关属性

  • name: 设置注入的bean的id
  • type: 设置注入的bean的类型,接收的参数为Class类型
集合注入(待查找)

加载第三方资源(@Bean)

Druid数据源
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
package com.zs.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;


@Component //此处后面会替换成专门的配置文件注解
public class JDBCConfig {

    @Bean("dataSource")
    public DruidDataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/testdb");
        ds.setUsername("iplat62");
        ds.setPassword("iplat62");
        return ds;
    }
}

properties文件(@PropertySource)

@PropertySource
类注解
加载properties文件中的属性值

package com.zs.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;


@Component
@PropertySource(value = {"classpath:jdbc.properties","a.properties"},ignoreResourceNotFound = true)
public class JDBCConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")
    public DruidDataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
//        ds.setDriverClassName("com.mysql.jdbc.Driver");
//        ds.setUrl("jdbc:mysql://localhost:3306/testdb");
//        ds.setUsername("iplat62");
//        ds.setPassword("iplat62");
        return ds;
    }
}

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/testdb
jdbc.username=iplat62
jdbc.password=iplat62

说明:

  • 不支持*通配格式,一旦加载,所有spring控制的bean中均可使用对应属性值

相关属性

  • value:设置properties文件名
  • ignoreResourceNotFound: 如果资源未找到,是否忽略,默认false

⛳️注解驱动(@Configurable,@ComponentScan)

@Configuration、@ComponentScan
类注解
设置当前类为spring核心配置类

@Configurable//替换核心配置文件
@ComponentScan("com.zs") //注解扫描
public class SpringConfig{
}

团队开发,第三方bean配置与管理(Import)

@Import
类注解
导入第三方bean作为spring控制的资源

@Configurable
@Import(JDBCConfig.class) //导入数据源配置类
public class SpringConfig{
}

加载配置类

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

bean加载控制

bean的依赖关系(@DependsOn)

@DependsOn
类注解、方法注解
控制bean的加载顺序,使其在指定bean加载完毕后再加载

@Service("userService")
@DependsOn("userDao")//加载Service,需要先加载dao
public class UserServiceImpl implements UserService{

说明:

  • 配置在方法上,使@DependsOn指定的bean优先与@Bean配置的bean进行加载
  • 配置在类上,使@DependsOn指定的bean优先与当前类中所有@Bean配置的bean进行加载
  • 配置在类上,使@DependsOn指定的bean优先于@Component等配置的bean进行加载

配置类加载顺序(@Order)

配置类注解
控制配置类的加载顺序

@Order(1)
public class SpringConfig1{
}

@Order(2)
public class SpringConfig2{
}


ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig1.class,SpringConfig2.class);

bean的延迟加载(@Lazy)

类注解,方法注解
控制bean的加载时机,使其延迟加载

@Lazy
public class ClassName{}

应用场景

在这里插入图片描述

⛳️整合第三方技术(重点)

整合mybatis

在这里插入图片描述
JDBCConfig

package com.zs.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;




public class JDBCConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")
    public DruidDataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
//        ds.setDriverClassName("com.mysql.jdbc.Driver");
//        ds.setUrl("jdbc:mysql://localhost:3306/testdb");
//        ds.setUsername("iplat62");
//        ds.setPassword("iplat62");
        return ds;
    }
}

MybatisConfig

package com.zs.config;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;

public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.zs.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }
    @Bean
    public MapperScannerConfigurer getMapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.zs.mapper");
        return msc;
    }
}

SpringConfig

package com.zs.config;

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;

@Configurable
@ComponentScan("com.zs")
@Import({JDBCConfig.class,MybatisConfig.class})
@PropertySource(value = {"classpath:jdbc.properties","a.properties"},ignoreResourceNotFound = true)
public class SpringConfig {
}

AccountServiceImpl

package com.zs.service.impl;

import com.zs.mapper.AccountDao;
import com.zs.domain.Account;
import com.zs.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    //注入dao
    @Autowired
    private AccountDao accountDao;
    

    @Override
    public void save(Account account) {
        accountDao.save(account);
    }

    @Override
    public void delete(Integer id) {
        accountDao.delete(id);
    }

    @Override
    public void update(Account account) {
        accountDao.update(account);
    }

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }

    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }
}

AccountDao

package com.zs.mapper;

import com.zs.domain.Account;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface AccountDao {

    @Insert("insert into account(name,money) values(#{name},#{money})")
    void save(Account account);

    @Delete("delete from account where id = #{id}")
    void delete(Integer id);

    @Update("update account set name=#{name},money=#{money} where id=#{id}")
    void update(Account account);

    @Select("select * from account")
    List<Account> findAll();

    @Select("select * from account where id=#{id}")
    Account findById(Integer id);
}

整合Junit(注解)

在这里插入图片描述
引入整合需要的坐标

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

UserServiceTest 创建测试类测试

package com.zs.service;


import com.zs.config.SpringConfig;
import com.zs.domain.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

//设定spring专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
//加载核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//设定加载的spring上下文对应的配置类
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById(){
        Account ac = accountService.findById(1);
    }

    @Test
    public void testFindAll(){
        List<Account> all = accountService.findAll();
        all.forEach(System.out::println);
    }
}

IOC底层核心原理

IoC核心接口

在这里插入图片描述

  1. BeanFactory

    提供bean的基本操作

    • bean获取
      • 按名称获取
      • 按类型获取
    • bean供应商
    • bean基本信息
      • 是否存在
      • 是否单例
      • 类型获取
      • 类型检测
      • 别名获取
  2. HierarchicalBeanFactory

    提供bean分层结构,提出父子容器概念

    • 获取本地bean
  3. AutowireCapableBeanFactory

    提供bean自动装配功能

    • bean创建
    • bean装配
      • 装配方式
      • 前置动作
      • 后置动作
  4. ListableBeanFactory

提供容器内部遍历搜索bean的功能

  • 容器中的bean信息
    • bean存在性
    • bean的数量
    • bean的类型
  • bean相关信息获取
    • 由类型获取bean的名称
    • 由注解获取bean的名称
  • bean信息获取
    • bean的注解
    • bean的定义名称

组件扫描过滤器(@ComponentScan)

在这里插入图片描述
@ComponentScan
类注解
设置spring配置加载类扫描规则

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
ANNOTATION
@ComponentScan(
	value = "com.zs"						//设置基础扫描路径
	excludeFilters = @ComponentScan.Filter(	//设置过滤规则,过滤器
		type=FilterType.ANNOTATION			//按注解过滤
		classes=Repository.class)			
)
CUSTOM
@Configurable
@ComponentScan(value = "com.zs",
    excludeFilters = @ComponentScan.Filter(
            type= FilterType.CUSTOM,//自定义过滤
            classes = MyTypeFilter.class
    ))
public class SpringConfig {
}

自定义过滤器

package com.zs.config.filter;

import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;

public class MyTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //true拦截,false 不拦截
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        String className = classMetadata.getClassName();
        if (className.equals("")){
            return true;
        }
        return false;
    }
}

ASSIGNABLE_TYPE
ASPECTJ
REGEX

自定义导入器

CustomerImportSelector (工具类,直接用)
配合文件 import.properties使用

path=com.zs.dao.impl.*

将自定义导入器使用Import导入主配置类上

package com.zs.config.selector;

import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class CustomerImportSelector implements ImportSelector {

    private String expression;

    public CustomerImportSelector(){
        try {
            //初始化时指定加载的properties文件名
            Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("import.properties");
            //设定加载的属性名
            expression = loadAllProperties.getProperty("path");
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //1.定义扫描包的名称
        String[] basePackages = null;
        //2.判断有@Import注解的类上是否有@ComponentScan注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            //3.取出@ComponentScan注解的属性
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            //4.取出属性名称为basePackages属性的值
            basePackages = (String[]) annotationAttributes.get("basePackages");
        }

        //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
        if (basePackages == null || basePackages.length == 0) {
            String basePackage = null;
            try {
                //6.取出包含@Import注解类的包名
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //7.存入数组中
            basePackages = new String[] {basePackage};
        }
        //8.创建类路径扫描器
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        //9.创建类型过滤器(此处使用切入点表达式类型过滤器)
        TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
        //10.给扫描器加入类型过滤器
        scanner.addIncludeFilter(typeFilter);
        //11.创建存放全限定类名的集合
        Set<String> classes = new HashSet<>();
        //12.填充集合数据
        for (String basePackage : basePackages) {
            scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
        }
        //13.按照规则返回
        return classes.toArray(new String[classes.size()]);
    }
}

自定义注册器

使用方法同上,可以替换 扫描包的注解

package config.registrar;
 
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
 
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
 
public class CustomeImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
 
    private String expression;
 
    public CustomeImportBeanDefinitionRegistrar(){
        try {
            //初始化时指定加载的properties文件名
            Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("import.properties");
            //设定加载的属性名
            expression = loadAllProperties.getProperty("path");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //1.定义扫描包的名称
        String[] basePackages = null;
        //2.判断有@Import注解的类上是否有@ComponentScan注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            //3.取出@ComponentScan注解的属性
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            //4.取出属性名称为basePackages属性的值
            basePackages = (String[]) annotationAttributes.get("basePackages");
        }
        //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
        if (basePackages == null || basePackages.length == 0) {
            String basePackage = null;
            try {
                //6.取出包含@Import注解类的包名
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //7.存入数组中
            basePackages = new String[] {basePackage};
        }
        //8.创建类路径扫描器
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
        //9.创建类型过滤器(此处使用切入点表达式类型过滤器)
        TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
        //10.给扫描器加入类型过滤器
        scanner.addIncludeFilter(typeFilter);
        //11.扫描指定包
        scanner.scan(basePackages);
    }
}

bean初始化过程

在这里插入图片描述

BeanFactoryPostProcessor

  • 作用: 定义在bean工厂对象创建后,bean对象创建前执行的动作,用于对工厂进行创建后业务处理
  • 运行时机:当前操作用于对工厂进行处理,仅运行一次

BeanPostProcessor

  • 作用: 定义了所有bean初始化前后进行的统一动作,用于对bean进行创建前业务处理与创建后业务处理
  • 运行时机:当前操作伴随着每个bean的创建过程,每次创建bean均运行该操作

InitializingBean

  • 作用:定义了每个bean的初始化前进行的动作,属于非统一性动作,用于对bean进行创建前业务处理
  • 运行时机:当前操作伴随着任意一个bean的创建过程,保障其个性化业务处理

注意:需要被spring容器加载方可运行

在这里插入图片描述
FactoryBean

  • 对单一的bean的初始化过程进行封装,达到简化配置的目的

image-20220905011017583

image-20220905011245340

image-20220905011326870

image-20220905011553339

image-20220905011538935

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

悠闲的线程池

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

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

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

打赏作者

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

抵扣说明:

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

余额充值