本文以图片为例实现java版本的动静态代理,并分析他们的区别。
背景
我们使用一个具有展示功能的图片接口为例,通过代理类实现AOP功能,在展示的前后做一些事情。
静态代理
接口:
1 2 3
| public interface Image { void display(String name); }
|
实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class PngImage implements Image { @Override public void display(String name) { System.out.println("展示png图片:" + name); } }
public class JpgImage implements Image { @Override public void display(String name) { System.out.println("展示jpg图片:" + name); } }
|
代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class ImageProxy implements Image { private Image image;
public ImageProxy(Image image) { this.image = image; }
@Override public void display(String name) { System.out.println("调整下图片大小,美化一下..."); image.display(name); System.out.println("图片资源清理..."); } }
|
使用:
1 2 3 4 5 6 7
| final PngImage pngImage = new PngImage(); ImageProxy proxy = new ImageProxy(pngImage); proxy.display("jimo.png");
final JpgImage jpgImage = new JpgImage(); proxy = new ImageProxy(jpgImage); proxy.display("jimo.jpg");
|
结果:
1 2 3 4 5 6
| 调整下图片大小,美化一下... 展示png图片:jimo.png 图片资源清理... 调整下图片大小,美化一下... 展示jpg图片:jimo.jpg 图片资源清理...
|
动态代理
新的代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public class DynamicImageProxy {
public static void display(String name, Image image) { final Image proxiedImage = (Image) Proxy.newProxyInstance(Image.class.getClassLoader(), new Class[]{Image.class}, new ImageProxyHandler(image)); proxiedImage.display(name); }
private static class ImageProxyHandler implements InvocationHandler {
private Object image;
ImageProxyHandler(Object image) { this.image = image; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("调整下图片大小,美化一下..."); final Object invoke = method.invoke(image, args); System.out.println("图片资源清理..."); return invoke; } } }
|
需要解释一下Proxy.newProxyInstance()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 等价于: Proxy.getProxyClass(loader, interfaces). getConstructor(new Class[] { InvocationHandler.class }). newInstance(new Object[] { handler });
|
可以看到我们是通过反射来调用对象的方法。
使用:
1 2 3 4
| final PngImage pngImage = new PngImage(); DynamicImageProxy.display("jimo.png", pngImage);
DynamicImageProxy.display("jimo.jpg", new JpgImage());
|
结果当然是一样的:
1 2 3 4 5 6
| 调整下图片大小,美化一下... 展示png图片:jimo.png 图片资源清理... 调整下图片大小,美化一下... 展示jpg图片:jimo.jpg 图片资源清理...
|
动静态代理的区别
静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类:在程序运行时,运用反射机制动态创建而成。
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
说明:本文的静态代理并不只针对一个类,而是一个接口,实际上很多场景没有接口,只有一个类,针对每个类都需要写一个代理类。而java里的动态代理使用接口的代理范围更广泛。
应用场景
1.设计模式里的代理模式
下面是一个简单的例子:
2.AOP
比如spring的AOP,或者本文的例子。
3.RPC
- 关于RPC和RESTful接口的区别需要了解下:参考
知乎上有一段比较简明:
1 2 3 4 5 6
| http好比普通话,rpc好比团伙内部黑话。
讲普通话,好处就是谁都听得懂,谁都会讲。
讲黑话,好处是可以更精简、更加保密、更加可定制,坏处就是要求“说”黑话的那一方(client端)也要懂, 而且一旦大家都说一种黑话了,换黑话就困难了。
|
在我们使用Java实现RPC时,会遇到客户端和服务端都要调用同一个接口,所以那个接口应该是公用的。当然,学习和做demo的时候都是放在一个项目里,而项目开发可以提取出一个公共模块,专门存放远程接口,参考https://blog.csdn.net/gao763024185/article/details/79916406.
而今天我们关注RPC中的代理模式的使用,大家可以参考以下资料学习完整的RPC:
- dubbo
- RPC原理及JAVA实现
- Java实现简单的RPC框架
下面我们省略RPC里的服务端和客户端的网络请求以及注册中心,直接关注代理模块:
项目结构:
下面是代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class ClientProxy { private static ClientInvoker invoker = new ClientInvoker();
public static Object getObject(Class<?> clazz) { return Proxy.newProxyInstance( clazz.getClassLoader(), new Class<?>[]{clazz}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final Object params = new Object();
return invoker.invoke(params, "localhost", 8088); } }); } }
|
这样客户端就可以统一请求,获取自己需要的对象了:
1 2
| final HelloService helloService = (HelloService) ClientProxy.getObject(HelloService.class); helloService.sayHello();
|