响应式布局网站/产品软文范例800字
代理设计模式
代理模式(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();}@Overridepublic 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 {@Overridepublic void buyStock(String stockName, double totalMoney) {System.out.println("成功购买了股票" + stockName + " 共" + totalMoney + "元");}@Overridepublic 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;}@Overridepublic 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.** <p>{@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, <em>s</em>, is present* and any of the following conditions is met:* <ul>* <li> 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;</li>* <li> 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};</li>* <li> 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.</li>* </ul>* @throws NullPointerException if the {@code interfaces} array* argument or any of its elements are {@code null}, or* if the invocation handler, {@code h}, is* {@code null}*/@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}}
先通过提供的接口和类加载器创建出代理类,明显这句代码就是创建动态代理的核心逻辑
/** Look up or generate the designated proxy class.*/Class<?> cl = getProxyClass0(loader, intfs);
断点跟踪进去
/*** a cache of proxy classes*/private static final WeakCache<ClassLoader, Class<?>[], 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 ProxyClassFactoryreturn 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;}@Overridepublic 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依赖
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
声明被代理类(为了演示与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() {@Overridepublic 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修饰的方法并没增强。
CGLIB动态代理原理
在调用生成动态代理前,通过设置就可以得到动态代理的.class文件
public static void main(String[] args) {//设置动态代理的.class文件生成路径(用于分析,非必须)System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class");//生成动态代理StockService cglibProxy = (StockService) getCglibProxy(StockService.class);cglibProxy.buyStock("001",100);cglibProxy.sellStock("002",300);}
会生成3个.class文件
先看最核心的代理类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package com.proxy.dynamicproxy.stockproxy.cglibproxy;import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;public class StockService$$EnhancerByCGLIB$$f50a685c extends StockService implements Factory {private boolean CGLIB$BOUND;public static Object CGLIB$FACTORY_DATA;private static final ThreadLocal CGLIB$THREAD_CALLBACKS;private static final Callback[] CGLIB$STATIC_CALLBACKS;private MethodInterceptor CGLIB$CALLBACK_0;private static Object CGLIB$CALLBACK_FILTER;private static final Method CGLIB$buyStock$0$Method;private static final MethodProxy CGLIB$buyStock$0$Proxy;private static final Object[] CGLIB$emptyArgs;private static final Method CGLIB$equals$1$Method;private static final MethodProxy CGLIB$equals$1$Proxy;private static final Method CGLIB$toString$2$Method;private static final MethodProxy CGLIB$toString$2$Proxy;private static final Method CGLIB$hashCode$3$Method;private static final MethodProxy CGLIB$hashCode$3$Proxy;private static final Method CGLIB$clone$4$Method;private static final MethodProxy CGLIB$clone$4$Proxy;static void CGLIB$STATICHOOK1() {CGLIB$THREAD_CALLBACKS = new ThreadLocal();CGLIB$emptyArgs = new Object[0];Class var0 = Class.forName("com.basic.proxy.dynamicproxy.stockproxy.cglibproxy.StockService$$EnhancerByCGLIB$$f50a685c");Class var1;Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());CGLIB$equals$1$Method = var10000[0];CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");CGLIB$toString$2$Method = var10000[1];CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");CGLIB$hashCode$3$Method = var10000[2];CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");CGLIB$clone$4$Method = var10000[3];CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");CGLIB$buyStock$0$Method = ReflectUtils.findMethods(new String[]{"buyStock", "(Ljava/lang/String;D)V"}, (var1 = Class.forName("com.basic.proxy.dynamicproxy.stockproxy.cglibproxy.StockService")).getDeclaredMethods())[0];CGLIB$buyStock$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;D)V", "buyStock", "CGLIB$buyStock$0");}final void CGLIB$buyStock$0(String var1, double var2) {super.buyStock(var1, var2);}public final void buyStock(String var1, double var2) {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$buyStock$0$Method, new Object[]{var1, new Double(var2)}, CGLIB$buyStock$0$Proxy);} else {super.buyStock(var1, var2);}}final boolean CGLIB$equals$1(Object var1) {return super.equals(var1);}public final boolean equals(Object var1) {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);return var2 == null ? false : (Boolean)var2;} else {return super.equals(var1);}}final String CGLIB$toString$2() {return super.toString();}public final String toString() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy) : super.toString();}final int CGLIB$hashCode$3() {return super.hashCode();}public final int hashCode() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);return var1 == null ? 0 : ((Number)var1).intValue();} else {return super.hashCode();}}final Object CGLIB$clone$4() throws CloneNotSupportedException {return super.clone();}protected final Object clone() throws CloneNotSupportedException {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}return var10000 != null ? var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy) : super.clone();}public static MethodProxy CGLIB$findMethodProxy(Signature var0) {String var10000 = var0.toString();switch (var10000.hashCode()) {case -636135857:if (var10000.equals("buyStock(Ljava/lang/String;D)V")) {return CGLIB$buyStock$0$Proxy;}break;case -508378822:if (var10000.equals("clone()Ljava/lang/Object;")) {return CGLIB$clone$4$Proxy;}break;case 1826985398:if (var10000.equals("equals(Ljava/lang/Object;)Z")) {return CGLIB$equals$1$Proxy;}break;case 1913648695:if (var10000.equals("toString()Ljava/lang/String;")) {return CGLIB$toString$2$Proxy;}break;case 1984935277:if (var10000.equals("hashCode()I")) {return CGLIB$hashCode$3$Proxy;}}return null;}public StockService$$EnhancerByCGLIB$$f50a685c() {CGLIB$BIND_CALLBACKS(this);}public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {CGLIB$THREAD_CALLBACKS.set(var0);}public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {CGLIB$STATIC_CALLBACKS = var0;}private static final void CGLIB$BIND_CALLBACKS(Object var0) {StockService$$EnhancerByCGLIB$$f50a685c var1 = (StockService$$EnhancerByCGLIB$$f50a685c)var0;if (!var1.CGLIB$BOUND) {var1.CGLIB$BOUND = true;Object var10000 = CGLIB$THREAD_CALLBACKS.get();if (var10000 == null) {var10000 = CGLIB$STATIC_CALLBACKS;if (var10000 == null) {return;}}var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];}}public Object newInstance(Callback[] var1) {CGLIB$SET_THREAD_CALLBACKS(var1);StockService$$EnhancerByCGLIB$$f50a685c var10000 = new StockService$$EnhancerByCGLIB$$f50a685c();CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;}public Object newInstance(Callback var1) {CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});StockService$$EnhancerByCGLIB$$f50a685c var10000 = new StockService$$EnhancerByCGLIB$$f50a685c();CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;}public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {CGLIB$SET_THREAD_CALLBACKS(var3);StockService$$EnhancerByCGLIB$$f50a685c var10000 = new StockService$$EnhancerByCGLIB$$f50a685c;switch (var1.length) {case 0:var10000.<init>();CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;default:throw new IllegalArgumentException("Constructor not found");}}public Callback getCallback(int var1) {CGLIB$BIND_CALLBACKS(this);MethodInterceptor var10000;switch (var1) {case 0:var10000 = this.CGLIB$CALLBACK_0;break;default:var10000 = null;}return var10000;}public void setCallback(int var1, Callback var2) {switch (var1) {case 0:this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;default:}}public Callback[] getCallbacks() {CGLIB$BIND_CALLBACKS(this);return new Callback[]{this.CGLIB$CALLBACK_0};}public void setCallbacks(Callback[] var1) {this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];}static {CGLIB$STATICHOOK1();}
}
这个代理类的声明是像下面这样的,它继承了被代理类,实现了一个Factory接口,既然是继承,那当父类的方法被声明为final,这个子类是不能重写它的。
public class StockService$$EnhancerByCGLIB$$f50a685c extends StockService implements Factory {...}
代理类核心代理段如下
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$buyStock$0$Method;
private static final MethodProxy CGLIB$buyStock$0$Proxy;static void CGLIB$STATICHOOK1() {CGLIB$THREAD_CALLBACKS = new ThreadLocal();CGLIB$emptyArgs = new Object[0];Class var0 = Class.forName("com.basic.proxy.dynamicproxy.stockproxy.cglibproxy.StockService$$EnhancerByCGLIB$$f50a685c");Class var1;Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());CGLIB$equals$1$Method = var10000[0];CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");CGLIB$toString$2$Method = var10000[1];CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");CGLIB$hashCode$3$Method = var10000[2];CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");CGLIB$clone$4$Method = var10000[3];CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");CGLIB$buyStock$0$Method = ReflectUtils.findMethods(new String[]{"buyStock", "(Ljava/lang/String;D)V"}, (var1 = Class.forName("com.basic.proxy.dynamicproxy.stockproxy.cglibproxy.StockService")).getDeclaredMethods())[0];CGLIB$buyStock$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;D)V", "buyStock", "CGLIB$buyStock$0");}public final void buyStock(String var1, double var2) {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$buyStock$0$Method, new Object[]{var1, new Double(var2)}, CGLIB$buyStock$0$Proxy);} else {super.buyStock(var1, var2);}}
代理类的这个buyStock方法是重写父类StockService的,然后它通过一个MethodInterceptor的实例去调用intercept方法,当然,这个实例是我们在创建cglib动态代理时传进来的。调用时,第一个参数传的代理类本身,第二个参数CGLIB$buyStock$0$Method实际是在静态代码块中加载的buyStock方法对象,也是可以用于反射调用原方法的,第三个参数就是调用方法所需的参数列表值,第四个参数是一个MethodProxy,实际使用时可以直接使用这个方法代理调用原对象的方法。回顾一下
public class CglibStockTest {public static void main(String[] args) {//设置动态代理的.class文件生成路径(用于分析,非必须)System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class");//生成动态代理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() {@Overridepublic 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;}
}
可见MethodInterceptor的intercept的四个参数刚好对上。到这里大概知道是怎么调用到intercept方法的了。然后我们看一下下面这句代码的源码
Object result = proxy.invokeSuper(obj, args);
这个是利用MethodProxy去执行原方法
源码如下
/*** Invoke the original (super) method on the specified object.* @param obj the enhanced object, must be the object passed as the first* argument to the MethodInterceptor* @param args the arguments passed to the intercepted method; you may substitute a different* argument array as long as the types are compatible* @see MethodInterceptor#intercept* @throws Throwable the bare exceptions thrown by the called method are passed through* without wrapping in an <code>InvocationTargetException</code>*/public Object invokeSuper(Object obj, Object[] args) throws Throwable {try {init();FastClassInfo fci = fastClassInfo;return fci.f2.invoke(fci.i2, obj, args);} catch (InvocationTargetException e) {throw e.getTargetException();}}
通过init方法来生成代理类和被代理类的FastClass对象,fci.f2是代理类的FastClass对象,因此可观察这个对象的invoke方法。断点发现fci.i2的值为17,obj为生成的代理对象,args为参数值列表
代理类的FastClass
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {StockService..EnhancerByCGLIB..f50a685c var10000 = (StockService..EnhancerByCGLIB..f50a685c)var2;int var10001 = var1;try {switch (var10001) {case 0:return new Boolean(var10000.equals(var3[0]));case 1:return var10000.toString();case 2:return new Integer(var10000.hashCode());case 3:return var10000.clone();case 4:return var10000.newInstance((Callback[])var3[0]);case 5:return var10000.newInstance((Callback)var3[0]);case 6:return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);case 7:var10000.buyStock((String)var3[0], ((Number)var3[1]).doubleValue());return null;case 8:var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);return null;case 9:f50a685c.CGLIB$SET_THREAD_CALLBACKS((Callback[])var3[0]);return null;case 10:f50a685c.CGLIB$SET_STATIC_CALLBACKS((Callback[])var3[0]);return null;case 11:var10000.setCallbacks((Callback[])var3[0]);return null;case 12:return var10000.getCallback(((Number)var3[0]).intValue());case 13:return var10000.getCallbacks();case 14:return f50a685c.CGLIB$findMethodProxy((Signature)var3[0]);case 15:return var10000.CGLIB$toString$2();case 16:return new Integer(var10000.CGLIB$hashCode$3());case 17:var10000.CGLIB$buyStock$0((String)var3[0], ((Number)var3[1]).doubleValue());return null;case 18:return var10000.CGLIB$clone$4();case 19:return new Boolean(var10000.CGLIB$equals$1(var3[0]));case 20:f50a685c.CGLIB$STATICHOOK1();return null;case 21:var10000.sellStock((String)var3[0], ((Number)var3[1]).doubleValue());return null;}} catch (Throwable var4) {throw new InvocationTargetException(var4);}throw new IllegalArgumentException("Cannot find matching method/constructor");}
fci.i2的值为17(当然,每次执行都会不同),那么执行的是
case 17:var10000.CGLIB$buyStock$0((String)var3[0], ((Number)var3[1]).doubleValue());return null;
var10000是代理类对象,所以上面实际执行的是
final void CGLIB$buyStock$0(String var1, double var2) {super.buyStock(var1, var2);}
所以 实际就是执行了其父类的buyStock。到此,整个cglib动态代理的增强逻辑已经粗略介绍完。
相关文章:

