反射
反射
反射是通过在运行时获取类的信息,然后进行操作。
Class 获取
Class 对象是 Java 类的运行时类型信息,是 Java 反射机制的入口。它的获取方式,主要有三种:
1. 通过 Class.forName() 获取
Class<?> clazz = Class.forName("com.example.MyClass");2. 通过类的字面量来获取
Class<?> clazz = MyClass.class;3. 通过对象获取
MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();通过获取到 Class 对象,我们就可以得到这个类的所有元信息,例如:全限名,父类,实现的接口,类上的注解,属性字段,方法,构造函数等。(方法和方法字段也可以有注解,也能够获取到)
[!IMPORTANT] 对于同一个类来说,在 JVM 堆中创建的 Class 对象有且仅有一个,例如:一个 User 类,无论创建多少个实例对象,这些实例对象指向的 Class 对象都是同一个。
创建对象
通过上一步获取到 Class 对象之后,我们就能够创建对象了。主要的方式有两种:
1. 通过 newInstance() 方法创建对象(已废弃)
MyClass obj = clazz.newInstance();[!WARNING] 此方法已被废弃,原因是
newInstance()方法在创建对象时,会调用对象的无参构造函数,如果对象没有无参构造函数,就会抛出异常。
2. 通过获取构造器来创建对象(推荐)
这种方法更加灵活,可以创建有参构造函数的对象,也可以创建无参构造函数的对象,还可以创建私有构造函数的对象。
// 无参构造函数
MyClass obj = clazz.getConstructor().newInstance();
// 有参构造函数
MyClass obj = clazz.getConstructor(String.class).newInstance("value");调用方法
通过反射调用方法,主要分为静态和实例方法。如果是静态方法,不用传入实例对象;如果是实例方法,需要传入实例对象。
// 静态方法
Method method = clazz.getMethod("staticMethod");
method.invoke(null, args);
// 实例方法
Method method = clazz.getMethod("instanceMethod");
method.invoke(obj, args);操作属性
通过反射操作属性,主要分为获取属性和设置属性。
// 获取属性
Field field = clazz.getDeclaredField("fieldName");
field.get(obj);
// 设置属性
Field field = clazz.getDeclaredField("fieldName");
field.set(obj, value);常用 API
| 方法 | 说明 |
|---|---|
clazz.getName() | 获取类的全限定名 |
clazz.getSuperclass() | 获取父类 |
clazz.getInterfaces() | 获取实现的接口 |
clazz.getAnnotation(Annotation.class) | 获取类上的注解 |
clazz.getField("fieldName") | 获取指定 public 属性字段 |
clazz.getMethod("methodName", Class<?>...) | 获取指定 public 方法 |
clazz.getConstructor(Class<?>...) | 获取指定 public 构造器 |
获取方法的两种方式:
clazz.getMethods():获取 public 方法,包含父类的clazz.getDeclaredMethods():获取本类所有方法(含 private),不包含父类
获取属性字段的两种方式:
clazz.getFields():获取 public 属性字段,包含父类的clazz.getDeclaredFields():获取本类所有属性字段(含 private),不包含父类
其他:
clazz.getDeclaringClass():获取当前操作对象是由哪个类定义的,常用于动态代理中屏蔽 Object 类方法的干扰(hashCode、equals、toString等)
动态代理
动态代理是通过在运行时创建代理对象,然后在代理对象上调用方法,从而实现对目标对象的代理。
JDK 动态代理
JDK 动态代理是通过实现 InvocationHandler 接口来实现的。 具体如下:
MyInterface target = new MyClass();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class<?>[]{MyInterface.class},
(p, method, args) -> {
// 处理 Object 方法(避免进入你的增强逻辑,也避免语义混乱)
if (method.getDeclaringClass() == Object.class) {
return switch (method.getName()) {
case "toString" -> "Proxy(" + MyInterface.class.getName() + ")@" +
Integer.toHexString(System.identityHashCode(p));
case "hashCode" -> System.identityHashCode(p);
case "equals" -> p == args[0];
default -> method.invoke(target, args);
};
}
// 前置
// ...before...
try {
Object ret = method.invoke(target, args); // 转发到目标对象
// 后置
// ...after...
return ret;
} catch (InvocationTargetException e) {
throw e.getCause(); // 抛出真实业务异常(常见写法)
}
}
);参数解析
- proxy:代理对象本身(一般不要 method.invoke(proxy, ...),会递归)
- method:本次调用对应的 Method(可能来自接口,也可能是 Object 的)
- args:参数数组(无参时可能是 null)
限制:
- JDK 动态代理只能代理接口;没有接口就用 CGLIB(Spring 会自动选策略)。
CGLIB 动态代理
CGLIB 动态代理是通过实现 MethodInterceptor 接口来实现的。主要解决了 JDK 动态代理必须要实现接口的一个弊端,CGLIB 主要是通过创建一个子类,继承目标类,然后在子类上进行增强,从而实现对目标对象的代理。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());//设置父类,也就是代理的目标类
enhancer.setCallback(new MethodInterceptor() {//设置回调函数,也可以设置多个,用 setCallbacks()方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置
// ...before...
try {
Object ret = methodProxy.invokeSuper(obj, args); //调用父类的实现,走代理
Object ret = method.invoke(target, args); // 转发到目标对象,如果内部嵌套调用了,不走代理
// 后置
// ...after...
return ret;
} catch (InvocationTargetException e) {
throw e.getCause(); // 抛出真实业务异常(常见写法)
}
}
});参数解析:
- obj:代理对象本身(CGLIB 生成的子类实例)
- method:本次调用对应的 Method(可能来自接口,也可能是 Object 的) 可以用它做,获取方法名:method.getName(),获取注解:method.getAnnotation(...),获取参数类型:method.getParameterTypes(),获取返回类型:method.getReturnType()等
- args:当前方法调用的参数数组(无参时可能是 null)
- proxy:MethodProxy,CGLIB 提供的用于快速调用目标方法的代理关键的两个调用方式:
- methodProxy.invokeSuper(obj, args):调用“父类(Target)”的原始实现(最常用、最正确)在代理子类里,执行 super.method(…)
- methodProxy.invoke(target, args):调用某个“具体对象 target”上的该方法(不一定是 super)这里感觉是本身持有了这个具体的 target,然后执行逻辑在这个 target 之中,不一定是我们当前代理类的父类。应该是这样理解的。哈哈哈哈
限制:
CGLIB 动态代理只能代理类;没有类就用 JDK 动态代理(Spring 会自动选策略)
CGLIB 动态代理不能代理 final 类;
CGLIB 动态代理不能代理 final,private,static方法;
- 当目标类内部用 this.otherMethod() 调用自己另一个方法时,很多 AOP/代理场景会出现:不经过代理入口,导致增强逻辑不生效(Spring AOP 经典坑)。
个人理解
代理是否生效,本质取决于:调用是否经过代理入口(proxy)。
- 外部通过 proxy.xxx() 调用:通常会被拦截并增强
- 直接对 target.xxx() 调用:绕过代理入口,不会增强
- 在 Spring AOP 中,proxy.a() 虽然会拦截,但 a() 内部 this.b() 属于对象内部自调用,常常不会再次经过 proxy,从而出现“b 上的增强不生效”的现象
感觉还是有一些错误没有纠正 待定!!
