1. 谈谈代理
假如你在深圳,现在武汉有一套房子准备出售,你把卖房的消息发出来了,陆续的有很多买家找你咨询,你每天要回复很多买家的消息(这个房子多少平米,这个房子是坐南朝北的吗,这个房子小区环境怎样啊,这个房子小区的物业怎样啊),也无法满足买家想要亲自去看看的要求,这时候你的头都大了,严重影响了你日常工作。怎么解决呢?这个时候你选择了把房子交给房屋中介,由他们代理处理。你发现你的世界清静了。
假如你现在要注册一个公司,你不知道注册公司的流程是怎样的,不知道要准备哪些资料,要去哪些地方盖章,而且你还有其他重要的事情做,也不希望这个工作影响到你,所以你找了一家代理注册公司来帮你做这件事。
房屋中介和代理注册公司就是现实生活中的代理。它帮助我们解决我们不熟悉或者是无法亲自做的事情。
编程世界也需要代理,因为也有像上面说的需要代理帮忙处理的需求。在某些情况下,一个对象不想或者不能直接引用另一个对象,这时候我们就需要一个代理对象来协调处理。
2. 代理模式
Java中讲到代理涉及到的就是代理模式,这个模式的定义是:
对其他对象提供一种代理以控制对这个对象的访问。
分析这个定义,我们提出几个关键点:
- 其他对象
- 代理
- 这个对象
之所以提出这三个关键点,是因为这正是代理模式的三种角色。就好比上面第一个卖房的例子,房屋屋主、买家和房屋中介是参与卖房这个交易的三种角色一样。只有这几种角色参与,才能说是完整的一个代理模式的体现。
这里还有一点,还是拿卖房的例子,房屋屋主和房屋中介都有同一个目的,就是把房子卖出去,不能说房屋屋主想卖房子而房屋中介去卖你的家具。也就是代理对象和被代理对象必有某些约定。这个在编程语言的代理模式中是有所体现的(通过接口约定)。
综上,编程语言代理模式中提出了下面四种角色:
- Subject(主体):以接口的方式定义了代理对象和被代理对象的一致性行为。形如:房屋屋主和房屋中介都是为了卖房子这个约定目的
- Proxy(代理人):形如:房屋中介
- RealSubject(实际的主体):形如:房屋屋主
- Client(请求者):形如:买家
根据代理类的生成方式,把代理模式又分成了两类:静态代理模式和动态代理模式。
- 静态代理模式是指代理类已经在编译期明确。
- 动态代理模式是指运行期才生成的代理。
3. 静态代理
静态代理一般又分为类继承代理与对象组合代理。类继承就是继承超类获得超类的功能,通过访问子类来从而控制对超类的访问,而我们所说的代理模式使用的是组合代理。
现在我们通过一个需求来描述静态代理模式的使用。
有这样的一个订单支付类,我们需要在支付前和支付后分别输出时间以查看支付花费的时长,如下:
log.png我们使用静态代理模式来书写代码。代理模式要求代理对象和被代理对象实现统一的接口,所以我们这里定义一个 Payment的接口,同时定义我们的代理类如下:
interface Payment {
void pay();
}
class OrderPayment implements Payment {
public void pay() {
System.out.println("Order Pay.");
}
}
class OrderPaymentProxy implements Payment {
private Payment payment;
public OrderPaymentProxy(Payment payment) {
this.payment = payment;
}
public void pay() {
System.out.println("Begin: " + System.currentTimeMillis());
payment.pay();
System.out.println("End: " + System.currentTimeMillis());
}
}
public class StaticProxyDemo {
public static void main(String[] args) {
OrderPayment orderPayment = new OrderPayment();
Payment payment = new OrderPaymentProxy(orderPayment);
payment.pay();
}
}
运行结果:
Begin: 1550570169668
Order Pay.
End: 1550570169668
日志的输出不是我们OrderPayment的主要业务,没有必要书写在pay()方法中,而是分离出来放到了OrderPaymentProxy中。这样非业务代码和业务代码分离,使得程序的可读性和结构性更强。
现在假设我们又有这样的一个需求,有一个订单退款类OrderRefund,同样需要打印时间日志以计算退款花费的时间,有上面的例子作为参考,于是乎我们这样写:
interface Refund {
void refund();
}
class OrderRefund implements Refund {
public void refund() {
System.out.println("Order Refund.");
}
}
class OrderRefundProxy implements Refund {
private Refund refund;
public OrderRefundProxy(Refund refund) {
this.refund = refund;
}
public void refund() {
System.out.println("Begin: " + System.currentTimeMillis());
refund.refund();
System.out.println("End: " + System.currentTimeMillis());
}
}
public class StaticProxyDemoA {
public static void main(String[] args) {
OrderRefund orderRefund = new OrderRefund();
Refund refund = new OrderRefundProxy(orderRefund);
refund.refund();
}
}
比对代码我们可以发现OrderPaymentProxy和OrderRefundProxy做的是相似的事情,代码看上去有些冗余,而且如果又出现需要打印时间的其他业务需求,我们就需要重新定义一个其他业务的代理类。
这时候我们就会思考既然代理类做的事情都是一样的,那么有没有办法不用写这么多代理,来消除这种冗余呢。
Java的动态代理模式就是用来解决这种问题。
4. 动态代理
关于为什么用动态代理,这里再做下说明:
为每一个需要代理的对象书写一个代理类理论上是可行的方式,但是如果每个书写的代理类对象的逻辑相似,那么这种方式不仅使得工作量变大,而且会产生很多冗余的代理,而且对于无法在编译期就确定代理类的情形来说,这种方式无法满足,所以需要根据需要动态的创建代理类,这就是为什么需要动态代理。
JDK提供了动态代理的机制,还有一些第三方的解决方案,我们分别加以说明。
使用JDK动态代理
JDK为我们提供了动态代理的解决机制,主要涉及到两个类 java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler ,现在我们就用 JDK的动态代理机制来重写我们上面的代码。
package proxy;
interface Payment {
void pay();
}
class OrderPayment implements Payment {
public void pay() {
System.out.println("Order Pay.");
}
}
interface Refund {
void refund();
}
class OrderRefund implements Refund {
public void refund() {
System.out.println("Order Refund.");
}
}
class TimeTraceInvocationHandler implements InvocationHandler {
private Object target;
public TimeTraceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Begin: " + System.currentTimeMillis());
Object o = method.invoke(target, args);
System.out.println("End: " + System.currentTimeMillis());
return o;
}
}
public class JDKDynamicProxyDemo {
public static void main(String[] args) {
OrderPayment orderPayment = new OrderPayment();
Payment payment = (Payment) Proxy.newProxyInstance(JDKDynamicProxyDemo.class.getClassLoader(),
orderPayment.getClass().getInterfaces(), new TimeTraceInvocationHandler(orderPayment));
payment.pay();
System.out.println("=====================");
OrderRefund orderRefund = new OrderRefund();
Refund refund = (Refund) Proxy.newProxyInstance(JDKDynamicProxyDemo.class.getClassLoader(),
orderRefund.getClass().getInterfaces(), new TimeTraceInvocationHandler(orderRefund));
refund.refund();
}
}
运行结果如下:
Begin: 1550573556540
Order Pay.
End: 1550573556541
=====================
Begin: 1550573556543
Order Refund.
End: 1550573556544
可以看到TimeTraceInvocationHandler这一个类处理了通用的逻辑,消除了上面例子中定义多代理类所造成的冗余。
JDKDynamicProxyDemo类main方法中的payment和refund就是JDK为我们生成的动态代理对象,既然它们是对象,那么就会有相应的类型,那它们又分别是什么类的实例呢。
我们通过一个工具类来输出和查看payment代理对象对应的类的内容。
下面是工具类ProxyUtils,它根据类信息生成二进制字节码并保存到硬盘中。
package proxy;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author rocky.hu
*/
public class ProxyUtils {
/*
* 将根据类信息 动态生成的二进制字节码保存到硬盘中, 默认的是clazz目录下 params :clazz 需要生成动态代理类的类
* proxyName : 为动态生成的代理类的名称
*/
public static void generateClassFile(Class clazz, String proxyName) {
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
// 保留到硬盘中
out = new FileOutputStream(paths + proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在代码payment.pay()前面加上下面这段代码:
ProxyUtils.generateClassFile(payment.getClass(), "OrderPaymentProxy");
运行,在硬盘中生成了如下的OrderPaymentProxy.class这个文件,通过Class文件查看工具查看此类的内容如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Payment;
public final class OrderPaymentProxy
extends Proxy
implements Payment
{
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
public OrderPaymentProxy(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void pay()
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("proxy.Payment").getMethod("pay", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
从代码中可以看到,JDK生成的代理类大体上跟我们自己定义的代理类是类似的,都实现了约定的接口,实现了接口中定义的方法。
public final void pay()
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
我们通过上面的方法来看看不同。在我们写的静态代理类中,pay()方法里面的逻辑是写死的,主动调用payment对象的pay()方法:
System.out.println("Begin: " + System.currentTimeMillis());
payment.pay();
System.out.println("End: " + System.currentTimeMillis());
而我们看到JDK为我们生成的代理类的pay()中没有任何相关的内容,那么被代理对象的pay()的调用是在哪里,怎么发生的呢?
JDK动态代理使用了反射机制来在运行时动态调用对象的方法。
首先,通过反射机制确定动态代理类方法要执行代理对象的那个方法,我们看下m3属性:
m3 = Class.forName("proxy.Payment").getMethod("pay", new Class[0]);
这个说明了OrderPaymentProxy对象执行pay()方法是要执行被代理对象的pay()方法。
那么执行的代码在哪,既然是反射调用,那么就应该有形如:
m3.invoke(被代理对象,参数);
这样的代码。
的确是有的,因为代理类处理逻辑是跟业务相关联的,是动态变化的,所以JDK不可能生成一个代理类把这部分逻辑包含进去,JDK通过定义InvocationHandler这个接口把这部分逻辑分离出来,具体的用户实现这个接口书写具体的逻辑。而代理对象通过反射调用方法的这部分代码,正是在这个接口的实现中。
class TimeTraceInvocationHandler implements InvocationHandler {
private Object target;
public TimeTraceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Begin: " + System.currentTimeMillis());
Object o = method.invoke(target, args);
System.out.println("End: " + System.currentTimeMillis());
return o;
}
}
上面代码:
Object o = method.invoke(target, args);
为被代理对象方法的调用。
下面通过一个图来梳理一下,JDK动态代理机制的调用流程:
JDK动态代理调用流程.png
我们来看一下 JDK创建动态代理的这个newProxyInstance方法:
newProxyInstance.png注意一下黄色的标记,对于这个方法的第二个参数, API的描述是:
the list of interfaces for the proxy class to implement.
也就是说如果想使用JDK动态代理的话那么被代理的这个类就必须实现接口。那么没有实现任何接口的类,JDK是无法帮其实现代理的,这也是JDK动态代理的局限性,但是如果现在就是有类未实现接口,而我们还是希望为其生成代理类,那么应该怎么办呢?
我们下面讲的就是使用其他的框架来解决这个问题。
使用ASM实现动态代理
下面是 ASM框架的描述:
ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or dynamically generate classes, directly in binary form. Provided common transformations and analysis algorithms allow to easily assemble custom complex transformations and code analysis tools.
ASM offer similar functionality as other bytecode frameworks, but it is focused on simplicity of use and performance. Because it was designed and implemented to be as small and as fast as possible, it makes it very attractive for using in dynamic systems.
ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
拿到我们这里来说,使用 ASM,就是利用它的修改类字节码的能力来为我们实现动态代理。
OrderPayment类没有实现任何的接口,而我们仍然需要实现打印日志的功能。
package study.proxy.asm;
public class OrderPayment {
public void pay() {
System.out.println("Order Pay.");
}
}
使用ASM来实现动态代理的的所有类如下 (使用ASM3.3.1 版本):
package study.proxy.asm;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
public class PaymentLogClassAdapter extends ClassAdapter {
private String enhancedSuperName;
public PaymentLogClassAdapter(ClassVisitor cv) {
super(cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
String enhancedName = name + "$EnhancedByASM";
enhancedSuperName = name;
super.visit(version, access, enhancedName, signature, enhancedSuperName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
MethodVisitor wrappedMv = mv;
if (mv != null) {
if (name.equals("pay")) {
wrappedMv = new PaymentLogMethodAdapter(mv);
} else if (name.equals("<init>")) {
wrappedMv = new ChangeToChildConstructorMethodAdapter(mv, enhancedSuperName);
}
}
return wrappedMv;
}
}
package study.proxy.asm;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class PaymentLogMethodAdapter extends MethodAdapter {
public PaymentLogMethodAdapter(MethodVisitor mv) {
super(mv);
}
@Override
public void visitCode() {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
mv.visitLdcInsn("Begin: ");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= Opcodes.IRETURN && opcode<=Opcodes.RETURN) || opcode == Opcodes.ATHROW ) {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
mv.visitLdcInsn("End: ");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
}
mv.visitInsn(opcode);
}
}
package study.proxy.asm;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class ChangeToChildConstructorMethodAdapter extends MethodAdapter {
private String superClassName;
public ChangeToChildConstructorMethodAdapter(MethodVisitor mv, String superClassName) {
super(mv);
this.superClassName = superClassName;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {
owner = superClassName;
}
super.visitMethodInsn(opcode, owner, name, desc);
}
}
package study.proxy.asm;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
public class AsmDynamicDemo {
private static PaymentLogGeneratorClassLoader classLoader = new PaymentLogGeneratorClassLoader();
public static void main(String[] args) throws Exception {
ClassReader cr = new ClassReader("study.proxy.asm.OrderPayment");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter classAdapter = new PaymentLogClassAdapter(cw);
cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
byte[] data = cw.toByteArray();
OrderPayment orderPayment = (OrderPayment) classLoader.defineClassFromClassFile
("study.proxy.asm.OrderPayment$EnhancedByASM", data).newInstance();
orderPayment.pay();
}
private static class PaymentLogGeneratorClassLoader extends ClassLoader {
public Class defineClassFromClassFile(String className, byte[] classFile) throws ClassFormatError {
return defineClass(className, classFile, 0, classFile.length);
}
}
}
运行AsmDynamicDemo,输出结果如下:
Begin: 1550646447173
Order Pay.
End: 1550646447173
使用 CGLIB实现动态代理
上面我们虽然使用了 ASM来实现动态代理,但是由于 ASM在创建Class 字节码的过程中,操纵的级别是底层JVM的指令级别,这要求 ASM使用者要对class 组织结构和JVM指令有一定的了解。从上面的代码我们可以看出操作起来还不是那么容易的,这里我们使用一个新的框架 CGLIB来实现动态代理。
CGLIB : Code Generation Library
是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现Java 接口。 Hibernate用它来实现 PO(Persistent Object 持久化对象) 字节码的动态生成。
Cglib框架的执行依赖于ASM,也就是说对 ASM进行了重新的包装,使得我们更容易使用 ASM(当然了还有另外一些自己的功能)。
现在我们就使用 Cglib(cglib-2.2.2.jar asm-3.3.1.jar)来实现我们上面的需求,代码如下:
package study.proxy.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Begin: " + System.currentTimeMillis());
Object result = proxy.invokeSuper(obj, args);
System.out.println("End: " + System.currentTimeMillis());
return result;
}
}
package study.proxy.cglib;
public class CglibDynamicProxyDemo {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
OrderPayment orderPayment = (OrderPayment) proxy.getProxy(OrderPayment.class);
orderPayment.pay();
}
}
使用 Javassist实现动态代理
我们现在再来介绍另外的一个动态代理实现框架Javassist。
Javassist 是一个开源的分析、编辑和创建 Java 字节码 的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶滋)所创建的。它已加入了开放源代码JBoss应用服务器项目 ,通过使用Javassist 对 字节码操作为JBoss 实现动态AOP 框架。
关于java字节码的处理,目前有很多工具,如 bcel , asm。不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用javassist 。javassist是 jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
使用Javassist(3.18.2-GA)来重新实现上面的功能:
package study.proxy.javassist;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
public class JavassistDynamicProxyDemo {
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("study.proxy.javassist.OrderPayment");
CtMethod m = cc.getDeclaredMethod("pay");
m.insertBefore("System.out.println(\"Begin: \" + System.currentTimeMillis());");
m.insertAfter("System.out.println(\"End: \" + System.currentTimeMillis());");
Class c = cc.toClass();
OrderPayment orderPayment = (OrderPayment) c.newInstance();
orderPayment.pay();
}
}
网友评论