Spring注解及其原理

Spring注解原理

Spring注解

原来Spring的使用

1.导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

2.配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">

    <!-- 注册组件 -->
    <bean id="person" class="com.atguigu.bean.Person">
        <property name="age" value="18"></property>
        <property name="name" value="liayun"></property>
    </bean>

</beans>

3.实体类

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

4.测试获取对象

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        Object person = ac.getBean("person");
        System.out.println(person);
    }
}

注解的方式

现在我们不想再使用配置文件了,我们想用配置类代替配置文件

配置类

/**
 * User:曹帅
 * Date:2021/1/15
 * Version:1.0
 * 原来的配置文件现在被这个配置类所代替了
 */
@Configuration   //此注解声明这是一个配置类
public class MainConfig {

    //将这个返回的Person注入到Spring容器中,id默认时方法名
    @Bean
    public Person person(){
        return new Person("lisi",20);
    }
}
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
        Object person = ac.getBean("person");
        System.out.println(person);

使用注解排除一些类的加载

@Configuration   //此注解声明这是一个配置类
@ComponentScan(value = "com.atguigu",excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
})
public class MainConfig {

    //将这个返回的Person注入到Spring容器中,id默认时方法名
    @Bean
    public Person person(){
        return new Person("lisi",20);
    }
}

Filter规则都有哪些

public enum FilterType {
    ANNOTATION,   //按照注解
    ASSIGNABLE_TYPE,  //自定义规则
    ASPECTJ,  //使用这个表达式
    REGEX,  //正则表达式
    CUSTOM;  //自定义规则

    private FilterType() {
    }
}

自定义规则比较常用

建立一个类去实现接口,

/**
 * User:曹帅
 * Date:2021/1/16
 * Version:1.0
 */
public class MyTypeFilter implements TypeFilter {

    /*
    metadataReader:读取到的当前正在扫描的类的信息
    MetadataReaderFactory:可以获取到其他任何类的信息
     */
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前正在扫描的类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();
        String className = classMetadata.getClassName();
        System.out.println("=====>"+className);
        if (className.contains("er")) {
            return true;
        }
        return false;
    }
}

在配置类配置使用

@Configuration   //此注解声明这是一个配置类
@ComponentScan(value = "com.atguigu",includeFilters = {
        //@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}),
        @ComponentScan.Filter(type = FilterType.CUSTOM,classes = MyTypeFilter.class)
})
public class MainConfig {

    //将这个返回的Person注入到Spring容器中,id默认时方法名
    @Bean
    public Person person(){
        return new Person("lisi",20);
    }
}

注解填充进Spring容器中的对象默认是单实例的

        //默认是单实例的,
        Object bean = ac.getBean("person");
        Object bean2 = ac.getBean("person");
        System.out.println(bean==bean2);

实例的类型

prototype:

    @Scope("prototype")
    @Bean("person")
    public Person person(){
        return new Person("曹帅",22);
    }

多例,容器启动时不会自动生成类,获取对象的时候对象被new出来,获取几次new几次

singleton:

单例默认情况下容器启动就会加载类,

懒加载,单例默认情况下不会再在容器创建的时候就创建对象了,第一次获取对象时才创建对象

    @Lazy
    //@Scope("prototype")
    @Bean("person")
    public Person person(){
        System.out.println("person创建了");
        return new Person("曹帅",22);
    }

request

session

注册之前条件判断

在Bean被注入到容器中可以先进行判断,如果不符合条件就不加入进去

实现接口,重写方法,

public class LinuxCondition implements Condition {

    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //1.获取到ioc使用的beanfactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //2.获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        //3.获取当前环境信息
        Environment environment = conditionContext.getEnvironment();
        //4.获取到bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        //可以判断容器中bean的注册情况
        boolean definition = registry.containsBeanDefinition("person");
        String property = environment.getProperty("os.name");
        if (property.contains("linux")) {
            return true;
        }
        return false;
    }
}
    @Conditional({WindowsCondition.class})
    @Bean("ym")
    public Person person01(){
        return new Person("杨梦",21);
    }

