Java注解和反射

版权声明:本文是博主原创文章,博客地址:Java注解和反射_肖家添的博客-CSDN博客,未经允许不可转载。

联系方式:xiaojiatian.canton@qq.com

前言

在软件开发人才市场饱和的环境下,我们应如何提升自己的竞争力?

企业招聘时,如对技术水平要求较高,大部分测验的会是底层理论其次才是业务逻辑,那么你如果是以下这类工程师,会因自身实际技术水平低下,导致失去工作机会现有的开源框架,亦或是日新月异出现的新技术,只要掌握其部分,就能着手开发业务功能,意味着,只要工程师在技术层面或框架领域略懂一二,业务代码就可以写出来,这样的工程师是不具备行业竞争力的,只是业务熟练度时间上的问题罢了;所以,我们不能只沉迷于开源框架,得精益求精,掌握更多底层知识,让自己出类拔萃,比人更胜一筹。

一、注解

(1)什么是注解

相信大家对注解应该并不陌生,在现在信息飞速发展的年代,各种优秀的框架或许都离不开注解的使用,像我们在实现接口一个方法时,也会有@Override注解。注解说白了就是对程序做出解释,与我们在方法、类上的注释没有区别,但是注解可以被其他程序所读取,进行信息处理,否则与注释没有太大的区别。

(2)内置注解

1.2.1 @Override

修辞方法,表示打算重写超类中的方法声明。

image.png

1.2.2 @Deprecated

我们可能看不到这个注解,但是我们肯定在使用一些方法时会出现横线。表示废弃,这个注释可以修辞方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为他很危险或有更好的选择。

image.pngimage.png

1.2.3 @SuppressWarnings

这个注解主要是用来抑制警告信息的,我们在写程序时,可能会报很多黄线的警告,但是不影响运行,我们就可以用这个注解来抑制隐藏它。与前俩个注解不同的是我们必须给注解参数才能正确使用他。

参数值

作用目标

deprecation

使用了过时的类或方法的警告

unchecked

执行了未检查的转换时的警告 如:使用集合时未指定泛型

fallthrough

当在switch语句使用时发生case穿透

path

在类路径、源文件路径中有不存在路径的警告

serial

当在序列化的类上缺少serialVersionUID定义时的警告

finally

任何finally子句不能完成时的警告

all

关于以上所有的警告

image.png

(3)元注解

我们在自定义注解时,需要使用java提供的元注解,就是负责注解的其他注解。java定义了四个标准的meta-annotation类型,他们被用来提供对其他注解类型声明。

1.3.1 @Target

用于描述注解的使用范围(即:被描述的注解可以使用在什么地方)

策略值

作用目标

ElementType.TYPE

接口、类、枚举、注解

ElementType.FIELD

字段、枚举的常量

ElementType.METHOD

方法
ElementType.PARAMETER方法参数
ElementType.CONSTRUCTOR构造函数
ElementType.LOCAL_VARIABLE局部变量
ElementType.ANNOTATION_TYPE注解
ElementType.PACKAGE

1.3.2 @Retention

这个注解的作用就是我们需要告诉编译器我们需要在什么级别保存该注释信息,用于描述注解的生命周期。

策略值

作用目标

RetentionPolicy.SOURCE

注解只在源码中存在,编译成.class文件就不存在了
RetentionPolicy.CLASS注解在源码和.class文件中都存在(如:JDK内置系统注解:@Override、@Deprecated、@Suppvisewarnings)
RetentionPolicy.RUNTIME在运行阶段还起作用,甚至会影响运行逻辑的注解(如:Spring中@Autowried,程序运行时,把成员变量自动注入)

1.3.3 @Document

用于javadoc文档生成,表明这个注解应该被javadoc工具记录,属于标记注解。

1.3.4 @Inherited

表示该注解会被衍生类继承,如果衍生类需要继承使用基础类的注解则需要在注解上加上这个。

(4)自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

1.4.1 定义注解的格式

public @interface 注解名 {定义体}

1.4.2 可支持数据类型

  • 所有基本数据类型: int, float, boolean, byte, double, char, long, short;
  • String类型;
  • Class类型;
  • enum类型;
  • Annotation类型;
  • 以上所有类型的数组。