代理设计模式JDK动态代理CGLIB动态代理原理
代理设计模式 代理模式(Proxy),为其它对象提供一种代理以控制对这个对象的访问。如下图 从上面的类图可以看出,通过代理模式,客户端访问接口时的实例实际上是Proxy对象,Proxy对象持有RealSubject的引用&am…...

[陇剑杯 2021]webshell
[陇剑杯 2021]webshell 题目做法及思路解析(个人分享) 问一:单位网站被黑客挂马,请您从流量中分析出webshell,进行回答: 黑客登录系统使用的密码是_____________。 题目思路: 分析题目&…...

美易官方:小米汽车交付时间传闻被官方辟谣
在科技与互联网的快速发展浪潮中,各类信息传播速度之快令人咋舌。然而,信息的真实性却时常成为公众关注的焦点。近日,关于小米汽车交付时间的谣言再次引起市场的广泛关注。小米公司发言人迅速作出回应,明确指出这些关于小米汽车交…...

MySQL 简介
什么是MySQL?(熟悉) MySQL是一个开源的、使用标准SQL语言的、可运行于多个系统的、支持多语言的、支持大型数据库的关系型数据库管理系统。由瑞典 MySQL AB 公司开发,目前属于 Oracle 旗下产品。我们通常使用关系型数据库管理系统…...

