代理模式
定义:为其他对象提供一种代理,以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外的服务。
举例:我想购买一个火车票,正常我们是去火车站进行买,但是,由于我们家比较远,我们可能去火车票代售点进行购买,而且,火车票代售点可能会有电话预约的额外功能,但是,可能不支持退票服务。就像我们的代理模式:去掉功能服务,增加额外服务。
常见代理模式
1.远程代理:比如说我有一个售货店,我可以通过远程代理监控各个店铺使之能直观的了解店内信息。
2.虚拟代理: 根据需要将资源消耗很大的对象进行延迟,真正需要的时候,进行创建。比如,我们浏览新闻的时候,有图片和文字,如果图片的内容很大,我们就可以给一张虚拟的图片来代替原来的图片,这样就可以看文字了。当图片加载完毕以后再进行显示 。
3.保护代理:权限控制
4.智能引用代理:提供一些额外服务。
代理实现方式
静态代理 代理和被代理对象在代理之前都是确定的。他们都实现相同的接口或者继承相同的抽象类。
示例:我们有一个车,这个车有一个行驶的方法,我们想通过代理的方法给他增加一个纪录行驶时间的方法。
package proxy;
public interface Movable {
void move() ;
}
先写一个Movable的实现类
package proxy;
import java.util.Random;
public class Car implements Movable {
@Override
public void move() {
try {
//实现开车,通过线程来模拟开车时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然后通过继承的方式实现代理:(功能增加)
package proxy;
public class Car2 extends Car {
@Override
public void move(){
long startTime = System.currentTimeMillis();
super.move();
long stopTime = System.currentTimeMillis();
System.out.println("汽车行驶时间:" + (stopTime - startTime));
}
}
测试:
package proxy;
public class Car2 extends Car {
@Override
public void move(){
long startTime = System.currentTimeMillis();
super.move();
long stopTime = System.currentTimeMillis();
System.out.println("汽车行驶时间:" + (stopTime - startTime));
}
}
结果如下:
可以看到,这个车不但可以跑而且还增加了纪录时间的功能。
刚刚是通过继承方式实现代理:下面通过聚合方式来实现代理:
package proxy;
/**
* 用聚合的方法来实现
* 聚合:在一个类中调用另一个对象
* 通过构造方法把Car传进来
*/
public class Car3 implements Movable {
private Car car;
public Car3(Car car){
super();
this.car = car;
}
@Override
public void move() {
long startTime = System.currentTimeMillis();
car.move();
long stopTime = System.currentTimeMillis();
System.out.println("汽车行驶时间:" + (stopTime - startTime));
}
}
测试如下:
package proxy;
public class ProxyTest {
public static void main(String[] args) {
/* Movable car2 = new Car2();
car2.move();*/
Movable car3 = new Car3(new Car());
car3.move();
}
}
结果可以达到预期。
那么是用继承的方式好呢?还是用聚合的方式好呢?
首先,先看继承的方式:
这是我想加上行驶时间的car,如果我想加入权限,或者日志功能,而且他们之间会随意组合,则还会有很多car很不方便,不推荐。
下面用聚合的方式实现:
日志类:
package proxy;
public class CarLogProxy implements Movable {
private Movable m;
public CarLogProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
System.out.println("日志开始");
m.move();
System.out.println("日志结束");
}
}
同时把刚刚的Car3进行更改,改成接口形式:
package proxy;
/**
* 用聚合的方法来实现
* 聚合:在一个类中调用另一个对象
* 通过构造方法把Car传进来
*/
public class CarTime implements Movable {
private Movable car;
public CarTime(Movable car){
super();
this.car = car;
}
@Override
public void move() {
long startTime = System.currentTimeMillis();
car.move();
long stopTime = System.currentTimeMillis();
System.out.println("汽车行驶时间:" + (stopTime - startTime));
}
}
下面进行测试:
package proxy;
public class ProxyTest {
public static void main(String[] args) {
/* Movable car2 = new Car2();
car2.move();*/
/* Movable car3 = new Car3(new Car());
car3.move();*/
Car car = new Car();
CarTime car3 = new CarTime(car);
CarLogProxy carLogProxy = new CarLogProxy(car3);
carLogProxy.move();
}
}
注意上面的两个箭头,成功的把日志和时间进行了增加,carTime中的move只有时间,然后把car3进行聚合,这样carlogProxy就加入了日志功能。如果想要先日志后时间,则两个箭头的代码进行相互转换就可以了。随意组合。聚合会灵活很多。
感觉,这个代理模式很强。挺有意思。
静态代理还可以进行扩展,扩展成普通代理:普通代理能使调用者只知道代理存在就可以,不用知道代理了谁,简化了场景类:简化了调用者:
下面以打游戏为例子:模拟出使用静态代理和普通代理的区别;
下面模拟普通的静态代理
先定义一个抽象主题类的接口:
package proxy;
public interface IGamePlayer {
public void login(String user, String password);
public void killBoss();
public void upgrade();
}
再定义一个真实主题类
package proxy;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
public class GamePlayer implements IGamePlayer {
private String name = "";
public GamePlayer(String name){
this.name = name;
}
@Override
public void login(String user, String password) {
System.out.println("登陆");
}
@Override
public void killBoss() {
System.out.println("打怪");
}
@Override
public void upgrade() {
System.out.println("升级");
}
}
下面实现的是代理类:
package proxy;
// 代理类也要实现抽象主题接口
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
public GamePlayerProxy(IGamePlayer _gamePlayer){
this.gamePlayer = _gamePlayer;
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user,password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
这时候,我们再写一个场景类:
package proxy;
public class Client {
public static void main(String[] args) {
GamePlayer player = new GamePlayer("张三");
GamePlayerProxy proxy = new GamePlayerProxy(player);
// 开始打游戏
proxy.login("张三","password");
// 开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
}
}
测试后结果如下所示:
可以发现满足预期,但是,这里的问题也出现了;
这里需要给传入真实对象;
需要知道代理了谁;
这样对使用者来说比较麻烦,而且也不希望可以来使用new出这个对象,因为可能需要保护这个对象;
这时候,静态代理的普通代理就可以解决这个问题:
先定义一个抽象主题类的接口:
package proxy;
public interface IGamePlayer {
public void login(String user, String password);
public void killBoss();
public void upgrade();
}
再修改一下真实主题类
package proxy;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
public final class GamePlayer implements IGamePlayer {
private String name = "";
public GamePlayer(IGamePlayer _gamePlayer, String _name) throws Exception{
// 就这个例子中这个,IGamePlayer是没有用的的,但是,在这里可以检查谁可以创建真实对象;
if(_gamePlayer.getClass() != GamePlayerProxy.class){
System.out.println("如果传入的不是GamePlayerProxy的claa对象就不能创建对象");
throw new Exception("不能创建真实对象");
}else {
this.name = name;
}
}
@Override
public void login(String user, String password) {
System.out.println("登陆");
}
@Override
public void killBoss() {
System.out.println("打怪");
}
@Override
public void upgrade() {
System.out.println("升级");
}
}
再写一下代理类:
package proxy;
// 代理类也要实现接口的方法
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
public GamePlayerProxy(String name) {
try {
// 传入的是this所以可以创建对象
gamePlayer = new GamePlayer(this, name);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
在写一下client场景类:
package proxy;
public class Client {
public static void main(String[] args) {
GamePlayerProxy proxy = new GamePlayerProxy("zhangsan");
// 开始打游戏
proxy.login("张三","password");
// 开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
}
}
结果对的:
而且,传递进来只是一个代理者名称,即可进行代理,调用者只知道代理存在就可以,不用知道代理了谁。代码也会简介很多:
这时候,问题又来了,我们现在解决的是汽车的代理,比如说我们来了一个火车,自行车,也是要计算运行时间,和日志记录功能,可以吗?不可能在写一个TrainLogProxy吧,因为这是相同代码。
所以,可以看到静态代理的主要问题是,每个代理角色代理了一个真实角色,如果需要被代理的真实角色很多,我们就不得不写多个代理角色,这样就会使代码比较难维护;
这时候,就可以使用动态代理来解决这个问题:
动态代理
先写一个接口
package proxy;
public interface IGamePlayer {
public void login(String user, String password);
public void killBoss();
public void upgrade();
}
在写一个实现类:
package proxy;
public final class GamePlayer implements IGamePlayer {
private String name = "";
public GamePlayer(String name) {
this.name = name;
}
@Override
public void login(String user, String password) {
System.out.println("登陆" + user + " " + password);
}
@Override
public void killBoss() {
System.out.println("打怪");
}
@Override
public void upgrade() {
System.out.println("升级");
}
}
下面写一个动态代理类:
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class GamePlayIH implements InvocationHandler {
// 被代理者 感觉这个可以不要,不要也是可以执行的
Class cls = null;
// 被代理的实例
Object obj = null;
// 我要代理谁
public GamePlayIH(Object _obj){
this.obj = _obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(obj, args);
return result;
}
}
在客户端上进行测试:
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
// 定义一个痴迷的玩家
IGamePlayer player = new GamePlayer("张三");
// 定义一个handler
InvocationHandler handler = new GamePlayIH(player);
// 开始打游戏,记下时间戳
System.out.println("开始时间是:2020-8-8 10:22");
// 获得类的class loader
ClassLoader cl = player.getClass().getClassLoader();
// 动态产生一个代理者
IGamePlayer proxy = (IGamePlayer)Proxy.newProxyInstance(cl, new Class[]{IGamePlayer.class}, handler);
// 登录
proxy.login("zhangsan","password");
// 开始杀怪
proxy.killBoss();
// 记录游戏结束时间
System.out.println("结束时间是: 2020-8-8 12:11");
}
}
结果如下:
这就是动态代理,当我们需要增加一个功能,比如说:游戏登录后发一个信息给我们;
这时候,我们就可以修改GamePlayIH动态代理类;
在进行测试:结果如下:
动态代理:首先要实现一个动态代理类:如上是GamePlayIH类;然后,执行动态代理对象的所有方法时,都会被替换成执行它的invoke方法;
然后,动态产生一个代理者,执行它的方法就是执行动态代理类的invoke方法了;
注意实现动态代理的首要条件,被代理类必须实现一个接口;而不能实现类:那么为什么呢?
我们首先获取代理对象的.class文件的方法,这样就可以获取到该对象了;
会发现该对象会继承Proxy,然后,再实现我们的IGamePlayer接口:
这个时候,我们就可以分析出来 它为什么调用了Proxy.newProxyInstance() 方法后,就产生了代理对象,同时,为什么被代理类要实现一个接口;因为Java不支持多继承,而JDK的动态代理在创建代理对象时,默认让代理对象继承了Proxy类,所以,JDK只能通过接口去实现动态代理;$Proxy0实现了IGamePlayer接口,所以重写了接口中的三个方法;
当调用upgrade()时,它会去调用super.h.invoke方法,super代表的是父类,h就是我们定义的InvocationHandler
所以,会调用到GamePlayIH.invoke()方法,然后在通过反射method.invoke()去调用目标对象的方法;在这里做到增强的作用;