所有的努力,不是为了让别人觉得你了不起,而是为了能让自己打心底里看得起自己。
代理模式是设计模式中结构型模式的一种。当访问一个对象的时候因为一些原因不去访问这个对象,而是通过一个代理对象去访问这个对象,这个模式就是代理模式。
例如:购买火车票不去车站买,而是去代售点买票。
静态代理的使用需要被代理的对象和代理对象拥有相同的方法,因此实现同一个接口是最好的做法。
下面演示通过代理对象打印被代理对象方法的入参和出参信息:
实现的接口:
/** * @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 张三静态代理的缺点很明显,就是如果被代理对象改变了,那么代理对象就要跟着改变。如果有很多需要被代理的对象,就要写很多代理类,非常不方便。
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实现动态代理需要被代理对象实现接口。
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 修饰的方法也不能被代理,虽然不会报错,但是代理增强的功能会失效。