注解相关知识整理

注解(Annotation)

public interface Annotation
所有 annotation 类型都要扩展的公共接口。注意,手动扩展该公共接口的接口不 定义 annotation 类型。还要注意此接口本身不定义 annotation 类型。

引入注解的优点

1、能够灵活的使用框架提供的注解,最后能够读懂框架的源码
2、能够配合反射将代码书写的更加灵活,更加易于维护易于扩展(通过修改配置文件,properties配置、XML配置、注解配置【java配置】,不动源代码)
3、极大程度的简化了代码,提高了代码的可读性

注解的概述

Java提供了一种源程序中元素任何信息或者元数据关联的一种方法或者渠道

源程序中元素:Java文件、构造方法、成员变量、成员方法…
任何信息:就是一些配置数据,也可以理解为是注释
元数据:元数据可以描述代码间关系或者代码与其它资源的关系,比如说在web.xml中需要将请求路径跟处理请求的servlet对应起来,比如说在Hibernate中需要.hbm文件来描述实体类与数据表之间的映射关系。元数据可以提供某些信息来协助我们程序的运行。
关联:元素和注释绑定成为注解

简单一点的来说:就是相当于一些类似于注释的信息和源程序中的元素绑定的一种技术,这种技术叫做注解

这种手段可以通过反射来获取元素对象,在获取到这个元素上面绑定的注解,通过绑定的注解获取里面的配置信息,从而能够达到在程序运行阶段通过这些信息动态的改变程序的运行逻辑

JDK中常见的注解有:
@Override:表示注解修饰的方法必须满足重写规则
@Deprecated:表示成员已经过时了,编译器可以在程序运行的时候获取到该注解
@SupressWarnings:表示忽略编译器的警告
@FunctionlInterface:表示该接口是一个函数式接口,并且可以作为Lanbda表达式参数传入

注解的分类:
按照运行的机制分类:
源码注解:注解只在源码中生效,当编译生成字节码文件时,注解就不存在了
编译时注解:注解在源码和编译时期有效,当程序运行时,注解不存在
运行时注解:注解在源码和编译有效,同时在程序的执行过程中也存在(反射获取运行时注解,从而获取到注解的配置信息,动态的改变程序的逻辑)
元注解:对注解进行的注解的注解
按照来源分类:
1、JDK中自带的注解: @Override,@Deprecated
2、第三方注解: @Table,@Column,@Component,@Service,@Bean
3、自定义注解: 自己定义注解自己使用在自己的代码中
4、元注解: 对注解进行注解的注解

举例代码如下


// 这是一个注解测试类
public class AnnotationDemo01 {
	// desc="主方法", method="main", arguments=args
	public static void main(String[] args) {
	}
}

class Fu {
	@SuppressWarnings("unused")
	public void method() {
//		@SuppressWarnings("unused")
		int a;
	}
}

@Deprecated
class Zi extends Fu{
	@Override
	@Deprecated
	public void method() {
		super.method();
	}
}

@FunctionalInterface
interface Inter{
	void show();
//	void method();
}

注解的使用

格式一: @注解的名称(属性名1=属性值1,属性名2=属性值2,属性名3=属性值3,…属性名n=属性值n)
注意: 如果有两个以上的属性,有多少个属性就必须给多少个属性赋值
格式二: 适用于注解只有一个成员
@注解的名称(属性值)
格式三: 适用于成员是数组的情况
@注解的名称({属性值1,属性值2,属性值3,…属性值n})
格式四: 使用标记注解
@注解的名称

举例代码如下

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class AnnotationDemo01 {
	public static void main(String[] args) {
		
	}
}

// 希望当前注解MyAnnotation可以用在类,接口,构造方法,方法上
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD })
// 希望当前注解MyAnnotation可以在运行时有效
@Retention(RetentionPolicy.RUNTIME)
// 希望当前注解可以在子类中生效
@Inherited
// 希望当前注解可以在API文档中生成
@Documented
@interface MyAnnotation {
	String desc();

	int val() default 0;

	String[] hobbys();
}

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@interface Column {
	String value();
}

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@interface PrimaryKey {
	boolean isAutoIncrease();
	String name();
}

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

@Fu
class Person {
	
}

