设计模式学习笔记:代理模式(Proxy)

    技术2022-07-11  120

    文章目录

    一、代理模式介绍二、Java 中代理的三种方式1、静态代理2、JDK 动态代理3、CGLIB 动态代理

    所有的努力,不是为了让别人觉得你了不起,而是为了能让自己打心底里看得起自己。

    一、代理模式介绍

    代理模式是设计模式中结构型模式的一种。当访问一个对象的时候因为一些原因不去访问这个对象,而是通过一个代理对象去访问这个对象,这个模式就是代理模式。

    例如:购买火车票不去车站买,而是去代售点买票。

    二、Java 中代理的三种方式

    1、静态代理

    静态代理的使用需要被代理的对象和代理对象拥有相同的方法,因此实现同一个接口是最好的做法。

    下面演示通过代理对象打印被代理对象方法的入参和出参信息:

    实现的接口:

    /** * @author ZhengNC * @date 2020/7/1 10:12 */ public interface Hello { String hello(String name); }

    被代理对象:

    /** * @author ZhengNC * @date 2020/7/1 10:12 */ public class HelloImpl implements Hello { @Override public String hello(String name) { System.out.println("执行 hello 方法。。。"); return "hello " + name; } }

    Hello 的日志代理对象:

    /** * Hello 的日志代理 * * @author ZhengNC * @date 2020/7/1 10:14 */ public class HelloLogProxy implements Hello { private Hello target; public HelloLogProxy(Hello target){ this.target = target; } @Override public String hello(String name) { System.out.println("入参:"+name); String result = target.hello(name); System.out.println("出参:"+result); return result; } }

    测试代码:

    /** * @author ZhengNC * @date 2020/7/1 10:17 */ public class Test { public static void main(String[] args) { //创建被代理的对象 Hello hello = new HelloImpl(); //创建代理对象 Hello helloLogProxy = new HelloLogProxy(hello); //执行对象的方法 String result = helloLogProxy.hello("张三"); //打印执行方法的返回结果 System.out.println(result); } }

    运行结果:

    入参:张三 执行 hello 方法。。。 出参:hello 张三 hello 张三

    静态代理的缺点很明显,就是如果被代理对象改变了,那么代理对象就要跟着改变。如果有很多需要被代理的对象,就要写很多代理类,非常不方便。

    2、JDK 动态代理

    JDK动态代理的特点:

    代理类是利用JDK的API动态生成的。不需要与被代理类实现同样的接口。

    同样使用打印日志的例子演示动态代理的使用:

    被代理对象实现的接口:

    /** * @author ZhengNC * @date 2020/7/1 10:24 */ public interface Hello { String hello(String name); }

    被代理对象:

    /** * @author ZhengNC * @date 2020/7/1 10:12 */ public class HelloImpl implements Hello { @Override public String hello(String name) { System.out.println("执行 hello 方法。。。"); return "hello " + name; } }

    创建代理对象的工厂:

    import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; /** * 创建对象的日志代理 * 代理对象在执行时会打印出入参和出参信息 * * @author ZhengNC * @date 2020/7/1 10:26 */ public class LogInvocation<T> implements InvocationHandler { /** * 被代理对象 */ private T target; public LogInvocation (T target){ this.target = target; } /** * * @param proxy 代理对象 * @param method 方法 * @param args 参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("入参:" + Arrays.toString(args)); Object result = method.invoke(target, args); System.out.println("出参:" + result); return result; } /** * 根据目标对象类型创建代理对象 * @param implClass * @param <T> * @return 代理对象 */ public static<T> T createProxy(Class implClass){ LogInvocation logInvocation = null; try { logInvocation = new LogInvocation(implClass.newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } T proxyObj = (T) Proxy.newProxyInstance(implClass.getClassLoader(), implClass.getInterfaces(), logInvocation); return proxyObj; } }

    测试代码:

    /** * @author ZhengNC * @date 2020/7/1 10:47 */ public class Test { public static void main(String[] args) { //创建代理对象 Hello proxyHello = LogInvocation.createProxy(HelloImpl.class); //执行对象的方法 String result = proxyHello.hello("张三"); //打印方法执行的结果 System.out.println(result); } }

    测试结果:

    入参:[张三] 执行 hello 方法。。。 出参:hello 张三 hello 张三

    JDK实现动态代理需要被代理对象实现接口。

    3、CGLIB 动态代理

    CGLIB 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。其底层是使用一个字节码框架ASM来转换字节码并生成新的类。

    CGLIB 实现需要引入 CGLIB 的 jar 包,Spring 的核心包中已经包括了 CGLIB 的功能。

    下面还是使用日志代理的例子演示 CGLIB 的代理实现:

    被代理的类:

    /** * @author ZhengNC * @date 2020/7/1 11:04 */ public class Hello { public String hello(String name) { System.out.println("执行 hello 方法。。。"); return "hello " + name; } }

    创建日志代理对象的工厂:

    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; /** * 创建对象的日志代理 * 代理对象在执行时会打印出入参和出参信息 * * @author ZhengNC * @date 2020/7/1 11:06 */ public class LogProxyFactory<T> implements MethodInterceptor { private T target; public LogProxyFactory(T target){ this.target = target; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("入参:" + Arrays.toString(objects)); Object result = method.invoke(target, objects); System.out.println("出参:" + result); return result; } public static<T> T createProxy(Class targetClass){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(targetClass); try { LogProxyFactory logProxyFactory = new LogProxyFactory(targetClass.newInstance()); enhancer.setCallback(logProxyFactory); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return (T) enhancer.create(); } }

    测试代码:

    /** * @author ZhengNC * @date 2020/7/1 11:13 */ public class Test { public static void main(String[] args) { //创建代理对象 Hello helloProxy = LogProxyFactory.createProxy(Hello.class); //执行对象的方法 String result = helloProxy.hello("张三"); //打印结果 System.out.println(result); } }

    测试结果:

    入参:[张三] 执行 hello 方法。。。 出参:hello 张三 hello 张三

    CGLIB 原理是创建对象的子类来实现代理。

    因此被代理的类不能是 final 修饰的,如果使用 final 修饰类在运行时会报错。

    被 final 或 static 修饰的方法也不能被代理,虽然不会报错,但是代理增强的功能会失效。

    Processed: 0.012, SQL: 10