从没有人将代理模式分析得如此透彻

}

}

运行结果如下图所示。

file

但是,上面的场景有个弊端,就是自己的父亲只会帮自己的子女去物色对象,别人家的孩子是不会管的。但社会上这项业务发展成了一个产业,出现了媒婆、婚介所等,还有各种各样的定制套餐。如果还使用静态代理成本就太高了,需要一个更加通用的解决方案,满足任何单身人士找对象的需求。这就由静态代理升级到了动态代理。采用动态代理基本上只要是人(IPerson)就可以提供相亲服务。动态代理的底层实现一般不用我们亲自去实现,已经有很多现成的API。在Java生态中,目前普遍使用的是JDK自带的代理和CGLib提供的类库。首先基于JDK的动态代理支持来升级一下代码。

首先创建媒婆(婚介所)类JdkMeipo。

public class JdkMeipo implements InvocationHandler {

private IPerson target;

public IPerson getInstance(IPerson target){

this.target = target;

Class<?> clazz = target.getClass();

return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);

}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

before();

Object result = method.invoke(this.target,args);

after();

return result;

}

private void after() {

System.out.println(“双方同意,开始交往”);

}

private void before() {

System.out.println(“我是媒婆,已经收集到你的需求,开始物色”);

}

}

然后创建一个类ZhaoLiu。

public class ZhaoLiu implements IPerson {

public void findLove() {

System.out.println(“符合赵六的要求”);

}

public void buyInsure() {

}

}

最后客户端测试代码如下。

public static void main(String[] args) {

JdkMeipo jdkMeipo = new JdkMeipo();

IPerson zhaoliu = jdkMeipo.getInstance(new ZhaoLiu());

zhaoliu.findLove();

}

运行结果如下图所示。

file

2 三层架构中的静态代理


小伙伴们可能会觉得还是不知道如何将代理模式应用到业务场景中,我们来看一个实际的业务场景。在分布式业务场景中,通常会对数据库进行分库分表,分库分表之后使用Java操作时就可能需要配置多个数据源,我们通过设置数据源路由来动态切换数据源。首先创建Order订单类。

public class Order {

private Object orderInfo;

private Long createTime;

private String id;

public Object getOrderInfo() {

return orderInfo;

}

public void setOrderInfo(Object orderInfo) {

this.orderInfo = orderInfo;

}

public Long getCreateTime() {

return createTime;

}

public void setCreateTime(Long createTime) {

this.createTime = createTime;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

}

创建OrderDao持久层操作类。

public class OrderDao {

public int insert(Order order){

System.out.println(“OrderDao创建Order成功!”);

return 1;

}

}

创建IOrderService接口。

public interface IOrderService {

int createOrder(Order order);

}

创建OrderService实现类。

public class OrderService implements IOrderService {

private OrderDao orderDao;

public OrderService(){

//如果使用Spring,则应该是自动注入的

//为了使用方便,我们在构造方法中直接将orderDao初始化

orderDao = new OrderDao();

}

@Override

public int createOrder(Order order) {

System.out.println(“OrderService调用orderDao创建订单”);

return orderDao.insert(order);

}

}

然后使用静态代理,主要完成的功能是:根据订单创建时间自动按年进行分库。根据开闭原则,我们修改原来写好的代码逻辑,通过代理对象来完成。创建数据源路由对象,使用ThreadLocal的单例实现DynamicDataSourceEntry类。

//动态切换数据源

public class DynamicDataSourceEntry {

//默认数据源

public final static String DEFAULT_SOURCE = null;

private final static ThreadLocal local = new ThreadLocal();

private DynamicDataSourceEntry(){}

//清空数据源

public static void clear() {

local.remove();

}

//获取当前正在使用的数据源名字

public static String get() {

return local.get();

}

//还原当前切换的数据源

public static void restore() {

local.set(DEFAULT_SOURCE);

}

//设置已知名字的数据源

public static void set(String source) {

local.set(source);

}

//根据年份动态设置数据源

public static void set(int year) {

local.set(“DB_” + year);

}

}

创建切换数据源的代理类OrderServiceSaticProxy。

public class OrderServiceStaticProxy implements IOrderService {

private SimpleDateFormat yearFormat = new SimpleDateFormat(“yyyy”);

private IOrderService orderService;

public OrderServiceStaticProxy(IOrderService orderService){

this.orderService = orderService;

}

public int createOrder(Order order) {

before();

Long time = order.getCreateTime();

Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));

System.out.println(“静态代理类自动分配到【DB_” + dbRouter + “】数据源处理数据”);

DynamicDataSourceEntry.set(dbRouter);

orderService.createOrder(order);

after();

return 0;

}