@MyAnnotation(desc = "Student Class", val = 18, hobbys = { "篮球", "足球", "乒乓球" })
class Student extends Person {

	@Column("sid")
	@PrimaryKey(isAutoIncrease = true, name = "sid")
	private String id;
	@Column("sname")
	private String name;
	@Column("sage")
	private int age;

	public Student() {
		super();
	}

	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	@Override
	@MyAnnotation(desc = "1111", val = 18, hobbys = { "篮球", "足球", "乒乓球" })
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

}

注解的语法

1、使用@interface关键字来定义注解
2、成员是以没有参数和没有异常的方式声明
3、可以使用default为成员指定一个默认值
4、注解的成员类型:基本类型、Class、注解、枚举、一维数组
5、如果注解只有一个成员,则成员的命名必须叫做value,再使用这个注解的时候可以不写等号
6、没有任何成员的注解叫做标识注解,它本身的存在与否就是一种意义,就类似于注释

举例代码如下

public class AnnotationDemo02 {

}

@interface MyAnnotation{
	
	String name() default "zhangsan";
	int age() default 18;
//	String address() throws Exception;
	
//	ArrayList<String> list();
	Class c();
	Override override();
	ElementType eType();
	String[] list();
}


interface Inter{
	String address() throws Exception;
}

@interface MyAnno{
//	String id();
	String value();
}

元注解

@target:标识它所标识的注解能够作用在什么元素上
如果一个注解的@target上面的注解范围显示了ANNOTATION_TYPE,那么标识该注解是元注解
@Retention:标识它所标识的注解的生命周期
RetentionPolicy.SOURCE:标识该注解只在源码中存在
RetentionPolicy.CLASS:表示该注解只在源码和编译时存在
RetentionPolicy.RUNTIME:表示该注解在源码和编译时以及在运行时存在

@Inherited:表示该注解可以被继承
@Document:表示该注解在生成API文档时可以显示在文档上

使用反射解析注解

通过反射解析注解
通过反射可以获取字节码文件对象,通过字节码文件对象就能够获取所有的有关该类的元素,然后注解也是类元素的一种,所以可以获取到对应的注解,但是这个注解必须运行时注解

<A extends Annotation> A	getAnnotation(Class<A> annotationClass)

如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。

Annotation[]	getAnnotations() 

返回此元素上存在的所有注释。

isAnnotation() 

如果此 Class 对象表示一个注释类型则返回 true。

boolean	isAnnotationPresent(Class<? extends Annotation> annotationClass) 

如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。
举例代码如下

public class AnnotationDemo01 {
	public static void main(String[] args) throws ClassNotFoundException {
		Class<?> c = Class.forName("com.sxt.annotationdemo02.Student");
		
		// 判断该字节码文件中是否存在@MyAnnotation注解
		boolean isAnnotationPresent = c.isAnnotationPresent(MyAnnotation.class);
		
		if (isAnnotationPresent) {
			MyAnnotation annotation = c.getDeclaredAnnotation(MyAnnotation.class);
			System.out.println(annotation.value());
		}
		
		System.out.println("----------成员方法-------------");
		Method[] methods = c.getMethods();
		for (Method m : methods) {
			boolean present = m.isAnnotationPresent(MyAnnotation.class);
			if (present) {
				MyAnnotation declaredAnnotation = m.getDeclaredAnnotation(MyAnnotation.class);
				System.out.println(declaredAnnotation.value());
			}
		}
		
		System.out.println("---------构造方法--------------");
		Constructor<?>[] constructors = c.getConstructors();
		for (Constructor<?> constructor : constructors) {
			boolean present = constructor.isAnnotationPresent(MyAnnotation.class);
			if (present) {
				MyAnnotation declaredAnnotation = constructor.getDeclaredAnnotation(MyAnnotation.class);
				System.out.println(declaredAnnotation.value());
			}
		}
		
		System.out.println("---------成员变量--------------");
		Field[] fields = c.getDeclaredFields();
		for (Field field : fields) {
			boolean present = field.isAnnotationPresent(MyAnnotation.class);
			if (present) {
				MyAnnotation declaredAnnotation = field.getDeclaredAnnotation(MyAnnotation.class);
				System.out.println(declaredAnnotation.value());
			}
		}
		
		System.out.println("---------toString--------------");
		System.out.println("获取tostring方法");
		boolean annotationPresent = c.isAnnotationPresent(Override.class);
		if (annotationPresent) {
			MyAnnotation annotation = c.getDeclaredAnnotation(MyAnnotation.class);
			System.out.println(annotation.value());
		}
		
		System.out.println("---------获取PrimaryStudent上的注解--------------");
		Class<?> c2 = Class.forName("com.sxt.annotationdemo02.PrimaryStudent");
		boolean result = c2.isAnnotationPresent(MyAnnotation.class);
		System.out.println(result);
	}
}