1.4.3 参数成员的规范

  • 只有一个参数成员时,按照Java规范,参数命名设定为 value ,在使用是即可忽略成员名和赋值号;
  • 成员以无参无异常方式声明;
  • 可以用 default 为成员指定一个默认值;
  • 可无成员,无成员时,注解为标识注解,如: @Document文档注解。

1.4.4 自定义注解示例

image.png

1.4.5 使用自定义注解

image.png

二、反射

(1)反射的概述

  • 反射是框架设计的灵魂;
  • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制;
  • 要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。

(2)反射的本质

(3)反射常用类介绍

2.3.1 Class 类常用方法

方法

描述

int getModifiers();

获取修饰符

Class Class.forName();

动态加载类

Class newInstance();

根据对象的class新建一个对象

Classs getSuperclass();

获取继承的父类

Class<?>[] getInterfaces();

获取继承的接口

Field getField(String name);

反射中获得目标字段

Field[] getFields();

获得所有公共的字段

Field[] getDeclaredFields();

获取所有的字段

Method[] getMethods();

获得所有公共的方法

Method getMethod(String name, Class<?>... parameterTypes);

获得对应参数类型的目标方法

Method[] getDeclaredMethods();

获取所有的方法

Constructor[] getConstructors();

获得所有构造函数

Constructors getConstructor(Class<?>... parameterTypes);

获取对应参数类型的构造函数

String getPackage();

反射中获得package包

boolean isEnum();

判断是否为枚举类型

boolean isArray();

判断是否为数组类型

2.3.2 Constructor 类常用方法

方法

描述

int getModifiers();

获取修饰符

void setAccessible(boolean flag);

是否越过修饰符安全检查

String getName();

获取构造方法的名称

Class<?>[] getParameterTypes();

获取构造方法中参数得到类型

Object newInstance(Object.class...initargs);

向构造方法中传递参数,实例化对象

2.3.3 Method 类常用方法

方法

描述

int getModifiers();

获取修饰符

void setAccessible(boolean flag);

是否越过修饰符安全检查

String getName();

获取方法名

Class getReturnTypes();

获取返回值类型

Class[] getParameterTypes();

获得参数类型的数组

Object invoke(Object sourceObj, Object...args);

执行方法

2.3.4 Field 类常用方法

方法

描述

int getModifiers();

获取修饰符

boolean isAccessible();

判断此属性是否可被外部访问

void setAccessible(boolean flag);

是否越过修饰符安全检查

String getName();

获取属性名

void set(Object sourceObj, Object value);

设置指定对象中属性的具体内容

Object get(Object sourceObj);

得到指定对象中属性的具体内容

(4)高级应用

2.4.1 构造函数

代码示例

package com.myrcib.middleware.core.idworker;

import java.lang.reflect.Constructor;

/**
 * 反射高级应用之构造函数
 * @Author 肖家添
 * @Date 2020/11/26 18:01
 */
public class ShowTime {

    public ShowTime(){

    }

    public ShowTime(String userName, String phone){
        this.userName = userName;
        this.phone = phone;
    }

    public String userName = "张三";
    public String phone = "15814850000";

    public static void main(String[] args) throws Exception{
        Class cls = ShowTime.class;
        Constructor constructor = cls.getDeclaredConstructor(String.class, String.class);
        ShowTime showTime = (ShowTime) constructor.newInstance("李四", "119");
        System.out.println(showTime.toString());
    }

    @Override
    public String toString() {
        return "ShowTime{" +
                "userName='" + userName + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}

执行结果

ShowTime{userName='李四', phone='119'}

2.4.2 成员变量

代码示例

package com.myrcib.middleware.core.idworker;

import java.lang.reflect.Field;

/**
 * 反射高级应用之成员变量
 * @Author 肖家添
 * @Date 2020/11/26 18:19
 */
public class ShowTime {

    public String userName = "张三";
    public String phone = "15814850000";

    public static void main(String[] args) throws Exception{
        Class cls = ShowTime.class;
        ShowTime showTime = (ShowTime) cls.newInstance();
        for (Field field : cls.getFields()) {
            Object object = field.get(showTime);
            field.set(showTime, object + "-after");
        }
        System.out.println(showTime.toString());
    }

    @Override
    public String toString() {
        return "ShowTime{" +
                "userName='" + userName + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}

执行结果

ShowTime{userName='张三-after', phone='15814850000-after'}

2.4.3 成员方法

代码示例

package com.myrcib.middleware.core.idworker;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * 反射高级应用之成员方法
 * @Author 肖家添
 * @Date 2020/11/26 18:45
 */
public class ShowTime {

