zoukankan      html  css  js  c++  java
  • Java动态代理

    代理模式是指给某个对象提供一个代理对象,用户不直接访问原对象而是通过代理对象间接访问。

    我们可以使用代理模式实现面向切面编程(AOP), 由动态代理将切面功能织入目标方法而不侵入调用方的业务代码。

    或者使用代理模式实现远程过程调用(RPC), 调用方像调用本地方法一样调用代理方法,而不必关心代理调用远程方法细节。

    JDK提供了基于反射机制的动态代理实现,而被广泛使用的第三方库CGLIB则基于字节码操作框架ASM实现动态代理。

    本文将简单介绍两种动态代理使用方法,做抛砖引玉之用。

    content:

    代理模式

    实现静态代理模式是非常简单的, 首先我们定义一个接口:

    public interface MyInterface {
    
        void foo();
    
    }
    

    编写被代理的对象:

    public class MyService implements MyInterface {
    
        @Override
        public void foo() {
            System.out.println("foo");
        }
    }
    

    在代理模式中被代理的类通常被称作委托类。

    编写静态代理:

    public class MyProxy implements MyInterface {
    
        private MyInterface subject;
    
        MyProxy(MyInterface subject) {
            this.subject = subject;
        }
    
        @Override
        public void foo() {
            long start = System.System.currentTimeMillis();
            subject.foo();
            long elapseTime = System.currentTimeMillis() - start;
            System.out.println("elapse time: " + elapseTime);
        }
    }
    

    静态代理是指代理类在编译时生成,与之相对动态代理则是运行时生成代理类的字节码并加载到JVM中。

    静态代理的问题在于编写代理类时必须了解接口细节, 即编写MyProxy时必须了解MyInterface的定义。

    以AOP框架为例,框架无法预先了解接口信息只能在运行时根据Class对象创建代理对象,因此编写此类框架必须要有动态代理机制的支持。

    java动态代理

    Java的反射机制可以在运行时创建类,Java标准库中提供了基于反射的代理机制。

    代理类应该实现java.lang.reflect.InvocationHandler接口,该接口只有一个方法: invoke

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
    

    当我们通过代理访问委托类方法时,会调用代理类的invoke方法。我们可以通过invoke方法的参数获得调用信息,并用反射机制调用为委托类的方法。

    invoke方法的三个参数为:

    • Object porxy: 被调用方法的动态代理实例
    • Method method: 被调用的方法
    • Object[] args: 调用时传入的参数

    java.lang.reflect.Proxy.newProxyInstance方法用于创建代理对象,它使用反射机制在运行时创建了代理类并加载到JVM中。该方法有三个参数:

    • ClassLoader loader: 加载代理类的加载器
    • Class<?>[] interfaces: 代理类要实现的方法
    • InvocationHandler h: 在调用代理类方法时,嵌入调用过程的InvocationHandler实例

    直接描述较难理解,我们来看代码:

    public class MyProxy implements InvocationHandler {
    
        private Object subject;
    
        public MyProxy(Object subject) {
            this.subject = subject;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1
            return method.invoke(subject, args); 
        }
    
        @SuppressWarnings("unchecked")
        public static <T> T createProxy(T subject) {
            return (T)Proxy.newProxyInstance(subject.getClass().getClassLoader(), // 2
                    subject.getClass().getInterfaces(), new MyProxy(subject));
        }
    
        public static void main(String[] args) {
            MyInterface obj = createProxy(new MyService());  // 3
            obj.foo();
        }
    
    }
    

    MyInterfaceMyService和上文中完全相同:

    public interface MyInterface {
    
        void foo();
    
    }
    
    public class MyService implements MyInterface {
    
        @Override
        public void foo() {
            System.out.println("foo");
        }
    }
    

    MyProxy类实现了InvocationHandler接口,并提供了createProxy静态方法。

    main方法中我们为MyService对象创建了一个代理,并通过代理调用了foo方法。

    在代码中有几处细节值得了解:

    1. main方法中通过obj.getClass().getName()获得代理类的类名为:com.sun.proxy.$Proxy0。这个类名表示这个类是动态生成的Proxy类,0表示它是当前JVM中第一个动态代理类。

    2. 在注释2处我们通过newProxyInstance创建了代理类即上文中的$Proxy0类:

      1. loader为被代理类的加载器,也可以使用MyInterface.class.getClassLoader(),它们都是AppClassLoader实例。
      2. interfaces参数为subject.getClass().getInterfaces()表示动态代理类$Proxy0实现了subject的所有接口,但$Proxy0不是Subject类的子类。
        因此在main方法中使用MyInterface obj = createProxy(new MyService());而不能使用MyService obj = createProxy(new MyService());
      3. 我们使用MyProxy实例作为InvocationHandler拦截动态代理类所有方法调用。
    3. 在注释1处我们实现了invoke方法拦截动态代理对象所有方法调用:

      1. 传入的实参Object proxy是动态代理类的实例,即main方法中的obj, 它是$Proxy0类的实例。注意InvocationHandler实例不是动态代理实例,handler是编译生成的静态类。
      2. 传入的实参Method method是接口类的Method对象,即MyInterface.class.getMethod("foo")而非委托类MyService的Method对象
      3. method.invoke(subject, args)用反射的方式调用了被代理实例的方法,我们可以在invoke方法中添加其它代码以增强委托类的功能

    cglib动态代理

    CGLIB是一个强大的动态代理库,SpringAOP和dynaop框架使用CGLIB进行方法拦截和增强,Hibernate使用CGLIB进行代理关联,此外JMock也使用CGLIB提供Mock对象。

    在实现上,CGLIB使用高性能轻量级字节码操作框架ASM来动态生成代理类。值得一提的是,CGLIB比JDK动态代理还要快。

    CGLIB将动态代理类实现为委托类的子类,因此可以代理没有实现接口或对接口进行了扩展的类。因为子类无法覆盖final方法,因此只能代理非final方法。

    首先使用maven导入cglib依赖:

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.6</version>
    </dependency>
    

    编写CGLIB动态代理:

    public class CglibProxy {
    
        @SuppressWarnings("unchecked")
        public static <T> T createProxy(T subject) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(subject.getClass());
            enhancer.setCallback(new MyInterceptor());
            return (T) enhancer.create();
        }
    
        private static class MyInterceptor implements MethodInterceptor {
    
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                return proxy.invokeSuper(obj, args);
            }
        }
    
        public static void main(String[] args) {
            MyService obj = createProxy(new MyService());
            obj.foo();
        }
    
    }
    

    Enhancer用于对委托类进行增强,它可以拦截方法调用以实现代理机制。

    MethodInterceptor.intercept方法用于拦截方法调用,它的几个参数为:

    • Object obj: 动态代理对象
    • Method method: 被拦截的方法,本例main方法中被拦截的方法是: MyService.class.getMethod("foo")
    • Object[] args: 调用实参
    • MethodProxy proxy: 用来调用委托类方法的快捷代理,不是动态代理类。

    示例中定义的动态代理没有添加任何额外功能,这种特殊的Callback可以使用net.sf.cglib.proxy.NoOp代替:

        public static <T> T createProxy(T subject) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(subject.getClass());
            enhancer.setCallback(NoOp.INSTANCE);
            return (T) enhancer.create();
        }
    

    Enhancer可以使用CallbackFilter为每个方法调用配置过滤器:

    class MyService {
    
        void foo0() {
            System.out.println("0");
        }
    
        void foo1() {
            System.out.println("1");
        }
    
    }
    
    public class CglibProxy {
    
        @SuppressWarnings("unchecked")
        public static <T> T createProxy(T subject) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(subject.getClass());
    
            Callback callback0 = NoOp.INSTANCE;
            Callback callback1 = NoOp.INSTANCE;
            enhancer.setCallbacks(new Callback[]{callback0, callback1});
    
            enhancer.setCallbackFilter(new MyCallbackFilter());
            return (T) enhancer.create();
        }
    
        private static class MyCallbackFilter implements CallbackFilter {
    
            @Override
            public int accept(Method method) {
                if ("foo0".equals(method.getName())) {
                    return 0;
                } else {
                    return 1;
                }
            }
        }
    
        public static void main(String[] args) {
            MyService obj = createProxy(new MyService());
            obj.foo0();
            obj.foo1();
        }
    
    }
    

    当Enhancer设置了CallbackFilter之后,在拦截方法前会先调用CallbackFilter.accept()方法判断使用哪个Callback实例进行拦截。

    CallbackFilter.accept()返回值即为拦截器的下标,即若accept方法返回0,则调用enhancer.callbacks[0]进行拦截。

  • 相关阅读:
    Spring 中的邮件任务
    Spring 定时任务
    java中同步交互 与 异步交互
    Springboot 版本包冲突
    Derby 配置环境变量
    Springboot中的Web服务Tomcat改为Jetty
    二叉树中和为某一值的路径
    0-Java中this和super的用法总结
    树9:二叉搜索树的后序遍历
    位运算-输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
  • 原文地址:https://www.cnblogs.com/Finley/p/8733984.html
Copyright © 2011-2022 走看看