@MyAnnotation("T_STU")
class Student{
	@MyAnnotation("NAME")
	private String name;
	@MyAnnotation("AGE")
	private int age;
	
	@MyAnnotation("STU")
	public Student() {
		super();
	}

	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	@MyAnnotation("getName")
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	@MyAnnotation("setAge")
	public void setAge(int age) {
		this.age = age;
	}

	
	@MyAnnotation("I'm a toString method")
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	
}

class PrimaryStudent extends Student{
	
}

@Inherited//注解可以被继承
@Documented//生成API文档的时候注解可以显示在文档里面
@Retention(RetentionPolicy.RUNTIME)//标识注解的声明周期
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.CONSTRUCTOR})//标识注解作用的元素
@interface MyAnnotation{
	String value();
}


案例:使用注解编写一个工具类实现Map和bean之间的转换

将任意的Map集合转换成对应Bean
Map-------->JavaBean

在这里插入图片描述
学生类源码

package com.zhouym.day_0509;

public class Student {
	@Column("name")
	private String name;
	@Column("age")
	private Integer age;
	public Student() {
		super();
	}
	public Student(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public Integer getAge() {
		return age;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	

}

老师类源码

package com.zhouym.day_0509;

public class Teacher {
	@Column("name")
	private String name;
	@Column("age")
	private Integer age;
	@Column("teachYear")
	private Integer teachYear;
	public Teacher() {
		super();
	}
	public Teacher(String name, Integer age, Integer teachYear) {
		super();
		this.name = name;
		this.age = age;
		this.teachYear = teachYear;
	}
	public String getName() {
		return name;
	}
	public Integer getAge() {
		return age;
	}
	public Integer getTeachYear() {
		return teachYear;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public void setTeachYear(Integer teachYear) {
		this.teachYear = teachYear;
	}
	@Override
	public String toString() {
		return "Teacher [name=" + name + ", age=" + age + ", teachYear=" + teachYear + "]";
	}
	
}

注解类源码

package com.zhouym.day_0509;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Inherited//表示可以被子类继承
@Retention(RetentionPolicy.RUNTIME)//表示注解可以在源码和编译,以及在运行时存在
//自定义注解
public @interface Column {
	String value();
}

map转javabean类源码

package com.zhouym.day_0509;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;

public class MapToBeanUtils {
	
	private MapToBeanUtils() {}
	
	public static <T>T  mapToBeanUtils(Map<String, Object> map,Class<T> clazz) throws Exception{
		//通过反射创建一个新的class文件对象
		T t = clazz.newInstance();
		//通过反射获取每个成员变量对象
		Field[] fields = clazz.getDeclaredFields();
		//遍历所有的成员变量对象
		for (Field field : fields) {
			//判断元素中是否存在指定类型的注释
			if (field.isAnnotationPresent(Column.class)) {
				//获取指定类型的注释
				Column column = field.getAnnotation(Column.class);
				//判断注释是否为空
				if (column != null) {
					//获取注释对应的键
					String key = column.value();
					//通过key在map中获取对应的value
					Object value = map.get(key);
					//判断值是否为空
					if (value != null) {
						//获取成员变量名
						String fieldName = field.getName();
						//因为是框架访问,它是不能采用暴力访问,使用set方法访问
						//获取set方法,方法名是set+成员变量名(第一个字母要大写)
						String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1);
						//通过反射获取成员方法对象并且调用
						Method setMethod = clazz.getMethod(setMethodName, value.getClass());/*参数类型,框架也是不知道的,通过获取的value来确定,Object的getClass方法
*/
						setMethod.invoke(t, value);
					}
				}
			}
			
		}
	
		return t;
		
	}
}

测试类源码

package com.zhouym.day_0509;

import java.util.HashMap;

public class Test {

	public static void main(String[] args) throws Exception {
		
		HashMap<String, Object> hashMap = new HashMap<String, Object>();
		hashMap.put("name", "小明");
		hashMap.put("age",25);
		
		
		HashMap<String, Object> hashMap2 = new HashMap<String, Object>();
		hashMap2.put("name", "李老师");
		hashMap2.put("age", 35);
		hashMap2.put("teachYear", 10);
		
		Student s = MapToBeanUtils.mapToBeanUtils(hashMap, Student.class);
		Teacher t = MapToBeanUtils.mapToBeanUtils(hashMap2, Teacher.class);
		System.out.println(s);
		System.out.println(t);
	}

}

运行结果:
在这里插入图片描述
将Bean转换成对应的Map集合

学生类,老师类,注解类内容不变
javabean转map源码

package com.zhouym.day_0509;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class MapToBeanUtils {
	
	private MapToBeanUtils() {}
	
	public static <T>T  mapToBeanUtils(Map<String, Object> map,Class<T> clazz) throws Exception{
		//通过反射创建一个新的class文件对象
		T t = clazz.newInstance();
		//通过反射获取每个成员变量对象
		Field[] fields = clazz.getDeclaredFields();
		//遍历所有的成员变量对象
		for (Field field : fields) {
			//判断元素中是否存在指定类型的注释
			if (field.isAnnotationPresent(Column.class)) {
				//获取指定类型的注释
				Column column = field.getAnnotation(Column.class);
				//判断注释是否为空
				if (column != null) {
					//获取注释对应的键
					String key = column.value();
					//通过key在map中获取对应的value
					Object value = map.get(key);
					//判断值是否为空
					if (value != null) {
						//获取成员变量名
						String fieldName = field.getName();
						//因为是框架访问,它是不能采用暴力访问,使用set方法访问
						//获取set方法,方法名是set+成员变量名(第一个字母要大写)
						String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1);
						//通过反射获取成员方法对象并且调用
						Method setMethod = clazz.getMethod(setMethodName, value.getClass());/*参数类型,框架也是不知道的,通过获取的value来确定,Object的getClass方法
*/
						setMethod.invoke(t, value);
					}
				}
			}
			
		}
	
		return t;
		
	}
	
	/*将bean转换成对应的map集合*/
	
	public static <T> Map<String,Object> beanToMap(T t) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		
		//创建一个容器来装载Bean的数据
		Map<String, Object> map = new HashMap<String, Object>();
		//获取bean对象中的字节码文件对象
		Class<?> c = t.getClass();
		//获取所有的成员变量对象
		Field[] fields = c.getDeclaredFields();
		for (Field field : fields) {
			if (field.isAnnotationPresent(Column.class)) {
				Column column = field.getAnnotation(Column.class);
				if (column != null) {
					String key = column.value();
					//通过注解绑定的属性名,区bean中获取值
					String fieldName = field.getName();
					//获取get方法
					String getMenthod = "get"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
					Method method = c.getMethod(getMenthod);
					Object getVolue = method.invoke(t);
					map.put(key, getVolue);
					
				}
			}
		}
		return map;

		
	}
}