private void before(){

System.out.println(“Proxy before method.”);

}

private void after(){

System.out.println(“Proxy after method.”);

}

}

来看客户端测试代码。

public static void main(String[] args) {

try {

Order order = new Order();

SimpleDateFormat sdf = new SimpleDateFormat(“yyyy/MM/dd”);

Date date = sdf.parse(“2017/02/01”);

order.setCreateTime(date.getTime());

IOrderService orderService = new OrderServiceStaticProxy(new OrderService());

orderService.createOrder(order);

}catch (Exception e){

e.printStackTrace();;

}

}

运行结果如下图所示。

file

由上图可知,结果符合预期。再来回顾一下类图,看是否与我们最先画的一致,如下图所示。

file

动态代理和静态代理的基本思路是一致的,只不过动态代理的功能更强大,随着业务的扩展,适应性更强。

3 使用动态代理实现无感知切换数据源


在理解了上面的案例后,再来看数据源动态路由业务,帮助小伙伴们加深对动态代理的印象。创建动态代理的类OrderServiceDynamicProxy,代码如下。

public class OrderServiceDynamicProxy implements InvocationHandler {

private SimpleDateFormat yearFormat = new SimpleDateFormat(“yyyy”);

private Object target;

public Object getInstance(Object target){

this.target = target;

Class<?> clazz = target.getClass();

return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);

}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

before(args[0]);

Object object = method.invoke(target,args);

after();

return object;

}

private void before(Object target){

try {

System.out.println(“Proxy before method.”);

Long time = (Long) target.getClass().getMethod(“getCreateTime”).invoke(target);

Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));

System.out.println(“静态代理类自动分配到【DB_” + dbRouter + “】数据源处理数据”);

DynamicDataSourceEntry.set(dbRouter);

}catch (Exception e){

e.printStackTrace();

}

}

private void after(){

System.out.println(“Proxy after method.”);

}

}

编写客户端测试代码如下。

public static void main(String[] args) {

try {

Order order = new Order();

SimpleDateFormat sdf = new SimpleDateFormat(“yyyy/MM/dd”);

Date date = sdf.parse(“2018/02/01”);

order.setCreateTime(date.getTime());

IOrderService orderService = (IOrderService)new OrderServiceDynamicProxy().

getInstance(new OrderService());

orderService.createOrder(order);

}catch (Exception e){

e.printStackTrace();

}

}

由上面代码可以看出,依然能够达到相同的运行效果。但是,使用动态代理实现之后,不仅能实现Order的数据源动态路由,还可以实现其他任何类的数据源路由。当然,有一个比较重要的约定,必须实现getCreateTime()方法,因为路由规则是根据时间来运算的。可以通过接口规范达到约束的目的,在此不再举例。

4 手写JDK动态代理核心原理


不仅知其然,还得知其所以然。既然JDK动态代理的功能如此强大,那么它是如何实现的呢?现在来探究一下原理,并模仿JDK动态代理手写一个属于自己的动态代理。

我们都知道JDK动态代理采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的。JDK动态代理生成对象的步骤如下。

(1)获取被代理对象的引用,并且获取它的所有接口,反射获取。

(2)JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口。

(3)动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码调用(在代码中体现)。

(4)编译新生成的Java代码.class文件。

(5)重新加载到JVM中运行。

以上过程就叫作字节码重组。JDK中有一个规范,在ClassPath下只要是$开头的.class文件,一般都是自动生成的。那么有没有办法看到代替后的对象的“真容”呢?做一个这样的测试,将内存中的对象字节码通过文件流输出到一个新的.class文件,然后使用反编译工具查看源码。