@Conditional 也可以放在类上,代表着满足当前条件,这个类中配置的所有bean注册才能生效

@Import

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
	 * or regular component classes to import.
	 */
	Class<?>[] value();

}

可以直接导入外部的组件

@Configuration
@Import(LinuxCondition.class)

id默认是组件的全名,

() 里面可以是一个数组,可以一下子导入多个

另外还可以自定义选择器,

@Configuration
@Import({LinuxCondition.class, MyImportSelector.class})
public class MyImportSelector implements ImportSelector {

    //返回值就是要导入到容器中的组件全类名
    //annotationMetadata:当前标注 @Import注解的类的所有注解信息
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.atguigu.condition.WindowsCondition"};
    }
}

自定义一个类实现接口,重写方法,容器会将方法返回的值对应的类加入到容器中

ImportBeanDefinitionRegistrar方式,直接手动注册




```java
public class MyImportBeanRegister implements ImportBeanDefinitionRegistrar {
    /**
     * @param importingClassMetadata 当前类的注解信息
     * @param registry               BeanDefinitionRegistry注册类
     *                               把所有需要添加到容器中的bean,
     *                               BeanDefinitionRegistry有方法可以注册进去
     */
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean definition = registry.containsBeanDefinition("com.atguigu.condition.LinuxCondition");
        boolean definition1 = registry.containsBeanDefinition("com.atguigu.condition.WindowsCondition");
        if (definition && definition1) {
            //指定bean定义信息:类型,作用域
            BeanDefinition beanDefinition = new RootBeanDefinition(Sunny.class);
            //注册一个bean指定bean名字
            registry.registerBeanDefinition("sunny", beanDefinition);
        }
    }
}

FactoryBean,方式,在Spring和其他框架整合的时候经常会用到




```java
/**
 * User:曹帅
 * Date:2021/1/16
 * Version:1.0
 * 创建一个Spring定义的FactoryBean
 */
public class SunnyFactoryBean implements FactoryBean<Sunny> {

    //返回一个Color对象,这个对象会添加到容器中
    public Sunny getObject() throws Exception {
        System.out.println("SunnyFactoryBean------getObject");
        return new Sunny();
    }

    public Class<?> getObjectType() {
        return Sunny.class;
    }

    //是否是单例
    public boolean isSingleton() {
        return true;
    }
}
    @Test
    public void testImport() {
        printBeans(ac);
        Object bean = ac.getBean("sunnyFactoryBean");
        //将这个工厂bean注册到容器中,虽然看似是这个类型的,
        //但是实际获取到的却是Sunny类型的
        System.out.println("bean类型"+bean.getClass());
    }

默认获取到的是工厂bean创建的对象,getObject() 返回的对象,

要获取工厂bean本身,

    @Test
    public void testImport() {
        printBeans(ac);
        Object bean = ac.getBean("&sunnyFactoryBean");
        //将这个工厂bean注册到容器中,虽然看似是这个类型的,
        //但是实际获取到的却是Sunny类型的
        System.out.println("bean类型"+bean.getClass());
    }

容器中对象的生命周期

1.通过@Bean注解指定

public class Car {
    public Car(){
        System.out.println("car constructor");
    }
    public void init(){
        System.out.println("car---init");
    }
    public void destroy(){
        System.out.println("car----destroy");
    }
}
/**
 * User:曹帅
 * Date:2021/1/16
 * Version:1.0
 * bean的生命周期
 * 创建---初始化---销毁的过程
 * 容器管理bean的生命周期
 * 我们可以自定义初始化和销毁方法:
 *  容器在bean进行到当前生命周期的时候,调用这些方法
 * 1.指定初始化销毁方法
 */
@Configuration
public class MainConfigOfLifeCycle {

    @Bean(initMethod = "init",destroyMethod = "destroy")
    public Car car() {
        return new Car();
    }
}
    @Test
    public void testImport() {
        printBeans(ac);
        ac.close();
    }

初始化:对象创建完成,并赋值好,调用初始化方法

销毁:容器关闭,但是多例情况下,容器调用销毁方法

其原理就是让Bean实现一些接口来实现的,