测试类源码

package com.zhouym.day_0509;

import java.util.HashMap;
import java.util.Map;

public class Test {

	public static void main(String[] args) throws Exception {
		
		HashMap<String, Object> hashMap = new HashMap<String, Object>();
		hashMap.put("name", "小明");
		hashMap.put("age",25);
		
		
		HashMap<String, Object> hashMap2 = new HashMap<String, Object>();
		hashMap2.put("name", "李老师");
		hashMap2.put("age", 35);
		hashMap2.put("teachYear", 10);
		
		Student s = MapToBeanUtils.mapToBeanUtils(hashMap, Student.class);
		Teacher t = MapToBeanUtils.mapToBeanUtils(hashMap2, Teacher.class);
		System.out.println(s);
		System.out.println(t);
		
		
		System.out.println("==========================");
		/*bean转换map集合*/
		
		Student ss = new Student("小汪",40);
		Map<String, Object> btm = MapToBeanUtils.beanToMap(ss);
		System.out.println(btm);
		
		Teacher tt = new Teacher("王老师", 45, 15);
		Map<String, Object> btp = MapToBeanUtils.beanToMap(tt);
		System.out.println(btp);
		
	}

}

需求:
已知一个包下分别有Student、Teacher、Employee三个Java源文件,
请据此实现以下功能:
(1)、自定义一个名为Component的注解,
要求该注解只能用于类且代码运行时该注解依然有效
(2)、为Student和Teacher类添加Component注解
(3)、在Application类中定义静态代码块,
该代码块可自动将标记有Component注解修饰的类动态创建对象并放在Map集合中;
(4)然后定义一个名为getBean的static方法,
要求传入参数为Class类实例时返回该Class类对应类的对象,
如果没有Class类对应类的对象,
则引发自定义异常NoSuchBeanDefinitionException异常
并提示 No qualifying bean of type