动态规划最后一天(回文串)
目录 647. 回文子串 看到题目的第一想法 看到代码随想录之后的想法 自己实现过程中遇到的困难(看代码) 516.最长回文子序列 看到题目的第一想法 看到代码随想录之后的想法 自己实现过程中遇到的困难(看代码) 647. 回文子串 力扣题目链接…...

c语言之scanf函数
scanf函数语法格式与printf函数很相似,语法是scanf(格式控制,地址列表)组成 其中格式控制分为两部分,一部分由双引号括起来的,%和格式字符组成的格式字符串 普通字符串则是原样输出 地址列表是若干地址组成的表列,可以是变量的…...

ORM-02-JPA Java Persistence API 注解入门介绍
拓展阅读 The jdbc pool for java.(java 手写 jdbc 数据库连接池实现) The simple mybatis.(手写简易版 mybatis) JPA JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射…...

【MQ01】什么是消息队列?用哪个消息队列?
什么是消息队列?用哪个消息队列? 来了来了,消息队列系列总算来咯。对于搜索引擎相关的知识大家消化的怎么样呀?其实对于搜索引擎来说,我们学习的内容还是挺全面的,也算是比较深入了。而对于消息队列来说&am…...

2023年度AI盘点 AIGC|AGI|ChatGPT|人工智能大模型
前言 「作者主页」:雪碧有白泡泡 「个人网站」:雪碧的个人网站 2023年是人工智能大语言模型大爆发的一年,一些概念和英文缩写也在这一年里集中出现,很容易混淆,甚至把人搞懵。 文章目录 前言01 《ChatGPT 驱动软件开…...

