两种模式:静态代理、动态代理。
两个目的:保护目标对象、增强目标对象。
定义:代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问,属于结构型模式。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
应用场景:生活中的租房中介、售票黄牛、婚介、经纪人、快递、事务代理、非侵入式日志监听等,都是代理模式的实际体现。当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。
代理模式一般由共同接口类、被代理类、代理类组成。
静态代理
这里举一个父亲帮儿子找对象的案例。人有很多行为:要谈恋爱、要工作、要买房买车,谈恋爱只是行为中的一项。父亲和儿子有个共同的行为是找对象,于是抽取成一个共同接口。
共同接口类 Person:
1 2 3
| public interface Person { void findLove(); }
|
被代理类 Son:
1 2 3 4 5 6
| public class Son implements Person{ @Override public void findLove() { System.out.println("儿子要找对象,要求:富婆"); } }
|
代理类 Father:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Father implements Person{ private Son son;
public Father(Son son) { this.son = son; }
@Override public void findLove() { System.out.println("父亲帮儿子找富婆"); this.son.findLove(); System.out.println("儿子很强壮,富婆很喜欢,在一起了"); } }
|
测试类 Test:
1 2 3 4 5 6 7
| public class StaticMain { public static void main(String[] args) { Father father = new Father(new Son()); father.findLove(); } }
|
动态代理模式
反射(java.lang.reflect)
Class类:代表一个类。
Class是一个类,class是关键字。
Class类只有一个私有的构造函数,只有JVM能够创建Class类的实例。
JVM中只有唯一一个和类相对应的Class对象来描述其类型信息。
Field类:代表类的成员变量(成员变量也称为类的属性)。
Method类:代表类的方法。
Constructor类:代表类的构造方法。
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
获取Class对象的三种方式:
1、通过Object.getClass() 获取
2、任何数据类型都有一个静态的class属性
3、通过 Class.forName() 的静态方法获取(常用)
这里还是找对象的案例,但现在是让媒婆帮忙物色对象了,“我”对于媒婆来说是顾客,那么关系就是媒婆帮“我”找对象。下面基于JDK实现动态代理:
被代理类 顾客 Customer:
1 2 3 4 5 6 7 8
| public class Customer implements Person { @Override public void findLove() { System.out.println("[客户] 要求1:白富美"); System.out.println("[客户] 要求2:有车有房"); System.out.println("[客户] 要求3:有存款"); } }
|
代理类 媒婆 JDKMeipo:
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
| public class JDKMeipo implements InvocationHandler { private Object target;
public Object getProxyInstance(Object target) { this.target = target; Class<?> clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object o = method.invoke(target, args); after(); return o; }
private void before() { System.out.println("[媒婆] 正在查找对象..."); }
private void after() { System.out.println("[媒婆] 查找完成..."); } }
|
测试类 Test:
1 2 3 4
| public static void main(String[] args) { Person person = (Person) new JDKMeipo().getProxyInstance(new Customer()); person.findLove(); }
|
实现原理: JDK动态代理采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的。
- 获取被代理对象的引用,并且获取它的所有接口,反射获取。
- JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口。
- 动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码调用。
- 编译新生成的Java代码.class文件。
- 重新加载到JVM中运行。
使用 CGLib 动态代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class CGlibProxy implements MethodInterceptor { public Object getInstance(Class<?> clazz) throws Exception{ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); }
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object obj = methodProxy.invokeSuper(o,objects); after(); return obj; }
private void before(){ System.out.println("called before request()."); }
private void after(){ System.out.println("called after request()."); } }
|
- CGLib和JDK动态代理对比
1.JDK 动态代理实现了被代理对象的接口,CGLib 代理继承了被代理对象。
2.JDK 动态代理和CGLib代理都在运行期生成字节码,JDK 动态代理直接生成 Class 字节码,CGLib 代理使用ASM框架生成Class字节码,CGlib 代理实现更复杂,生成代理类比 JDK 动态代理效率低。
3.JDK动态代理调用代理方法是通过反射机制调用的,CGLib 代理是通过 FastClass 机制直接调用方法的,CGLib 代理的执行效率更高。
静态代理和动态代理的区别
1.静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违背开闭原则。
2.动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
3.若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无须修改代理类的代码。
代理模式在Spring中的应用
Spring利用动态代理实现AOP时有两个非常重要的类:JdkDynamicAopProxy类和CglibAopProxy类
代理选择原则:
1.当Bean有实现接口时,Spring就会用JDK动态代理。
2.当Bean 没有实现接口时,Spring 会选择CGLib代理。
3.Spring 可以通过配置强制使用CGLib代理,只需在Spring的配置文件中加入如下代码:
1
| <aop:aspectj-autoproxy proxy-target-class="true"/>
|
代理模式的优缺点
优点:
1.代理模式能将代理对象与真实被调用目标对象分离。
2.在一定程度上降低了系统的耦合性,扩展性好。
3.可以起到保护目标对象的作用。
4.可以增强目标对象的功能。
缺点:
1.代理模式会造成系统设计中类的数量增加。
2.在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢。
3.增加了系统的复杂度。
附:手写实现JDK的动态代理
动态代理和静态代理的基本思路是一致的,只不过动态代理的功能更加强大,随着业务的扩展适应性更强。下面是一个使用JDK动态代理实现的一个相亲找对象的案例。
JDK实现方式
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 58
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public class JDKMeipo implements InvocationHandler { private Object target;
public Object getInstance(Object target) { this.target = target; Class<?> clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object obj = method.invoke(this.target, args); after(); return obj; }
private void before() { System.out.println("我是媒婆,正在帮你找对象,现在已经确认了你喜欢哪种妹子了"); System.out.println("开始物色..."); }
private void after() { System.out.println("已经帮你找到这样的妹子了,觉得合适的话,就准备办喜事吧"); }
}
class Customer implements Person { @Override public void findLove() { System.out.println("富婆"); System.out.println("身高166cm"); System.out.println("无不良嗜好"); } }
interface Person { void findLove(); }
class TestApplication { public static void main(String[] args) { Person obj = (Person) new JDKMeipo().getInstance(new Customer()); obj.findLove(); } }
|
运行结果如下:
1 2 3 4 5 6
| 我是媒婆,正在帮你找对象,现在已经确认了你喜欢哪种妹子了 开始物色... 富婆 身高166cm 无不良嗜好 已经帮你找到这样的妹子了,觉得合适的话,就准备办喜事吧
|
手写实现JDK动态代理
JDK的动态代理是采用字节重组的方式实现的,重新生成对象来替代原始对象,以达到动态代理的目的。JDK动态代理生成对象的步骤如下:
(1)获取被代理对象的引用,并获取它的所有接口,反射获取;
(2)JDK动态代理类重新生成一个新的类,同时生成新的类要实现被代理类实现的所有接口;
(3)动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码调用(在代码中体现);
(4)编译新生成的Java代码为.class文件;
(5)重新加载到JVM运行;
以上过程称为字节重组。JDK中有一个规范,在ClassPath下只要是$开头的.class文件,一般都是自动生成的。那么我们有没有办法看到代替后的对象的“真容”呢?做一个这样的测试:将内存中的对象字节码通过文件流输出到新的.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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| import com.fun.async.test.proxy.Person; import java.lang.reflect.*;
public final class $proxy0 extends Proxy implements Person {
public $proxy0(InvocationHandler invocationhandler) { super(invocationhandler); }
public final boolean equals(Object obj) { try { return ((Boolean)super.h.invoke(this, m1, new Object[] { obj })).booleanValue(); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
public final void findLove() { try { super.h.invoke(this, m3, null); return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
public final String toString() { try { return (String)super.h.invoke(this, m2, null); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
public final int hashCode() { try { return ((Integer)super.h.invoke(this, m0, null)).intValue(); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
private static Method m1; private static Method m3; private static Method m2; private static Method m0;
static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("com.fun.async.test.proxy.Person").getMethod("findLove", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch(NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch(ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } }
|
从反编译的结果来看,$Proxy0 继承了Proxy类,同时还实现了Person接口,并且还重写了findLove()方法。在静态块中用反射查找到了目标的所有方法,而且保存了所有方法引用,重写的方法用反射调用目标对象的方法。上面这些代码是哪来的?这是JDK自动生成的。现在不依赖JDK,自己实现动态生成源代码、动态完成编译,然后替代对象并执行。