    public void run(){
        System.out.println("无参方法执行完毕...");
    }

    public void run(String userName, int age){
        System.out.println(String.format("用户:%s, 年龄: %s", userName, age));
        System.out.println("带参执行完毕...");
    }

    public static void main(String[] args) throws Exception{
        Class cls = ShowTime.class;
        System.out.println("ShowTime中所有方法:");
        for (Method method : cls.getDeclaredMethods()) {
            StringBuffer sb = new StringBuffer();
            sb.append(method.getReturnType().getName());
            sb.append(" ").append(method.getName());
            sb.append("(");
            for (Parameter parameter : method.getParameters()) {
                sb.append(parameter.getType()).append(" ").append(parameter.getName()).append(", ");
            }
            sb.append(")");
            System.out.println(sb.toString());
        }
        System.out.println("执行 run 方法:");
        Object sourceCls = cls.newInstance();
        cls.getMethod("run").invoke(sourceCls);
        cls.getMethod("run", String.class, int.class).invoke(sourceCls, "肖家添", 19);
    }
}

执行结果

ShowTime中所有方法:
void main(class [Ljava.lang.String; args, )
void run()
void run(class java.lang.String userName, int age, )
执行 run 方法:
无参方法执行完毕...
用户:肖家添, 年龄: 19
带参执行完毕...

2.4.4 越过泛型检查

代码示例

package com.myrcib.middleware.core.idworker;

import java.util.ArrayList;
import java.util.List;

/**
 * 反射高级应用之越过泛型检查
 * @Author 肖家添
 * @Date 2020/11/26 18:46
 */
public class ShowTime {

    public static void main(String[] args) throws Exception{
        List<String> stringList = new ArrayList<>();
        stringList.add("张三");
        System.out.println(stringList);
        Class cls = stringList.getClass();
        Object objectCls = cls.newInstance();
        cls.getMethod("add", Object.class).invoke(objectCls, 1);
        System.out.println(objectCls.toString());
    }
}

执行结果

[张三]
[1]

2.4.5 越过修饰符权限

代码示例

package com.myrcib.middleware.core.idworker;

import java.lang.reflect.Field;

/**
 * 反射高级应用之越过修饰符权限
 * @Author 肖家添
 * @Date 2020/11/26 19:05
 */
public class ShowTime {

    public static void main(String[] args) throws Exception{
        Class cls = UserModel.class;
        Object sourceObj = cls.newInstance();
        Field field = cls.getDeclaredField("userName");
        field.setAccessible(true);
        System.out.println(field.get(sourceObj));
        field.set(sourceObj, "张三");
        System.out.println(field.get(sourceObj));
    }
}

class UserModel{
    private String userName;
}
class UserModel{
    private String userName;
}

执行结果

null
张三

2.4.6 通用业务处理器

代码示例

package com.myrcib.middleware.core.idworker;

import com.alibaba.fastjson.JSONObject;

import java.lang.reflect.Method;

/**
 * 反射高级应用之通用业务处理器
 * @Author 肖家添
 * @Date 2020/11/26 19:34
 */
public class ShowTime {

    JSONObject datums;

    public ShowTime(Object formType, JSONObject datums) throws Exception{
        this.datums = datums;
        Method method = this.getClass().getDeclaredMethod(String.format("handler_%s", formType));
        // 越权
        method.setAccessible(true);
        // 执行
        method.invoke(this);
    }

    private void handler_01(){
        System.out.println("== handler_01 ==");
        System.out.println(datums);
    }

    private void handler_02(){
        System.out.println("== handler_02 ==");
        System.out.println(datums);
    }

    public static void main(String[] args) throws Exception{
        JSONObject datums = new JSONObject(){{
            put("userName", "肖家添");
        }};
        new ShowTime("01", datums);
        new ShowTime("02", datums);
    }
}

执行结果

== handler_01 ==
{"userName":"肖家添"}
== handler_02 ==
{"userName":"肖家添"}

三、注解与反射的高级应用

(1)字段校验

代码示例

package com.myrcib.middleware.core.calibrator;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 通用校验注解
 * @Author 肖家添
 * @Date 2020/11/25 15:25
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Calibrator {

    String[] message() default { "不能为空!" };

    ValidationType[] type() default { ValidationType.NOT_NULL };

}
package com.myrcib.middleware.core.calibrator;

/**
 * 验证类型
 * @Author 肖家添
 * @Date 2020/11/25 16:24
 */
public enum ValidationType {

    NOT_NULL(1),
    URL(2),
    PHONE(3),
    NUM(4);

    public final Integer value;

    ValidationType(int value) {
        this.value = value;
    }
}
package com.myrcib.middleware.core.calibrator;

import com.myrcib.middleware.core.base.exception.ServiceException;
import com.myrcib.middleware.util.StringHandler;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * 校验器助手
 * @Author 肖家添
 * @Date 2020/11/26 20:07
 */
public class CalibratorHelper<T> {

    /**
     * 对Bean进行校验
     * @Author 肖家添
     * @Date 2020/11/26 20:07
     */
    public void executeSourceObj(T sourceObj){
        Class cls = sourceObj.getClass();
        for (Field field : cls.getDeclaredFields()) {
            execute(sourceObj, field);
        }
    }

    /**
     * 对变量进行校验
     * @Author 肖家添
     * @Date 2020/11/26 20:07
     */
    public void execute(T sourceObj, Field field){
        field.setAccessible(true);
        Annotation annotation = field.getAnnotation(Calibrator.class);
        if(null == annotation) return;
        Calibrator calibrator = ((Calibrator) annotation);
        ValidationType[] types = calibrator.type();
        for (int i = 0; i < types.length; i++) {
            int type = calibrator.type()[i].value;
            String message = calibrator.message()[i];
            try{
                new Actuator(type, field.get(sourceObj));
            }catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException ex){
                ex.printStackTrace();
                throw new ServiceException(String.format("未定义验证器type【%s】的执行方法!", type));
            }catch (InvocationTargetException ex){
                throw new ServiceException(message);
            }
        }
    }

    /**
     * 通用业务执行器
     * @Author 肖家添
     * @Date 2020/11/26 20:08
     */
    private class Actuator{

        Object object;

        Actuator(int type, Object object) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            this.object = object;
            this.getClass().getDeclaredMethod(String.format("verify_%s", type)).invoke(this);
        }

        public void verify_1(){
            this.boolThrow(StringHandler.isValidObj(object));
        }
        public void verify_2(){
            this.boolThrow(object.toString().startsWith("http"));
        }
        public void verify_3(){
            this.boolThrow(StringHandler.isPhoneLegal(object.toString()));
        }
        public void verify_4(){
            this.boolThrow(StringHandler.isInteger(object.toString()));
        }

        private void boolThrow(boolean bool){
            if(!bool) throw new ServiceException();
        }
    }

}
package com.myrcib.middleware.core.idworker;

import com.myrcib.middleware.core.calibrator.Calibrator;
import com.myrcib.middleware.core.calibrator.CalibratorHelper;
import com.myrcib.middleware.core.calibrator.ValidationType;

/**
 * 字段校验器
 * @Author 肖家添
 * @Date 2020/11/26 20:10
 */
public class ShowTime {

