LHJ's Blog

✨ Java代理

代理从字面意思上讲就是代为处理,比如说相亲的时候都是一方想中介说明要求的情况,然后由中介去找到满足条件的人,在通知委托人,而不是由委托人去寻找。其中中介就起了代理的作用。

在Java中,代理分为静态代理和动态代理,而动态代理又可以分为JDK代理和CGLIB代理。Spring的AOP其底层的原理就是采用了代理模式。Java代理本身并不实现代理的具体内容,而是调用具体的代理对象实例的方法,只不过代理会在调用之前和之后做一些额外的处理。

🚁 静态代理和动态代理的区别

静态代理和动态代理的主要区别是他们的加载时机不同,静态代理可以在编译阶段就确定具体的代理类,而动态代理需要在运行时才能确定,即是动态的。

🚀 静态代理

由程序员编写的源码,在编译期间就已经确定了代理类和被代理类,并且在程序运行前,就以.class文件存在。

静态代理简单实现

以之前举的相亲的例子作为场景简单实现一个静态代理。

首先创建一个Person的接口,接口中定义一个方法findLove(int age, int height)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.longhujing.proxy;

/**
* 接口
*
* @author longhujing
*/
public interface Person {

/**
* 寻找生命另一半
*
* @param age 年龄
* @param height 身高
*/
public void findLove(int age, int height);

}

接着做一个Person的简单实现。即需要联系中介的相亲男士。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.longhujing.proxy;

/**
* 寻找真爱的男士
*
* @author longhujing
*/
public class Man implements Person {

@Override
public void findLove(int age, int height) {
System.out.println("想找一个年龄: " + age + "岁, 身高: " + height + "cm的人度过下半生");
}

}

接着我们就需要一个中介了,这个中介也应该实现Person接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.longhujing.proxy;

/**
* 中介
*
* @author longhujing
*/
public class ManProxy implements Person {

private Person person;

public ManProxy(Person person) {
this.person = person;
}

@Override
public void findLove(int age, int height) {
person.findLove(age, height);
System.out.println("正在寻找合适的人选...");
}

}

接下来是具体的使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.longhujing.proxy;

/**
* @author longhujing
*/
public class Main {

public static void main(String[] args) {
Person person = new Man();
ManProxy proxy = new ManProxy(person);
proxy.findLove(18, 170);
}

}

/* 运行结果
想找一个年龄: 18岁, 身高: 170cm的人度过下半生
正在寻找合适的人选...
*/

静态代理还是很简单的,代理类和被代理类实现了同一个接口,代理类中维护了一个被代理类的对象,代理类并不实现接口的具体方法,而是调用被代理类的具体实现,只是再调用前后调用后做了额外的操作。

静态代理的缺陷也很明显:如果接口多了方法,那么代理类就必须做出更改。后期的维护很困难。

🚀 动态代理

代理类在程序运行时创建被称为动态代理

上述静态代理在编译之后就会有Person.classMan.classManProxy.class这几个文件出现,也就是代理类和被代理类在编译的时候就已经确定了。而动态代理则不同,动态代理的代理对象需要在程序运行时动态的生成,因此在编译阶段是不可能出现ManProxy.class文件的。动态代理的另一个好处就是,不像静态代理那样,接口添加了新的方法又要补充新的接口,维护更加的简单。

JDK代理

动态代理的原理很简单,回想以下Java 的反射机制,获取到一个方法对象Method后,只需要调用invoke(Object obj, Object[] args)方法即可调用对应的方法。

JDK动态代理的使用步骤

1️⃣ 首先需要有一个InvocationHandler,在这里面定义方法的前置和后置处理

2️⃣ 然后调用Proxy.newProxyInstance(ClassLoader classlodader, Class[] interfaces, InvocationHandler invocationHandler)获取到代理对象

3️⃣ 最后调用获取到的代理对象的对应方法就可以了

这边使用了Lambda表达式,把第一步和第二步直接合并了。

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
package com.longhujing.proxy;

import java.lang.reflect.Proxy;