代码实现

在这里插入图片描述
学生类源码

package com.zhouym.day_0509Demo;

@Component
public class Student {	
	private String name;
	
	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}
	

}

老师类源码

package com.zhouym.day_0509Demo;
@Component
public class Teacher {
	
	private String name;
	
	@Override
	public String toString() {
		return "Teacher [name=" + name + "]";
	}
	
}

员工类源码

package com.zhouym.day_0509Demo;
@Component
public class Employee {
	
	private String name;
	
	@Override
	public String toString() {
		return "Teacher [name=" + name + "]";
	}
	
}

注解类源码

package com.zhouym.day_0509Demo;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Documented
//自定义注解
public @interface Component {
}

自定义异常类源码

package com.zhouym.day_0509Demo;

class NoSuchBeanDefinitionException extends RuntimeException{

	private static final long serialVersionUID = -8192643702060017574L;
	
	public NoSuchBeanDefinitionException() {}
	
	public NoSuchBeanDefinitionException(String message) {
		super(message);
	}
}

Application类源码

package com.zhouym.day_0509Demo;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class Application {
	public static Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();
	// 将带有Component的注解的类动态创建并且保存到Map中【控制反转】
	//在Application类中定义静态代码块
	static {
		//创建属性集对象
		Properties p = new Properties();
		try {
			//将文件对象中的数据加载到属性集对象
			p.load(new FileReader("config/info.properties"));
			//获取指定参数的属性集对应的属性
			String packageName = p.getProperty("packageName");
			//创建一个新的文件对象,文件路径:src/包名,com.zhouym.day_0509Demo中的.用/替换
			File srcFile = new File("src/"+packageName.replace(".", "/"));
			//获取文件对象中的所有元素
			File[] listFiles = srcFile.listFiles();
			for (File file : listFiles) {
				//获取文件名
				String fileName = file.getName();
				//获取类名,包名+类名(去掉后缀名)
				String className = packageName + "." + fileName.substring(0, fileName.indexOf("."));
				//通过反射获取类名字节码文件对象
				Class<?> clazz = Class.forName(className);
				//获取所有的注释信息
				Annotation[] das = clazz.getDeclaredAnnotations();
				for (Annotation annotation : das) {
					if (annotation instanceof Component) {
						Object obj = clazz.newInstance();
						map.put(clazz, obj);
					}
				}
				
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
	public static <T> T getBean(Class<T> clazz) throws NoSuchBeanDefinitionException{
		T t = (T)map.get(clazz);
		if (t == null) {
			throw new NoSuchBeanDefinitionException("No qualifying bean of type ");
		}

		return t;
		
		
		
		
	}
}

测试类源码

package com.zhouym.day_0509Demo;

public class Test {

	public static void main(String[] args) {
		
		//获取学生对象
		com.zhouym.day_0509Demo.Student s = Application.getBean(com.zhouym.day_0509Demo.Student.class);
		System.out.println(s);
		
		//获取老师对象
		com.zhouym.day_0509Demo.Teacher t = Application.getBean(com.zhouym.day_0509Demo.Teacher.class);
		System.out.println(t);
		
		//获取员工对象
		com.zhouym.day_0509Demo.Employee e = Application.getBean(com.zhouym.day_0509Demo.Employee.class);
		System.out.println(e);
	}

}

输出结果:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值