public static void main(String[] args) {

try {

IPerson obj = (IPerson)new JdkMeipo().getInstance(new Zhangsan());

obj.findLove();

//通过反编译工具查看源代码

byte [] bytes = ProxyGenerator.generateProxyClass(“$Proxy0”,new Class[]{IPerson.class});

FileOutputStream os = new FileOutputStream(“E://$Proxy0.class”);

os.write(bytes);

os.close();

} catch (Exception e) {

e.printStackTrace();

}

}

运行以上代码,可以在E盘找到一个 P r o x y 0. c l a s s 文 件 。 使 用 J a d 反 编 译 , 得 到 Proxy0.class文件。使用Jad反编译,得到 Proxy0.class文件。使用Jad反编译,得到Proxy0.jad文件,打开文件看到如下内容。

import com.tom.pattern.proxy.dynamicproxy.jdkproxy.IPerson;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements IPerson {

private static Method m1;

private static Method m3;

private static Method m2;

private static Method m4;

private static Method m0;

public $Proxy0(InvocationHandler var1) throws {

super(var1);

}

public final boolean equals(Object var1) throws {

try {

return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();

} catch (RuntimeException | Error var3) {

throw var3;

} catch (Throwable var4) {

throw new UndeclaredThrowableException(var4);

}

}

public final void findLove() throws {

try {

super.h.invoke(this, m3, (Object[])null);

} catch (RuntimeException | Error var2) {

throw var2;

} catch (Throwable var3) {

throw new UndeclaredThrowableException(var3);

}

}

public final String toString() throws {

try {

return (String)super.h.invoke(this, m2, (Object[])null);

} catch (RuntimeException | Error var2) {

throw var2;

} catch (Throwable var3) {

throw new UndeclaredThrowableException(var3);

}

}

public final void buyInsure() throws {

try {

super.h.invoke(this, m4, (Object[])null);

} catch (RuntimeException | Error var2) {

throw var2;

} catch (Throwable var3) {

throw new UndeclaredThrowableException(var3);

}

}

public final int hashCode() throws {

try {

return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();

} catch (RuntimeException | Error var2) {

throw var2;

} catch (Throwable var3) {

throw new UndeclaredThrowableException(var3);

}

}

static {

try {

m1 = Class.forName(“java.lang.Object”).getMethod(“equals”,

new Class[]{Class.forName(“java.lang.Object”)});

m3 = Class.forName(“com.tom.pattern.proxy.dynamicproxy.jdkproxy.IPerson”)

.getMethod(“findLove”, new Class[0]);

m2 = Class.forName(“java.lang.Object”).getMethod(“toString”, new Class[0]);

m4 = Class.forName(“com.tom.pattern.proxy.dynamicproxy.jdkproxy.IPerson”)

.getMethod(“buyInsure”, new Class[0]);

m0 = Class.forName(“java.lang.Object”).getMethod(“hashCode”, new Class[0]);

} catch (NoSuchMethodException var2) {

throw new NoSuchMethodError(var2.getMessage());

} catch (ClassNotFoundException var3) {

throw new NoClassDefFoundError(var3.getMessage());

}

}

}

我们发现,$Proxy0继承了Proxy类,同时实现了Person接口,而且重写了findLove()等方法。在静态代码块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,重写的方法用反射调用目标对象的方法。小伙伴们此时一定会好奇:这些代码是从哪里来的?其实是JDK自动生成的。现在我们不依赖JDK,自己来动态生成源码、动态完成编译,然后替代目标对象并执行。

创建GPInvocationHandler接口。

public interface GPInvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable;

}

创建GPProxy类。

/**

  • 用来生成源码的工具类

  • Created by Tom.

*/

