代理设计模式
代理模式(Proxy),为其它对象提供一种代理以控制对这个对象的访问。如下图
从上面的类图可以看出,通过代理模式,客户端访问接口时的实例实际上是Proxy对象,Proxy对象持有RealSubject的引用,这样一来Proxy在可以在实际执行RealSubject前后做一些操作,相当于是对RealSubject的Reques方法做了增强。
/** * @author kangming.ning * @date 2021/5/8 9:51 */ public class Client { public static void main(String[] args) { Subject subject = new Proxy(); subject.request(); } }
Proxy类对RealSubject的request方法进行了增强
/** * @author kangming.ning * @date 2021/5/8 9:49 */ public class Proxy implements Subject { private RealSubject realSubject; public Proxy() { realSubject = new RealSubject(); } @Override public void request() { System.out.println("proxy do something before"); realSubject.request(); System.out.println("proxy do something after"); } }
代理设计模式就是这么简单,但却很好用。像上面这种提前设计好的代理可以称为静态代理,因为它是针对某个需要增强的接口直接进行编码设计的。这种方式事实上用的很少,因为它需要针对每个需要增强的接口添加代理类,试想类似于mybatis的Mapper代理如果都是静态代理,是不是会导致我们编写大量代理类,实在麻烦。所以项目中通常都是使用动态代理,所谓动态代理,可以简单理解为代理类可以在运行时动态创建并加载到JVM中,下面做详细介绍。
动态代理
动态代理:从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
其本质和静态代理是一样的,只不过被设计为可以动态生成代理类,使用更加方便,在实际的开发中有大量应用。比如spring的AOP(Aspect Oriented Programming) 切面编程这种唬人的技术,其本质不过就是利用代理模式对方法进行增强。当然spring aop用的是动态代理技术,再具体就是利用JDK动态代理或CGLIB动态代理对针对接口或类生成动态代理类,然后实际执行方法时,实际执行的代理类的逻辑,代理类可以在方法前后做一些操作(增强)。
JDK动态代理
JDK动态代理Proxy是JDK提供的一个用于创建动态代理的一个工具类。下面用一个简单例子说明如何应用JDK动态代理
首先还是需要有被代理的接口,自定股票买卖行为
/** * 股票接口 * @author kangming.ning * @date 2024-01-19 16:40 * @since 1.0 **/ public interface StockService { /** * 购买股票接口 * @param stockName 买的哪个股票 * @param totalMoney */ void buyStock(String stockName,double totalMoney); /** * 卖出股票接口 * @param stockName 卖的哪个股票 * @param totalMoney */ void sellStock(String stockName,double totalMoney); }
接口的实现 ,即买卖股票需要做的一些事情。
/** * @author kangming.ning * @date 2024-01-19 16:54 * @since 1.0 **/ public class StockServiceImpl implements StockService { @Override public void buyStock(String stockName, double totalMoney) { System.out.println("成功购买了股票" + stockName + " 共" + totalMoney + "元"); } @Override public void sellStock(String stockName, double totalMoney) { System.out.println("成功卖出了股票" + stockName + " 共" + totalMoney + "元"); } }
没有代理的情况,买卖这些事情是需要股民自己去做的
/** * 没有代理的情况 * * @author kangming.ning * @date 2024-01-22 09:50 * @since 1.0 **/ public class StockDirectClient { public static void main(String[] args) { StockService stockService = new StockServiceImpl(); stockService.buyStock("001", 100); stockService.sellStock("002", 200); } }
而有代理的情况,通常表现为,我们买卖的基金,其背后实际是大部分是股票(偏股基金),基金经理可以认为是我们的代理。
/** * 韭菜侠(又称为基民) * @author kangming.ning * @date 2024-01-19 16:57 * @since 1.0 **/ public class FragrantFloweredGarlicMan { public static void main(String[] args) { //韭菜侠发现投资商机,基金好像跌到底部了,果断去抄底 StockService stockService = new StockServiceImpl(); StockService proxy = (StockService)Proxy.newProxyInstance( //目标类的类加载器 stockService.getClass().getClassLoader(), stockService.getClass().getInterfaces(), new StockInvocationHandler(stockService)); proxy.buyStock("003",100); proxy.sellStock("004",200); } }
StockInvocationHandler如下
/** * @author kangming.ning * @date 2024-01-19 17:06 * @since 1.0 **/ public class StockInvocationHandler implements InvocationHandler { /** * 代理中的真实对象 */ private final Object target; public StockInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //调用方法之前,我们可以添加自己的操作 System.out.println("before method " + method.getName()); //执行被代理对象原方法 Object invoke = method.invoke(target, args); //调用方法之后,我们同样可以添加自己的操作 System.out.println("after method " + method.getName()); return invoke; } }
上面的打印结果类似
before method buyStock 成功购买了股票003 共100.0元 after method buyStock before method sellStock 成功卖出了股票004 共200.0元 after method sellStock
可以看出,通过动态代理,对原接口调用前后都分别处理了额外的逻辑,和静态代理实现的效果是一致的。只是动态代理不需要事先编辑好相关代理类,而是在执行过程中动态生成代理类,这样一来,接口变动我们也不必修改代理类,所有调整适配工作都在InvocationHandler的实现类里处理。
JDK动态代理原理
通过上面的案例,我们知道怎么去使用JDK代理了。下面探讨一下其实现原理。Proxy创建动态代理的方法如下
/** * Returns an instance of a proxy class for the specified interfaces * that dispatches method invocations to the specified invocation * handler. * *{@code Proxy.newProxyInstance} throws * {@code IllegalArgumentException} for the same reasons that * {@code Proxy.getProxyClass} does. * * @param loader the class loader to define the proxy class * @param interfaces the list of interfaces for the proxy class * to implement * @param h the invocation handler to dispatch method invocations to * @return a proxy instance with the specified invocation handler of a * proxy class that is defined by the specified class loader * and that implements the specified interfaces * @throws IllegalArgumentException if any of the restrictions on the * parameters that may be passed to {@code getProxyClass} * are violated * @throws SecurityException if a security manager, s, is present * and any of the following conditions is met: *
-
*
- the given {@code loader} is {@code null} and * the caller's class loader is not {@code null} and the * invocation of {@link SecurityManager#checkPermission * s.checkPermission} with * {@code RuntimePermission("getClassLoader")} permission * denies access; *
- for each proxy interface, {@code intf}, * the caller's class loader is not the same as or an * ancestor of the class loader for {@code intf} and * invocation of {@link SecurityManager#checkPackageAccess * s.checkPackageAccess()} denies access to {@code intf}; *
- any of the given proxy interfaces is non-public and the * caller class is not in the same {@linkplain Package runtime package} * as the non-public interface and the invocation of * {@link SecurityManager#checkPermission s.checkPermission} with * {@code ReflectPermission("newProxyInPackage.{package name}")} * permission denies access. *
先通过提供的接口和类加载器创建出代理类,明显这句代码就是创建动态代理的核心逻辑
/* * Look up or generate the designated proxy class. */ Class> cl = getProxyClass0(loader, intfs);
断点跟踪进去
/** * a cache of proxy classes */ private static final WeakCache[], Class>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); private static Class> getProxyClass0(ClassLoader loader, Class>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }
proxyClassCachhe定义了一个ProxyClassFactory,明显是用来生成代理的,跟踪进去,最终能在这个工厂类看到如下核心生成代理类的逻辑
/* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); }
返回后,通过代理类直接调用接口方法会直接进入到InvocationHandler 的invoke方法了,因为这里的代理类是动态生成节点码再加载到JVM中的,所以断点并不能看出为什么会进入invoke方法。要明白这点,我们可以通过上面的ProxyGenerator来生成一个代理class节点码文件,再反编译就可以看到动态代理的类定义。
public static void main(String[] args) { createProxyClassFile(); } private static void createProxyClassFile() { //代理对象名称 随便起就行 String name = "stockServiceJdkProxy"; byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{StockService.class}); FileOutputStream out = null; try { out = new FileOutputStream(name + ".class"); System.out.println((new File("hello")).getAbsolutePath()); out.write(data); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (null != out) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
生成的代理类如下
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // import com.javaguide.basic.proxy.dynamicproxy.jdkfundproxy.StockService; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class stockServiceJdkProxy extends Proxy implements StockService { private static Method m1; private static Method m2; private static Method m4; private static Method m3; private static Method m0; public stockServiceJdkProxy(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 void sellStock(String var1, double var2) throws { try { super.h.invoke(this, m4, new Object[]{var1, var2}); } catch (RuntimeException | Error var5) { throw var5; } catch (Throwable var6) { throw new UndeclaredThrowableException(var6); } } public final void buyStock(String var1, double var2) throws { try { super.h.invoke(this, m3, new Object[]{var1, var2}); } catch (RuntimeException | Error var5) { throw var5; } catch (Throwable var6) { throw new UndeclaredThrowableException(var6); } } 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"); m4 = Class.forName("com.basic.proxy.dynamicproxy.jdkfundproxy.StockService").getMethod("sellStock", Class.forName("java.lang.String"), Double.TYPE); m3 = Class.forName("com.basic.proxy.dynamicproxy.jdkfundproxy.StockService").getMethod("buyStock", Class.forName("java.lang.String"), Double.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()); } } }
这个代理类继承了Proxy类实现了我们提供的接口StockService,静态代码块中,将两个接口通过Class.forName("xxx").getMethod的加载了接口方法定义到成员Method m3,m4变量中。然后就是对接口的实现方法
public final void sellStock(String var1, double var2) throws { try { super.h.invoke(this, m4, new Object[]{var1, var2}); } catch (RuntimeException | Error var5) { throw var5; } catch (Throwable var6) { throw new UndeclaredThrowableException(var6); } } public final void buyStock(String var1, double var2) throws { try { super.h.invoke(this, m3, new Object[]{var1, var2}); } catch (RuntimeException | Error var5) { throw var5; } catch (Throwable var6) { throw new UndeclaredThrowableException(var6); } }
可以看出只有一句核心代码 super.h.invoke(this, m4, new Object[]{var1, var2}); super指的是Proxy类,于是回头找找super.h是啥
/** * the invocation handler for this proxy instance. * @serial */ protected InvocationHandler h;
就是Proxy里面的一个 InvocationHandler。再回头看看我们的InvocationHandler实现
package com.basic.proxy.dynamicproxy.jdkfundproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author kangming.ning * @date 2024-01-19 17:06 * @since 1.0 **/ public class StockInvocationHandler implements InvocationHandler { /** * 代理中的真实对象 */ private final Object target; public StockInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //调用方法之前,我们可以添加自己的操作 System.out.println("before method " + method.getName()); //执行被代理对象原方法 Object invoke = method.invoke(target, args); //调用方法之后,我们同样可以添加自己的操作 System.out.println("after method " + method.getName()); return invoke; } }
所以动态代理在调用某个代理方法时,实际就是调用了 InvocationHandler的invoke方法,第一个参数proxy即为代理类本身,第二个参数method为当前调用方法的方法对象,有了它,就可以利用反射调用实际的方法。最后一个是参数列表传进来的值,反射调用某个方法必须要提供实现方法的对象和调用方法所必须的参数值。可以看到,对于方法的具体实现类,我们是可以自由替换,或者直接不提供实现,通过方法的定义的处理一些业务(mybatis的Mapper动态代理就是这样处理的),总的来讲是非常灵活的。
CGLIB动态代理
JDK动态代理只能代理实现了接口的类,一个没实现任何接口的类是不能被JDK的Proxy进行代理的。CGLIB动态代理则可以完成这个任务,下面看一个简单的使用案例。
引用cglib依赖
cglib cglib3.3.0
声明被代理类(为了演示与JDK动态代理的区别,这里使用一个没实现接口的类)
/** * @author kangming.ning * @date 2024-01-22 13:38 * @since 1.0 **/ public class StockService { public void buyStock(String stockName, double totalMoney) { System.out.println("成功购买了股票" + stockName + " 共" + totalMoney + "元"); } /** * CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法 * 此方法无法增强 */ public final void sellStock(String stockName, double totalMoney) { System.out.println("成功卖出了股票" + stockName + " 共" + totalMoney + "元"); } }
创建cglib动态代理
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @author kangming.ning * @date 2024-01-22 13:46 * @since 1.0 **/ public class CglibStockTest { public static void main(String[] args) { StockService cglibProxy = (StockService) getCglibProxy(StockService.class); cglibProxy.buyStock("001",100); cglibProxy.sellStock("002",300); } private static Object getCglibProxy(Class> clazz) { //创建动态代理增强类 Enhancer enhancer = new Enhancer(); //是否使用缓存 enhancer.setUseCache(false); //设置类加载器 enhancer.setClassLoader(clazz.getClassLoader()); //设置代理类 enhancer.setSuperclass(clazz); //设置方法拦截器 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { //调用方法之前,我们可以添加自己的操作 System.out.println("before method " + method.getName()); Object result = proxy.invokeSuper(obj, args); //调用方法之后,我们同样可以添加自己的操作 System.out.println("after method " + method.getName()); return result; } }); //创建代理对象 Object proxy = enhancer.create(); return proxy; } }
执行打印
before method buyStock 成功购买了股票001 共100.0元 after method buyStock 成功卖出了股票002 共300.0元
可以看到,cglib可以正常代理没实现接口的普通类。但由于CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。从上面的执行结果可以看到,被final修饰的方法并没增强。
猜你喜欢
网友评论
- 搜索
- 最新文章
- 热门文章