0
点赞
收藏
分享

微信扫一扫

谈谈反射机制,动态代理基于什么原理

谈谈反射机制,动态代理基于什么原理

Java 反射机制?

反射机制是Java语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

  • ​​https://docs.oracle.com/javase/tutorial/reflect/index.html​​

反射提供的 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对于跨越不同对象或类的分散、纠缠逻辑表现力不够,比如在不同模块的特定阶段做一些事情,类似日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等,你可以參考下面这张图。

谈谈反射机制,动态代理基于什么原理_动态代理image

通过代理静默地解决一些业务无关的问题,比如远程、安全、事务、日志、资源关闭……让应用开发者可以只关心他的业务

谈谈反射机制,动态代理基于什么原理_动态代理_02

微信号:程序员开发者社区

博客:王小明

关注我们,了解更多

关注公众号:【程序员开发者社区】 回复:资源


举报

相关推荐

0 条评论