public class GPProxy {

public static final String ln = “\r\n”;

public static Object newProxyInstance(GPClassLoader classLoader, Class<?> [] interfaces,

GPInvocationHandler h){

try {

//1.动态生成源码.java文件

String src = generateSrc(interfaces);

//2.Java文件输出磁盘

String filePath = GPProxy.class.getResource(“”).getPath();

File f = new File(filePath + “$Proxy0.java”);

FileWriter fw = new FileWriter(f);

fw.write(src);

fw.flush();

fw.close();

//3.把生成的.java文件编译成.class文件

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

StandardJavaFileManager manage = compiler.getStandardFileManager(null,null,null);

Iterable iterable = manage.getJavaFileObjects(f);

JavaCompiler.CompilationTask task = compiler.getTask(null,manage,null,null,null,iterable);

task.call();

manage.close();

//4.编译生成的.class文件加载到JVM中

Class proxyClass = classLoader.findClass(“$Proxy0”);

Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);

f.delete();

//5.返回字节码重组以后的新的代理对象

return c.newInstance(h);

}catch (Exception e){

e.printStackTrace();

}

return null;

}

private static String generateSrc(Class<?>[] interfaces){

StringBuffer sb = new StringBuffer();

sb.append(GPProxy.class.getPackage() + “;” + ln);

sb.append("import " + interfaces[0].getName() + “;” + ln);

sb.append(“import java.lang.reflect.*;” + ln);

sb.append("public class $Proxy0 implements " + interfaces[0].getName() + “{” + ln);

sb.append(“GPInvocationHandler h;” + ln);

sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);

sb.append(“this.h = h;”);

sb.append(“}” + ln);

for (Method m : interfaces[0].getMethods()){

Class<?>[] params = m.getParameterTypes();

StringBuffer paramNames = new StringBuffer();

StringBuffer paramValues = new StringBuffer();

StringBuffer paramClasses = new StringBuffer();

for (int i = 0; i < params.length; i++) {

Class clazz = params[i];

String type = clazz.getName();

String paramName = toLowerFirstCase(clazz.getSimpleName());

paramNames.append(type + " " + paramName);

paramValues.append(paramName);

paramClasses.append(clazz.getName() + “.class”);

if(i > 0 && i < params.length-1){

paramNames.append(“,”);

paramClasses.append(“,”);

paramValues.append(“,”);

}

}

sb.append("public " + m.getReturnType().getName() + " " + m.getName() + “(” + paramNames.toString() + “) {” + ln);

sb.append(“try{” + ln);

sb.append(“Method m = " + interfaces[0].getName() + “.class. getMethod(”” + m.getName() + “”,new Class[]{" + paramClasses.toString() + “});” + ln);

sb.append((hasReturnValue(m.getReturnType()) ? "return " : “”) + getCaseCode(“this.h.invoke(this,m,new Object[]{” + paramValues + “})”,m.getReturnType()) + “;” + ln);

sb.append(“}catch(Error _ex) { }”);

sb.append(“catch(Throwable e){” + ln);

sb.append(“throw new UndeclaredThrowableException(e);” + ln);

sb.append(“}”);

sb.append(getReturnEmptyCode(m.getReturnType()));

sb.append(“}”);

}

sb.append(“}” + ln);

return sb.toString();

}

private static Map<Class,Class> mappings = new HashMap<Class, Class>();

static {

mappings.put(int.class,Integer.class);

}

private static String getReturnEmptyCode(Class<?> returnClass){

if(mappings.containsKey(returnClass)){

return “return 0;”;

}else if(returnClass == void.class){

return “”;

}else {

return “return null;”;

}

}

private static String getCaseCode(String code,Class<?> returnClass){

if(mappings.containsKey(returnClass)){

return “((” + mappings.get(returnClass).getName() + “)” + code + “).” + returnClass.getSimpleName() + “Value()”;

}

return code;

}

private static boolean hasReturnValue(Class<?> clazz){

return clazz != void.class;

}

private static String toLowerFirstCase(String src){

char [] chars = src.toCharArray();

chars[0] += 32;

return String.valueOf(chars);

}

}

创建GPClassLoader类。

