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

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

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

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

GZ号:程序员开发者社区

微信公众号

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie
应支付0元
点击重新获取
扫码支付

支付成功即可阅读