InitializingBean DisposableBean

那么既然实际上是继承了两个接口达到了这个目的,我们直接继承两个接口,写方法也可以的

@Component
public class Cat implements InitializingBean, DisposableBean {
    public Cat(){
        System.out.println("cat constructor");
    }

    public void destroy() throws Exception {
        System.out.println("aaaa, a cat has been destroyed");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("hhh,a cat become");
    }
}
@ComponentScan("com.atguigu.bean")
@Configuration
public class MainConfigOfLifeCycle {

    @Scope("prototype")
    @Bean(initMethod = "init",destroyMethod = "destroy")
    public Car car() {
        return new Car();
    }

}
    @Test
    public void testImport() {
        //printBeans(ac);
        ac.getBean("cat");
        ac.close();
    }

可以使用JSR250里面的规范

@PostConstruct,新生时

@PreDestroy,销毁时

@Component
public class Dog {
    public Dog(){
        System.out.println(" a dog constructor");
    }
    //对象创建并赋值之后调用
    @PostConstruct
    public void init(){
        System.out.println("dog init");
    }
    //容器移除对象之前
    @PreDestroy
    public void destroy(){
        System.out.println("dog ---preDestroy");
    }
}

BeanPostProcessor bean的后置处理器,在bean初始化前后进行一些处理工作

postProcessBeforeInitialization:在初始化之前工作

postProcessAfterInitialization:在构造方法执行之后

属性赋值

非常传统的直接赋值

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    /*
    1.基本数值
    2.SPEL #{}
    3.${} 取出配置文件中的值(在运行环境变量中的值)
     */
    @Value("${person.nickName}")
    private String name;
    @Value("#{20-2}")
    private Integer age;
}

在配置类上声明扫描哪些配置文件,spring会把这些配置文件的值放入环境中去

@PropertySource(value = {"classpath:application.properties"})
@Configuration
public class MainConfigOfPropertyValue {

    @Bean
    public Person person(){
        return new Person();
    }

}
    @Test
    public void testImport() {
        printBeans(ac);
        Object person = ac.getBean("person");
        System.out.println(person);
        ConfigurableEnvironment environment = ac.getEnvironment();
        String property = environment.getProperty("person.nickName");
        System.out.println(property);
        ac.close();
    }
person.nickName=杨小梦

自动注入

@Autowired会自动进行注入,

    @Test
    public void test01() {
        BookService bookService = ac.getBean(BookService.class);
        bookService.print();
        BookDao bookDao = ac.getBean(BookDao.class);
        System.out.println(bookDao);  //两个对象的hash值相等
    }

有时候也会有冲突,

    @Test
    public void test01() {
        BookService bookService = ac.getBean(BookService.class);
        //这个自动装配的是label 1, 默认按照类型去容器里面找,
        //2.如果找到相同类型的多个,就按照属性名作为Id去容器里面找
        bookService.print();
        BookDao bookDao = ac.getBean(BookDao.class);
        System.out.println(bookDao.getLabel());
    }

当然最有效的是不让他们产生冲突,直接指定好名字就行了

@Service
public class BookService {
    @Qualifier("bookDao")
    @Autowired
    private BookDao bookDao;

    public void print() {
        System.out.println(bookDao.getLabel());
    }
}

@Primary可以声明这个bean比较优先装配

也可以使用@Resource这个注解注入到Spring容器中

@Resource(JSR250)

@Inject(JSR330)需要导入 javax.inject的包,和Autowired功能一样

@Autowired: 构造器,参数,方法,属性

@Component
public class Boss {

    private Car car;
    public Car getCar() {
        return car;
    }

    //标注在方法上,Spring创建当前对象就会调用方法完成赋值
    //方法使用的参数,自定义类型的值从ioc容器中获取
    @Autowired
    public void setCar(Car car) {
        this.car = car;
    }
}

构造器所用的组件,也都是从容器获取

@Component
public class Boss {

    private Car car;
    @Autowired
    public Boss(Car car) {
        this.car = car;
        System.out.println("Boss's constructor with parameter");
    }
}
    public Boss(@Autowired Car car) {
        this.car = car;
        System.out.println("Boss's constructor with parameter");
    }