    @Calibrator(message = {"姓名不能为空!"})
    private String name = "肖家添";

    @Calibrator(message = {"手机号不能为空!", "手机号格式不正确!"}, type = {ValidationType.NOT_NULL, ValidationType.PHONE})
    private String phone = "15814850000";

    @Calibrator(message = {"请选择年龄!", "年龄格式不正确!"}, type = {ValidationType.NOT_NULL, ValidationType.NUM})
    private String age = "123";

    @Calibrator(message = {"网址不能为空!", "网址格式不正确!"}, type = {ValidationType.NOT_NULL, ValidationType.URL})
    private String url = "htt1";

    public static void main(String[] args) throws Exception{
        ShowTime test = ShowTime.class.newInstance();
        new CalibratorHelper().executeSourceObj(test);
    }
}

执行结果

Exception in thread "main" com.myrcib.middleware.core.base.exception.ServiceException: 网址格式不正确!
	at com.myrcib.middleware.core.calibrator.CalibratorHelper.execute(CalibratorHelper.java:34)
	at com.myrcib.middleware.core.calibrator.CalibratorHelper.executeSourceObj(CalibratorHelper.java:15)
	at com.myrcib.middleware.core.idworker.ShowTime.main(ShowTime.java:30)

(2)接口访问记录

代码示例

package com.cmw.interceptor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 访问日志
 * @Author 肖家添
 * @Date 2020/11/27 17:15
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebLog {
    boolean value() default true;
}
package com.cmw.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 请求前置增强 -> 访问信息记录
 * @Author 肖家添
 * @Date 2020/11/27 17:15
 */
@Aspect
@Component
@Slf4j
public class WebLogAspect {

