zoukankan      html  css  js  c++  java
  • 【Java】代理模式,静态代理和动态代理(基于JDK或CGLib)

    当我们需要在一个方法之前或之后添加一段逻辑时,自然会想到使用代理类。代理类帮我们代理了实际类的调用,然后可以在实际调用之前和之后添加一些逻辑,从而不浸入实际类。

    拓展:由于代理类能在实际类调用之前和之后添加逻辑,那么可做的事情就多了,常见的有4种,用AOP的术语描述就是:

    • 前置增强:在实际方法前添加逻辑。比如,在方法执行前打印入参;在方法执行前判断用户是否有执行此方法的权限
    • 后置增强:在实际方法后添加逻辑。比如,在方法执行后打印结果
    • 环绕增强:在实际方法之前和之后都添加逻辑。
    • 抛出增强:当实际方法发生异常时执行添加的逻辑。

    不用代理

    有时,需要在一些方法前后都打印一些日志。

    这是一个处理float类型加法的方法,我想在调用它前打印一下参数,调用后打印下计算结果。(至于为什么不直接用+号运算,见【Java】Float计算不准确

    package com.nicchagil.study.java.demo.No09代理.No01不用代理;
    
    import java.math.BigDecimal;
    
    public class FloatCalculator {
    
        public float add(float a, float b) {
            
            BigDecimal b1 = new BigDecimal(a + "");
            BigDecimal b2 = new BigDecimal(b + "");
            float f = b1.add(b2).floatValue();
            
            return f;
            
        }
    }

    我想在它运行前后打印,最直接的方式就是调用时打印了

    package com.nicchagil.study.java.demo.No09代理.No01不用代理;
    
    public class Call {
    
        public static void main(String[] args) {
            float f1 = 1f;
            float f2 = 1f;
            
            System.out.println("f1 -> " + f1 + ", f2 -> " + f2);
            float result = new FloatCalculator().add(f1, f2);
            System.out.println("result -> " + result);
        }
    }

    看到这日志,我很欣慰!

    f1 -> 1.0, f2 -> 1.0
    result -> 2.0

    静态代理

    随着项目变大,调用此方法的地方变得越来越多,如果有10个调用的地方,我岂不是要写100次打印的方法。

    这时,静态代理的方式能帮助我们。

    定义个接口

    package com.nicchagil.study.java.demo.No09代理.No02静态代理;
    
    
    public interface ICalculator {
        
        /**
         * <p>add</p>
         */
        public float add(float a, float b);
        
    }

    真实业务类

    package com.nicchagil.study.java.demo.No09代理.No02静态代理;
    
    import java.math.BigDecimal;
    
    public class FloatCalculator implements ICalculator {
    
        @Override
        public float add(float a, float b) {
            
            BigDecimal b1 = new BigDecimal(a + "");
            BigDecimal b2 = new BigDecimal(b + "");
            float f = b1.add(b2).floatValue();
            
            return f;
            
        }
        
    }

    代理类,这个类中,处理执行实际业务,还一并捆绑打印日志的任务

    package com.nicchagil.study.java.demo.No09代理.No02静态代理;
    
    
    
    public class FloatCalculatorProxy implements ICalculator {
        
        ICalculator c = null;
        
        /**
         * 构造方法
         * @param c    需被代理的对象
         */
        public FloatCalculatorProxy(ICalculator c) {
            super();
            this.c = c;
        }
    
        @Override
        public float add(float f1, float f2) {
            System.out.println("f1 -> " + f1 + ", f2 -> " + f2);
            
            float result = this.c.add(f1, f2);
            
            System.out.println("result -> " + result);
            return result;
        }
        
    }

    然后,我们调用时,只需调用代理类,不仅计算得结果,日志也乖乖地出来了

    package com.nicchagil.study.java.demo.No09代理.No02静态代理;
    
    public class Call {
        
        public static void main(String[] args) {
            System.out.println("代理的对象:");
            ICalculator c2 = new FloatCalculatorProxy(new FloatCalculator());
            c2.add(1f, 1f);
        }
    
    }

    看到日志,我很镇静

    代理的对象:
    f1 -> 1.0, f2 -> 1.0
    result -> 2.0

    动态代理

    JDK基于接口的动态代理

    如果现在不仅FloatCalculator这个类需要打印日志,还有其他各种类也需要打印日志,那么我们岂不是要写好多个代理类了?

    这时需要使用动态代理。JDK有提供动态代理的实现,通过反射机制为我们实现动态代理。

    接口类(ICalculator)、真实业务类(FloatCalculator)如同静态代理,不再重复

    调用处理类。这个类实现InvocationHandler,主要任务是:

    • 注入被调用对象
    • 调用被调用对象的对应方法(在调用方法前后可自行添加逻辑
    package com.nicchagil.study.java.demo.No09代理.No03动态代理;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class DynamicProxyHandler implements InvocationHandler {
        
        private Object proxied = null;
        
        public DynamicProxyHandler(Object proxied) {
            this.proxied = proxied;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            System.out.println("Clazz    -> " + proxy.getClass());
            System.out.println("method    -> " + method);
            for (int i = 0; i < args.length; i++) {
                System.out.println("args[" + i + "]    -> " + args[i]);
            }
            
            Object result = method.invoke(proxied, args);
            
            System.out.println("result    -> " + (result != null ? result.toString() : ""));
            return result;
        }
    
    }

    调用类。这里通过Proxy的newProxyInstance方法生成代理类,这个方法的入参有3个:

    • 被代理类的类加载器
    • 被代理类实现的接口(这也是JDK动态代理的缺点之一,需实现接口。基于此,Spring的AOP在类有实现接口时,使用JDK动态代理,无实现接口时,使用CGlib)
    • 被代理对象
    package com.nicchagil.study.java.demo.No09代理.No03动态代理;
    
    import java.lang.reflect.Proxy;
    
    import com.nicchagil.study.java.demo.No09代理.No02静态代理.FloatCalculator;
    import com.nicchagil.study.java.demo.No09代理.No02静态代理.ICalculator;
    
    public class Call {
        
        public static void main(String[] args) {
            /* 代理的对象 */
            System.out.println("代理的对象:");
            ICalculator c2 = (ICalculator)Proxy.newProxyInstance(ICalculator.class.getClassLoader(), 
                    new Class[] {ICalculator.class}, new DynamicProxyHandler(new FloatCalculator()));
            c2.add(1f, 1f);
        }
    
    }

    日志

    代理的对象:
    Clazz    -> class $Proxy0
    method    -> public abstract float com.nicchagil.study.java.demo.No09代理.No02静态代理.ICalculator.add(float,float)
    args[0]    -> 1.0
    args[1]    -> 1.0
    result    -> 2.0

    在main方法加入如下代码可生成动态代理类的代码:

    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

    你会在com.sun.proxy包下发现动态生成的代理类:

    package com.sun.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements ICalculator {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final float add(float var1, float var2) throws  {
            try {
                return (Float)super.h.invoke(this, m3, new Object[]{var1, var2});
            } catch (RuntimeException | Error var4) {
                throw var4;
            } catch (Throwable var5) {
                throw new UndeclaredThrowableException(var5);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m3 = Class.forName("ICalculator").getMethod("add", Float.TYPE, Float.TYPE);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }

    查看代码,你会发现:

    1. extends Proxy implements ICalculator
    2. 有私有的、静态的Method变量,在静态代码块用反射赋予变量具体的引用
    3. 构造方法的入参为InvocationHandler对象,在本例中就是DynamicProxyHandler对象,也就是具体的方法中的super.h,本代理中会直接调用之前我们重写的invoke方法
    4. 实现ICalculator的各方法,实现体为用反射调用Method变量、InvocationHandler对象

     super.h就是Proxy中的InvocationHandler,构造方法如下:

        protected Proxy(InvocationHandler h) {
            Objects.requireNonNull(h);
            this.h = h;
        }

     CGLib动态代理

    JDK动态代理有个缺点,只能对实现了接口的类进行代理,如果目标类没有实现接口,我们可以使用CGLib。

    引入相关包:

    <dependencies>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>
    </dependencies>

    定义了需被代理的UserService,方法拦截器类,和main方法:

    package com.nicchagil.exercise.cglib;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    public class Call {
    
        public static void main(String[] args) {
            UserService userServiceProxy = (UserService) Enhancer.create(UserService.class, new MyMethodInterceptor());
    
            userServiceProxy.getById("123");
            userServiceProxy.getById("Nick Huang");
        }
    
        /**
         * 测试的方法拦截器
         */
        static class MyMethodInterceptor implements MethodInterceptor {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("before logic, parameter : " + Arrays.toString(objects));
                Object result = methodProxy.invokeSuper(o, objects);
                System.out.println("after logic, result : " + result);
    
                return result;
            }
        }
    
        /**
         * User业务类
         */
        static class UserService {
    
            public Object getById(String id) {
                System.out.println("getById");
                return new Object();
            }
    
            public Object getByName(String name) {
                System.out.println("getByName");
                return new Object();
            }
        }
    
    }

    日志:

    before logic, parameter : [123]
    getById
    after logic, result : java.lang.Object@573fd745
    before logic, parameter : [Nick Huang]
    getById
    after logic, result : java.lang.Object@15327b79
  • 相关阅读:
    JAVA小技能-之远程调试
    征集系统功能开发进度总结
    征集系统功能开发进度总结
    linux常用的监控命令
    Makefile中 =、:=和 += 的区别
    Linux驱动编译错误:implicit declaration of function “copy_form_user”,“copy_to_user“
    Android 第一个驱动之 word_count(一)
    Ubuntu16.04 默认 gcc、g++ 版本过高会导致Android2.3.4 , Android 4.0.1_r1 编译报错
    降低 make 版本教程
    Tensorflow教程分享:TensorFlow 基础详解
  • 原文地址:https://www.cnblogs.com/nick-huang/p/4777290.html
Copyright © 2011-2022 走看看