甚至如果只有一个有参构造器,还可以省略

    public Boss(Car car) {
        this.car = car;
        System.out.println("Boss's constructor with parameter");
    }

@Bean标注的方法创建对象的时候,方法参数的值从容器中获取,不写@Autowired也是可以的

自定义组件想要使用Spring容器底层的一些组件(ApplicationCOntext,Beanfactory,);

那么自定义组件实现xxxAware ,这个组件就可以获取到类中的内容

@Component
public class Red implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        System.out.println("传入的ioc"+applicationContext);
    }
}

这些xxxAware,是使用xxxProcessor来进行处理的

Profile

/**
 * User:曹帅
 * Date:2021/1/17
 * Version:1.0
 * Profile:
 *  Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能
 *
 * 开发环境,测试环境,生产环境
 * 数据源:A/B/C
 * @Profile可以指定环境使用
 * 加入了环境标识的bean只有这个环境被激活的时候才能注册到容器中,
 * 环境默认是 default
 */
@PropertySource("classpath:application.properties")
@Configuration
public class MainConfigOfProfile {

    @Value("${db.user}")
    private String user;
    @Value("${db.password}")
    private String password;
    @Value("${db.driverClass}")
    private String driverClass;

    @Profile(value = "test")
    @Bean("testDataSource")
    public DataSource dataSourceTest(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        dataSource.setUrl("jdbc:mysql://localhost:3306/ssm");
        dataSource.setDriverClassName(driverClass);

        return dataSource;
    }

    @Profile(value = "dev")
    @Bean("devDataSource")
    public DataSource dataSourceDev(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        dataSource.setUrl("jdbc:mysql://localhost:3306/ssm");
        dataSource.setDriverClassName(driverClass);

        return dataSource;
    }

    @Profile(value = "prod")
    @Bean("prodDataSource")
    public DataSource dataSourceProd(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        dataSource.setUrl("jdbc:mysql://localhost:3306/ssm");
        dataSource.setDriverClassName(driverClass);

        return dataSource;
    }
}
    @Test
    public void test01() {
        String[] names = ac.getBeanNamesForType(DataSource.class);
        for (int i = 0; i < names.length; i++) {
            System.out.println(names[i]);
        }
    }

修改环境:

1.在运行test方法的时候设置虚拟机环境参数,-Dspring.profile.active=test

2.上下文对象中获取环境,set参数,可以设置一个数组

    @Test
    public void test01() {
        ac.getEnvironment().setActiveProfiles("test","dev");
        ac.register(MainConfigOfProfile.class);
        ac.refresh();
        String[] names = ac.getBeanNamesForType(DataSource.class);
        for (int i = 0; i < names.length; i++) {
            System.out.println(names[i]);
        }
    }

3.在配置类的头上写Profile注解,代表着只有在那样一个环境下,才会生效

AOP

导包

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

aop注解需要配置很多东西,

首先应该在配置类上面把它开启

@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {

    @Bean
    public MathCalculator mathCalculator(){
        return new MathCalculator();
    }

    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}

最起码有一个目标类,这个目标类就很普通了

public class MathCalculator {
    public int div(int i, int j) {
        return i / j;
    }
}

还要有一个专门的aop的类

/**
 * User:曹帅
 * Date:2021/1/17
 * Version:1.0
 * this is a log Aspects class:
 *  2.the methods in this class should to be aware the procedure for a reality class
 *  3.aware method:
 *      before information(@Before):    logStart,
 *      after information(@After):      logEnd  it always has been execute after target method has been executed ever normal or has exception
 *      return information(@AfterReturning):     logReturn
 *      err information(@AfterThrowing):    logException
 *      around information(@Around):   dynamic proxy, push target method active by ourselves(joinPoint.proceed   )
 *  4.target when and where active for methods in aspect class
 *  5.put aspect class and target class into IOC
 *  6.information spring which class is the aspect class
 *  7.open enable AspectProxy
 */
@Aspect
public class LogAspects {

