技术标签: JDK动态代理 JAVA设计模式 Cglib动态代理
先来谈谈JDK动态代理
需求背景:
一电商项目的程序猿辛辛苦苦写完支付接口并测试成功且上线。这个时候项目经理跑过来提了一个需求,需要在客户支付之前记录客户支付日志,方便日后进行统计支付数据。
为了不影响到之前写好的支付接口,不破坏接口的开闭原则。我们通过PayProxy绑定原先的支付接口,来解决这问题。
pay.java
public interface Pay {
public void doPay();
}
AliPay.java
public class AliPay implements Pay{
@Override
public void doPay() {
System.out.println("do AliPay");
}
}
PayProxy.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class PayProxy implements InvocationHandler {
private Object target;
public Object bind(Object target){
this.target = target;
return Proxy.
newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
public void before() {
System.out.println("do something before pay");
}
public void after() {
System.out.println("do something after pay");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
}
PayCenter.java
public class PayCenter {
public static void main(String[] args) {
Pay aliPay = (Pay)new PayProxy().bind(new AliPay());
aliPay.doPay();
}
}
输出
do something before pay
do AliPay
do something after pay
思考:
为什么现在的Pay接口调用的doPay()会打印before和after方法?现在Pay还是原先的Pay接口吗?
aliPay = {
$Proxy0@528} "[email protected]"
通过调试可以发现,现在的Pay已经不是我们原先创建的Pay接口了;而是 P r o x y 0 @ 528 这 个 对 象 ; 为 了 更 好 的 理 解 J D K 动 态 代 理 的 原 理 , 我 们 可 以 打 印 出 [email protected]这个对象;为了更好的理解JDK动态代理的原理,我们可以打印出 Proxy0@528这个对象;为了更好的理解JDK动态代理的原理,我们可以打印出[email protected]对象的反编译代码;
$Proxy0.class反编译后的文件
import com.sun.proxy.$Proxy0;
import java.lang.reflect.*;
public final class $Proxy extends Proxy
implements $Proxy0
{
public $Proxy(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final InvocationHandler getInvocationHandler(Object obj)
throws IllegalArgumentException
{
try
{
return (InvocationHandler)super.h.invoke(this, m5, new Object[] {
obj
});
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final Class getProxyClass(ClassLoader classloader, Class aclass[])
throws IllegalArgumentException
{
try
{
return (Class)super.h.invoke(this, m6, new Object[] {
classloader, aclass
});
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final Class getClass()
{
try
{
return (Class)super.h.invoke(this, m11, null);
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final void notifyAll()
{
try
{
super.h.invoke(this, m13, null);
return;
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final void wait()
throws InterruptedException
{
try
{
super.h.invoke(this, m8, null);
return;
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final void doPay()
{
try
{
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final void notify()
{
try
{
super.h.invoke(this, m12, null);
return;
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final Object newProxyInstance(ClassLoader classloader, Class aclass[], InvocationHandler invocationhandler)
throws IllegalArgumentException
{
try
{
return (Object)super.h.invoke(this, m7, new Object[] {
classloader, aclass, invocationhandler
});
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final void wait(long l)
throws InterruptedException
{
try
{
super.h.invoke(this, m10, new Object[] {
Long.valueOf(l)
});
return;
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final boolean isProxyClass(Class class1)
{
try
{
return ((Boolean)super.h.invoke(this, m4, new Object[] {
class1
})).booleanValue();
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final void wait(long l, int i)
throws InterruptedException
{
try
{
super.h.invoke(this, m9, new Object[] {
Long.valueOf(l), Integer.valueOf(i)
});
return;
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m1;
private static Method m5;
private static Method m2;
private static Method m6;
private static Method m11;
private static Method m13;
private static Method m0;
private static Method m8;
private static Method m3;
private static Method m12;
private static Method m7;
private static Method m10;
private static Method m4;
private static Method m9;
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m5 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getInvocationHandler", new Class[] {
Class.forName("java.lang.Object")
});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m6 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getProxyClass", new Class[] {
Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;")
});
m11 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getClass", new Class[0]);
m13 = Class.forName("com.sun.proxy.$Proxy0").getMethod("notifyAll", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m8 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[0]);
m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("doPay", new Class[0]);
m12 = Class.forName("com.sun.proxy.$Proxy0").getMethod("notify", new Class[0]);
m7 = Class.forName("com.sun.proxy.$Proxy0").getMethod("newProxyInstance", new Class[] {
Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"), Class.forName("java.lang.reflect.InvocationHandler")
});
m10 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[] {
Long.TYPE
});
m4 = Class.forName("com.sun.proxy.$Proxy0").getMethod("isProxyClass", new Class[] {
Class.forName("java.lang.Class")
});
m9 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[] {
Long.TYPE, Integer.TYPE
});
}
catch(NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
提取关键代码
public $Proxy(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final void doPay()
{
try
{
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("doPay", new Class[0]);
通过上述的代码可以看出,$Proxy对象是JVM自动生成的对象,实现了Pay接口,并重写了接口里的所有方法;
我们提取dopay方法可以看出,实际调用的是invocationhandler.invoke方法,这正是我们之前PayProxy类中的invoke方法,通过JDK的反射技术调用实际Object target即AliPay的dopay方法。
最后给出JDK动态代理简单的时序图(有点丑,见谅,哈哈哈)
JDK动态代理的简单总结:
CGLib动态代理
区别于JDK动态代理,CGLib动态代理的被代理类则不需要实现固定的接口;CGLib的动态代理会自动生成被代理类的子类,并重写父类的方法,从而实现代理;
实现代码
CglibProxy.java
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
public Object getInstance(Object target){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
//回调intercept方法
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
public void before(){
System.out.println("Before");
}
public void after(){
System.out.println("After");
}
}
AliPay.java
public class AliPay {
public void doPay(){
System.out.println("alipay");
}
}
CglibTest.java
import net.sf.cglib.core.DebuggingClassWriter;
public class CglibTest {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "G://cglib_folder");
AliPay alipay = (AliPay)new CglibProxy().getInstance(new AliPay());
alipay.doPay();
}
}
输出
before
doPay
after
我们通过以下代码输出CgLib的class文件
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "G://cglib_folder");
进入G://cglib_folder会发现有三个类
通过调试我们发现:
alipay = {
AliPay$$EnhancerByCGLIB$$e2467acf@671} "[email protected]"
AliPayEnhancerByCGLIBe2467acf.class文件正是Cglib动态获取的代理类,反编译该class文件;
public class AliPay$$EnhancerByCGLIB$$e2467acf extends AliPay
implements Factory
{
final void CGLIB$doPay$0()
{
super.doPay();
}
public final void doPay()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
JVM INSTR pop ;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVM INSTR dup ;
JVM INSTR ifnull 37;
goto _L3 _L4
_L3:
break MISSING_BLOCK_LABEL_21;
_L4:
break MISSING_BLOCK_LABEL_37;
this;
CGLIB$doPay$0$Method;
CGLIB$emptyArgs;
CGLIB$doPay$0$Proxy;
intercept();
return;
super.doPay();
return;
}
}
CgLib代理类继承了AliPay类,并重写了父类的所有方法,调用doPay方法时会调用CGLIB$doPay$0方法,该方法正是父类的doPay方法;接着调用了intercept()方法。
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
进入MethodProxy类的invokeSuper方法。
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
查看MethodProxy.FastClassInfo可以看到
private static class FastClassInfo {
FastClass f1;
FastClass f2;
int i1;
int i2;
private FastClassInfo() {
}
}
这里的f1和f2便是上述G://cglib_folder目录下的
- AliPay__EnhancerByCGLIB__e2467acf__FastClassByCGLIB__9697d901.class(代理类的FastClass)
- AliPay__FastClassByCGLIB__bf557141.class(被代理类的FastClass)
总结
- CgLib动态代理类之所以执行效率比JDK动态代理类快,是因为其FastClass机制,直接通过生成的FastClass类执行对应的方法;
- CgLib动态代理需要生成三个class类,所以比JDK动态代理生成时效率要慢;
最后附上一份手写JDK动态代理的git地址
https://github.com/TomiCat/DynamicProxyForMyself
此文章为本人平时学习整理巩固所写,如有错误,欢迎大家纠正~谢谢!
ngx_lua 模块将 Lua 整合在 NginX 中,使用 Lua coroutine per request 机制实现无缝的 I/O 复用处理。受益于 Lua 解释器的极低开销和原生 coroutine 支持,用户代码仍然能以习惯的顺序方式编写,丝毫不会感受到 NginX I/O 复用结构的存在,同时又能享受到其天然的非阻塞大并发能力和非凡的速度。借助 agentzh 在 ngx_echo...
上回我们用python实现了自动刷抖音的功能(https://blog.csdn.net/u012539700/article/details/100058939),这回我们用android app的形式来实现自动刷抖音,这样就可以不用连接电脑啦。 原理很简单,使用android无障碍里面的AccessibilityService就可以啦。 首先在res目录下新建一个xml文件...
本来想测试下,结果无论本地或生产环境都是nts,所以这里只说安装,不说使用,因为没用上。windows版本的下载地址http://windows.把pthreadVC2.dll和php_pthreads.dll文件,把vc2文件放到php.exe同级目录,把php_pthreads.dll放到扩展目录下。1、修改php.ini文件 添加extension=php_pthreads.dll2、Apa...
整合定时任务1.引入依赖`<!--定时任务的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>简单测试@Componentpubl
图片切换#box{width:400px;margin:0 auto;}#box>div{text-align: center;margin:10px auto;display: block;}#box div>input{margin-left: 5px;border-radius: 3px;border:1px solid #ccc;}#box>p{text-align: cente
如果在串口初始化的时候没有开启终止符,并且要求读取的字节数小于接收缓冲区现有的字节数,那么返回的字符串长度就会等于指定读取的字节数长度,反之UART控制器会将缓冲区中所有的数据全部返回;另外,对于刚刚接触LabVIEW的新用户来说,经常会把VISA函数当成串口函数,实际上NI-VISA驱动包本质上是将整个仪器行业的通信协议(RS232、GPIB、USB、Ethernet等)封装在一起,供不同行业的工程师使用,这样可以简化仪器通信。一般情况下,在读取串口缓冲区中的数据之前,需要指定读取字节的数量。
ES与数据库比较 查询操作Elasticsearch中当我们设置Mapping(分词器、字段类型)完毕后,就可以按照设定的方式导入数据。有了数据后,我们就需要对数据进行检索操作。根据实际开发需要,往往我们需要支持包含但不限于以下类型的检索:1)精确匹配,类似mysql中的 “=”操作;2)模糊匹配,类似mysql中的”like %关键词% “查询操作;3)前缀匹配;4)...
文章目录导航栏透明+背景图填充+悬浮气泡背景图填充导航栏透明图片中的点击向下跳转的样式悬浮气泡导航栏透明+背景图填充+悬浮气泡背景图填充效果图:下面的小框扩展到大框,让后面的导航栏透明更完善一些。编辑Theme/components/HomeBlog.vue下的.hero { margin $navbarHeight auto 0 position relative box-sizing border-box padding 0 20px hei
上代码:import { defineConfig } from 'vite';import vue from '@vitejs/plugin-vue';import { resolve } from 'path';// https://vitejs.dev/config/export default defineConfig({ server: { port: 9090, strictPort: true, // 严格端口 true:如果端口已被使用,则直接退出,而不会再
背景某些情况下(如在三维绘图),需要绘制系统坐标系。首先来看一下三维迪卡尔坐标系的组成:坐标原点(0,0,0),带箭头的坐标轴,坐标轴标签。因此在绘制坐标系,需要绘制这些元素。Mathematica实现coordinateSystem3D = { {RGBColor[{1, 0, 0}], Arrowheads[0.05], Arrow[Tube[{{0, 0, 0}, {1, 0,...
1. TPS(每秒传输的事物处理个数)概念:即服务器每秒处理的事务数。TPS是软件测试结果的测量单位。TPS包括一条消息入和一条消息出,加上一次用户数据库访问。业务 TPS = CAPS × 每个呼叫平均TPS一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。一般的,评价系统...
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 2.应尽量避免在 where 子句中使用!=或 3.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num is null 可以在num上设置