反射和动态代理放有一定的相关性, 但单纯的说动态代理是由反射机制实现的, 其实是不够全面不准确的, 动态代理是一种功能行为, 而它的实现方法有很多. 要怎么理解以上这句话, 请看下文.
一, 反射
反射机制是 Java 语言提供的一种基础功能, 赋予程序在运行时自省 (introspect, 官方用语) 的能力. 通过反射我们可以直接操作类或者对象, 比如获取某个对象的类定义, 获取类声明的属性和方法, 调用方法或者构造对象, 甚至可以运行时修改类定义.
1, 获取类 (Class) 对象
获取类对象有三种方法:
通过 forName() -> 示例: Class.forName("PeopleImpl")
通过 getClass() -> 示例: new PeopleImpl().getClass()
直接获取. class -> 示例: PeopleImpl.class
2, 类的常用方法
getName(): 获取类完整方法;
getSuperclass(): 获取类的父类;
newInstance(): 创建实例对象;
getFields(): 获取当前类和父类的 public 修饰的所有属性;
getDeclaredFields(): 获取当前类 (不包含父类) 的声明的所有属性;
getMethod(): 获取当前类和父类的 public 修饰的所有方法;
getDeclaredMethods(): 获取当前类 (不包含父类) 的声明的所有方法;
更多方法: http://icdn.apigo.cn/blog/class-all-method.png
3, 类方法调用
反射要调用类中的方法, 需要通过关键方法 "invoke()" 实现的, 方法调用也分为三种:
静态 (static) 方法调用
普通方法调用
私有方法调用
以下会分别演示, 各种调用的实现代码, 各种调用的公共代码部分, 如下:
- // 此段代码为公共代码
- interface People {
- int parentAge = 18;
- public void sayHi(String name);
- }
- class PeopleImpl implements People {
- private String privSex = "男";
- public String race = "汉族";
- @Override
- public void sayHi(String name) {
- System.out.println("hello," + name);
- }
- private void prvSayHi() {
- System.out.println("prvSayHi~");
- }
- public static void getSex() {
- System.out.println("18 岁");
- }
- }
3.1 静态方法调用
- // 核心代码(省略了抛出异常的声明)
- public static void main(String[] args) {
- Class myClass = Class.forName("example.PeopleImpl");
- // 调用静态 (static) 方法
- Method getSex = myClass.getMethod("getSex");
- getSex.invoke(myClass);
- }
静态方法的调用比较简单, 使用 getMethod(xx) 获取到对应的方法, 直接使用 invoke(xx)就可以了.
3.2 普通方法调用
普通非静态方法调用, 需要先获取类示例, 通过 "newInstance()" 方法获取, 核心代码如下:
- Class myClass = Class.forName("example.PeopleImpl");
- Object object = myClass.newInstance();
- Method method = myClass.getMethod("sayHi",String.class);
- method.invoke(object,"老王");
getMethod 获取方法, 可以声明需要传递的参数的类型.
3.3 调用私有方法
调用私有方法, 必须使用 "getDeclaredMethod(xx)" 获取本类所有什么的方法, 代码如下:
- Class myClass = Class.forName("example.PeopleImpl");
- Object object = myClass.newInstance();
- Method privSayHi = myClass.getDeclaredMethod("privSayHi");
- privSayHi.setAccessible(true); // 修改访问限制
- privSayHi.invoke(object);
除了 "getDeclaredMethod(xx)" 可以看出, 调用私有方法的关键是设置 setAccessible(true) 属性, 修改访问限制, 这样设置之后就可以进行调用了.
4, 总结
1. 在反射中核心的方法是 newInstance() 获取类实例, getMethod(..) 获取方法, 使用 invoke(..) 进行方法调用, 通过 setAccessible 修改私有变量 / 方法的访问限制.
2. 获取属性 / 方法的时候有无 "Declared" 的区别是, 带有 Declared 修饰的方法或属性, 可以获取本类的所有方法或属性 (private 到 public), 但不能获取到父类的任何信息; 非 Declared 修饰的方法或属性, 只能获取 public 修饰的方法或属性, 并可以获取到父类的信息, 比如 getMethod(..) 和 getDeclaredMethod(..).
二, 动态代理
动态代理是一种方便运行时动态构建代理, 动态处理代理方法调用的机制, 很多场景都是利用类似机制做到的, 比如用来包装 RPC 调用, 面向切面的编程(AOP).
实现动态代理的方式很多, 比如 JDK 自身提供的动态代理, 就是主要利用了上面提到的反射机制. 还有其他的实现方式, 比如利用传说中更高性能的字节码操作机制, 类似 ASM,cglib(基于 ASM)等.
动态代理解决的问题?
首先, 它是一个代理机制. 如果熟悉设计模式中的代理模式, 我们会知道, 代理可以看作是对调用目标的一个包装, 这样我们对目标代码的调用不是直接发生的, 而是通过代理完成. 通过代理可以让调用者与实现者之间解耦. 比如进行 RPC 调用, 通过代理, 可以提供更加友善的界面. 还可以通过代理, 可以做一个全局的拦截器.
1,JDK Proxy 动态代理
JDK Proxy 是通过实现 InvocationHandler 接口来实现的, 代码如下:
- interface Animal {
- void eat();
- }
- class Dog implements Animal {
- @Override
- public void eat() {
- System.out.println("The dog is eating");
- }
- }
- class Cat implements Animal {
- @Override
- public void eat() {
- System.out.println("The cat is eating");
- }
- }
- // JDK 代理类
- class AnimalProxy implements InvocationHandler {
- private Object target; // 代理对象
- public Object getInstance(Object target) {
- this.target = target;
- // 取得代理对象
- return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("调用前");
- Object result = method.invoke(target, args); // 方法调用
- System.out.println("调用后");
- return result;
- }
- }
- public static void main(String[] args) {
- // JDK 动态代理调用
- AnimalProxy proxy = new AnimalProxy();
- Animal dogProxy = (Animal) proxy.getInstance(new Dog());
- dogProxy.eat();
- }
如上代码, 我们实现了通过动态代理, 在所有请求之前和之后打印了一个简单的信息.
注意: JDK Proxy 只能代理实现接口的类(即使是 extends 继承类也是不可以代理的).
JDK Proxy 为什么只能代理实现接口的类?
这个问题要从动态代理的实现方法 newProxyInstance 源码说起:
- @CallerSensitive
- public static Object newProxyInstance(ClassLoader loader,
- Class<?>[] interfaces,
- InvocationHandler h)
- throws IllegalArgumentException
- {
- // 省略其他代码
来看前两个源码参数说明:
- * @param loader the class loader to define the proxy class
- * @param interfaces the list of interfaces for the proxy class to implement
loader: 为类加载器, 也就是 target.getClass().getClassLoader()
interfaces: 接口代理类的接口实现列表
所以这个问题的源头, 在于 JDK Proxy 的源码设计. 如果要执意动态代理, 非接口实现类就会报错:
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx
2,Cglib 动态代理
JDK 动态代理机制只能代理实现了接口的类, Cglib 是针对类来实现代理的, 他的原理是对指定的目标类生成一个子类, 并覆盖其中方法实现增强, 但因为采用的是继承, 所以不能对 final 修饰的类进行代理.
Cglib 可以通过 Maven 直接进行版本引用, Maven 版本地址: https://mvnrepository.com/artifact/cglib/cglib
本文使用的是最新版本 3.2.9 的 Cglib, 在 pom.xml 添加如下引用:
- <dependency>
- <groupId>cglib</groupId>
- <artifactId>cglib</artifactId>
- <version>3.2.9</version>
- </dependency>
Cglib 代码实现, 如下:
- class Panda {
- public void eat() {
- System.out.println("The panda is eating");
- }
- }
- class CglibProxy implements MethodInterceptor {
- private Object target; // 代理对象
- public Object getInstance(Object target) {
- this.target = target;
- Enhancer enhancer = new Enhancer();
- // 设置父类为实例类
- enhancer.setSuperclass(this.target.getClass());
- // 回调方法
- enhancer.setCallback(this);
- // 创建代理对象
- return enhancer.create();
- }
- @Override
- public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
- System.out.println("调用前");
- Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用
- System.out.println("调用后");
- return result;
- }
- }
- public static void main(String[] args) {
- // CGLIB 动态代理调用
- CglibProxy proxy = new CglibProxy();
- Panda panda = (Panda)proxy.getInstance(new Panda());
- panda.eat();
- }
cglib 的调用通过实现 MethodInterceptor 接口的 intercept 方法, 调用 invokeSuper 进行动态代理的, 可以直接对普通类进行动态代理.
三, JDK Proxy VS Cglib
JDK Proxy 的优势:
最小化依赖关系, 减少依赖意味着简化开发和维护, JDK 本身的支持, 更加可靠;
平滑进行 JDK 版本升级, 而字节码类库通常需要进行更新以保证在新版上能够使用;
Cglib 框架的优势:
可调用普通类, 不需要实现接口;
高性能;
总结: 需要注意的是, 我们在选型中, 性能未必是唯一考量, 可靠性, 可维护性, 编程工作量等往往是更主要的考虑因素, 毕竟标准类库和反射编程的门槛要低得多, 代码量也是更加可控的, 如果我们比较下不同开源项目在动态代理开发上的投入, 也能看到这一点.
本文所有示例代码: https://github.com/vipstone/java-core-example.git
来源: https://www.cnblogs.com/vipstone/p/10104020.html