reference:https://blog.csdn.net/weixin_41427129/article/details/113561980
一、概述
本文主要讲解的是 CGLIB 的常用 API 及其使用方式。使用的 CGLIB 依赖如下所示:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
首先创建接口 CglibSampleInterface 和类 CglibSampleClass,如下所示:
1 public interface CglibSampleInterface { 2 String show(String name); 3 } 4 5 public class CglibSampleClass { 6 public String show(String name) { 7 return String.format("%s show love to you!", name); 8 } 9 }
二、API 详解
2.1 Enhancer
Enhancer 即字节码增强器,它和 JDK 动态代理中的 Proxy 类似,是 CGLIB 中最常用的一个类,既能代理普通的 Java 类,也能代理接口。Enhancer 通过创建一个被代理类的子类来拦截所有的方法调用(包括 Object#toString()、Object#hashCode()),但是它不能拦截 final 修饰的方法(如 Object#getClass()),也不能代理 final 修饰的类。示例如下所示:
1 public class EnhancerClass { 2 public static void main(String[] args) throws Exception { 3 // 字节码增强器 4 Enhancer enhancer = new Enhancer(); 5 // 设置代理类的父类 6 enhancer.setSuperclass(CglibSampleClass.class); 7 // 使用 FixedValue,拦截返回值,每次返回固定值 "Robin walk to you!" 8 enhancer.setCallback((FixedValue) () -> "Robin walk to you!"); 9 10 // 创建代理对象 11 CglibSampleClass sampleClass = (CglibSampleClass) enhancer.create(); 12 13 System.out.println(sampleClass.show("Robin")); 14 System.out.println(sampleClass.show("Nami")); 15 System.out.println(sampleClass.toString()); 16 17 // 无法对 final 修饰的 getClass() 方法进行拦截 18 System.out.println(sampleClass.getClass()); 19 // 因为 hashCode() 需要返回的是 Number 类型,但是 FixedValue 返回值是 String 类型,无法实现类型转换,故会抛出异常 20 System.out.println(sampleClass.hashCode()); 21 } 22 }
上述示例使用 FixedValue() 拦截所有的方法调用(包括非 final 修饰的 show()、toString()、hashCode() 方法)并返回相同的值,但是,由于 hashCode() 方法的返回值类型是 int 型,而我们返回的是一个 String,所以才会抛出 ClassCastException 异常。
此外,create(Class[] argumentTypes, Object[] arguments) 也可创建代理对象,用来匹配被增强类的不同构造方法,第一参数表示构造方法的参数类型,第二个参数表示构造方法的参数值,这两个参数都是数组类型。也可以使用 Enhancer#createClass() 来创建类的字节码,然后使用字节码加载完成后的类动态生成代理对象。
结果如下所示:
public class EnhancerClass { public static void main(String[] args) throws Exception { // 字节码增强器 Enhancer enhancer = new Enhancer(); // 设置代理类的父类 enhancer.setSuperclass(CglibSampleClass.class); // 使用 FixedValue,拦截返回值,每次返回固定值 "Robin walk to you!" enhancer.setCallback((FixedValue) () -> "Robin walk to you!"); // 创建代理对象 CglibSampleClass sampleClass = (CglibSampleClass) enhancer.create(); System.out.println(sampleClass.show("Robin")); System.out.println(sampleClass.show("Nami")); System.out.println(sampleClass.toString()); // 无法对 final 修饰的 getClass() 方法进行拦截 System.out.println(sampleClass.getClass()); // 因为 hashCode() 需要返回的是 Number 类型,但是 FixedValue 返回值是 String 类型,无法实现类型转换,故会抛出异常 System.out.println(sampleClass.hashCode()); } }
代理接口的示例如下所示,结果与上面的相同。
1 public class EnhancerInterface { 2 public static void main(String[] args) throws Exception { 3 Enhancer enhancer = new Enhancer(); 4 5 // 设置被代理的接口 6 enhancer.setInterfaces(new Class[]{CglibSampleInterface.class}); 7 8 enhancer.setCallback((FixedValue) () -> "Robin walk to you!"); 9 10 CglibSampleInterface cglibSampleInterface = (CglibSampleInterface) enhancer.create(); 11 12 System.out.println(cglibSampleInterface.show("Robin")); 13 System.out.println(cglibSampleInterface.show("Nami")); 14 System.out.println(cglibSampleInterface.toString()); 15 System.out.println(cglibSampleInterface.getClass()); 16 System.out.println(cglibSampleInterface.hashCode()); 17 } 18 }
2.2 Callback
Callback 即回调,其接口如下所示,是一个标识接口(不含任何方法)。它的回调时机是被代理类的方法被调用的时候,即被代理类的方法被调用时,Callback 的实现逻辑就会被调用。此外,可通过 Enhancer#setCallback() 和 Enhancer#setCallbacks() 设置 Callback,若设置了多个 Callback,则会按照设置的顺序进行回调。CGLIB 提供了以下几种 Callback 的子类:
1 package net.sf.cglib.proxy; 2 3 public interface Callback { 4 }
NoOpFixedValueInvocationHandlerMethodInterceptorDispatcherLazyLoader
2.2.1 NoOp
NoOp 即 No Operation,不做任何操作,该回调实现只是简单地将方法调用委托给被代理类的原始方法,即不加任何操作地调用原始类的原始方法,因此,该回调实现也不能做接口代理。实例如下所示:
public class NoOpDemo {
public static void main(String[] args) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CglibSampleClass.class);
// 设置 Callback 回调为 NoOp
enhancer.setCallback(NoOp.INSTANCE);
CglibSampleClass sampleClass = (CglibSampleClass) enhancer.create();
System.out.println(sampleClass.show("Robin"));
}
}
// 结果如下所示:
Robin show love to you!
2.2.2 FixedValue
FixedValue 即固定值。它提供了一个 loadObject() 方法并返回一个原方法调用想要的固定结果。此外,该 Callback 中看不到任何原方法的信息,也就没有调用原方法的逻辑。需要注意的是,若 loadObject() 方法的返回值并不能转换成原方法的返回值类型,则会抛出类型转换异常 (ClassCastException)。示例即前面两个 Enhancer 的 Demo。
2.2.3 InvocationHandler
InvocationHandler 即 net.sf.cglib.proxy.InvocationHandler,它和 JDK 动态代理中 java.lang.reflect.InvocationHandler 的功能类似,同样也提供了如下的一个方法:
Object invoke(Object proxy, Method method, Object[] objects)
- 1
不过需要注意的是,所有对 proxy 对象的方法调用都会被委托给同一个 InvocationHandler,所以可能会导致无限循环 (因为 invoke 中调用的任何被代理类的方法,均会重新代理到 invoke() 中)
public class InvocationHandlerDeadLoopDemo {
public static void main(String[] args) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CglibSampleClass.class);
// 设置 Callback 的子类 InvocationHandler
enhancer.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
// 错误做法,会重新调用 InvocationHandler 的 invoke()
// return method.invoke(proxy, objects);
if (!Objects.equals(method.getDeclaringClass(), Object.class) && Objects.equals(String.class, method.getReturnType())) {
return "Nami fall in love!";
}
return "No one fall in love with you!";
}
});
CglibSampleClass sampleClass = (CglibSampleClass) enhancer.create();
System.out.println(sampleClass.show("Robin"));
}
}
// 结果如下所示:
Nami fall in love!
2.2.4 MethodInterceptor
MethodInterceptor,即方法拦截器,它可以实现类似于 AOP 编程中的环绕增强(Around Advice)。它只有一个方法:
public Object intercept(Object proxy, // 代理对象
java.lang.reflect.Method method, // 方法
Object[] args, // 方法参数
MethodProxy methodProxy // 方法代理
) throws Throwable
设置了 MethodInterceptor 后,代理类的所有方法调用都会转而执行这个接口中的 intercept 方法而不是原方法。若需要在 intercept 方法中执行原方法,有以下两种方式:
- 使用参数
method基于代理对象proxy进行反射调用,但是使用方法代理methodProxy效率会更高(methodProxy基于整数数字的索引来直接调用方法)。 - 使用
MethodProxy调用invokeSuper()执行原方法,这种方式效率更好,推荐使用这种方式。
需要注意的是,使用 MethodProxy#invokeSuper() 相当于通过方法代理直接调用原类的对应方法,若调用 MethodProxy#invoke() 会进入死循环导致爆栈,原因跟 InvocationHandler 差不多。
2.2.5 Dispatcher
Dispatcher 即分发器,提供了一个 Object loadObject() throws Exception 方法,每次对增强对象进行方法调用都会回调 Dispatcher#loadObject() 方法并返回一个被代理类的对象来调用原方法。Dispatcher 可以类比为 Spring 中的 Prototype 类型。示例如下所示:
public class DispatcherDemo {
private static final AtomicInteger COUNTER = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
// 被代理接口的对象
CglibSampleInterfaceImpl impl = new CglibSampleInterfaceImpl();
Enhancer enhancer = new Enhancer();
enhancer.setInterfaces(new Class[]{CglibSampleInterface.class});
enhancer.setCallback(new Dispatcher() {
@Override
public Object loadObject() throws Exception {
COUNTER.incrementAndGet();
// 返回的被代理接口的对象
return impl;
}
});
CglibSampleInterface cglibSampleInterface = (CglibSampleInterface) enhancer.create();
System.out.println(cglibSampleInterface.show("Robin"));
System.out.println(cglibSampleInterface.show("Nami"));
System.out.println(COUNTER.get());
}
private static class CglibSampleInterfaceImpl implements CglibSampleInterface {
public CglibSampleInterfaceImpl() {
System.out.println("CglibSampleInterfaceImpl init...");
}
@Override
public String show(String name) {
return String.format("%s show love to you!", name);
}
}
}
// 结果如下所示:
CglibSampleInterfaceImpl init...
Robin show love to you!
Nami show love to you!
2
如输出结果所示,计数器的结果为 2,可以验证该结论:每次调用方法都会回调 Dispatcher 中的实例进行调用。
2.2.6 LazyLoader
LazyLoader 即懒加载器,它只提供了一个方法 Object loadObject() throws Exception ,loadObject() 方法会在第一次被代理类的方法调用时触发,它返回一个被代理类的对象,这个对象会被存储起来然后负责所有被代理类方法的调用。
适用于被代理类或者代理类的对象的创建比较麻烦,且不确定它是否会被使用。LazyLoader 可以类比为 Spring 中 Lazy 模式的 Singleton。示例如下所示:
public class LazyLoaderDemo {
private static final AtomicInteger COUNTER = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
// 被代理接口的对象
CglibSampleInterfaceImpl impl = new CglibSampleInterfaceImpl();
Enhancer enhancer = new Enhancer();
enhancer.setInterfaces(new Class[]{CglibSampleInterface.class});
enhancer.setCallback(new LazyLoader() {
@Override
public Object loadObject() throws Exception {
COUNTER.incrementAndGet();
// 返回被代理接口的对象
return impl;
}
});
CglibSampleInterface cglibSampleInterface = (CglibSampleInterface) enhancer.create();
System.out.println(cglibSampleInterface.show("Robin"));
System.out.println(cglibSampleInterface.show("Nami"));
System.out.println(COUNTER.get());
}
private static class CglibSampleInterfaceImpl implements CglibSampleInterface {
public CglibSampleInterfaceImpl() {
System.out.println("CglibSampleInterfaceImpl init...");
}
@Override
public String show(String name) {
return String.format("%s show love to you!", name);
}
}
}
// 结果如下所示:
CglibSampleInterfaceImpl init...
Robin show love to you!
Nami show love to you!
1
如输出结果所示,计数器的结果为 1,可以验证该结论:LazyLoader 中的实例只回调了1次。
2.3 BeanCopier
BeanCopier 即 JavaBean 属性拷贝器,提供从一个 JavaBean 实例中拷贝属性到另一个 JavaBean 实例中的功能,类型必须完全匹配,属性才能拷贝成功(基本数据类型和其包装类不属于相同类型)。它还提供了一个 net.sf.cglib.core.Converter 转换器回调接口让使用者控制拷贝的过程。
此外,BeanCopier 内部使用了缓存和基于 ASM 动态生成 BeanCopier 的子类(该子类实现的转换方法中直接使用实例的 Getter 和 Setter 方法),拷贝速度极快(BeanCopier 属性拷贝比直接的 Setter、Getter 稍慢,原因在于首次需要动态生成 BeanCopier 的子类,一旦子类生成完成之后就和直接调用 Setter、Getter 效率一致,但是效率远远高于其他使用反射的工具类库)。示例如下所示:
public class BeanCopierDemo {
// 缓存 BeanCopier 实例,BeanCopier 生成是一个耗时的操作
private static final Map<String, BeanCopier> CACHE = new ConcurrentHashMap<>();
public static void main(String[] args) throws Exception {
//这里 useConverter 设置为 false,调用 copy 方法的时候不能传入转换器实例
BeanCopier beanCopier;
String key = generateCacheKey(Person.class, Person.class);
if (CACHE.containsKey(key)) {
beanCopier = CACHE.get(key);
} else {
beanCopier = BeanCopier.create(Person.class, Person.class, false);
CACHE.put(key, beanCopier);
}
Person person = new Person();
person.setId(10086L);
person.setName("Robin");
person.setAge(25);
Person newPerson = new Person();
beanCopier.copy(person, newPerson, null); //这里转换器实例要传 null
System.out.println(newPerson);
}
private static String generateCacheKey(Class<?> source, Class<?> target) {
return String.format("%s-%s", source.getName(), target.getName());
}
@ToString
@Data
private static class Person {
private Long id;
private String name;
private Integer age;
}
}
// 结果如下所示:
BeanCopierDemo.Person(id=10086, name=throwable, age=25)
2.4 ImmutableBean
ImmutableBean 即不可变的 Bean,它可以创建一个对象的包装类,但这个包装类是不可变的,否则会抛出 IllegalStateException 异常,但是可以通过操作底层对象来改变包装类的对象。示例如下所示:
public class ImmutableBeanDemo {
public static void main(String[] args) throws Exception {
Person person = new Person();
person.setName("波雅汉考克");
Person immutablePerson = (Person) ImmutableBean.create(person);
System.out.println(immutablePerson.getName());
// 通过修改底层对象来改变包装类的对象
person.setName("白星公主");
System.out.println(immutablePerson.getName());
// 此处修改了包装类的对象,会抛出异常
immutablePerson.setName("蕾贝卡");
System.out.println(immutablePerson.getName());
}
@Data
private static class Person {
private String name;
}
}
// 结果如下所示:
波雅汉考克
白星公主
Exception in thread "main" java.lang.IllegalStateException: Bean is immutable
2.5 BeanGenerator
BeanGenerator 即 Bean 生成器,它能够在运行时动态的创建一个JavaBean。可以直接设置父类,生成的 JavaBean 就是父类类型的实例。示例如下所示:
public class BeanGeneratorDemo {
public static void main(String[] args) throws Exception {
BeanGenerator beanGenerator = new BeanGenerator();
// 添加 JavaBean 的属性及其类型
beanGenerator.addProperty("name", String.class);
Object target = beanGenerator.create();
Method setter = target.getClass().getDeclaredMethod("setName", String.class);
Method getter = target.getClass().getDeclaredMethod("getName");
// 设置属性的值
setter.invoke(target, "千鹤");
System.out.println(getter.invoke(target));
}
}
// 结果如下所示:
千鹤
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
2.6 BeanMap
BeanMap 类实现了 JDK 的 java.util.Map 接口,它可以将一个 JavaBean 对象中的所有属性转换为一个 <String, Object> 的Map实例。示例如下所示:
public class BeanMapDemo {
public static void main(String[] args) throws Exception {
Person person = new Person();
person.setName("Nami");
BeanMap beanMap = BeanMap.create(person);
System.out.println(beanMap);
System.out.println(beanMap.get("name"));
}
@Data
private static class Person {
private String name;
}
}
// 结果如下所示:
{name=Nami}
Nami
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
2.7 Mixin
Mixin 能够将多个接口的多个实现合并到同一个接口的单个实现中。示例如下所示:
public class MixinDemo {
interface InterfaceFirst {
String first();
}
interface InterfaceSecond {
String second();
}
static class ImplFirst implements InterfaceFirst {
@Override
public String first() {
return "First one";
}
}
static class ImplSecond implements InterfaceSecond {
@Override
public String second() {
return "Second one";
}
}
interface MixinImpl extends InterfaceFirst, InterfaceSecond {
}
public static void main(String[] args) throws Exception {
Mixin mixin = Mixin.create(
new Class[]{InterfaceFirst.class, InterfaceSecond.class, MixinImpl.class}, // 接口数组
new Object[]{new ImplFirst(), new ImplSecond()} // 代理对象数组
);
MixinImpl mixinImpl = (MixinImpl) mixin;
System.out.println(mixinImpl.first());
System.out.println(mixinImpl.second());
}
}
// 结果如下所示:
First one
Second one
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
2.8 FastClass
FastClass 就是对 Class 对象进行特定的处理,可以理解为索引类,比如通过数组保存 method 引用,因此 FastClass 引出了一个 index 下标的新概念。通过数组存储 method,constructor 等 class 信息,从而将原先的反射调用,转化为 class.index 的直接调用以提高效率,从而体现所谓的 FastClass。此外, 在接口或者代理类的方法比较少的时候,使用 FastClass 进行方法调用有可能比原生反射方法调用 Method#invoke() 的效率高。示例如下所示:
public class FastClassDemo {
public static void main(String[] args) throws Exception {
FastClass fastClass = FastClass.create(CglibSampleClass.class);
FastMethod fastMethod = fastClass.getMethod("show", new Class[]{String.class});
CglibSampleClass cglibSampleClass = new CglibSampleClass();
// 使用 FastMethod 进行调用
System.out.println(fastMethod.invoke(cglibSampleClass, new Object[]{"Robin"}));
// 获得方法的下标索引 index
System.out.println(fastMethod.getIndex());
}
}
// 结果如下所示:
Robin show love to you!
0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
三、扩展
3.1 LazyLoader 实现延迟加载
public class LazyLoaderExt {
// 计数器
private static final AtomicInteger COUNTER = new AtomicInteger(0);
public static class PictureAlbum {
private String topic;
private List<PictureContent> pictureContentList;
public PictureAlbum() {
this.topic = "海贼王图片集";
this.pictureContentList = getPictureContentList();
}
private List<PictureContent> getPictureContentList() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(List.class);
enhancer.setCallback(new LazyLoader() {
@Override
public Object loadObject() throws Exception {
List<PictureContent> list = new ArrayList<>();
list.add(new PictureContent("Nami"));
list.add(new PictureContent("Lufei"));
list.add(new PictureContent("波雅汉考克"));
COUNTER.getAndIncrement();
return list;
}
});
return (List<PictureContent>) enhancer.create();
}
}
// 图片实体
public static class PictureContent {
private String name;
public PictureContent(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
// 实例化 PictureAlbum
PictureAlbum pictureAlbum = new PictureAlbum();
System.out.println(pictureAlbum.topic);
System.out.println("COUNTER ==> " + COUNTER.get());
System.out.println("=====图片名=====");
for (PictureContent pictureContent : pictureAlbum.pictureContentList) {
System.out.println(pictureContent.name);
}
System.out.println("COUNTER ==> " + COUNTER.get());
}
}
// 结果如下所示:
海贼王图片集
COUNTER ==> 0
=====图片名=====
Nami
Lufei
波雅汉考克
COUNTER ==> 1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
从计数器的输出结果可以看到:即使实例化了 PictureAlbum,pictureContentList 的赋值只有在调用它的时候,才会通过 LazyLoader#loadObject 方法去赋值。
3.2 Dispathcer 扩展类的接口
该示例中有 UserService 类、IMethodInfo 接口以及该接口的实现类 DefaultMethodInfo,在这里,我们通过 CGLIB 创建一个代理类,该代理类的父类是 UserService,且实现了 IMethodInfo 接口,将接口 IMethodInfo 中所有的方法都转发给 DefaultMethodInfo 处理,代理类中的其他方法转发给父类 UserService 处理。
简而言之,就是对 UserService 进行了增强,使其具有 IMethodInfo 接口中的功能。
public class DispatcherExt {
public static class UserService {
public void add() {
System.out.println("新增用户");
}
public void update() {
System.out.println("更新用户信息");
}
}
// 用来获取方法信息的接口
public interface IMethodInfo {
// 获取方法数量
int methodCount();
// 获取被代理的对象中方法名称列表
List<String> methodNames();
}
// IMethodInfo 的默认实现
public static class DefaultMethodInfo implements IMethodInfo {
private Class<?> targetClass;
public DefaultMethodInfo(Class<?> targetClass) {
this.targetClass = targetClass;
}
@Override
public int methodCount() {
return targetClass.getDeclaredMethods().length;
}
@Override
public List<String> methodNames() {
return Arrays.stream(targetClass.getDeclaredMethods()).
map(Method::getName).
collect(Collectors.toList());
}
}
public static void main(String[] args) {
Class<?> targetClass = UserService.class;
Enhancer enhancer = new Enhancer();
// 设置代理的父类
enhancer.setSuperclass(targetClass);
// 设置代理需要实现的接口列表
enhancer.setInterfaces(new Class[]{IMethodInfo.class});
// 创建一个方法统计器,即 IMethodInfo 的默认实现类
IMethodInfo methodInfo = new DefaultMethodInfo(targetClass);
// 创建回调用器列表
Callback[] callbacks = {
// 处理 UserService 中所有的方法
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
},
// 处理 IMethodInfo 接口中的方法
new Dispatcher() {
@Override
public Object loadObject() throws Exception {
/**
* 用来处理代理对象中 IMethodInfo 接口中的所有方法
* 所以此处返回的为 IMethodInfo 类型的对象,
* 将由这个对象来处理代理对象中 IMethodInfo 接口中的所有方法
*/
return methodInfo;
}
}
};
enhancer.setCallbacks(callbacks);
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
// 当方法在 IMethodInfo 中定义的时候,返回 callbacks 中的第二个元素
return method.getDeclaringClass() == IMethodInfo.class ? 1 : 0;
}
});
Object proxy = enhancer.create();
//代理的父类是UserService
UserService userService = (UserService) proxy;
userService.add();
//代理实现了IMethodInfo接口
IMethodInfo mf = (IMethodInfo) proxy;
System.out.println(mf.methodCount());
System.out.println(mf.methodNames());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100