【Flink-CDC】Flink CDC 介绍和原理概述
【Flink-CDC】Flink CDC 介绍和原理概述 1)基于查询的 CDC 和基于日志的 CDC2)Flink CDC3)Flink CDC原理简述4)基于 Flink SQL CDC 的数据同步方案实践4.1.案例 1 : Flink SQL CDC JDBC Connector4.2.案例 2 : CDC Streaming ETL…...

长城资产信息技术岗24届校招面试面经
本文介绍2024届秋招中,中国长城资产管理股份有限公司的信息技术岗岗位一面的面试基本情况、提问问题等。 10月投递了中国长城资产管理股份有限公司的信息技术岗岗位,所在部门为长城新盛信托有限责任公司。目前完成了一面,在这里记录一下一面经…...

【计算机网络】TCP握手与挥手:三步奏和四步曲
这里写目录标题 前言三次握手四次挥手三次握手和四次挥手的作用TCP三次握手的作用建立连接防止已失效的连接请求建立连接防止重复连接 TCP四次挥手的作用:安全关闭连接避免数据丢失避免半开连接 总结: 总结 前言 TCP(传输控制协议)…...

设计模式学习总结
责任链模式 使用方法: 1.创建接口 2.定义实现类,每个实现类实现接口,并拥有一个ArchiveHandle的成员,用作责任链的链接 public interface ArchiveHandle {void handle(ArchiveVO archiveVO); } public class ArchivePreHandle i…...

「HDLBits题解」Cellular automata
本专栏的目的是分享可以通过HDLBits仿真的Verilog代码 以提供参考 各位可同时参考我的代码和官方题解代码 或许会有所收益 题目链接:Rule90 - HDLBits module top_module(input clk,input load,input [511:0] data,output [511:0] q );always (posedge clk) begin…...

