代理模式
本文使用的 jdk
版本为:1.8.0_144
静态代理
package org.ml;
/**
* @author mfh
* @date 2020/2/25 16:55
*/
public interface Chargable {
void charge();
}
package org.ml;
import java.util.Random;
/**
* 闪电快充
* @author mfh
* @date 2020/2/25 16:56
*/
public class LightningCharger implements Chargable {
@Override
public void charge() {
System.out.println("闪电快充开始充电...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
需求:现要记录充电的时间
修改源码
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:03
*/
public class Client {
public static void main(String[] args) {
Chargable lightning = new LightningCharger();
long begin = System.currentTimeMillis();
lightning.charge();
System.out.println("共充电:" + (System.currentTimeMillis() - begin) + "毫秒");
}
}
以上的实现前提是能得到源码的情况下。
如果得不到源码应该如何实现呢?最不幸的方法可以使用继承。
继承
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:11
*/
public class LightningChargerSub extends LightningCharger {
@Override
public void charge() {
long begin = System.currentTimeMillis();
super.charge();
System.out.println("共充电:" + (System.currentTimeMillis() - begin) + "毫秒");
}
}
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:03
*/
public class Client {
public static void main(String[] args) {
Chargable lightning = new LightningChargerSub();
lightning.charge();
}
}
关于继承的缺点自行度娘吧,这里不再说了,比较好的方式应该使用组合。
组合具体类
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:15
*/
public class LightningChargerTimeProxy {
private LightningCharger charger;
public LightningChargerTimeProxy(LightningCharger charger) {
this.charger = charger;
}
public void recordTime() {
long begin = System.currentTimeMillis();
this.charger.charge();
System.out.println("共充电:" + (System.currentTimeMillis() - begin) + "毫秒");
}
}
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:03
*/
public class Client {
public static void main(String[] args) {
LightningChargerTimeProxy proxy = new LightningChargerTimeProxy(new LightningCharger());
proxy.recordTime();
}
}
以上的组合方式,LightningChargerTimeProxy 只能代理 LightningCharger,如果将 LightningCharger 改为接口 Chargable,那么将会有更好的灵活性,即只要实现了 Chargable 接口的类就能被代理。
组合接口
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:15
*/
public class LightningChargerTimeProxy {
private Chargable charger;
public LightningChargerTimeProxy(Chargable charger) {
this.charger = charger;
}
public void recordTime() {
long begin = System.currentTimeMillis();
this.charger.charge();
System.out.println("共充电:" + (System.currentTimeMillis() - begin) + "毫秒");
}
}
现不仅要记录充电的时间,还要记录充电前的电量和充电后的电量。
为了实现新的需求,很容易想到的一个方式是,新增一个代理类,然后在组合一个 Chargable 接口,记录电量。
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:54
*/
public class BatteryRecordProxy {
private Chargable chargable;
public BatteryRecordProxy(Chargable chargable) {
this.chargable = chargable;
}
public void recordBattery() {
System.out.println("充电前的电量");
chargable.charge();
System.out.println("充电后的电量");
}
}
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:03
*/
public class Client {
public static void main(String[] args) {
LightningCharger lightningCharger = new LightningCharger();
LightningChargerTimeProxy lightningChargerTimeProxy = new LightningChargerTimeProxy(lightningCharger);
BatteryRecordProxy batteryRecordProxy = new BatteryRecordProxy(lightningCharger);
lightningChargerTimeProxy.recordTime();
batteryRecordProxy.recordBattery();
}
}
闪电快充开始充电...
共充电:2568毫秒
充电前的电量
闪电快充开始充电...
充电后的电量
通过这种方式,可实现充电前后的电量,但是与记录充电时间的操作就不在同一次操作,实际的需求是记录充电时间,并记录电量,是在同一次充电操作完成的。
代理与被代理类实现相同接口
代理与被代理的类实现同一接口,这样代理类与被代理类就可以相互嵌套,实现代理传递,类似于装饰模式。
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:15
*/
public class LightningChargerTimeProxy implements Chargable {
private Chargable charger;
public LightningChargerTimeProxy(Chargable charger) {
this.charger = charger;
}
@Override
public void charge() {
long begin = System.currentTimeMillis();
this.charger.charge();
System.out.println("共充电:" + (System.currentTimeMillis() - begin) + "毫秒");
}
}
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:54
*/
public class BatteryRecordProxy implements Chargable {
private Chargable chargable;
public BatteryRecordProxy(Chargable chargable) {
this.chargable = chargable;
}
@Override
public void charge() {
System.out.println("充电前的电量");
chargable.charge();
System.out.println("充电后的电量");
}
}
package org.ml;
/**
* @author mfh
* @date 2020/2/25 17:03
*/
public class Client {
public static void main(String[] args) {
LightningChargerTimeProxy proxy = new LightningChargerTimeProxy(
new BatteryRecordProxy(
new LightningCharger()
)
);
proxy.charge();
}
}
充电前的电量
闪电快充开始充电...
充电后的电量
共充电:2440毫秒
静态代理的代理类都是通过硬编码的方式提前写好的,而动态代理则是在运行期间,动态生成的代理类。
动态代理
jdk
package org.ml;
/**
* @author mfh
* @date 2020/2/25 16:55
*/
public interface Chargable {
void charge();
}
package org.ml;
import java.util.Random;
/**
* 闪电快充
* @author mfh
* @date 2020/2/25 16:56
*/
public class LightningCharger implements Chargable {
@Override
public void charge() {
System.out.println("闪电快充开始充电...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package org.ml;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author mfh
* @date 2020/2/25 18:28
*/
public class ChargerInvocationHandler implements InvocationHandler {
private Chargable chargable;
public ChargerInvocationHandler(Chargable chargable) {
this.chargable = chargable;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long begin = System.currentTimeMillis();
method.invoke(chargable, args);
System.out.println("共充电:" + (System.currentTimeMillis() - begin) + "毫秒");
return null;
}
}
package org.ml;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author mfh
* @date 2020/2/25 17:03
*/
public class Client {
public static void main(String[] args) {
Chargable charger = new LightningCharger();
InvocationHandler ih = new ChargerInvocationHandler(charger);
Chargable proxy = (Chargable)Proxy.newProxyInstance(charger.getClass().getClassLoader(),
charger.getClass().getInterfaces(),
ih);
proxy.charge();
}
}
闪电快充开始充电...
共充电:567毫秒
newProxyInstance
java.lang.reflect.Proxy#newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler hi)throws IllegalArgumentException
参数
ClassLoader loader:一般情况使用与被代理的类相同的类加载器即可
Class<?>[] interfaces:被代理的类实现的接口们,这里是一个 Class 类型的数组
InvocationHandler hi:顾名思义,执行处理器。它处理在执行动态代理时,即调用被代理的类的方法时需要做的事情,也就是在调用被代理对象的方法前后干点什么事情。
当执行 proxy.charge();
代码后, org.ml.ChargerInvocationHandler#invoke
方法就被调用了,感觉有点神奇,我们并没有调用org.ml.ChargerInvocationHandler#invoke
方法,那么究竟是谁调用了org.ml.ChargerInvocationHandler#invoke
方法呢?其实不难猜出,肯定是proxy.charge();
方法内部调用了 org.ml.ChargerInvocationHandler#invoke
,所以玄机就在proxy
这个代理对象。它就是运行时动态生成的代理类,存在于内存中。如果我们可以看到此代理类的源码,很多问题就可以得到解答,下面我们就让此代理类现行。
让代理类现形的三种方式
方式一
采用文件写入的方式,具体代码如下:
package org.ml;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author mfh
* @date 2020/2/25 17:03
*/
public class Client {
public static void main(String[] args) {
String path = "E:/$Proxy0.class";
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Chargable.class});
try(FileOutputStream out = new FileOutputStream(path)) {
out.write(classFile);
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
方式二
对于 1.8.0_144 版本的 jdk
,需要加入虚拟机参数,-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
如下图所示:
方式三
如果是较新的版本(具体到哪一版没有考证过),则不需要添加虚拟机参数,直接在 main 方法开始出加入
System.getProperties().put("jdk.misc.ProxyGenerator.saveGeneratedFiles", true);
代码即可,程序运行后,就会看见生成的代理类:com/sun/proxy/$Proxy0.class
,如下图所示:
代理类源码
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.ml.Chargable;
public final class $Proxy0 extends Proxy implements Chargable {
private static Method m1;
private static Method m2;
private static Method m3;
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});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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 charge() throws {
try {
super.h.invoke(this, m3, (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);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("org.ml.Chargable").getMethod("charge");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
分析代理类
实现接口的用处
通过源码得知,代理类继承自 Proxy,实现了 Chargable 接口。它为什么知道要实现 Chargable 接口?就是因为调用java.lang.reflect.Proxy#newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler hi)throws IllegalArgumentException
方法时,传入了接口,根据接口,就得知需要实现哪些方法,所以它生成了 charge 方法。当执行proxy.charge()
代码后,实际上调用的就是com.sun.proxy.$Proxy0#charge
,charge() 方法源码如下:
public final void charge() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
Proxy 类
super 即 Proxy 类,我们看下 Proxy 类部分源码:
public class Proxy implements java.io.Serializable {
private static final long serialVersionUID = -2222568056686623797L;
protected InvocationHandler h;
private Proxy() {
}
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
....
}
成员变量 InvocationHandler h 的赋值
成员变量 h,就是调用java.lang.reflect.Proxy#newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler hi)throws IllegalArgumentException
时,给 InvocationHandler hi 参数传递的 ChargerInvocationHandler 对象。所以执行super.h.invoke(this, m3, (Object[])null);
就是调用我们定义的 ChargerInvocationHandler 类的Object invoke(Object proxy, Method method, Object[] args)
方法。
ChargerInvocationHandler 的 invoke 方法的参数们
参数一:this,所以org.ml.ChargerInvocationHandler#invoke
方法的Object proxy
参数,实际上就是生成的代理类的对象。虽然我们一般不会用到它,但既然传了就有可能会有应用的场景。
参数二:m3,通过生成的代理类的静态代码块中的代码m3 = Class.forName("org.ml.Chargable").getMethod("charge");
得知,m3 其实就是 Chargable 类的 charge 方法。那么当执行method.invoke(chargable, args);
时,由于 chargable 是 Chargable 接口,根据多态原理, 在运行时,实际调用的就是实现了 Chargable 接口的类的 charge 方法,很具有灵活性。chargable 当然也可以是被包装了很多层的 Chargable 类型的对象,即与装饰模式搭配使用。
参数三:调用 charge 方法时需要的参数,由于 charge 没有参数,所以这里传 null。
介绍完代理类的调用过程,下面简单说说 jdk 是如何生成代理类的。
代理类的生成
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
...
}
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
由上面注释得知,如果存在由实现了给定接口的给定加载器定义的代理类,则将仅返回缓存的副本;否则,它将通过ProxyClassFactory 创建代理类。
proxyClassCache 定义如下:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
它是 WeakCache 类型的对象,在构造时使用了 ProxyClassFactory。
第一次从缓存中获取肯定获取不到,所以要通过 ProxyClassFactory 创建代理类,所以会调用java.lang.reflect.Proxy.ProxyClassFactory#apply
方法。
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
...
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
...
}
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
// 真正生成代理类的方法,具体就不在跟进了,底层使用的是 Asm 技术
final byte[] var4 = var3.generateClassFile();
// saveGeneratedFiles 为是否生成代理类的标记,就是之前设置的那个
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}
Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
}
return var4;
}
cglib
package com.mfh.cglib;
/**
* @author mfh
* @date 2020/2/26 11:29
*/
public class TypecCharger {
public void charge() {
System.out.println("TypeC 充电...");
}
}
package org.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author mfh
* @date 2020/2/26 11:30
*/
public class TimeRecordInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long begin = System.currentTimeMillis();
Object result = methodProxy.invoke(o, objects);
System.out.println("共充电:" + (System.currentTimeMillis() - begin) + "毫秒");
return result;
}
}
package org.cglib;
/**
* @author mfh
* @date 2020/2/26 11:29
*/
public class TypecCharger {
public void charge() {
System.out.println("TypeC 充电...");
}
}
package org.cglib;
import net.sf.cglib.proxy.Enhancer;
/**
* @author mfh
* @date 2020/2/26 11:33
*/
public class Client {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TypecCharger.class);
enhancer.setCallback(new TimeRecordInterceptor());
TypecCharger typecCharger = (TypecCharger)enhancer.create();
typecCharger.charge();
}
}
jdk 实现的动态代理要实现接口,cglib 却不需要实现接口。由代码 enhancer.setSuperclass(TypecCharger.class);
得知,生成的代理类是被代理类的子类。
应用场景
接口式调用
只提供接口,不需要实现类,也可以生成动态代理,即直接代理接口。比如只代理 Chargable 接口。
package org.ml;
/**
* @author mfh
* @date 2020/2/25 16:55
*/
public interface Chargable {
void charge();
}
package org.ml;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author mfh
* @date 2020/2/25 18:28
*/
public class ChargerInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("做点什么呢?");
return null;
}
}
package org.ml;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author mfh
* @date 2020/2/25 17:03
*/
public class Client {
public static void main(String[] args) {
InvocationHandler ih = new ChargerInvocationHandler();
Chargable proxyInstance = (Chargable)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{Chargable.class}, ih);
proxyInstance.charge();
}
}
类似于 Mybatis 的实现,只有接口,没有实现类,但是可以执行持久化方法。
结合工厂使用
package org.factory;
/**
* 收钱
* @author mfh
* @date 2020/2/25 22:19
*/
public interface Waiter {
String collectMoney();
}
package org.factory;
/**
* 前置通知
* @author mfh
* @date 2020/2/25 22:22
*/
public interface BeforeAdvice {
void before();
}
package org.factory;
/**
* 后置通知
* @author mfh
* @date 2020/2/25 22:22
*/
public interface AfterAdvice {
void after();
}
package org.factory;
import java.util.Random;
/**
* @author mfh
* @date 2020/2/25 22:20
*/
public class FemaleWaiter implements Waiter {
@Override
public String collectMoney() {
int money = new Random().nextInt(10000);
System.out.printf("共收费%d元\n", money);
return money > 1000 ? "^_^" : ">_<";
}
}
package org.factory;
import java.lang.reflect.Proxy;
/**
* @author mfh
* @date 2020/2/25 22:23
*/
public class ProxyFactory<T> {
T target;
private BeforeAdvice beforeAdvice;
private AfterAdvice afterAdvice;
public ProxyFactory(T target, BeforeAdvice beforeAdvice, AfterAdvice afterAdvice) {
this.target = target;
this.beforeAdvice = beforeAdvice;
this.afterAdvice = afterAdvice;
}
public T createProxy() {
ClassLoader loader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
return (T)Proxy.newProxyInstance(loader, interfaces, (proxy, method, args) -> {
if (null != beforeAdvice) {
beforeAdvice.before();
}
Object result = method.invoke(target, args);
if (null != afterAdvice) {
afterAdvice.after();
}
return result;
});
}
public T getTarget() {
return target;
}
public void setTarget(T target) {
this.target = target;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
}
package org.factory;
/**
* @author mfh
* @date 2020/2/25 22:30
*/
public class Client {
public static void main(String[] args) {
Waiter waiter = new FemaleWaiter();
ProxyFactory<Waiter> pf = new ProxyFactory(waiter,
() -> System.out.println("您好,要结账吗?"),
() -> System.out.println("欢迎下次光临!"));
Waiter proxy = pf.createProxy();
String face = proxy.collectMoney();
System.out.println(face);
}
}
限制方法调用
老板开了一家公司做生意,雇佣了一秘书作为代理人,帮老板决定一些事情。比如有客户想一起赚钱,秘书当然会同意;如果有客户想解散公司,秘书当然不会放行,如果是老板要解散公司,自然是可以的。
package org.limited;
/**
* @author mfh
* @date 2020/2/26 10:45
*/
public interface DoBusiness {
/**
* 挣钱
* @return
*/
int makeMoney();
/**
* 解散公司
*/
void disbanded();
}
package org.limited;
import java.util.Random;
/**
* @author mfh
* @date 2020/2/26 10:43
*/
public class Boss implements DoBusiness {
@Override
public int makeMoney() {
return new Random().nextInt(100_00_00);
}
@Override
public void disbanded() {
System.out.println("老板解散了公司...");
}
}
package org.limited;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author mfh
* @date 2020/2/26 10:54
*/
public class Secretary implements InvocationHandler {
private DoBusiness business;
public Secretary(DoBusiness business) {
this.business = business;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("makeMoney".equals(method.getName())) {
return method.invoke(business, args);
} else if ("disbanded".equals(method.getName()) && !business.getClass().getSimpleName().equals("Boss")) {
throw new IllegalAccessException("对不起,您越界了!");
} else {
method.invoke(business, args);
}
return null;
}
}
package org.limited;
import java.util.Random;
/**
* @author mfh
* @date 2020/2/26 11:00
*/
public class Partner implements DoBusiness {
@Override
public int makeMoney() {
return new Random().nextInt(1000);
}
@Override
public void disbanded() {
System.out.println("我要解散公司!!");
}
}
package org.limited;
import java.lang.reflect.Proxy;
/**
* @author mfh
* @date 2020/2/26 10:57
*/
public class Client {
public static void main(String[] args) {
DoBusiness partner = new Partner();
DoBusiness db = (DoBusiness) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{DoBusiness.class}, new Secretary(partner));
int money = db.makeMoney();
System.out.printf("赚了%d元\n", money);
db.disbanded();
}
}
赚了840元
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy0.disbanded(Unknown Source)
at org.limited.Client.main(Client.java:14)
Caused by: java.lang.IllegalAccessException: 对不起,您越界了!
at org.limited.Secretary.invoke(Secretary.java:22)
... 2 more
将DoBusiness partner = new Partner();
改为DoBusiness partner = new Boss();
赚了579038元
老板解散了公司...
https://www.cnblogs.com/youzh...
https://blog.csdn.net/lizhiyu...
https://www.cnblogs.com/zuido...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。