Java代理机制

Java代理分为静态代理与动态代理。

Java提供了两种主要类型的代理:静态代理和动态代理。动态代理是在运行时生成代理对象,通常基于接口,而静态代理是在编译时手动编写代理类。

静态代理就不说了,可以去本站的文章 Spring:AOP简介查看。

从JVM角度来说 :

  • 静态代理是在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
  • 动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

就 Java 来说,动态代理的实现方式有很多种,比如 JDK动态代理 、 CGLIB动态代理等。

由于miniSpring中使用JDK动态代理实现,这里详细介绍JDK动态代理 .

JDK动态代理

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

以计算器为例,这样一个计算机接口 :

1
2
3
4
5
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}

其实现为 :

1
2
3
4
5
6
7
8
9
10
11
12
public class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}

@Override
public int subtract(int a, int b) {
return a - b;
}
}

目标:对CalculatorImpl形成代理,让其能够打印日志。

生成动态代理对象:newProxyInstance()

生成一个代理对象:使用Proxy 类中使用频率最高的方法:newProxyInstance()

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 1. **loader** :类加载器,用于加载代理对象。
* 2. **interfaces** : 被代理类实现的一些接口;
* 3. **handler** : 实现了 `InvocationHandler` 接口的对象;
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler handler)
throws IllegalArgumentException
{
......
}

当我们对一个接口的实现类做代理时,参数 loaderinterfaces就有了。

1
2
3
Calculator calculator = new CalculatorImpl();//对Calculator做代理
//loader : Calculator.class.getClassLoader(),
//interfaces : new Class<?>[] { Calculator.class },

还缺 handler参数 。

自定义处理逻辑:InvocationHandler

要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。

也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

1
2
3
4
5
6
7
8
9
10
11
public interface InvocationHandler {
/**
* 当你使用代理对象调用方法的时候实际会调用到这个方法
三个参数
1.proxy :动态生成的代理类
2.method : 与代理类对象调用的方法相对应
3.args : 当前 method 方法的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

总体流程

1. 创建接口: 首先,你需要定义一个接口,该接口包含要被代理的方法。例如,假设你有一个简单的计算器接口如下:

1
2
3
4
javaCopy codepublic interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}

2. 创建目标对象: 接下来,你需要创建一个实现了上述接口的目标对象,即真正执行业务逻辑的对象。

1
2
3
4
5
6
7
8
9
10
11
javaCopy codepublic class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}

@Override
public int subtract(int a, int b) {
return a - b;
}
}

3. 创建 InvocationHandler: 你需要实现 InvocationHandler 接口,该接口包含一个方法 invoke,在代理对象调用方法时会被触发。在 invoke 方法中,你可以添加额外的逻辑,例如日志记录、权限检查等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
javaCopy codeimport java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class CalculatorInvocationHandler implements InvocationHandler {
private Calculator target;

public CalculatorInvocationHandler(Calculator target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在方法调用前执行额外逻辑
System.out.println("Method " + method.getName() + " is called.");

// 调用目标对象的方法 method是被调用的方法,target是要调用方法的对象,args是参数列表
Object result = method.invoke(target, args);

// 在方法调用后执行额外逻辑
System.out.println("Method " + method.getName() + " returned " + result);

return result;
}
}

4. 创建代理对象: 使用 java.lang.reflect.Proxy 类的 newProxyInstance 方法创建代理对象。你需要传入一个类加载器、一个接口数组和一个 InvocationHandler 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
javaCopy codeimport java.lang.reflect.Proxy;

public class Main {
public static void main(String[] args) {
Calculator calculator = new CalculatorImpl();
CalculatorInvocationHandler handler = new CalculatorInvocationHandler(calculator);

Calculator proxy = (Calculator) Proxy.newProxyInstance(
Calculator.class.getClassLoader(),
new Class<?>[] { Calculator.class },
handler
);

int result = proxy.add(10, 5);
System.out.println("Result: " + result);
}
}

在上述示例中,proxy 对象是一个动态生成的代理对象,它实现了 Calculator 接口并拦截了 add 方法的调用,执行了 CalculatorInvocationHandler 中定义的额外逻辑。这种方式允许你在不修改原始 CalculatorImpl 类的情况下,添加日志记录、权限检查等功能。

CGLIB动态代理

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。

拦截方法与获取代理类

在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

  • 你需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。

  • 可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

1
2
3
4
5
6
7
8
9
10
public interface MethodInterceptor extends Callback{
// 拦截被代理类中的方法
/**
obj : 被代理的对象(需要增强的对象)
method : 被拦截的方法(需要增强的方法)
args : 方法入参
proxy : 用于调用原始方法
*/
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}

总体流程

1. 添加依赖: 首先,你需要添加CGLIB库的依赖。通常,你可以使用Maven或Gradle来添加CGLIB的依赖。例如,使用Maven:

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version> <!-- 根据实际版本选择 -->
</dependency>

2.创建目标对象: 与JDK动态代理不同,CGLIB可以代理没有实现接口的类。因此,你需要创建一个目标类,该类将成为代理的目标。

1
2
3
4
5
 class MyService {
public void doSomething() {
System.out.println("MyService is doing something...");
}
}

3. 创建CGLIB代理: 使用CGLIB,你可以创建一个代理对象来拦截目标对象的方法调用。

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
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/*
*自定义MethodInterceptor
*/
public class CglibProxy implements MethodInterceptor {
public Object createProxy(Class<?> targetClass) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setSuperclass(targetClass);
// 设置方法拦截器
enhancer.setCallback(this);
// 创建代理类
return enhancer.create();
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 调用方法之前,我们可以添加自己的操作
System.out.println("Before method call: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
//调用方法之后 ,也可以添加自己的操作
System.out.println("After method call: " + method.getName());
return result;
}
}

在上述代码中,我们创建了一个 CglibProxy 类,该类实现了 MethodInterceptor 接口,用于拦截方法调用。在 intercept 方法中,我们可以在方法调用前后添加额外的逻辑。

createProxy 方法中,我们使用 Enhancer 类创建代理对象,并指定目标类的类类型。

4. 使用CGLIB代理: 最后,我们可以使用CGLIB代理来代替目标对象。

1
2
3
4
5
6
7
8
public class CglibProxyExample {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
MyService proxy = (MyService) cglibProxy.createProxy(MyService.class);

proxy.doSomething();
}
}

在上述示例中,我们创建了 CglibProxy 对象,并使用它来创建 MyService 类的代理对象。当我们调用代理对象的 doSomething() 方法时,代理会在方法执行前后输出日志。

JDK vs CGLIB

  1. JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
  2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
  3. JDK动态代理: 不需要额外的依赖,因为它是Java标准库的一部分。CGLIB动态代理: 需要添加CGLIB库的依赖,通常使用Maven或Gradle进行依赖管理。