1 代理模式简介
代理(Proxy)模式或者叫委托模式是一种结构型设计模式,提供了对目标对象另外的访问方式—即通过代理对象访问目标对象。这样做的好处是可以在目标对象实现的基础上,扩展目标对象的功能。
现实生活中有好多类似代理的长江,比如所代购,打官司等。
proxy [ˈprɑːksi] 代理权,代表权;代理人
代理模式主要涉及的三个角色:
- 接口(Subject):目标类和代理类都应该实现的接口;
- 目标类(Real Subject)是真正用来完成业务功能的类;
- 代理类(Proxy):持有对目标类的引用,自身的请求用目标类来实现(在其所实现的接口方法中调用目标类中相应的接口方法执行),代理类并不真正的去做业务功能;
以下是代理模式的结构图:
通过以下代码来说明代理模式。比如说有一个歌手对象是 Singer,这个对象有一个唱歌方法是 sing():
public class Singer {
public void sing() {
System.out.println("sing a song");
}
}
假如这个时候,希望通过某种方式创建的歌手对象,在唱歌前后还要向观众问好和答谢,也就是对目标对象的 sing() 方法进行功能扩展:
public void sing() {
System.out.println("say hello");
System.out.println("sing a song");
System.out.println("say thanks");
}
这个时候如果不能对源代码进行修改,就可以考虑代理模式。
2 静态代理
静态代理在程序运行前就已经将代理类的源码编译成字节码文件,代理类和委托类的关系在运行前就已经确定了。
静态代理需要先定义接口,被代理的对象和代理对象一起实现相同的接口,代理类持有目标类的引用,然后通过调用相同的方法来调用目标对象的方法。 以下代码是静态代理的实现方式:
public interface ISinger {
void sing();
}
public class Singer implements ISinger {
@Override
public void sing() {
System.out.println("sing a song");
}
}
public class SingerProxy implements ISinger {
private Singer singer;
@Override
public void sing() {
System.out.println("say hello");
if (singer == null) {
singer = new Singer();
}
singer.sing();
System.out.println("say thanks");
}
}
class SingTest {
public static void main(String[] args) {
SingerProxy singerProxy = new SingerProxy();
singerProxy.sing();
}
}
// say hello
// sing a song
// say thanks
目标类 Singer 和代理类 SingerProxy 都实现接口 ISinger。在代理类 SingerProxy.sing() 方法中调用了目标类 Singer.sing() 方法,并做了拓展。
静态代理相当于多写了一个代理类,在调用的时候调用的是代理类中的相关逻辑。这样做的缺点是:需要为每个被代理的对象创建一个代理类。
3 动态代理
从编码的角度来看,代理模式分为静态代理和动态代理,上面的例子是静态代理,在代码运行前就已经存在了代理类 .class 的编译文件,而动态代理则是在代码运行时通过反射来动态地生成代理类的对象,来确定到底代理谁,也就是在编码阶段不需要知道代理谁,而是在代码运行时决定的。
Java 提供了动态的代理接口 InvocationHandler,实现该接口需要重写 invoke 方法。和静态代理一样,是对代理类的扩展。由于 Java 底层封装了实现细节,格式基本上是固定的。调用 Proxy 类的静态方法 newProxyInstance 即可,该方法会返回代理类对象:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
其中的三个参数依次为:
- ClassLoader loader:指定当前目标对象使用类加载器,写法固定;
- Class<?>[] interfaces:目标对象实现的接口类型,写法固定;
- InvocationHandler h:事件处理接口,需要传入一个实现类,一般直接使用匿名内部类;
invocation [ˌɪnvəˈkeɪʃn](计算机)调用,启用;(法权的)行使
以下是动态代理的相关代码:
class SingTest {
public static void main(String[] args) {
Singer singer = new Singer();
ISinger proxy = (ISinger) Proxy.newProxyInstance(singer.getClass().getClassLoader(), singer.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("say hello");
Object returnValue = method.invoke(singer, args);
System.out.println("say thanks");
return returnValue;
}
});
proxy.sing();
}
}
动态代理类是在程序运行期间由 JVM 根据反射等机制动态生成的,所以不会有代理类的字节码文件,代理类和委托类的关系是在程序运行时确定的。
参考
https://www.cnblogs.com/baorantHome/p/16136213.html
https://www.jb51.net/article/222323.htm
https://wenku.baidu.com/view/d21555f130d4b14e852458fb770bf78a65293a91.html
https://blog.csdn.net/qq_41931364/article/details/122662857