03反射及动态代理
反射与动态代理
反射
- 反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义
- Java9中引入了所谓 Open 的概念,只有当被反射操作的模块和指定的包对反射调用者模块 Open,才能使用 setAccessible
静态代理
- 通过手动编写代理类来增强或控制对目标对象的访问。静态代理的核心是代理类和目标类实现相同的接口
动态代理
- 动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面的编程(AOP)
JDK动态代理
动态代码生成发生在newProxyInstance 生成代理类实例的时候
为什么只能对接口进行动态代理?
- 因为代理类继承了Proxy类
- 也可以绕过这一限制,在invoke方法中判断一下,如果接口类和当前类不一致,则通过下面代码获取meth 然后在调用
1
2
3
4
5
6
7
8
9
10public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO:扩展的新功能
if (method.getDeclaringClass() != impl.getClass()) {
// 找到相同签名的方法
Method realMethod = impl.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
return realMethod.invoke(impl, args);
}else{
return method.invoke(impl,args);
}
}
在newProxyInstance的过程中生成的代理类,需要调用的方法里直接调用了InvocationHandler的invoke方法。
具体步骤
- 获取 RealSubject 上的所有接口列表;
- 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX;
- 根据需要实现的接口信息,在代码中动态创建 该 Proxy 类的字节码;
- 将对应的字节码转换为对应的 class 对象;
- 创建 InvocationHandler 实例 handler,用来处理 Proxy 所有方法调用;
- Proxy 的 class 对象 以创建的 handler 对象为参数,实例化一个 proxy 对象。
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
76import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 1. 定义业务接口
interface UserService {
void createUser(String username);
void deleteUser(String username);
}
// 2. 实现业务接口(真实对象)
class UserServiceImpl implements UserService {
@Override
public void createUser(String username) {
System.out.println("【真实调用】创建用户: " + username);
}
@Override
public void deleteUser(String username) {
System.out.println("【真实调用】删除用户: " + username);
}
}
// 3. 实现调用处理器(增强逻辑)
class LoggingHandler implements InvocationHandler {
private final Object target; // 被代理的真实对象
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("\n[日志] 开始执行方法: " + method.getName());
System.out.println("[参数] " + (args != null ? String.join(", ", (CharSequence[]) args) : "无"));
// 调用真实对象的方法
Object result = method.invoke(target, args);
// 后置增强
System.out.println("[日志] 方法执行完成");
// 返回调用结果(本例中方法返回void,此处返回null)
return result;
}
}
public class DynamicProxyDemo {
public static void main(String[] args) {
// 1. 创建真实对象
UserService realService = new UserServiceImpl();
// 2. 创建调用处理器(传入真实对象)
InvocationHandler handler = new LoggingHandler(realService);
// 3. 创建动态代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(), // 类加载器
new Class<?>[] { UserService.class }, // 代理接口
handler // 调用处理器
);
// 4. 通过代理对象调用方法
proxy.createUser("张三");
proxy.deleteUser("李四");
// 5. 打印代理类信息
System.out.println("\n代理类: " + proxy.getClass().getName());
System.out.println("代理类的父类: " + proxy.getClass().getSuperclass().getName());
System.out.println("代理类实现的接口: ");
for (Class<?> intf : proxy.getClass().getInterfaces()) {
System.out.println(" - " + intf.getName());
}
}
}
CGlib
- 主要没有JDK动态代理必须依赖接口的性质
- 创建目标类的子类的方式,因为是子类化,我们可以达到近似使用被调用者本身的效果
- 基于继承代理(字节码操作 + 子类继承)
- 需要默认构造函数(子类化要求)
- CGLib代理创建成本高,应缓存复用
- 实现MethodInterceptor接口
- 简单使用
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
32public static void main(String[] args) {
// 实现了MethodInterceptor的类
ServiceProxy serviceProxy = new ServiceProxy();
Enhancer enhancer = new Enhancer();
// 设置要代理的类
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new NoOp() {
@Override
public int hashCode() {
// 生成的代理类使用原来的hashCode,确保不是同一个对象才初始化
return super.hashCode();
}
});
enhancer.setCallback(serviceProxy);
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
return 0;
}
});
UserService userService = (UserService) enhancer.create();
}
public class ServiceProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("here is interceptor1");
return proxy.invokeSuper(obj,args);
}
}
callback
- 这里的callback可以认为是cglib用于生成字节码的实现手段,cglib一共实现了6种callback,用于对代理类目标进行不同手段的代理,非常灵活,分别为:
- FixedValue
- InvocationHandler
- LazyLoader
- MethodInterceptor
- Dispatcher
- NoOp
Dispatcher
- 实现Dispatcher接口,要求实现loadObject方法,返回期望的代理类。值的一提的是,loadobject方法在每次调用被拦截方法的时候都会被调用一次
1
2
3
4
5
6
7
8
9
10/****
* 与lazy不同的是,每一次调用代理方法的时候,都会调用一次Dispatcher的loadObject获取对象
* 而lazy则会缓存下来。
*/
public class DispatcherCallBack implements Dispatcher {
public Object loadObject() throws Exception {
CallbackBean callbackBean = new CallbackBean();
return callbackBean;
}
}
LazyLoader
- 实现LazyLoader的loadObject方法,返回对象实例,该实例只有第一次调用的时候进行初始化,之后不再重新调用,proxy类初始化时进行了成员的赋值,之后使用该成员进行调用父类方法
1
2
3
4
5
6
7
8
9
10
11
12
13/**
*
* 延迟加载初始化
* 类似于spring prototype的singleton ,在第一次调用的时候进行初始化,并且将此实例存储起来,之后都将返回改实例
* 可参考资料:
* https://shensy.iteye.com/blog/1881277
*/
public class LazyLoaderCallback implements LazyLoader {
public Object loadObject() throws Exception {
CallbackBean callbackBean = new CallbackBean();
return callbackBean;
}
}
FixedValue
- 实现FixedValue接口,该callback同样要求实现一个loadobject方法,只不过需要注意的是该loadobject方法相同与重写了被代理类的相应方法,因为在被代理之后,FixedValue callback只会调用loadobject,而不会再调用代理目标类的相应方法!
1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* 该callback相当于重写了相应的函数实现。并不会调用原函数
*/
public class FixValueCallback implements FixedValue {
/**
* 被代理方法的指定函数将会无条件的返回改object,动态的变更返回值
* @return
* @throws Exception
*/
public Object loadObject() throws Exception {
System.out.println("this is fixvalue callback ..... overwrite the code....");
return true;
}
}
InvocationHandler
- 需要实现InvocationHandler接口,实现invoke对象,该拦截传入了proxy对象,用于自定义实现,与MethodInterceptor相似,慎用method的invoke方法。切忌不要造成循环调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class InvocationHandlerCallback implements InvocationHandler {
/**
* invocationHandler的invoke方法传入的method和proxy都是代理本身对象
* 切忌重复调用,会循环调用
* @param proxy 代理类本身
* @param method 代理类内部的方法
* @param args 参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invocationHandlerCallback Before....");
method.invoke(proxy.getClass().getSuperclass().newInstance(),args);
//会无限循环
//method.invoke(proxy,args);
System.out.println("invocationHandlerCallback after....");
return null;
}
}
MethodInterceptor
- 实现MethodInterceptor的intercept,实现被代理对象的逻辑植入。也是最常用的callback
1
2
3
4
5
6
7
8
9
10
11/**
* 生成代理类字节码。对方法进行拦截调用
*/
public class MethodInterceptorCallback implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before invoke........");
proxy.invokeSuper(obj,args);
System.out.println("after invoke.........");
return null;
}
}
NoOp
- 通过接口声明了一个单例对象,该代理不对被代理类执行任何操作
1
2
3
4
5
6
7
8
9
10
11/**
* Methods using this {@link Enhancer} callback will delegate directly to the
* default (super) implementation in the base class.
*/
public interface NoOp extends Callback
{
/**
* A thread-safe singleton instance of the <code>NoOp</code> callback.
*/
public static final NoOp INSTANCE = new NoOp() { };
}
callBackFilter
- 当传入多个callback类时,可以通过callBackFilter来选择使用哪个callback,当多个符合时,使用首个匹配的callback
AspectJ
- Aspect Oriented Programming,可以插入字节码的位置
- 在方法(包括构造方法)被调用的位置。
- 在方法体(包括构造方法)的内部。
- 在读写变量的位置。
- 在静态代码块内部。
- 在异常处理的位置的前后
- 此外,它也可以直接将原位置的代码替换为自定义的代码。
- 缺点:
- 切入点固定
- 正则表达式的局限性
- 性能比较低
03反射及动态代理
https://x-leonidas.github.io/2022/02/01/04Java/java基础/03反射及动态代理/