动态代理(JDK、Cglib、Bytebuddy)

文章目录

  • 动态代理
      • JDK动态代理
          • 代理对象的生成
          • 动态代理执行流程
          • 简化代码
          • 静态代理
      • Cglib动态代理
          • 代理类的生成
          • 动态代理执行流程
      • bytebuddy
          • 常用api
            • 生成一个类
            • 对实例方法进行插桩
            • 实例方法进行插桩的扩展
            • 三种增强方式
            • 插入新方法
            • 方法委托
            • 动态修改入参
            • 对构造方法进行插桩
            • 对静态方法进行插桩
            • 清空方法体
          • javaagent
            • 拦截实例方法

动态代理

JDK动态代理

基于接口实现,为实现类生成代理对象

1、举例

  • 接口
public interface UserRepository {int delete(Integer id);
}
  • 实现类
public class UserRepositoryImpl implements UserRepository {@Overridepublic int delete(Integer id) {System.out.println("删除id = "+ id +"数据");return 1;}
}
  • InvocationHandler:调用处理器
public class MyInvocationHandler implements InvocationHandler {// 代理类private Object target;public MyInvocationHandler(Object target) {this.target = target;}// 重写InvocationHandler接口的invoke方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1.前置增强System.out.println("前置增强");// 2.代理对象方法执行Object result = method.invoke(target, args);// 3.后置增强System.out.println("后置增强");return result;}
}
  • 测试
	public static void main(String[] args) {// 1.目标类UserRepository target = new UserRepositoryImpl();// 2.调用处理器MyInvocationHandler h = new MyInvocationHandler(target);// 3.生成Jdk动态代理类UserRepository proxy = (UserRepository)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),h);// 4.动态代理类调用目标类的方法int result = proxy.delete(1);}
代理对象的生成

1、生成动态代理对象的方法:Proxy.newProxyInstance简略如下

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){final Class<?>[] intfs = interfaces.clone();// 步骤一:生成接口对应的实现类的代理类的字节码.classClass<?> cl = getProxyClass0(loader, intfs);// 步骤二:生产代理对象的构造方法final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;//步骤三:生成代理类的实例并把InvocationHandlerImpl的实例传给构造方法return cons.newInstance(new Object[]{h});}

2、解析步骤一:生成接口对应的实现类的代理类的字节码.class

WeakCache#get方法简略如下

public V get(K key, P parameter) {Supplier<V> supplier = valuesMap.get(subKey);Factory factory = null;while (true) {if (supplier != null) {// supplier might be a Factory or a CacheValue<V> instance// 函数式接口Supplier.get,本质是WeakCache内部类Factory.get,返回代理对象字节码.classV value = supplier.get();if (value != null) {return value;}}}

3、Factory.get方法简略如下

@Override
public synchronized V get() {// 通过valueFactory.apply(key, parameter)获取代理类字节码.class// BiFunction<K, P, V> valueFactory函数式接口.apply,底层是// Proxy类中内部类ProxyClassFactory.apply方法V value = Objects.requireNonNull(valueFactory.apply(key, parameter));return value;
}

4、ProxyClassFactory.apply方法简略如下

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {// 1. 为代理类生成名字long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;// 2.真正生成代理类的字节码文件的地方!!!byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);// 3.使用类加载器将代理类的字节码文件加载到JVM中(这里先不关注)return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);}

5、真正生成代理类字节码的方法ProxyGenerator.generateProxyClass

  • 这里我们也使用这种方式手写个demo,将代理类的字节码.class,反编译生成内容,便于查看学习
	public static void main(String[] args) throws Exception {// 1.生成UserRepositoryImpl目标类的代理对象.class内容byte[] proxyClassFile = ProxyGenerator.generateProxyClass("$Proxy1",UserRepositoryImpl.class.getInterfaces());// 2.使用流读取.class内容,结果存放的路径String path = "D:\\spring源码\\spring-framework-5.1.4.RELEASE\\spring5_mjp\\src";// 3.使用流读取.class内容,并输出try (FileOutputStream out = new FileOutputStream(new File(path + "$Proxy1.class"))) {out.write(proxyClassFile);}}

6、手写生成的代理类字节码反编译简略结果如下

public final class $Proxy1 extends Proxy implements UserRepository {private static Method m3;public $Proxy1(InvocationHandler var1) throws  {super(var1);}public final int delete(Integer var1) throws  {return (Integer)super.h.invoke(this, m3, new Object[]{var1});}static {m3 = Class.forName("com.mjp.aop.stopwatch.UserRepository").getMethod("delete",Class.forName("java.lang.Integer"));}
}

1)类

继承了Proxy类、实现了我们自定义的接口UserRepository

所以,Jdk动态代理是基于实现接口的方式

2)静态代理块中,反射获取delete方法信息

通过反射,获取到目标类的方法Method m3信息

  • 方法声明处为:UserRepository,是在此接口中声明的
  • 其方法名为:delete
  • 其参数类型为:Integer
动态代理执行流程
	public static void main(String[] args) {// 1.目标类UserRepository target = new UserRepositoryImpl();// 2.调用处理器MyInvocationHandler h = new MyInvocationHandler(target);// 步骤三.生成Jdk动态代理类UserRepository proxy = (UserRepository)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),h);// 步骤四.动态代理类调用目标类的方法int result = proxy.delete(1);}

直接看步骤四:proxy.delete(1)

1、执行proxy.delete(1),结合上文生成的代理对象.class文件反编译结果$Proxy1看

public final int delete(Integer var1) throws  {return (Integer)super.h.invoke(this, m3, new Object[]{var1});
}
  • (Integer):即delete方法的结果返回值类型
  • super即父类Proxy
  • super.h即父类Proxy中的InvocationHandler,因为步骤三种,在生成代理类时,将我们自定义的MyInvocationHandler作为方法入参h,传递过去了。所以这里的super.h,即我们自定义的MyInvocationHandler
  • super.h.invoke,即MyInvocationHandler.invoke

2、myInvocationHandler.invoke(this, m3, new Object[]{var1})

  • this:即代理对象$Proxy1
  • m3:即反射生成的目标对象的delete方法
  • new Object[]{var1}:即步骤四中proxy.delete(1)的方法入参1

进入我们MyInvocationHandler中查看重写的invoke方法详情

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1.前置增强System.out.println("前置增强");// 2.代理对象方法执行Object result = method.invoke(target, args);// 3.后置增强System.out.println("后置增强");return result;
}
  • 先执行前置增强

  • 在执行method.invoke(target, args)

    底层就是反射执行target目标对象的method方法

    • method:即上述传2中递过来的m3,即delete方法
    • target:是通过MyInvocationHandler构造方法传递过来的目标对象
    • args:是上述2中传递过来的new Object[]{var1},方法参数
  • 最后执行后置增强

简化代码
// 步骤二.调用处理器
MyInvocationHandler h = new MyInvocationHandler(target);// 步骤三.生成Jdk动态代理类
UserRepository proxy = (UserRepository)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),h);

1、匿名内部类

很明显,我们自定义的实现了了InvocationHandler接口的MyInvocationHandler类,可以通过匿名内部类的方式创建,这样就无需单独创建一个MyInvocationHandler类

  • 步骤二、三的目的就是生成代理对象,故将二者封装在JdkProxyUtil#getProxy中
public class JdkProxyUtil {public static UserRepository getProxy(UserRepository target){// 步骤二:自定义处理器InvocationHandler h = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args){System.out.println("前置增强");Object result = null;try {result = method.invoke(target, args);} catch (Exception e) {// 异常处理}System.out.println("后置增强");return result;}};// 步骤三:生成目标类的代理对象Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),h);return (UserRepository) proxy;}
}

2、Lambda表达式

public class JdkProxyUtil {public static UserRepository getProxy(UserRepository target){InvocationHandler h = (proxy, method, args) -> {System.out.println("前置增强");Object result = null;try {result = method.invoke(target, args);} catch (Exception e) {// 异常处理}System.out.println("后置增强");return result;};Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),h);return (UserRepository) proxy;}
}
静态代理

了解了Jdk动态代理,我们再看下静态代理

  • 静态代理类
public class StaticProxy implements UserRepository{private UserRepository userRepository;public StaticProxy(UserRepository userRepository) {this.userRepository = userRepository;}@Overridepublic int delete(Integer id) {// 1.前置增强System.out.println("前置增强");// 2.执行目标对象方法int result = userRepository.delete(id);// 3.后置增强System.out.println("后置增强");return result;}
}
  • 测试
UserRepository target = new UserRepositoryImpl();
StaticProxy staticProxy = new StaticProxy(target);
int delete = staticProxy.delete(1);
  • 分析
    • 顾名思义,静态代理是静态的即提前写死的代理类StaticProxy,不是在代码执行期间动态生成的$Proxy1代理类
    • 耦合:静态代理类持有目标类对象
    • 扩展性差:如果接口新增方法,则除了目标对象需要重写外,静态代理对象也需要重写方法。

Cglib动态代理

在jdk1.7之前,jdk动态代理(底层基于反射)性能不及cglib动态代理(底层基于Asm框架技术)。后续反射性能优化了,现在就是基于接口实现就用Jdk动态代理,基于继承就用cglib动态代理

1、举例

  • 目标类(父类)
public class UserDao {public int delete(Integer id) {System.out.println("删除id = "+ id+ " 数据");return 1;}
}
  • MyInterceptor
public class MyInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 1.前置增强System.out.println("前置增强");// 2.代理对象(子类o),调用其父类(目标对象UserDao)的目标方法(delete)Object result = methodProxy.invokeSuper(o, objects);// 3.后置增强System.out.println("后置增强");return result;}
}
  • 测试
	public static void main(String[] args) {// 1.创建调用的对象Enhancer enhancer = new Enhancer();// 2.设置父类(目标对象)enhancer.setSuperclass(UserDao.class);// 3.设置回调对象-自定义拦截器enhancer.setCallback(new MyInterceptor());// 4.生成目标对象(父类)的cglib代理对象(子类)UserDao proxy = (UserDao) enhancer.create();// 5.通过cglib代理对象(子类),调用目标类(父类)的目标方法int result = proxy.delete(1);}
代理类的生成

0、new Enhance(),先通过AbstractClassGenerator#generate创建Enhance的属性EnhancerKey的代理对象

	public interface EnhancerKey {public Object newInstance(String type,String[] interfaces,WeakCacheKey<CallbackFilter> filter,Type[] callbackTypes,boolean useFactory,boolean interceptDuringConstruction,Long serialVersionUID);}

因为后续要使用其内部的newInstance对象

1、生成cglib动态代理对象的入口:enhancer.create()

enhancer.create() -->> createHelper() -->> super.create(key)

– >> data.get -->> generatedClass.generate -->> loader.apply -->> gen.generate(生成.class文件并写入)

2、手动将动态代理创建的.class文件存储到本地,便于查看

  • 生成动态代理.class的方式
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:\\spring源码\\spring-framework-5.1.4.RELEASE\\spring5_mjp\\src");// 1.创建调用的对象Enhancer enhancer = new Enhancer();// 步骤二.设置父类(目标对象)enhancer.setSuperclass(UserDao.class);// 步骤三.设置回调对象-自定义拦截器enhancer.setCallback(new MyInterceptor());// 4.生成目标对象(父类)的cglib代理对象(子类)UserDao proxy = (UserDao) enhancer.create();// 5.通过cglib代理对象(子类),调用目标类(父类)的目标方法int result = proxy.delete(1);
动态代理执行流程

1、测试

UserDao proxy = (UserDao) enhancer.create();
int result = proxy.delete(1);

2、proxy.delete(1)

结合生成的cglib字节码文件,查看此方法

  • cglib动态代理类(目标类父类的子类)delete方法概述如下
public class UserDao$$EnhancerByCGLIB$$77046e5 extends UserDao implements Factory {// 2.callbackprivate MethodInterceptor CGLIB$CALLBACK_0;// 3.1 目标方法即要拦截的方法即要增强的方法private static final Method CGLIB$delete$0$Method;// 3.2 表示要触发父类(目标类)的方法对象private static final MethodProxy CGLIB$delete$0$Proxy;//一、目标方法public final int delete(Integer var1) {// 1).获取callBackMethodInterceptor var10000 = this.CGLIB$CALLBACK_0;// 2).如果设置了callBack,则调用callback的intercept方法if (var10000 != null) {Object var2 = var10000.intercept(this, CGLIB$delete$0$Method, new Object[]{var1}, CGLIB$delete$0$Proxy);return var2 == null ? 0 : ((Number)var2).intValue();} else {// 3).如果未设置callback,则直接调用cglib动态代理类(子类)的父类(目标类UserDao)的目标delete方法return super.delete(var1);}}// 二、设置callbackpublic void setCallback(int var1, Callback var2) {switch (var1) {case 0:this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;default:}}// 三、静态代码块static void CGLIB$STATICHOOK1() {//3.1 目标方法MethodCGLIB$delete$0$Method = ReflectUtils.findMethods(new String[]{"delete", "(Ljava/lang/Integer;)I"}, (var1 = Class.forName("com.mjp.aop.cglib.UserDao")).getDeclaredMethods())[0];// 3.2.MethodProxyCGLIB$delete$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Integer;)I", "delete", "CGLIB$delete$0");}
}

1)this.CGLIB$CALLBACK_0

  • 这个this.CGLIB$CALLBACK_0参数是个全局变量,在setCallback方法中被赋值了

本质是在步骤三: enhancer.setCallback(new MyInterceptor())完成了Callback内容的赋值

自定义MyInterceptor类,实现了Interceptor接口,Interceptor接口继承了CallBack接口

3)如果没有步骤三,即代码中未设置CallBack,则会直接调用父类目标方法,就相当于没有实现增强

2)如果有步骤三,即代码中设置了CallBack,即设置了Interceptor拦截器实现类,则会

var10000.intercept(this, CGLIB$delete$0$Method, new Object[]{var1}, CGLIB$delete$0$Proxy)
  • var10000,即自定义myInterceptor
  • var10000.intercept即myInterceptor.intercept
    • this:即cglib动态代理类(子类)
    • CGLIB$delete 0 0 0Method:在静态代码块CGLIB$STATICHOOK1()3.1中完成了赋值,本质是UserDao目标类的#delete方法,方法入参为Integrate类型,表示要拦截的方法即要增强的方法
    • new Object[]{var1}:目标方法的入参值1
    • CGLIB$delete 0 0 0Proxy:表示要触发父类(目标类)的方法对象,在静态代理块3.2中完成了赋值
public class MyInterceptor implements MethodInterceptor {/*** 拦截器* * @param o				:cglib动态代理类UserDao$$EnhancerByCGLIB$$77046e5(子类)* @param method		:目标方法即要拦截的方法UserDao#delete* @param objects		:目标方法参数:delete(1),即1* @param methodProxy	:要触发父类(目标类)的方法对象* @return				:目标方法即要拦截的方法delete方法的执行结果*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 1).前置增强System.out.println("前置增强");// 2).目标方法Object result = methodProxy.invokeSuper(o, objects);// 3).后置增强System.out.println("后置增强");return result;}
}

3、myInterceptor.intercept

  1. 前置增强

2)methodProxy.invokeSuper(o, objects)

–>> MethodProxy#invokeSuper

    public Object invokeSuper(Object obj, Object[] args) throws Throwable {FastClassInfo fci = this.fastClassInfo;return fci.f2.invoke(fci.i2, obj, args);}private static class FastClassInfo {FastClass f1;FastClass f2;int i1;int i2;}

–>>fci.f2.invoke

–>> 抽象类FastClass.invoke

本质会调用为了便于查看代理内容,而生成的UserDao F a s t C l a s s B y C G L I B FastClassByCGLIB FastClassByCGLIB1cb5c806#invoke,此类实现了FastClass重写了invoke方法

public class UserDao$$FastClassByCGLIB$$1cb5c806 extends FastClass {public Object invoke(int var1, Object var2, Object[] var3) {UserDao var10000 = (UserDao)var2;return new Integer(var10000.delete((Integer)var3[0]));}
}

所以本质还是调用的UserDao#delete(1)返回Integer

3)后置增强

bytebuddy

1、作用

正常情况下,.java通过编译生成.class后才能运行,但是bytebuddy可以在Java程序运行期间生成或修改字节码

<dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.14.15</version>
</dependency>
常用api
生成一个类
// 1.生成的字节码还未加载到jvm
DynamicType.Unloaded<Demo1> unloaded = new ByteBuddy()// 为了性能不校验自定义的name是否合法.with(TypeValidation.of(false))// 指定父类.subclass(Demo1.class)// 指定字节码的包.名.name("com.mjp.Demo1Sub").make();// 2.获取生成的字节码内容
byte[] bytes = unloaded.getBytes();// 3.将字节码写入指定文件
String path = Demo1.class.getClassLoader().getResource("").getPath();
unloaded.saveIn(new File(path));// 4.将生成的字节码注入某个jar
unloaded.inject(new File("jar的绝对路径.jar"));
对实例方法进行插桩

stub:对字节码进行修改增强

  • 父类
public class Demo1 {public String func() {return "hello";}
}
  • 增强
// 1.生成的字节码还未加载到jvm
DynamicType.Unloaded<Demo1> unloaded = new ByteBuddy().with(TypeValidation.of(false)).subclass(Demo1.class).name("com.mjp.Demo1Sub")// 拦截Demo1的func方法.method(ElementMatchers.named("func"))// 拦截到相应方法后,如何处理(这里使用自带的增强器).intercept(FixedValue.value("拦截增强")).make();// 2.将生成的字节码加载到jvm(loaded和unloaded都是继承DynamicType接口)
DynamicType.Loaded<Demo1> loaded = unloaded.load(getClass().getClassLoader());
loaded.saveIn(new File(Demo1.class.getClassLoader().getResource("").getPath()));// 3.获取加载到jvm后的class对象
Class<? extends Demo1> loadedLoaded = loaded.getLoaded();
// 4.实例化对象(这里可以使用Demo1接收是因为其为父类)
Demo1 instance = loadedLoaded.newInstance();
String result = instance.func();
System.out.println(result);
  • 子类.class文件
public class Demo1Sub extends Demo1 {public String func() {return "拦截增强";}public Demo1Sub() {}
}
实例方法进行插桩的扩展

1)匹配方法

  • 父类
public class Demo1 {public String func() {return "hello";}public Integer func(Integer id) {return 1;}
}
  • 增强
DynamicType.Unloaded<Demo1> unloaded = new ByteBuddy().with(TypeValidation.of(false)).subclass(Demo1.class).name("Demo1Sub").method(// 拦截Demo1的func方法ElementMatchers.named("func")// 并且.and(// 方法的返回值是String类型returns(String.class)// 或 方法的返回值是Integer类型.or(returns(Integer.class)))).intercept(FixedValue.nullValue()).make();DynamicType.Loaded<Demo1> loaded = unloaded.load(getClass().getClassLoader());
loaded.saveIn(new File(Demo1.class.getClassLoader().getResource("").getPath()));Class<? extends Demo1> aClass = loaded.getLoaded();
Demo1 instance = aClass.newInstance();
System.out.println(instance.func());
System.out.println(instance.func(1));
  • 子类
public class Demo1Sub extends Demo1 {public String func() {return null;}public Integer func(Integer var1) {return null;}public Demo1Sub() {}
}

2)拦截多个方法

.method()可以多次使用,达到拦截多个方法的作用

三种增强方式

1)subClass

2)rebase

变基

  • 不再像subClass基于继承
  • 原方法会被重命名为xxOriginal(),拦截后的方法名为xx

(在idea-view-show bytecode下查看)

  • 用于静态方法(静态方法不可被继承,所以不能使用subclass)
  • 不指定命名地址,则生成位置与目标类在同路径下

3)redifine

  • 变基,原方法不再保留,只有拦截后的方法名称xx

  • 不指定命名地址,则生成位置与目标类在同路径下

public class Demo1 {public String func() {return "hello";}public Integer func(Integer id) {return 1;}
}
  • 增强
DynamicType.Unloaded<Demo1> unloaded = new ByteBuddy().with(TypeValidation.of(false))//变基.redefine(Demo1.class).name("Demo1Sub").method(// 拦截Demo1的func方法ElementMatchers.named("func")// 并且.and(// 方法的返回值是String类型returns(String.class)// 或 方法的返回值是Integer类型.or(returns(Integer.class)))).intercept(FixedValue.nullValue()).make();unloaded.saveIn(new File(Demo1.class.getClassLoader().getResource("").getPath()));
  • 新生成的类
public class Demo1Sub {public Demo1Sub() {}public String func() {return null;}public Integer func(Integer var1) {return null;}
}
插入新方法

1)redefine方式

public class Demo1 {public String func() {return "hello";}
}
  • 增强
DynamicType.Unloaded<Demo1> unloaded = new ByteBuddy().with(TypeValidation.of(false)).redefine(Demo1.class).name("Demo1Sub")// 生成新的方法:方法名称、方法返回值类型、方法的修饰符类型.defineMethod("newFunc", String.class, Modifier.PUBLIC + Modifier.STATIC)// 新方法的方法入参类型.withParameter(List.class, "userNameList")// 拦截生成的新方法,方法的返回值内容.intercept(FixedValue.value("生成新的方法返回值")).make();unloaded.saveIn(new File(Demo1.class.getClassLoader().getResource("").getPath()));
  • 增强类
public class Demo1Sub {public Demo1Sub() {}// 原方法不动public String func() {return "hello";}// 生成新的方法public static String newFunc(List userNameList) {return "生成新的方法返回值";}
}

2)subClass方式

public class Demo1Sub extends Demo1 {// 直接生成新的方法public static String newFunc(List userNameList) {return "生成新的方法返回值";}public Demo1Sub() {}
}
方法委托

自定义拦截逻辑

  • 父类
public class Demo1 {public String func(Integer id) {return "hello " + id;}
}
  • 增强
DynamicType.Unloaded<Demo1> unloaded = new ByteBuddy().with(TypeValidation.of(false)).subclass(Demo1.class).name("Demo1Sub")// 要拦截的方法名称.method(ElementMatchers.named("func"))// 委托给自定义的MyInterceptor拦截器进行处理.intercept(MethodDelegation.to(new MyInterceptor())).make();DynamicType.Loaded<Demo1> loaded = unloaded.load(getClass().getClassLoader());
loaded.saveIn(new File(Demo1.class.getClassLoader().getResource("").getPath()));Class<? extends Demo1> aClass = loaded.getLoaded();
Demo1 instance = aClass.newInstance();
System.out.println(instance.func(1));
  • 自定义拦截器
public class MyInterceptor {/*** 参考cglib,自定义MethodInterceptor,重写intercept方法中的参数** @RuntimeType     :修饰拦截方法* @param o         :代理对象(Demo1Sub),只有拦截实例方法、构造方法时可用* @param method    : 要拦截的方法,只有拦截实例方法、静态方法时可用* @param objects   :要拦截的方法参数,任意方法的拦截都可用* @param targetObj :目标对象,只有拦截实例方法、构造方法时可用* @param callable  :用于调用目标对象的拦截方法(类似cglib中MethodProxy触发目标类的方法调用的对象)*/@RuntimeTypepublic Object intercept(@This Object o,@Origin Method method,@AllArguments Object[] objects,@Super Object targetObj,@SuperCall Callable<?> callable) {System.out.println("o: " + o);//Demo1Sub@12591ac8System.out.println("method: " + method);//public java.lang.String Demo1.func(java.lang.Integer)System.out.println("objects: " + Arrays.toString(objects));//[1]System.out.println("targetObj: " + targetObj);//Demo1Sub@12591ac8System.out.println("callable: " + callable);//Demo1Sub$auxiliary$WCUkqaLd@5a7fe64fObject call = null;try {System.out.println("只有前置增强");// 调用目标方法本身call = callable.call();//hello 1} catch (Exception e) {}return call;}
}
动态修改入参
  • 父类
public class Demo1 {public Integer func(Integer id) {return id;}
}
  • 自定义Callable
public interface MyCallable {public Object call(Object[] args);
}
  • 增强
DynamicType.Unloaded<Demo1> unloaded = new ByteBuddy().with(TypeValidation.of(false)).subclass(Demo1.class).name("Demo1Sub").method(ElementMatchers.named("func")).intercept(MethodDelegation.withDefaultConfiguration()// 指定参数类型是MyCallable.withBinders(Morph.Binder.install(MyCallable.class)).to(new MyInterceptor())).make();DynamicType.Loaded<Demo1> loaded = unloaded.load(getClass().getClassLoader());loaded.saveIn(new File(Demo1.class.getClassLoader().getResource("").getPath()));Class<? extends Demo1> aClass = loaded.getLoaded();Demo1 instance = aClass.newInstance();System.out.println(instance.func(1));//101
  • 自定义拦截器
public class MyInterceptor {/*** 参考cglib,自定义MethodInterceptor,重写intercept方法中的参数** @RuntimeType     :修饰拦截方法* @param o         :代理对象(Demo1Sub),只有拦截实例方法、构造方法时可用* @param method    : 要拦截的方法,只有拦截实例方法、静态方法时可用* @param objects   :要拦截的方法参数,任意方法的拦截都可用* @param targetObj :目标对象,只有拦截实例方法、构造方法时可用* @param callable  :用于调用目标对象的拦截方法(类似cglib中MethodProxy触发目标类的方法调用的对象)自定义可以传参的*/@RuntimeTypepublic Object intercept(@This Object o,@Origin Method method,@AllArguments Object[] objects,@Super Object targetObj,@Morph MyCallable callable) {System.out.println("o: " + o);//Demo1Sub@12591ac8System.out.println("method: " + method);//public java.lang.String Demo1.func(java.lang.Integer)System.out.println("objects: " + Arrays.toString(objects));//[1]System.out.println("targetObj: " + targetObj);//Demo1Sub@12591ac8System.out.println("callable: " + callable);//Demo1Sub$auxiliary$HGW8FpsQ@7dfb0c0fObject call = null;try {System.out.println("只有前置增强");// 调用目标方法本身之前,将方法的入参进行动态的修改,值+100if (objects != null && objects.length > 0) {objects[0]= (Integer)objects[0] + 100;}// 调用目标方法本身call = callable.call(objects);//1 + 100 = 101} catch (Exception e) {}return call;}
}
对构造方法进行插桩
  • 父类
public class Demo1 {public String func(Integer id) {return "hello " + id;}
}
  • 增强
DynamicType.Unloaded<Demo1> unloaded = new ByteBuddy().with(TypeValidation.of(false)).subclass(Demo1.class).name("Demo1Sub")// 拦截任意的构造方法.constructor(ElementMatchers.any()).intercept(// 在构造方法执行之后,委托给拦截器进行拦截SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(new MyInterceptor2()))).make();DynamicType.Loaded<Demo1> loaded = unloaded.load(getClass().getClassLoader());loaded.saveIn(new File(Demo1.class.getClassLoader().getResource("").getPath()));Class<? extends Demo1> aClass = loaded.getLoaded();Demo1 instance = aClass.newInstance();System.out.println(instance.func(1));
  • 拦截
public class MyInterceptor2 {/*** @RuntimeType     :修饰拦截方法* @param o         :代理对象(Demo1Sub),实例化方法和构造方法的拦截可用* @return*/@RuntimeTypepublic Object intercept(@This Object o) {System.out.println("实例化增强");return null;}
}
对静态方法进行插桩
  • 父类
public class Demo1 {public static Integer func(Integer id) {return id;}
}
  • 增强
DynamicType.Unloaded<Demo1> unloaded = new ByteBuddy().with(TypeValidation.of(false))// 这里必须使用rebase.rebase(Demo1.class).name("Demo1Sub")// 拦截静态方法.method(ElementMatchers.named("func").and(isStatic())).intercept(MethodDelegation.to(new MyInterceptor3())).make();DynamicType.Loaded<Demo1> loaded = unloaded.load(getClass().getClassLoader());loaded.saveIn(new File(Demo1.class.getClassLoader().getResource("").getPath()));Class<? extends Demo1> aClass = loaded.getLoaded();Method method = aClass.getMethod("func", Integer.class);Object result = method.invoke(null, 1);System.out.println(result);
  • 拦截器
public class MyInterceptor3 {/*** 参考cglib,自定义MethodInterceptor,重写intercept方法中的参数** @RuntimeType     :修饰拦截方法* @param method    : 要拦截的方法* @param objects   :要拦截的方法参数* @param callable  :用于调用目标对象的拦截方法(类似cglib中MethodProxy触发目标类的方法调用的对象)* @return*/@RuntimeTypepublic Object intercept(@Origin Class<?> aclass,@Origin Method method,@AllArguments Object[] objects,@SuperCall Callable<?> callable) {System.out.println("class: " + aclass);//class Demo1SubSystem.out.println("method: " + method);//public java.lang.String Demo1.func(java.lang.Integer)System.out.println("objects: " + Arrays.toString(objects));//[1]System.out.println("callable: " + callable);//Demo1Sub$auxiliary$HV1ZPPVt@3e0e1046Object call = null;try {System.out.println("只有前置增强");// 调用目标静态方法本身call = callable.call();} catch (Exception e) {}return call;}
}
清空方法体

为了保护源码,在编译期,将方法体先保存到数据库或者别的地方,然后使用redefine + method + 指定拦截器,将方法内容清空

DynamicType.Unloaded<Demo1> unloaded = new ByteBuddy().with(TypeValidation.of(false)).rebase(Demo1.class).name("Demo1Sub")// 拦截所有方法.method(any())// 将方法体清空.intercept(StubMethod.INSTANCE).make();
javaagent
拦截实例方法

1、背景

拦截输出SpringMvc的某个Controller的某个方法的耗时

2、实现

  • 启动类
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}, 
scanBasePackages = "com.mjp")
public class ApplicationLoader {public static void main(String[] args) {SpringApplication springApplication = new SpringApplication(ApplicationLoader.class);springApplication.run(args);System.out.println("=============启动成功=============");}
}
  • controller类
@RestController
public class UserController {@GetMapping("/query/{id}")public List<UserPO> query(@PathVariable("id") Long id){List<UserPO> userPOS = Lists.newArrayList();return userPOS;}@GetMapping("/select")public Integer select(){try {TimeUnit.SECONDS.sleep(1L);} catch (InterruptedException e) {}return 1;}
}
  • 定义agent
import net.bytebuddy.agent.builder.AgentBuilder;
import java.lang.instrument.Instrumentation;
import static net.bytebuddy.matcher.ElementMatchers.*;/*** Author:majinpeng* Date: 2024/05/11 15:26*/
public class MyAgent {private static final String REST_CONTROLLER_NAME = "org.springframework.web.bind.annotation.RestController";private static final String CONTROLLER_NAME = "org.springframework.stereotype.Controller";/*** agentmain对应动态加载方式:*     1、在MANIFEST.MF中指定Agent-Class: com.mjp.interceptmvc.MyAgent*     2、打jar包*     3、在main方法中,通过代码的方式执行javaagent* premain对应静态加载方式:*     1、在MANIFEST.MF中指定Premain-Class: com.mjp.interceptmvc.MyAgent*     2、打jar包*     3、指定VM Option参数为jar的路径* @param args* @param instrumentation*/public static void premain(String args, Instrumentation instrumentation) {AgentBuilder builder = new AgentBuilder.Default()// 忽略拦截的类.ignore(nameStartsWith("org.xxx").or(nameContains("MyService")))// 指定拦截的类(这里我们拦截被@XxxController注解修饰的类 或 直接指定具体的类名).type(isAnnotatedWith(named(REST_CONTROLLER_NAME).or(named(CONTROLLER_NAME))).or(nameContains("UserController")))// 指定要拦截的方法.transform(new MyTransform())// 指定监听器.with(new MyListener());builder.installOn(instrumentation);}
}
  • 定义transform
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.utility.JavaModule;import java.security.ProtectionDomain;import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.nameEndsWith;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.not;/*** Author:majinpeng* Date: 2024/05/11 15:39*/
public class MyTransform implements AgentBuilder.Transformer {private static  final String MAPPING_PAG = "org.springframework.web.bind.annotation";private static  final String MAPPING_SUFFIX = "Mapping";/*** 拦截指定的方法,并返回拦截链* 当指定的类第一次被加载时,会进入此transform方法* @param builder           : 拦截链(类似于Stream流),我们之前的subclass方法的返回值就是Builder*                              其后续的各种链式方法的返回值都是Builder类型,直到make()方法返回Unloaded* @param typeDescription   : 被拦截的类的信息* @param classLoader       : 类加载器* @param javaModule* @param protectionDomain* @return*/@Overridepublic DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,TypeDescription typeDescription,ClassLoader classLoader,JavaModule javaModule,ProtectionDomain protectionDomain) {DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition<?> intercept = builder// 这里我们拦截被@XxxMapping修饰的方法 或 方法名称含有select的.method(not(isStatic()).and(isAnnotatedWith(nameStartsWith(MAPPING_PAG).and(nameEndsWith(MAPPING_SUFFIX)))).or(nameContains("select")))// 指定拦截器处理.intercept(MethodDelegation.to(new MyInterceptor()));return intercept;}
}
  • 定义拦截器
import java.lang.reflect.Method;/*** Author:majinpeng* Date: 2024/05/11 15:55*/
public class MyInterceptor {/*** 参考cglib,自定义MethodInterceptor,重写intercept方法中的参数** @RuntimeType     :修饰拦截方法* @param o         :代理对象(Demo1Sub),只有拦截实例方法、构造方法时可用* @param method    : 要拦截的方法,只有拦截实例方法、静态方法时可用* @param objects   :要拦截的方法参数,任意方法的拦截都可用* @param targetObj :目标对象,只有拦截实例方法、构造方法时可用* @param callable  :用于调用目标对象的拦截方法(类似cglib中MethodProxy触发目标类的方法调用的对象)*/@RuntimeTypepublic Object intercept(@This Object o,@Origin Method method,@AllArguments Object[] objects,@Super Object targetObj,@Morph MyCallable callable) {long start = System.currentTimeMillis();Object call = null;try {// 调用目标方法本身call = callable.call(objects);} catch (Exception e) {} finally {long end = System.currentTimeMillis();System.out.println("方法总共耗时:" + (end - start));}return call;}
}
  • 定义Listeren
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.utility.JavaModule;/*** Author:majinpeng* Date: 2024/05/11 16:02*/
public class MyListener implements AgentBuilder.Listener {@Overridepublic void onDiscovery(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {// 当某个类被加载时,就会回调此方法// 和拦截基本无关,因为每个类都要被加载到内存,都会调用此方法// 一般不做处理}@Overridepublic void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b, DynamicType dynamicType) {// 当某个类完成了Transformation之后会回调此方法// 和拦截相关,表明类被拦截处理完成后System.out.println(typeDescription.getActualName() + ": 被拦截处理完成了");}@Overridepublic void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b) {// 被MyAgent中的ignore忽略的类 被加载时,会触发此方法// 一般不做处理}@Overridepublic void onError(String s, ClassLoader classLoader, JavaModule javaModule, boolean b, Throwable throwable) {// bytebuddy在transform过程中发生异常时,会调用此方法// 可以此处打印异常信息,并做异常处理等}@Overridepublic void onComplete(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {// 当某个类被(transform、ignore、onError)三个方法执行完成后,都会调用此方法,类似finally// 一般不做处理}
}

3、调用

1)静态

  • MANIFEST.MF

resources/META-INF/

Premain-Class: com.mjp.interceptmvc.MyAgent
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
  • pom
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>2.4</version><configuration><archive><manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile></archive></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.4</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration><createDependencyReducedPom>false</createDependencyReducedPom><finalName>agent-1.0-SNAPSHOT</finalName></configuration></execution></executions></plugin></plugins></build>
  • VM Option指定生成的jar路径
-javaagent:D:\CodeBetter\target\agent-1.0-SNAPSHOT.jar

2)动态

  • MANIFEST.MF
Agent-Class: com.mjp.interceptmvc.MyAgent
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
  • pom(和静态相同)
  • 代码
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;import java.util.List;@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}, scanBasePackages = "com.mjp")
public class ApplicationLoader {public static void main(String[] args) {List<VirtualMachineDescriptor> list = VirtualMachine.list();for (VirtualMachineDescriptor virtualMachineDescriptor : list) {try {VirtualMachine virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);virtualMachine.loadAgent("D:\\CodeBetter\\target\\agent-1.0-SNAPSHOT.jar");} catch (Exception e) {}}SpringApplication springApplication = new SpringApplication(ApplicationLoader.class);springApplication.run(args);System.out.println("=============启动成功=============");}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/10337.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【北京迅为】《iTOP-3588从零搭建ubuntu环境手册》-第3章 Ubuntu20.04系统设置

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

【论文阅读笔记】jTrans(ISSTA 22)

个人博客地址 [ISSTA 22] jTrans&#xff08;个人阅读笔记&#xff09; 论文&#xff1a;《jTrans: Jump-Aware Transformer for Binary Code Similarity》 仓库&#xff1a;https://github.com/vul337/jTrans 提出的问题 二进制代码相似性检测&#xff08;BCSD&#xff0…

2024数维杯数学建模B题完整论文讲解(含每一问python代码+结果+可视化图)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024数维杯数学建模挑战赛生物质和煤共热解问题的研究完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 B题论…

跟我学C++中级篇——封装对象的实践

一、对象封装 在面向对象编程中&#xff0c;首要的事情就是如何进行对象的封装。说的直白一些&#xff0c;就是如何设计类或者是结构体。许多开发者看过不少的书&#xff0c;也学过很多的设计方法&#xff0c;更看过很多别人的代码。那么如何指导自己进行对象的封装呢&#xf…

数学学习笔记1——二次函数中的数形结合

二次函数中的数形结合 一、解一元二次不等式 基本方法&#xff1a;配方。 x 2 − 4 x 3 < 0 → ( x − 2 ) 2 < 1 → ∣ x − 2 ∣ < 1 → 1 < x < 3 x^2-4x3<0\to(x-2)^2<1\to\lvert x-2\rvert<1\to1<x<3 x2−4x3<0→(x−2)2<1→∣x−…

VBA_MF系列技术资料1-605

MF系列VBA技术资料1-605 为了让广大学员在VBA编程中有切实可行的思路及有效的提高自己的编程技巧&#xff0c;我参考大量的资料&#xff0c;并结合自己的经验总结了这份MF系列VBA技术综合资料&#xff0c;而且开放源码&#xff08;MF04除外&#xff09;&#xff0c;其中MF01-0…

具备教学意义的实操(用队列实现栈)

225. 用队列实现栈 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/implement-stack-using-queues/description/ 实现逻辑 一个是先进先出&#xff08;队列&#xff09;&#xff0c;一个是后进先出&#xff08;栈&#xff09; 这里用两个队列导入一下数据…

JS实现递归功能

// 递归函数示例&#xff1a;计算阶乘 function factorial(n) {if (n 0) {return 1;} else {return n * factorial(n - 1);} }// 调用递归函数计算阶乘 const result factorial(5); console.log(result); // 输出 120 在上面的示例中&#xff0c;我们定义了一个递归函数fact…

项目管理 | 如何做好项目管理?

大部分人在做项目管理时会遇到以下问题&#xff1a; 团队沟通不畅&#xff0c;对于项目的各个环节和配合方无法掌控项目的任务分配和跟踪困难&#xff0c;项目进度不透明项目上线进度慢&#xff0c;没有威信难以服众 那项目管理怎么做&#xff1f;这篇就结合简道云团队的经验…

爬虫学习:XPath提取网页数据

目录 一、安装XPath 二、XPath的基础语法 1.选取节点 三、使用XPath匹配数据 1.浏览器审查元素 2.具体实例 四、总结 一、安装XPath 控制台输入指令&#xff1a;pip install lxml 二、XPath的基础语法 XPath是一种在XML文档中查找信息的语言&#xff0c;可以使用它在HTM…

MySQL 数据库中 Insert 语句的锁机制

在数据库系统中&#xff0c;Insert 语句是常用的操作之一&#xff0c;用于向数据库表中插入新的数据记录。然而&#xff0c;当多个会话&#xff08;或者线程&#xff09;同时对同一张表执行 Insert 操作时&#xff0c;可能会引发一些并发控制的问题&#xff0c;特别是涉及到锁的…

数据结构----二叉树

博主主页: 码农派大星. 关注博主带你了解更多数据结构知识 1. 树型结构 1.1 概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上…

【软考】模拟考卷错题本2024-05-11

1 设计模式- 适配器模式 基本上上述的图解已经涵盖了绝大多数主流的设计模式和其特点。理解记忆下即可&#xff0c;这里对下午的考题也有帮助的。 2 计算机组成原理 cpu 访问速度 这个真的是憨憨咯~看到内存就选内存&#xff0c;题目都没审好。这里的速度比cpu内部的要比外部的…

c++ STL 之栈—— stack 详解

vector 是 stl 的一个关联容器,名叫“栈”&#xff0c;何为“栈”&#xff1f;其实就是一个数组&#xff0c;但有了数组何必还需栈&#xff0c;这是一个高深的问题。 一、简介 1. 定义 栈&#xff0c;是一个柔性数组&#xff08;可变长数组&#xff09;&#xff0c;可以变大变小…

巩固学习5

ls.sort(keylambda x:x[1], reverseTrue) ls.sort(): 这是 Python 中用于对列表进行排序的方法之一。与 sorted() 函数不同&#xff0c;sort() 方法会直接修改原始列表&#xff0c;而 sorted() 函数则返回一个新的已排序列表。 keylambda x: x[1]: 这部分是一个关键参数&#x…

ASE docker related research

ASE 2022 Understanding and Predicting Docker Build Duration: An Empirical Study of Containerized Workflow of OSS Projects 理解和预测 Docker 构建持续时间&#xff1a;OSS 项目容器化工作流程的实证研究 Docker 构建是容器化工作流程的关键组成部分&#xff0c;它…

Centos7安装图形化界面

前言&#xff1a;原文在我的博客网站中&#xff0c;持续更新数通、系统方面的知识&#xff0c;欢迎来访&#xff01; Centos7安装图形化界面https://myweb.myskillstree.cn/43.html 目录 一、安装GNOME桌面 二、开机自启动修改为命令行模式 三、卸载图形化界面 一、安装GN…

Oracle 误操作insert delete update 数据回滚

查询回滚数据 select * from tablename AS OF TIMESTAMP TO_TIMESTAMP(2023-12-29 10:29:00,yyyy-mm-dd hh24:mi:ss) where not exists (select 1 from tablename A where A.xh tablename.xh and A.TIME tablename.TIME); TO_TIMESTAMP(2023-12-29 10:29:00,yyyy-mm-dd h…

【C++】string类的使用③(修改器Modifiers || 非成员函数重载Non-member function overloads)

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; STL || C 目录 前言&#x1f525;修改器&#xff08;Modifiers&#xff09;**operator**appendpush_back和pop_backassigninserterasereplaceswap &#x1f525;非成员函数重载&#xff…

Java入门基础学习笔记4——开发Helloworld入门程序

Java程序开发的三个步骤&#xff1a; 1&#xff09;编写代码 2&#xff09;编译代码 3&#xff09;运行代码 注意事项&#xff1a; 第一个java程序建议使用记事本来编写。 建议代码文件名全英文、首字母大写、满足驼峰模式&#xff0c;源代码文件的后缀必须是.java 注意&a…