谈谈反射机制,动态代理基于什么原理
Java 反射机制?
反射机制是Java语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。
反射提供的 Accessibleobject.setAccessible(boolean flag)。它的子类也大都重写了这个方法,这里的所谓 accessible可以理解成修饰成员的 public、 protected、 private,这意味着我们可以在运行时修改成员访问限制。
setAccessible 的应用场景非常普遍,遍布我们的日常开发、测试、依赖注入等各种框架中。比如,在O/R Mapping框架中,我们为一个Java实体对象,运行时自动生成 setter 和 getter 的逻辑。
class Employee { private int id; private String name; private int age; public Employee() { } public Employee(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } private void setId(int id) { this.id = id; } private int judge(int id) { return this.id - id; } private String sayHalo(String name) { return "Halo" + name; } } public class PrivateTest { public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Employee em = new Employee(1, "Alex", 22); // 获取Class对象 Class<?> emClass = em.getClass(); // 获取特定的声明了的方法 Method judgeMethod = emClass.getDeclaredMethod("judge", new Class[] { Integer.TYPE }); // setAccessible(boolean flag)使所有成员可以访问,访问之前设置 judgeMethod.setAccessible(true); // 获取所有声明的方法 Method[] allMethods = emClass.getDeclaredMethods(); // AccessibleObject.setAccessible(AccessibleObject[] array,boolean flag) //批量给访问权限 AccessibleObject.setAccessible(allMethods, true); // 下面就可以通过反射访问了 System.out.println(judgeMethod.invoke(em, new Object[] { 3 })); // or... for (Method method : allMethods) { } } }
动态代理
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装RPC调用、面向切面的编程(AOP) 实现动态代理的方式很多,比如JDK自身提供的动态代理,就是主要利用了上面提到的反射机制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似ASM、 Cglib(基于ASM)、 Javassist等
反射,引入运行时自省能力,赋予了Java语言令人意外的活力,通过运行时操作元数据或对象,Java可以灵活地操作运行时才能确定的信息。而动态代理,则是延伸岀来的一种广泛应用于产品开发中的技术,很多繁琐的重复编程,都可以被动态代理机制优雅地解决。
动态代理解决了什么问题?
首先,它是一个代理机制。设计模式中的代理模式,代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。其实很多动态代理场景,我认为也可以看作是装饰器( Decorator)模式的应用,通过代理可以让调用者与实现者之间解耦。比如进行RPC调用,框架内部的寻址、序列化、反序列化等。
JDK 动态代理
JDK 动态代理例子,实现了对应的 InvocationHandler ,然后以接口 Hello 为纽带,为被调用目标构建代理对象,进而应用程序就可以用代理对象间接运行调用目标的逻辑。
interface Hello{ void sayHello(); } class HelloImpl implements Hello{ public void sayHello() { System.out.println("Hello World"); }; } class MyInvocationHandler implements InvocationHandler{ private Object target; public MyInvocationHandler(Object target){ this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Invocking sayHello"); Object result = method.invoke(target, args); return result; } } public class MyDyamicProxy { public static void main(String[] args) { HelloImpl hello = new HelloImpl(); MyInvocationHandler hadler = new MyInvocationHandler(hello); // 构造代码示例 Hello myHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), hadler); // 调用代理方法 myHello.sayHello(); } }
但是 JDK 动态代理有个局限,它只能为接口创建代理,返回的代理对象也只能转换到接口类型,如果一个类没有接口,或者希望代理非接口中定义的方法。那 JDK 动态代理无法实现。
JDK 动态代理优点
- 最小化依赖关系,减少依赖意味着简化开发和维护, JDK 本身支持,比 CGLIb 可靠
- 平滑进行 JDK 版本升级,而字节码库通常需要进行更新保证最新 Java 上能够使用。
- 代码实现简单
CGLIB 动态代理
CGLIb 动态代理使用的是字节码技术, 采用的方式是创建代理类的子类,继承的方式
import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; public class CglibProxyHandler implements MethodInterceptor { /** * 维护目标对象 */ private Object target; public Object getProxyInstance(final Object target) { this.target = target; // Enhancer类是CGLIB中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展 Enhancer enhancer = new Enhancer(); // 将被代理的对象设置成父类 enhancer.setSuperclass(this.target.getClass()); // 回调方法,设置拦截器 enhancer.setCallback(this); // 动态创建一个代理类 return enhancer.create(); } @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("代理先进行谈判……"); // 唱歌需要明星自己来唱 Object result = methodProxy.invokeSuper(object, args); System.out.println("演出完代理去收钱……"); return result; } } public class Client { public static void main(String[] args) { Star realStar = new RealStar(); Star proxy = (Star) new CglibProxyHandler().getProxyInstance(realStar); proxy.sing(); } }
CGLIB 动态代理优点
- 调用对象可以不实现接口,可以打破 JDK 动态代理的限制
- 只关心操作的类,不必为其他相关类增加工作量
- 高性能
从性能角度,有人曾经得出结论说JDKProxγ比cgliB或者 Javassist慢几十倍。坦白说,不去争论具体的 benchmark细节,在主流JDK版本中,JDK Proxy在典型场景可以提供对等的性能水平,数量级的差距基本上不是广泛存在的。而且,反射机制性能在现代JDK中,自身已经得到了极大的改进和优化,同时,JDK很多功能也不完全是反射,同样使用了ASM进行字节码操作。
动态代理的应用
动态代理应用非常广泛,虽然最初多是因为RPC等使用进入我们视线,但是动态代理的使用场景远远不仅如此,它完美符合 Spring AOP等切面编程。简单来说它可以看作是对OOP的一个补充,因为OOP对于跨越不同对象或类的分散、纠缠逻辑表现力不够,比如在不同模块的特定阶段做一些事情,类似日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等,你可以參考下面这张图。