何为代理设计模式
代理设计模式就是提供一个替身(代理者)或者占位符来控制对这个对象的访问。 使用代理模式创建代表对象。代表对象控制这另一个对象的访问,被代表的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。
代理模式的类图如下。
代理模式是在生活中特别常见,比如玩的跑跑卡丁车国服版本是由世纪天成公司代理的,世纪天成可以在原有跑跑卡丁车的基础上新加一些属于自己的功能,比如未成年人游戏时长限制控制、游戏内部的广告牌打广告、还有游戏中的默认车牌都显示“世纪天成”字样等等。
实例
我们就以跑跑卡丁车国服游戏的例子来举例,同时结合 JDK 自带的动态代理。JDK 自带的动态代理必须要一个接口和实现需要代理的类,需要首先要一个 KartRider 接口,也就是原始的韩服跑跑卡丁车。
public interface KartRider {
void setAd(String ad);
void setCarId(String carId);
void play();
}
世纪天成代理的跑跑卡丁车国服,“跑跑卡丁车国服”就是需要代理的类。
public class ChineseKartRider implements KartRider {
private String ad;
private String carId;
private Integer age;
@Override
public void setAd(String ad) {
this.ad = ad;
}
@Override
public void setCarId(String carId) {
this.carId = carId;
}
@Override
public void play() {
System.out.println(this);
System.out.println("游戏已启动!");
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "ChineseKartRider{" +
"ad='" + ad + '\'' +
", carId='" + carId + '\'' +
", age=" + age +
'}';
}
}
创建一个代理类(“也就是世纪天成了”)并实现 InvocationHandler 接口,其中 Invoke 方法就是用于控制创建的 ChineseKartRider 对象实例的方法访问,实现未成年人游戏时长限制控制。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
public class TianCityInvocationHandler implements InvocationHandler {
private KartRider kartRider;
public TianCityInvocationHandler(KartRider kartRider) {
this.kartRider = kartRider;
}
/**
*
* @param proxy 调用该方法的代理实例
*
* @param method 调用其 getName 方法就知道是哪个方法名在使用
*
* @param args 调用的方法的参数个数
*
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
LocalDateTime date = LocalDateTime.now();
int week = date.getDayOfWeek().getValue();
int hour = date.getHour();
try {
if("play".equals(method.getName())) {
ChineseKartRider p = (ChineseKartRider) kartRider;
boolean req1 = week == 5 || week == 6 || week == 0; // 未成年人只能在这三天玩游戏
boolean req2 = p.getAge() == null || p.getAge() < 18; // 年龄规则
boolean req3 = hour == 20; // 1小时时长限制
// 未成年限制
if (req1 && req2 && req3) {
return method.invoke(kartRider, args);
}
// 年龄过 18 岁不受此限制
if (!req2) {
return method.invoke(kartRider, args);
}
throw new IllegalAccessException("通知要求,严格限制向未成年人提供网络游戏服务的时间, 所有网络游戏企业仅可在周五、周六、周日和法定节假日每日20时至21时向未成年人提供1小时服务!");
} else if (method.getName().startsWith("set")) {
return method.invoke(kartRider, args);
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
运行类(18岁以上不受限制,18岁以下在规定的时间就会提示)。
public class ProxyTest {
public static void main(String[] args) {
KartRider proxy = getProxy();
proxy.setAd("跑跑卡丁车 - 突破");
proxy.setCarId("世纪天成");
proxy.play();
}
public static KartRider getProxy() {
ChineseKartRider kartRider = new ChineseKartRider();
kartRider.setAge(18);
return (KartRider) Proxy.newProxyInstance(
kartRider.getClass().getClassLoader(),
kartRider.getClass().getInterfaces(),
new TianCityInvocationHandler(kartRider));
}
}
运行结果(18岁不受限制):
ChineseKartRider{ad='跑跑卡丁车 - 突破', carId='世纪天成', age=18}
游戏已启动!
把年龄设置为 10 岁,抛出了异常。
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy0.play(Unknown Source)
at com.example.demo_springboot.designpattern.proxy.ProxyTest.main(ProxyTest.java:11)
Caused by: java.lang.IllegalAccessException: 通知要求,严格限制向未成年人提供网络游戏服务的时间, 所有网络游戏企业仅可在周五、周六、周日和法定节假日每日20时至21时向未成年人提供1小时服务!
at com.example.demo_springboot.designpattern.proxy.ChineseInvocationHandler.invoke(ChineseInvocationHandler.java:51)
... 2 more
随处可见的代理模式
除了本例,Java RMI、Spring AOP、Spring Transaction Manager 等等也涉及到代理设计模式,面向切面编程的本质就是代理模式。
并且知道代理模式之后,也就明白为什么在被代理的对象(ChineseKartRider)内部调用方法时不受控制,若不确定该对象是被代理过的,可以通过 Proxy.isProxyClass()
方法来判断。
请勿发布违反中国大陆地区法律的言论,请勿人身攻击、谩骂、侮辱和煽动式的语言。