/**
* JDK动态代理
*
* @author longhujing
*/
public class ManDynamicProxy {

private Object target;

public ManDynamicProxy(Object target){
this.target = target;
}

public Object getProxyInstance(){
// 第二步:传入参数获取代理对象
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy1, method, args) -> {
Object result = method.invoke(target, args);
System.out.println("正在寻找合适的人选...");
return result;
});
}

}

接下来看看使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.longhujing.proxy;

/**
* @author longhujing
*/
public class Main {

public static void main(String[] args) {
Person person = new Man();
ManDynamicProxy manDynamicProxy = new ManDynamicProxy(person);
Person personProxy = (Person) manDynamicProxy.getProxyInstance();
personProxy.findLove(28,180);
}

}

使用JDK动态代理的过程中发现,调用Proxy.newProxyInstance的时候需要传入实现的接口!那么也就意味着代理类应该是实现了某一个接口才行。因此使用JDK动态代理的一个限制就是代理类需要实现接口

至于为什么可以通过查看生成的代理类文件查看。

首先修改Main的内容,添加System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");保存生成的代理类。

改造后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.longhujing.proxy;

/**
* @author longhujing
*/
public class Main {

public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
Person person = new Man();
ManDynamicProxy manDynamicProxy = new ManDynamicProxy(person);
Object proxyInstance = manDynamicProxy.getProxyInstance();
System.out.println(proxyInstance.getClass());
((Person) proxyInstance).findLove(28,180);
}

}

生成的.class文件由IDEA反编译后查看如下

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.longhujing.proxy.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final void findLove(int var1, int var2) throws {
try {
super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.longhujing.proxy.Person").getMethod("findLove", Integer.TYPE, Integer.TYPE);
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

这个代理类的定义是这样的public final class $Proxy0 extends Proxy implements Person,它和被代理类都实现了同样的一个接口,这样就很清楚了为什么被代理类需要实现接口了,因为动态生成的代理类需要实现和被代理类相同的接口来完成代理工作。

CGLIB代理

JDK代理需要被代理类实现接口,但是有的时候被代理类可能并没有实现任何的接口(这种情况还很常见),这样JDK代理就不能满足要求

为了解决这种问题,还有一种是CGLIB代理。

首先需要引入CGLIB的依赖

1
2
3
4
5
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>

先重新定义一个被代理类Woman,这个类不会实现任何的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.longhujing.proxy;

/**
* 被代理类 - 未实现接口
*
* @author longhujing
*/
public class Woman {

public void findLove(int age, int height){
System.out.println("想找一个年龄: " + age + "岁, 身高: " + height + "cm的人度过下半生");
}

}

然后定义一个代理生成的方法

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
package com.longhujing.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* @author longhujing
*/
public class WomanCglibProxy implements MethodInterceptor {

private Object target;

public Object getProxyInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = method.invoke(target, args);
System.out.println("正在寻找合适的人选...");
return result;
}
}

和JDK代理一样,CGLIB同样的也有一些限制:首先CGLIB代理不能代理final类型的Class类,其次CGLIB代理的类必须有公共的无参构造方法

这里可以查看一下CGLIB生成的代理类,改造后的main函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.longhujing.proxy;

import net.sf.cglib.core.DebuggingClassWriter;

public class Main {

public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "生成代理类路径");
Woman woman = new Woman();
WomanCglibProxy cglibProxy = new WomanCglibProxy();
Woman proxy = (Woman) cglibProxy.getProxyInstance(woman);
proxy.findLove(18, 180);
}

}

由于CGLIB生成的class文件太长,只保留一部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.longhujing.proxy;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class Woman$$EnhancerByCGLIB$$95a3defa extends Woman implements Factory {
//...
}

可以看出生成的代理类是继承了被代理类的,因此被代理类就不可以是final类型的,因为final类型的类是不可以被继承的,其次代理类是被代理类的子类,实例化子类的时候需要调用父类的构造方法,如果父类没有无参构造的话,代理类就无法完成实例化(毕竟如果这个做的面面俱到的话还是很困难的…)



 评论