    @Pointcut("@annotation(com.cmw.interceptor.WebLog)")
    public void pointcut(){

    }

    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        WebLog webLog = method.getAnnotation(WebLog.class);
        if(!webLog.value()) return;
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        log.info("URI : {}", request.getRequestURI());
        log.info("URL : {}", request.getRequestURL());
        log.info("HTTP_METHOD : {}", request.getMethod());
        log.info("IP : {}", request.getRemoteAddr());
    }

}

执行结果

URI : /insuranceType/list
URL : http://localhost:9310/insuranceType/list
HTTP_METHOD : GET
IP : 0:0:0:0:0:0:0:1

(3)依赖注入

代码示例

package com.myrcib.middleware.core.idworker;

import cn.hutool.core.lang.ClassScaner;
import com.alibaba.fastjson.JSONObject;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * Bean统一管理和依赖注入
 * @Author 肖家添
 * @Date 2020/11/26 20:00
 */
public class ShowTime {

    private static String classSimpleName2BeanName(String simpleName){
        return String.format("%s%s", simpleName.substring(0, 1).toLowerCase(), simpleName.substring(1));
    }

    public static void main(String[] args) throws Exception{
        //-- Bean扫描
        String packageName = "com.myrcib.middleware";
        Set<Class<?>> classSet = ClassScaner.scanPackage(packageName);
        TreeMap<String, String> beans = new TreeMap<>();
        for (Class<?> cls : classSet) {
            if(!cls.isAnnotationPresent(Service.class) && !cls.isAnnotationPresent(Mapper.class)) continue;
            Class[] interfaces = cls.getInterfaces();
            if(interfaces.length == 0) continue;
            Class interfaceCls = interfaces[0];
            String simpleName = interfaceCls.getSimpleName();
            String compilePackage = cls.getName();
            beans.put(classSimpleName2BeanName(simpleName), compilePackage);
        }
        System.out.println("Bean统一管理:");
        System.out.println(JSONObject.toJSONString(beans));

        //-- 依赖注入
        TreeMap<String, Object> dependencyInjection = new TreeMap();
        for (Map.Entry<String, String> entry : beans.entrySet()) {
            Class cls = Class.forName(entry.getValue());
            Object sourceObj = cls.newInstance();
            for (Field field : cls.getDeclaredFields()) {
                field.setAccessible(true);
                if(!field.isAnnotationPresent(Autowired.class)) continue;
                Class typeCls = field.getType();
                String simpleName = typeCls.getSimpleName();
                simpleName = classSimpleName2BeanName(simpleName);
                if(!beans.containsKey(simpleName)) throw new Exception(String.format("Bean not found for %s!", simpleName));
                Object fieldResult = Class.forName(beans.get(simpleName)).newInstance();
                field.set(sourceObj, fieldResult);
                dependencyInjection.put(field.getName(), field.get(sourceObj));
            }
        }
        System.out.println("依赖注入:");
        System.out.println(dependencyInjection);
    }
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Service{

}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Mapper{

}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Autowired{

}

interface ShowTimeService{
    void run();
}
@Service
class ShowTimeServiceImpl implements ShowTimeService{

    @Autowired
    private ShowTimeDao showTimeDao;

    @Override
    public void run() {
        System.out.println("== run ==");
        System.out.println(this.showTimeDao);
    }

}

interface ShowTimeDao{

}
@Mapper
class ShowTimeDaoImpl implements ShowTimeDao{

}

执行结果

Bean统一管理:
{"showTimeDao":"com.myrcib.middleware.core.idworker.ShowTimeDaoImpl","showTimeService":"com.myrcib.middleware.core.idworker.ShowTimeServiceImpl"}
依赖注入:
{showTimeDao=com.myrcib.middleware.core.idworker.ShowTimeDaoImpl@17f6480}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值