代理模式 代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
举一个例子:老师要批作业,可以找学习委员代收,学一委员整理好之后,再送到老师的办公室。这个场景中,学习委员就是一个代理。
##为什么要用代理模式
中介隔离作用: 在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
开闭原则,增加功能: 代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
静态代理 静态代理,就是采用代理模式思想,通过硬编码定义一个代理类,来实现代理的功能。由于静态代理灵活性较弱,在实际应用中,大多数采用动态代理模式。
动态代理模式 jdk动态代理 Jdk动态代理是基于接口的。
Step1 定义一个interface Hello
1 2 3 public interface Hello { void sayHello (String str) ; }
1 2 3 4 5 6 7 8 9 10 11 12 public final class HelloImpl implements Hello { private int [] array; public HelloImpl () { this .array = new int [1024 *10 ]; } @Override public void sayHello (String str) { System.out.println("hello " + str); } }
Step3 实现InvocationHandler的invoke方法
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 LogInvocationHandler implements InvocationHandler { private Hello hello; public LogInvocationHandler (Hello hello) { this .hello = hello; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if ("sayHello" .equals(method.getName())) { System.out.println("You said: " + Arrays.toString(args)); } return method.invoke(hello, args); } public static void main (String[] args) { Object proxy = Proxy.newProxyInstance(LogInvocationHandler.class.getClassLoader(), new Class<?>[]{Hello.class}, new LogInvocationHandler(new HelloImpl())); Hello hello = (Hello) proxy; hello.sayHello("world" ); System.out.println(hello); } }
但是Jdk动态代理使用上,有局限性。不能所有的类,都要实现一个interface,这会大大增加代码的冗余。如果使用继承的话,就能够解决这个问题。
CGLIB cglib通过继承来实现动态代理,cglib的底层实现,是基于节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括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 ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1 ]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } magic: 作为一个魔数,确定这个文件是否是一个能被虚拟机接受的class 文件,值固定为0xCAFEBABE 。 minor_version ,major_version :分别表示class 文件的副,主版本号,不同版本的虚拟机实现支持的Class 文件版本号不同。constant_pool_count :常量池计数器,constant_pool_count 的值等于常量池表中的成员数加1。constant_pool :常量池,constant_pool 是一种表结构,包含class 文件结构及其子结构中引用的所有字符常量、类或接口名、字段名和其他常量。access_flags :access_flags 是一种访问标志,表示这个类或者接口的访问权限及属性,包括有ACC_PUBLIC ,ACC_FINAL ,ACC_SUPER 等等。this_class :类索引,指向常量池表中项的一个索引。super_class :父类索引,这个值必须为0或者是对常量池中项的一个有效索引值,如果为0,表示这个class 只能是Object 类,只有它是唯一没有父类的类。interfaces_count :接口计算器,表示当前类或者接口的直接父接口数量。interfaces[]:接口表,里面的每个成员的值必须是一个对常量池表中项的一个有效索引值。 fields_count:字段计算器,表示当前class 文件中fields 表的成员个数,每个成员都是一个field_info 。 fields :字段表,每个成员都是一个完整的fields_info 结构,表示当前类或接口中某个字段的完整描述,不包括父类或父接口的部分。methods_count :方法计数器,表示当前class 文件methos 表的成员个数。methods :方法表,每个成员都是一个完整的method_info 结构,可以表示类或接口中定义的所有方法,包括实例方法,类方法,以及类或接口初始化方法。attributes_count :属性表,其中是每一个attribute_info ,包含以下这些属性,InnerClasses ,EnclosingMethod ,Synthetic ,Signature ,Annonation 等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class HelloConcrete { public final String sayHello (String str) { return "HelloConcrete: " + str; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept (Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("You said: " + Arrays.toString(args)); return proxy.invokeSuper(obj, args); } public static void main (String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(HelloConcrete.class); enhancer.setCallback(new MyMethodInterceptor()); HelloConcrete hello = (HelloConcrete)enhancer.create(); System.out.println(hello.sayHello("I love you!" )); } }
动态代理最佳实践 - SpringAOP 关于什么是SpringAOP,在此不继续阐述。
springAOP既采用了jdk proxy,也采用了cglib。 spring会根据情况,来选择jdk动态代理还是cglib动态代理。
如果要代理的对象实现了接口,则默认采用jdk proxy。
如果要代理的对象实现了接口,也可以强制使用cglib。需要配置<aop:aspectj-autoproxy proxy-target-class=”true”/>
如果要代理的对象没有实现接口,则采用cglib动态代理。
但是注意,敲黑板!敲黑板!敲黑板!
采用cglib的前提下,final类不能进行动态代理 会抛出异常java.lang.IllegalArgumentException: Cannot subclass final class xxx(final类不能被继承,所以你懂得)。
采用cglib的前提下,final方法也不能进行动态代理,不过不会抛出异常,而是直接跳过功能增强。
采用jdk proxy,final类和final方法都能正常工作。