由代理的创建时期,可以将代理分为静态代理和动态代理。
动态代理可以类比多态。
定义
代理模式就是给一个对象提供一个代理,我们可以通过代理对象实现对原对象的控制和引用。
代理对象就是对原对象的代理,任何想与原对象进行交互的行为都需要通过代理对象来实现。
代理模式使得原对象不必暴露在外,由代理对象作为中介与外部交互,在一定程度上保护了原对象。同时也减少了系统的耦合度。
代理模式可以:一 保护目标对象,二 增强目标对象。
优缺点
优点: 1、职责清晰。 2、高扩展性。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
模式结构
类图

注:图片来自菜鸟教程
静态代理
静态代理指的是代理类人为地创建代理类,即在程序运行前代理类的 .class 文件就已经存在。
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 31 32 33 34 35
| package proxy; public class ProxyTest { public static void main(String[] args) { Proxy proxy = new Proxy(); proxy.Request(); } }
interface Subject { void Request(); }
class RealSubject implements Subject { public void Request() { System.out.println("访问真实主题方法..."); } }
class Proxy implements Subject { private RealSubject realSubject; public void Request() { if (realSubject == null) { realSubject = new RealSubject(); } preRequest(); realSubject.Request(); postRequest(); } public void preRequest() { System.out.println("访问真实主题之前的预处理。"); } public void postRequest() { System.out.println("访问真实主题之后的后续处理。"); } }
|
运行结果:
1 2 3
| 访问真实主题之前的预处理。 访问真实主题方法... 访问真实主题之后的后续处理。
|
🌰2:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| 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(); } }
public class ProxyPatternDemo { public static void main(String[] args) { Image image = new ProxyImage("test_10mb.jpg"); image.display(); System.out.println(""); image.display(); } }
|
动态代理
动态代理指的是运行时通过反射机制动态地创建。
JDK 动态代理
JDK 动态代理机制中有两个重要的类和接口:
- InvocationHandler(接口)
- Proxy(类)
Proxy 和 InvocationHandler 是 JDK 实现动态代理的核心
InvocationHandler
官方描述
{@code InvocationHandler} is the interface implemented by the invocation handler of a proxy instance.
When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the {@code invoke} method of its invocation handler.
解释:
首先,InvocationHandler 是一个接口。
其次,Proxy 类中存在 InvocationHandler 成员变量,每个代理实例都要关联一个实现了 InvocationHandler 接口的调用类。
当我们通过动态代理对象调用一个方法的时候,这个方法的调用就会被转发到实现 InvocationHandler 接口类的 invoke 方法来调用。
1 2 3 4 5 6 7
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
|
Proxy
Proxy 是一个可以用来创建代理类实例的类,newProxyInstance方法可以创建一个代理类对象,返回的是指定接口的代理类实例。
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation
handler.
1 2 3
| public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
|
- ClassLoader loader:一个 classloader 对象,定义了由哪个 classloader 对象对生成的代理类进行加载
- Class<?>[] interfaces:一个interface对象数组,表示我们代理对象需要实现的接口。如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
- InvocationHandler h:一个 InvocationHandler 对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上
代理对象不需要实现接口,但是目标对象(被代理的对象)一定要实现接口,否则不能用动态代理。
代码实现
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public interface Eat { public void eat(); } public class Breakfast implements Eat{ @Override public void eat(){ System.out.println("恰早餐"); } } package proxy.dynamic;
public class Dinner implements Eat{ @Override public void eat(){ System.out.println("恰晚饭"); } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public class EatHandler implements InvocationHandler { private Eat eat; public EatHandler(){ } public EatHandler(Eat eat){ this.eat = eat; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); System.out.println("proxy.getClass().getName(): " + proxy.getClass().getName()); System.out.println("eat.getClass().getName(): " + eat.getClass().getName()); System.out.println("method.getClass().getName(): " + method.getName()); Object result = method.invoke(this.eat,args); after(); return result; }
private void before(){ System.out.println("做饭"); } private void after(){ System.out.println("洗碗"); } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy;
public class DynamicProxyDemo { public static void main(String args[]){ Eat eatLunch = new Lunch(); InvocationHandler eatHandler = new EatHandler(eatLunch);
Eat eatLunchProxy = (Eat)Proxy.newProxyInstance(eatLunch.getClass().getClassLoader(),eatLunch.getClass().getInterfaces(),eatHandler); eatLunchProxy.eat(); } }
|
1 2 3 4 5 6
| 做饭 proxy.getClass().getName(): com.sun.proxy.$Proxy0 eat.getClass().getName(): proxy.dynamic.Lunch method.getClass().getName(): eat 恰午饭 洗碗
|
invoke 接口中的 proxy 参数不能用于调用所实现接口的某些方法否则会栈溢出
CGLib 动态代理
JDK动态代理是基于接口的方式
被代理类需要有接口
代理类和目标类需要实现同一个接口
CGLib动态代理是代理类去继承目标类(不强制被代理类(目标类)有接口),然后重写其中目标类的方法,这样也可以保证代理类拥有目标类的同名方法。
JDK 动态代理提供一个 Proxy 类来创建代理类;
CGLib 动态代理提供类 Enhancer 创建代理类.
代码实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public class Dog { public void run(){ System.out.println("dog run" ); } } import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.printf("发起调用 obj: %s, method: %s, proxy: %s\n", o.getClass().getCanonicalName(), method.getName(), methodProxy.getClass().getCanonicalName() ); System.out.println("CGLIB 调用前"); Object object = methodProxy.invokeSuper(o, objects); System.out.println("CGLIB 调用后"); return object; } } package proxy.cglib;
import org.springframework.cglib.proxy.Enhancer;
public class CGLibProxy { static public void main(String[] args){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Dog.class); enhancer.setCallback(new CGLibInterceptor()); Dog dogProxy = (Dog)enhancer.create(); dogProxy.run();
} } 发起调用 obj: proxy.cglib.Dog$$EnhancerByCGLIB$$7adef180, method: run, proxy: org.springframework.cglib.proxy.MethodProxy CGLIB 调用前 dog run CGLIB 调用后
|
动态代理和静态代理的区别
静态代理和动态代理主要有以下几点区别:
- 静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,则代理类需要同步增加,违背开闭原则。
- 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
- 若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
应用场景
- 远程代理:这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
- 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
- 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
- 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
- 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。
参考
菜鸟教程
C语言中文网
互联网优秀博客