代理设计模式是我们日常开发中非常常见的设计模式。来看看下面的一段伪代码
class A{
void send(B b){
b.doingSomeThing();
}
}
这是一个标准的对象调用,由A调用B。此时的调用示意图是这样的。
那么某一天我们发现,B中的doingSomeThing()方法实现的功能不满足我们的需求了,或者我们要往doingSomeThing()里面加入新的需求。而B这个类是我们无权修改,例如是某个jar提供的类。那么这个时候就要用到代理设计模式来生成B类的代理类,在不改变B类的功能的前提下拓展B类的功能代码。
而实现代理方式一般有两种:
- 静态代理
- 动态代理
静态代理
在写静态代理之前,我们需要思考几个问题。
而为了实现代码的灵活性,传统的代理必须得有一个公共的接口,例如参考上面的B,如果B是一个接口,那么整个代码就很灵活,代理对象也只需要实现这个接口就行了,这样就能被当做入参传入,并实现相应的功能。
接下来想想代理类里要有什么,或者代理类要是什么样的,才能实现对应功能?
- 代理类肯定实现了和被代理对象一样的接口,也就是业务接口。
- 代理对象肯定要持有被代理对象的引用,这样才能调用对应的方法。
我们静态代理代码可以尝试着这么写:
业务接口
public interface IMessageSender{
/** 发送消息 */
void sendMessage(String message);
}
被代理类
public final class DefaultMessageSender implements IMessageSender{
@Override
public void sendMessage(String message){
System.out.print("发送消息:" + message);
}
}
客户端类
public class Phone{
public void sendMessage(String msg){
IMessageSender messageSender = new DefaultMessageSender();
messageSender.sendMessage(msg);
}
}
如果发送消息时,需要打开链接,发送完成以后需要关闭链接。那么代理类可以这么写
代理类
public Class MessageSenderProxy implements IMessageSender{
private IMessageSender messageSender;
public MessageSenderProxy(IMessageSender messageSender){
this.messageSender = messageSender;
}
private void connect(){
System.out.print("打开链接");
}
private void close(){
System.out.print("关闭链接");
}
@Override
public void sendMessage(String message){
connect();
messageSender.sendMessage(message);
close();
}
}
此时客户端类可以这么调用
public class Phone{
public void sendMessage(String msg){
IMessageSender messageSender = new MessageSenderProxy(new DefaultMessageSender());
messageSender.sendMessage(msg);
}
}
这样,就实现了一个标准的静态代理。由MessageSenderProxy来增强了原先的DefaultMessageSender。
缺陷
虽然代理类增强了被代理类的功能,但是代理类也必须实现对应的接口,意味着如果某一天接口新增了一个方法,那么代理类也需要做对应的修改。这种就是代理类与接口的强耦合,显然是不灵活也不合理的。
如果能解除接口、代理对象的耦合,让代理对象更关注增强逻辑的编写,而不需要关心所代理的接口是最好的。
如果能有一个类似于工厂模式的类,其中有一个方法入参是接口、被代理的接口对象、将要增强的逻辑,然后返回一个代理类就好了。强大的JDK开发人员当然想到了这种需求,所以就出现了动态代理
动态代理
动态代理的方式有两种,一种是JDK自带的动态代理,以及CGLIB动态代理。这里只介绍基于JDK的Proxy来实现的动态代理。
Proxy类,在其位置在java.lang.reflect包中。通过这个类可以动态的生成代理类
动态代理生成的写法法:
public class Test {
@org.junit.Test
public void test(){
IMessageSender defaultSender = new DefaultMessageSender();
Class<? extends IMessageSender> defaultSenderClass = defaultSender.getClass();
//创建代理对象
IMessageSender messageSenderProxy = (IMessageSender)Proxy.newProxyInstance(defaultSenderClass.getClassLoader(), defaultSenderClass.getInterfaces(), new MessageInvocationHandler(defaultSender));
Phone phone = new Phone();
phone.send(messageSenderProxy,"我来了");
}
/** 增强逻辑 */
class MessageInvocationHandler implements InvocationHandler{
/** 持有对象 */
private Object object;
public MessageInvocationHandler(Object object){
this.object = object;
}
private void connect(){
System.out.println("打开链接");
}
private void close(){
System.out.println("关闭链接");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
connect();
//反射调用
Object invoke = method.invoke(object, args);
close();
return invoke;
}
}
}
执行结果
和静态代理的结果是一样的。
Proxy.newProxyInstance()
方法结构
/**
* 生成代理类
* @param loader 类加载器
* @param interfaces 所需要实现的接口
* @param h 增强逻辑
* @throws
* @return java.lang.Object
* @author zhy
* @date 2021/8/19 16:30
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
return null;
}
- 类加载器
有类加载器就说明,这个方法可以往JVM中写入字节码信息。就是通过此类加载器生成的代理类。
- interfaces
所需要增强的接口,生成的代理类其实和静态代理里的写法一样,肯定是要实现接口的,这样才能实现增强具体的方法。
- InvocationHandler
增强逻辑所在的接口,稍后会详细看一下接口的结构。
InvocationHandler
我称之为增强业务类的规范接口,接口中只有一个方法:
interface InvocationHandler{
/**
* 调用代理方法,任何调用被代理类的方法都会走到此方法,由此方法进行调用被代理类的原始方法
* @param proxy 被代理的对象,这个参数用的一般不多
* @param method 要执行的接口方法名称
* @param args 传递的参数
* @throws Throwable 方法调用出现的错误,向外抛出
* @return java.lang.Object 方法的返回值
* @author zhy
* @date 2021/8/20 15:59
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
可以看到invoke方法其实是一个基于反射实现的方法,我们通过Proxy生成了代理类,而客户端类通过代理类调用的所有的接口声明的方法都会走到这个invoke()方法,我们在invoke()方法中对调用逻辑进行增强,添加我们所需要的业务逻辑。
通过观察这个方法,还可以发现在这个方法中获取很多有用的信息,例如我们可以根据method对象获取方法名,对不同的方法进行代理,如果方法名相同,我们还可以通过args参数列表来判断我们所需要代理的方法。还可以拿到返回值,进行处理。总之这个方法能实现很多功能。
总结
静态代理现在已经不常用了。但是静态代理对我们帮助我们初步了解代理设计模式。因为知道了其缺陷,我们才能更好的理解动态代理。
动态代理之所以被称之为动态,是因为这种模式能动态的创建代理对象,不需要硬编码去显式的编写任何一个代理类,这样很好的解决了静态代理所带来的的耦合问题,让接口和增强逻辑实现了解耦,让增强类只关注于增强逻辑的实现,而其中的invoke()方法的入参,则可以帮助我们拿到调用的所有信息,进行代理逻辑的细化。
动态代理还有一个特点就是动态对象的创建,这个动态对象生成在内存中,是由JVM去动态创建的,主要依靠的就是Proxy来完成的,由newProxyInstance()方法来完成,该方法其中的入参有个类加载器ClassLoader,获取了类加载器,Proxy就可以往JVM中写入字节码信息。而Proxy要写入的就是代理类的字节码信息。至于其底层如何实现,我现在也不知道,因为真的很底层了,都是一些JVM的操作。
而动态代理从生成代理对象到调用结束的时序图是这样的: