代理模式
- 在代理模式Proxy Pattern中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
- 意图:为其他对象提供一种代理,以控制对这个对象的访问。
- 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
- 优点: 1、职责清晰。 2、高扩展性。 3、智能化。
- 缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
- 使用场景:按职责来划分,通常有以下使用场景: 远程代理。Cache代理。防火墙(Firewall)代理。
- 注意事项:
1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
代理模式可以分为2种:静态代理模式和动态代理模式
静态模式:
//目标接口
public interface Image {
void display();
}
//真实对象
public class RealImage implements Image {真实对象
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
//代理对象
public class ProxyImage implements Image{
//代理对象中包含具体目标对象
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName){
this.fileName = fileName;
}
@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}
//从形式上看调用的是代理对象,不是具体工作的目标对象.
Image image = new ProxyImage("test.jpg");
//图像将从磁盘加载
image.display();
System.out.println("");
//图像将无法从磁盘加载
image.display();
缺点:需要目标接口,而且针对一个接口需要一个代理对象,可能会导致类爆炸问题
动态代理
有2种实现方式:
- 使用JDK的动态代理:要求采用JDK的反射机制实现,必须有对应的接口
- 使用 CGLib的动态代理:可以没有对应的接口
- 使用Cglib创建代理的效率略低于JDK动态代理
JDK反射包中的相关接口和工厂类
接口java.lang.reflect.InvocationHandler
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable; }
-proxy - 在其上调用方法的代理实例
-method - 对应于在代理实例上调用的接口方法的 Method 实例
-args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null
Proxy的静态方法newProxyInstance(ClassLoader loader, Class<>[] interfaces,InvocationHandler h)生成代理类
–loader - 定义代理类的类加载器
–interfaces - 代理类要实现的接口列表
–h - 指派方法调用的调用处理程序
案例1:
//目标接口,使用JDK的动态代理必须有对应的目标接口
public interface 歌星 {
void 唱歌();
}
//具体的目标类,实现目标接口
public class 刘德华 implements 歌星 {
@Override
public void 唱歌() {
System.out.println("冷冷的冰雨,在脸上胡乱的拍~~~");
}
}
//代理的回调处理类,不是具体的代理类,使用动态代理时没有具体的代理类
public class 宋喆的回调处理类 implements InvocationHandler {// 调用宋喆的任何方法实际上都是回调类对象处理
private Object target;
public 宋喆的回调处理类(Object target) {
this.target = target;
}
//调用代理对象的任何方法时,实际上就是执行这个回调方法,这里可以添加对应的判断,判断执行的是什么方法,例如唱歌方法或者演电影方法:arg1.getName():String获取方法名称
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
// 由代理对象限制目标对象的访问
System.out.println("签订演艺合同......");
Object res = arg1.invoke(target, arg2);
System.out.println("收钱收钱~~~~~~~");
return res;
}
}
//调用处不能直接生成代理对象,因为没有具体的代理类定义,代理对象需要依赖于反射包中的Proxy工具类生成。
歌星.class.getClassLoader()用于获取加载歌星接口定义的类加载器
歌星 proxy = (歌星) Proxy.newProxyInstance(歌星.class.getClassLoader(), new Class[] { 歌星.class, 影星.class这里定义所有可以代理的接口 },new 宋喆的回调处理类(new 刘德华()));// 动态创建一个对象对象,这个对象没有对应的类定义
proxy.唱歌();
案例2:
//接口
public interface 歌星 {public void 唱歌();}
//具体的实现
public class 汪峰 implements 歌星 {
@Override
public void 唱歌() {System.out.println("北京~北京~~~"); }
}
//没有对应的代理类,只有通过实现接口定义的代理回调处理类,调用代理对象的任何方法实际上都是执行invoke方法
public class 宋喆的回调处理类 implements InvocationHandler {
private Object 被代理对象;//这里的被代理对象使用Object进行定义,就是说可以代理任何对象,没有接口的束缚
public 宋喆的回调处理类(Object target){this.被代理对象=target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
System.out.println("签演艺合同!!!!!");
Object res=method.invoke(被代理对象, args); //意思是调用被代理对象的同名方法,并且传入参数args
System.out.println("收钱``````");
return res; }
}
//测试
歌星 obj = new 汪峰();
歌星 宋喆= (歌星) Proxy.newProxyInstance(歌星.class.getClassLoader(), new Class[] { 歌星.class }, new 宋喆的回调处理类(obj)); // 实际上是通过宋喆访问汪峰,由宋喆控制对汪峰的访问,这就是代理模式的核心
宋喆.唱歌(); //调用宋喆代理对象的任何方法实际上都是执行代理回调处理类中的invoke方法