public class GPClassLoader extends ClassLoader {

private File classPathFile;

public GPClassLoader(){

String classPath = GPClassLoader.class.getResource(“”).getPath();

this.classPathFile = new File(classPath);

}

@Override

protected Class<?> findClass(String name) throws ClassNotFoundException {

String className = GPClassLoader.class.getPackage().getName() + “.” + name;

if(classPathFile != null){

File classFile = new File(classPathFile,name.replaceAll(“\.”,“/”) + “.class”);

if(classFile.exists()){

FileInputStream in = null;

ByteArrayOutputStream out = null;

try{

in = new FileInputStream(classFile);

out = new ByteArrayOutputStream();

byte [] buff = new byte[1024];

int len;

while ((len = in.read(buff)) != -1){

out.write(buff,0,len);

}

return defineClass(className,out.toByteArray(),0,out.size());

}catch (Exception e){

e.printStackTrace();

}

}

}

return null;

}

}

创建GPMeipo类。

public class GpMeipo implements GPInvocationHandler {

private IPerson target;

public IPerson getInstance(IPerson target){

this.target = target;

Class<?> clazz = target.getClass();

return (IPerson) GPProxy.newProxyInstance(new GPClassLoader(),clazz.getInterfaces(),this);

}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

before();

Object result = method.invoke(this.target,args);

after();

return result;

}

private void after() {

System.out.println(“双方同意,开始交往”);

}

private void before() {

System.out.println(“我是媒婆,已经收集到你的需求,开始物色”);

}

}

客户端测试代码如下。

public static void main(String[] args) {

GpMeipo gpMeipo = new GpMeipo();

IPerson zhangsan = gpMeipo.getInstance(new Zhangsan());

zhangsan.findLove();

}

至此,手写JDK动态代理就完成了。小伙伴们是不是又多了一个面试用的“撒手锏”呢?

5 CGLib动态代理API原理分析


简单看一下CGLib动态代理的使用,还是以媒婆为例,创建CglibMeipo类。

public class CGlibMeipo implements MethodInterceptor {

public Object getInstance(Class<?> clazz) throws Exception{

//相当于JDK中的Proxy类,是完成代理的工具类

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(clazz);

enhancer.setCallback(this);

return enhancer.create();

}

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

before();

Object obj = methodProxy.invokeSuper(o,objects);

after();

return obj;

}

private void before(){

System.out.println(“我是媒婆,我要给你找对象,现在已经确认你的需求”);

System.out.println(“开始物色”);

}

private void after(){

System.out.println(“双方同意,准备办婚事”);

}

}

创建单身客户类Customer。

public class Customer {

public void findLove(){

System.out.println(“符合要求”);

}

}

这里有一个小细节,CGLib动态代理的目标对象不需要实现任何接口,它是通过动态继承目标对象实现动态代理的,客户端测试代码如下。

public static void main(String[] args) {

try {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。

最后祝愿各位身体健康,顺利拿到心仪的offer!

由于文章的篇幅有限,所以这次的蚂蚁金服和京东面试题答案整理在了PDF文档里

蚂蚁、京东Java岗4面:原理+索引+底层+分布式+优化等,已拿offer

蚂蚁、京东Java岗4面:原理+索引+底层+分布式+优化等,已拿offer

蚂蚁、京东Java岗4面:原理+索引+底层+分布式+优化等,已拿offer
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
ntln(“我是媒婆,我要给你找对象,现在已经确认你的需求”);

System.out.println(“开始物色”);

}

private void after(){

System.out.println(“双方同意,准备办婚事”);

}

}

创建单身客户类Customer。

public class Customer {

public void findLove(){

System.out.println(“符合要求”);

}

}

这里有一个小细节,CGLib动态代理的目标对象不需要实现任何接口,它是通过动态继承目标对象实现动态代理的,客户端测试代码如下。

public static void main(String[] args) {

try {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-JxzsV2AY-1713511971056)]

[外链图片转存中…(img-tvXmHIp9-1713511971056)]

[外链图片转存中…(img-ONLhm1b3-1713511971057)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。

最后祝愿各位身体健康,顺利拿到心仪的offer!

由于文章的篇幅有限,所以这次的蚂蚁金服和京东面试题答案整理在了PDF文档里

[外链图片转存中…(img-ze5glKSz-1713511971057)]

[外链图片转存中…(img-0FURC011-1713511971057)]

[外链图片转存中…(img-wM7PJVOF-1713511971057)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值