代理模式
代理模型-结构型模式
目标
:为其他对象提供一种代理,以控制对这个对象的访问。说白了就是一个类代表另一个类的功能。
关键代码
:增加中间层,实现代理类和委托类组合(需要二者继承同一个接口)
注意事项
:
1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
参考网址
:菜鸟教程
代码分析(先看代码示例):
- 这个代理模式可以类比静态代理,主要有三个成员,抽象委托接口,代理类和委托类,代理类和委托类实现同一个接口(即抽象委托接口),不然的话,在外部调用时,不能用抽象委托接口(Image)引用接收代理类对象。
- 代理类只需要把委托类里的方法在自己的方法里包装一下(处理一下),即可以被外部调用。这里,有个点,需要注意,代理类通过聚合,持有了一个委托类的引用,正因为这个引用的存在才可以在代理类中调用委托类的方法
- UML类图如下
补充:
- 如果根据字节码的创建时机来分类,可以分为静态代理和动态代理.
- 所谓静态代理
也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
- 而动态代理
的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。- java的动态代理有两种实现方式,
JDK 动态代理和Cglib 动态代理
JDK动态代理:
原理:利用拦截器(拦截器必须实现InvocationHandler)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
要求:委托类(目标对象)需要实现接口
CGLIB 动态代理:
原理:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
要求:委托类(目标对象)没有实现接口,必须使用CGLIB,如果实现了接口可以强制使用CGLIB。
参考链接:
Java动态代理
Java 动态代理详解 推荐
代码示例
//步骤1、创建一个代理类和委托类都需要实现的接口
interface Image {
void display();
}
// 步骤2、创建实现接口的委托类RealImage,代理类ProxyImage
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);
}
}
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没有初始化,会触发RealImage的构造器,第二次调用时,realImage不为null,构造器不会触发
realImage = new RealImage(fileName);
}
realImage.display();
}
}
// 步骤3、当被请求时,使用ProxyImage来获取RealImage类的对象
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// 图像将从磁盘加载
image.display();
System.out.println();
// 图像不需要从磁盘加载
image.display();
}
}
动态代理补充
针对JDK 动态代理,前提知识代理模式,静态代理及其缺点以及实现
三个主要成员
代理类 | 拦截器 | 委托类 |
---|---|---|
Proxy类的newProxyInstance() | 实现InvocationHandler接口 | 无 |
拦截器
拦截器必须实现InvocationHandler接口,作为调用处理器“拦截”对代理类方法的调用。
// 调用处理程序,当调用代理对象的方法时,这个“调用”会转到invoke方法中。这样一来,我们队代理类中所有的方法调用都会变成对invoke的调用,这样我们可以在invoke()方法中添加统一处理逻辑,也可以根据method参数对不同的代理方法做不同的处理。
public interface InvocationHandler {
Object invoke (Object proxy, Method method, Object[] args);
}
invoke()函数参数解析
proxy
:代理类对象作为proxy参数传入
method
:参数method标识了我们具体调用的是代理类的哪个方法
args
:为method方法的参数
举例
拦截器类
/**
* 注意需实现InvocationHandler 接口
*/
public class LoggerInterceptor implements InvocationHandler {
private Object target;//目标对象的引用,这里设计成Object类型,更具通用性
public LoggerInterceptor(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] arg) throws Throwable {
System.out.println("Entered "+target.getClass().getName()+"-"+method.getName()+",with arguments{"+arg[0]+"}");
Object result = method.invoke(target, arg);//调用目标对象的方法
System.out.println("Before return:"+result);
return result;
}
}
代理类
用Proxy类的newProxyInstance方法来获取一个代理类实例,这个代理类实现了指定接口并且会把方法调用分发到指定的调用处理器,方法声明如下:
public static Object newProxyInstance ( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h ) throws IllegalArgumentException {}
loader
:定义了代理类的ClassLoader
interfaces
:代理类实现的接口列表
h
: 拦截器对象
举例
public class Main {
public static void main(String[] args) {
AppService target = new AppServiceImpl();//生成目标对象
//接下来创建代理对象
AppService proxy = (AppService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new LoggerInterceptor(target));
proxy.createApp("Kevin Test");
}
}
小总结
首先通过newProxyInstance()方法获取代理类实例,而后我们便可以通过这个代理类实例调用代理类方法,对代理类的方法的调用实际上都会调用拦截器的invoke()方法,在invoke()方法中调用委托类的相应方法,并且可以添加自己的处理逻辑。
以上参考
Java动态代理:https://juejin.im/post/5ad3e6b36fb9a028ba1fee6a