    // common expression
    @Pointcut("execution(public int com.atguigu.aop.MathCalculator.*(..))")
    public void pointCut(){

    }
    //@Before before method expression
    @Before("pointCut()")
    public void logStart(){
        System.out.println("method start...");
    }

    @After("pointCut()")
    public void logEnd(){
        System.out.println("method end ...");
    }

    @AfterReturning("pointCut()")
    public void logReturn(){
        System.out.println("method return ...");
    }

    @AfterThrowing("pointCut()")
    public void logException(){
        System.out.println("method exception ...");
    }
}

这个类里面声明了一个方法,可以自动的找到要切入哪一个类的那个方法,方法名字,返回参数什么的都可以设定,也可以使用正则表达式类型的,

将这两个类都要放在容器里面,要不然是不会自动执行的

测试的时候,在目标类的方法执行的时候切面类就会发觉

也可以在切面中获取目标方法中的内容,都在JoinPoint里面,这个参数可传可不传

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        System.out.println("method:"+joinPoint.getSignature()+" start...   ,the args are"+ Arrays.asList(args));
    }

return 中可以获取到返回的内容

    @AfterReturning(value = "pointCut()",returning = "result")
    public void logReturn(Object result) {
        System.out.println("method return ..."+result);
    }

throwing异常情况下

    @AfterThrowing(value = "pointCut()",throwing = "e")
    public void logException(Exception e) {
        System.out.println("method exception ...");
    }

如果想要使用到JoinPoint里面的内容,那么JoinPoint一定要放到参数中的第一位

AOP的原理

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
}

调用@Import 给容器中导入这个类

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
}
public interface ImportBeanDefinitionRegistrar {

	/**
	 * Register bean definitions as necessary based on the given annotation metadata of
	 * the importing {@code @Configuration} class.
	 * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
	 * registered here, due to lifecycle constraints related to {@code @Configuration}
	 * class processing.
	 * @param importingClassMetadata annotation metadata of the importing class
	 * @param registry current bean definition registry
	 */
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

利用AspectJAutoProxyRegistrar给容器中注入自定义的类 AnnotationAwareAspectJAutoProxyCreator

如果一个类需要使用AOP,我们配置成功了,那么我们实际上放入IOC容器中的是我们自己生成的一个代理对象,(增强后的对象)

这个对象中有增强器,目标对象

事务

1.导入依赖

		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>

2.配置数据远

@PropertySource("classpath:application.properties")
@EnableTransactionManagement
@ComponentScan("com.atguigu.tx")
@Configuration
public class TxConfig {

    @Value("${db.user}")
    private String user;
    @Value("${db.password}")
    private String password;
    @Value("${db.driverClass}")
    private String driverClass;

    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        dataSource.setUrl("jdbc:mysql://localhost:3306/ssm");
        dataSource.setDriverClassName(driverClass);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(){
        //Sring对Configuratiojn文件会特殊处理,不会再次调用一遍方法的
        //给容器中加组件的方法,多次调用都只是从容器中找组件而已
        JdbcTemplate template = new JdbcTemplate(dataSource());
        return template;
    }
    //注册事务管理器在服务器中
    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(dataSource());
    }
}

事务拦截器:在目标方法执行的时候,执行拦截器链,

事务拦截器:先获取事务相关的属性,再来获取 PlateformTransactionManager

其余

事件监听

    @Test
    public void test01(){
        //发布事件
        ac.publishEvent(new ApplicationEvent(new String("我发布的事件")){});
        ac.close();
    }
/**
 * User:曹帅
 * Date:2021/1/17
 * Version:1.0
 * 自己开发一个事件
 * 步骤:
 *  写一个监听器来监听某个事件 ApplicationEvent极其子类
 *  把监听器加入到容器,
 *  只要容器中有相关事件的发布,我们就能监听到这个事件
 *  ContextRefreshedEvent:容器刷新完成所有bean都完全创建,会发布
 *  ContextClosedEvent:关闭容器也会发布事件
 * 我们自己也可以发布一个事件:
 *
 */
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
    //当容器中发布此事件后,方法触发
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("收到事件" + event);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值