什么是API ?
API(应用程序编程接口) 就像现成的家具套件相对于家居建设,用一些已经切好的木板组装一个书柜,显然比自己设计,寻找合适的木材,裁切至合适的尺寸和形状,找到正确尺寸的螺钉,然后再组…...

Pytest中conftest.py的用法
Pytest中conftest.py的用法 在官方文档中,描述conftest.py是一个本地插件的文件,简单的说就是在这个文件中编写的方法,可以在其他地方直接进行调用。 注意事项 只能在根目录编写conftest.py 插件加载顺序在搜集用例之前 基础用法 这里…...

java.lang.IllegalArgumentException: When allowCredentials is true
1.遇到的错误 java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a…...

vue折叠展开transition动画使用keyframes实现
需求,我正常的菜单功能有隐藏与显示功能,需要增加动画 打开的时候宽度从0到300,关闭的时候,宽度从300到0 <template> <div id"app"> <button click"toggleLength">Toggle Length</bu…...

书生·浦语大模型实战营-学习笔记5
LMDeploy 大模型量化部署实践 大模型部署背景 LMDeploy简介 轻量化、推理引擎、服务 核心功能-量化 显存消耗变少了 大语言模型是典型的访存密集型任务,因为它是decoder-by-decoder 先把数据量化为INT4存起来,算的时候会反量化为FP16 AWQ算法&a…...

10. Profile
1. 区分环境的配置 1.1. properties 配置 假设,一个应用的工作环境有:dev、test、prod 那么,我们可以添加 4 个配置文件: applcation.properties - 公共配置application-dev.properties - 开发环境配置application-test.proper…...

YOLO 自己训练一个模型
一、准备数据集 我的版本是yolov8 8.11 这个目录结构很重要 ultralytics-main | datasets|coco|train|val 二、训练 编写yaml 文件 # Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] path…...

3.Eureka注册中心
3.Eureka注册中心 假如我们的服务提供者user-service部署了多个实例,如图: 大家思考几个问题: order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?有多个user-service实例地址࿰…...

基于springboot+vue的墙绘产品展示交易平台系统(前后端分离)
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究背景…...

网络原理-初识(1)
目录 网络发展史 独立模式 网络互连 局域网LAN 广域网WAN 网络通信基础 IP地址 概念 格式 端口 概念 格式 认识协议 概念 作用 五元组 网络发展史 独立模式 独立模式:计算机之间相互独立; 网络互连 随着时代的发展,越来越需要计算机之间相互通信,共享软件和数…...

【GitHub项目推荐--人脸识别】【转载】
01 带有移动应用程序的人脸识别库 OpenFace 作为用于人脸识别的通用库,能够实现瞬态和移动人脸识别,目前在 GitHub 上斩获 14291 Star。以下为 LFW 数据集 Sylvestor Stallone 输入单个图像的流程。 项目地址:https://github.com/cmusatya…...

NLP自然语言处理介绍
自然语言处理(NLP,Natural Language Processing)是一门涉及计算机与人类语言之间交互的学科。它的目标是使计算机能够理解和生成人类语言,从而更好地处理和解析大量的文本数据。NLP不仅是人工智能领域中一个重要的分支,…...

在线WebOffce在HTML/VUE/Electron纯前端网页编辑Office之打开Word后自动处于修订模式
在线办公协同办公过程中,对于老板给出的文档修改,如果在错别字方面都要自己一个个字去看的话也太浪费时间了,其实word上就有一个修订模式,可以帮助大家高效完成文档的修改,在线WebOffce在HTML/VUE/Electron纯前端网页编…...

thinkphp+vue+mysql旅游推荐攻略分享网站p0667
基于php语言设计并实现了旅游分享网站。该系统基于B/S即所谓浏览器/服务器模式,应用thinkphp框架,选择MySQL作为后台数据库。系统主要包括用户、景点信息、攻略分类、旅游攻略、门票购买、留言反馈、论坛管理、系统管理等功能模块。运行环境:phpstudy/wa…...

华为系统底层是用Java写的吗?和安卓的区别?
HarmonyOS(鸿蒙操作系统)是华为自主开发的分布式操作系统。下面是对HarmonyOS底层的详细介绍: 微内核架构:HarmonyOS采用了微内核架构,将核心服务和功能放在微内核中,而将其他应用服务放在用户空间中。这种…...

sql server 修改表前 先判断是否有这个列
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME Users AND COLUMN_NAME userNum) BEGINALTER TABLE UsersADD userNum INT; END 在这个示例中: TABLE_NAME Users 表示我们正在检查Users这张表。COLUMN_NAME userNum 表示我们在查…...