Fork me on GitHub

Java代理模式

前言

代理(Proxy)是一种设计模式,提供了间接对目标对象进行访问的方式,即通过代理对象访问目标对象。

这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能。

这就符合了设计模式的开闭原则,即在对既有代码不改动的情况下进行功能的扩展。

正文

我们一般认为Java中有三种代理模式:静态代理、动态代理和Cglib代理。

其中Cglib代理需要借助cglib三方jar包实现。

我们来看下三种代理模式,以及它们的一些特点。

我们下面以代码来看下代理的例子。

我们现在有一个Subject接口,同时有两个实现类RunSubjectSingSubject,接口中有一个方法doSomething

1
2
3
4
5
6
7
public interface Subject {
/**
* 做一些事
* @param str
*/
public String doSomething(String str);
}
1
2
3
4
5
6
7
public class RunSubject implements Subject {
@Override
public String doSomething(String str) {
System.out.println(str + " running...");
return str + " running...";
}
}
1
2
3
4
5
6
7
public class SingSubject implements Subject {
@Override
public String doSomething(String str) {
System.out.println(str+" singing...");
return str+" singing...";
}
}

现在,我们想在doSomething之前和之后记录一些日志,如何实现呢。

静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。从而实现我们想要的功能。

根据上面所说,我们可以定义一个StaticProxy类实现此功能,该类需要实现Subject接口,当然,我们也需要被代理对象,以实现我们doSomething的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class JdkStaticProxyImpl implements Subject{

/**
* 被代理对象
*/
private Subject subject;

public JdkStaticProxyImpl(Subject subject) {
this.subject = subject;
}

@Override
public String doSomething(String str) {
//调用之前的逻辑
System.out.println("在方法调用之前记录日志----->");

String returnValue = subject.doSomething(str);

//调用之后的逻辑
System.out.println("在方法调用之后记录日志----->");
return returnValue;
}
}
1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
Subject runSubject = new RunSubject();
Subject subject = new JdkStaticProxyImpl(runSubject);
subject.doSomething("ccc");
}
}

输出结果:

1
2
3
在方法调用之前记录日志----->
ccc running...
在方法调用之后记录日志----->

这种代理方法优点是可以在不修改目标对象的功能前提下,对目标功能扩展。

但缺点也十分明显,上面只是对于Subject的代理,如果我们有其他的接口及其实现类,如果也需要该扩展功能,那么也要为其写代理类。

会导致我们会有很多代理类,且如果接口增加方法,那么我们也需要修改代理类,即便这个新增的方法可能不需要额外扩展功能。

动态代理

上面静态代理的缺点比较明显,如何解决呢?

我们可以使用动态代理,Java中为我们提供了生成代理对象的API,java.lang.reflect.Proxy

实现代理需要调用newProxyInstance方法,它接受三个参数,如下:

1
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){}
  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的,我们一般使用 getClass().getClassLoader();即可获取目标对象的类加载器。
  • Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型,一般使用getClass().getInterfaces();方式取得。
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。这个需要我们手动实现要实现的扩展功能。

对于上面的Subject接口的实现类RunSubjectSingSubject,我们以代码来看下JDK动态代理如何实现功能扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class JdkDynamicProxyImpl implements InvocationHandler {

private Object subject;

public JdkDynamicProxyImpl(Object subject) {
this.subject = subject;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用之前的逻辑
System.out.println("在"+method+"方法调用之前记录日志----->");

//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object returnValue = method.invoke(subject, args);

//调用之后的逻辑
System.out.println("在"+method+"方法调用之后记录日志----->");
return returnValue;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
Subject runSubject = new RunSubject();
InvocationHandler handler = new JdkDynamicProxyImpl(runSubject);
ClassLoader loader = runSubject.getClass().getClassLoader();
Class[] interfaces = runSubject.getClass().getInterfaces();
/**
* 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
*/
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);

System.out.println("动态代理对象的类型:"+subject.getClass().getName());

subject.doSomething("aaa");
}
}

执行后如下输出:

1
2
3
4
动态代理对象的类型:com.sun.proxy.$Proxy0
在public abstract java.lang.String com.zwt.helputils.utils.proxy.Subject.doSomething(java.lang.String)方法调用之前记录日志----->
aaa running...
在public abstract java.lang.String com.zwt.helputils.utils.proxy.Subject.doSomething(java.lang.String)方法调用之后记录日志----->

上面的JdkDynamicProxyImpl类,我们不仅可以用来代理Subject接口的实现类以实现日志增强功能,如果别的类(比如A)也想实现日志增强,那么只需要A实现一个自己的接口 AInterface 即可。

可以看到,对于一种增强,我们创建一个代理类即可,这比静态代理要方便简洁很多。

但这种动态代理有一个缺陷,就是被代理对象(目标对象)一定要实现接口,否则无法实现动态代理。

Cglib代理

上面的两种代理方式,也可以认为JDK传统的代理方式,目标对象必须实现接口,否则无法完成代理,但实际中,不一定所有的对象都会实现接口。

对于没有接口的对象,如果要实现对其的代理,应该如何实现呢?

我们可以使用继承目标类以目标对象子类的方式实现代理,这种方法就叫做Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

要实现Cglib代理,需要引入三方Cglib包,如下:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

代码示例如下:

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
public class CglibProxyImpl implements MethodInterceptor {
/**
* 代理对象
*/
private Object target;

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

/**
* 创建代理对象
* @return
*/
public Object getInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}


@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//调用之前的逻辑
System.out.println("在"+method+"方法调用之前记录日志----->");
Object value = methodProxy.invokeSuper(o, objects);
//调用之后的逻辑
System.out.println("在"+method+"方法调用之后记录日志----->");
return value;
}
}
1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
Subject runSubject = new RunSubject();
Subject subject1 = (Subject) new CglibProxyImpl(runSubject).getInstance();
subject1.doSomething("bbb");
}
}

输出结果:

1
2
3
public java.lang.String com.zwt.helputils.utils.proxy.RunSubject.doSomething(java.lang.String)方法调用之前记录日志----->
bbb running...
public java.lang.String com.zwt.helputils.utils.proxy.RunSubject.doSomething(java.lang.String)方法调用之后记录日志----->

这种代理方式显然有一个缺点,就是当目标对象类是final的时候,我们是无法继承目标类的,因此也就无法实现Cglib代理。

其它

代码优化

上面我们总结了3种代理方式,及其使用,对于Jdk动态代理和Cglib动态代理代码,我们可以整合成一个Factory,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DynamicProxyFactory {
private Object subject;

public DynamicProxyFactory(Object subject) {
this.subject = subject;
}

public Object getInstance(String type) {
switch (type){
case "JDK":
return Proxy.newProxyInstance(
subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(),
new JdkDynamicProxyImpl(subject)
);
case "Cglib":
return new CglibProxyImpl(subject).getInstance();
default:
throw new RuntimeException("找不到指定的代理方式!!!!");
}

}
}
1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
Subject runSubject = new RunSubject();
Subject subject3 = (Subject) new DynamicProxyFactory(runSubject).getInstance("JDK");
subject3.doSomething("1234");
Subject subject4 = (Subject) new DynamicProxyFactory(runSubject).getInstance("Cglib");
subject4.doSomething("5678");
}
}

可以选择自己适合的代理方式,Spring AOP中就有类似的代码,只不过逻辑判断等更复杂些。

在Spring AOP中,如果加入容器的目标对象有实现接口,那么就用JDK代理;如果目标对象没有实现接口,那么就用Cglib代理。

有兴趣的同学可以看下AOP的相关代码。在DefaultAopProxyFactory类中,部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
//部分代码略
}

Cglib警告

在使用Cglib进行代理的时候,我们可以看到如下警告:

1
2
3
4
5
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/E:/maven-localRepository/local/repo/cglib/cglib/3.3.0/cglib-3.3.0.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

我们可以看下ClassLoader.defineClass(String,byte[],int,int,ProtectionDomain)这个类。

upload successful

可以看到它是protected final的,理论上外部包是不能调用它的,那么Cglib是如何实现调用的呢。

打开Cglib的ReflectUtils类,我们看到下图。

upload successful

可以看到它通过Class.forName拿到java.lang.ClassLoader,然后拿到defineClass方法,改变了其可访问性defineClass.setAccessible(true);

这也就是为什么JVM会发出警告的原因,因为正常情况下我们是不被允许访问此方法的(非法反射)。

还可以看到如果拿不到该方法(被限制后,抛出异常),那么它会尝试去拿sun.misc.Unsafe.defineClass方法。

upload successful

如果我们不想看到这个警告,可以添加 VM 参数来屏蔽它。

1
--illegal-access=deny

upload successful

JDK动态代理源码分析

我们现在来分析下JDK动态代理是如何实现的,先看Proxy.newProxyInstance方法。

upload successful

可以看到Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);用来生成了构造器,而后通过newProxyInstance(caller, cons, h);生成对象。

我们看一下getProxyConstructor这个方法。

upload successful

它分为只有一个接口和实现多个接口的两种处理逻辑,我们看其中一个就行,主要是new ProxyBuilder(ld, clv.key()).build()这个方法,用来生成代理类。

upload successful

build方法里,我们看到这个调用Class<?> proxyClass = defineProxyClass(module, interfaces);,这就是生成代理类的方法。

继续跟踪defineProxyClass方法,如下:

upload successful

在这个方法中,我们可以看到生成代理类字节码的方法调用byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);

upload successful

跟踪generateProxyClass方法,方法里有一个参数saveGeneratedFiles用来是否保存生成的代理类。

正常情况下这个值是false,即不保存。

但是我们想看生成的代理类的话,由于ProxyGenerator.generateProxyClass类及方法本身都是non-public的,所以我们无法直接调用此方法生成代理类。

因此可以借助saveGeneratedFiles参数。

观察代码saveGeneratedFiles的定义。

1
2
3
4
5
/** debugging flag for saving generated class files */
private static final boolean saveGeneratedFiles =
java.security.AccessController.doPrivileged(
new GetBooleanAction(
"jdk.proxy.ProxyGenerator.saveGeneratedFiles")).booleanValue();

所以我们在 VM 变量里配置-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true就可以将生成的代理类保存到本地。

upload successful

如上图,运行后生成的代理类在com.sun.proxy包下。我们打开这个代理类(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
package com.sun.proxy;

import com.zwt.helputils.utils.proxy.Subject;
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 Subject {
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 String doSomething(String var1) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

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.zwt.helputils.utils.proxy.Subject").getMethod("doSomething", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

可以看到它继承自Proxy并实现了我们定义的Subject接口。也就是

1
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);

实际是Subject接口的一个实例,我们调用doSomething方法,实际调用代理类$Proxy0doSomething方法。

而在实现Subject接口方法的内部,通过反射调用了InvocationHandler实现类的invoke方法。

由上面内容可以看出,Java动态代理主要有以下几步:

  1. 通过实现InvocationHandler接口创建自己的调用处理器;
  2. 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

Cglib代理源码分析

我们再来看下Cglib的代理是如何实现的。根据如下方法,我们直接跟踪到create方法里。

1
2
3
4
5
6
7
8
public Object getInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}

代码如下:

1
2
3
4
5
public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}

继续看一下createHelper()方法。

upload successful

其主要方法为Object result = super.create(key);,用来创建代理类。其代码如下:

upload successful

主要方法Object obj = data.get(this, getUseCache());用来生成代理类,firstInstance((Class) obj);nextInstance(obj);用来生成代理对象。

先来看下get方法,如下:

1
2
3
4
5
6
7
8
public Object get(AbstractClassGenerator gen, boolean useCache) {
if (!useCache) {
return gen.generate(ClassLoaderData.this);
} else {
Object cachedValue = generatedClasses.get(gen);
return gen.unwrapCachedValue(cachedValue);
}
}

可以看到它调用了gen.generate(ClassLoaderData.this)用于生成代理类,继续跟踪调用。

可以看到generate最终调用了ReflectUtils.defineClass去生成代理类。

upload successful

ReflectUtils.defineClass方法中利用反射调用执行ClassLoader.defineClass方法去生成代理类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain) throws Exception {
Class c;
if (DEFINE_CLASS != null) {
Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), protectionDomain };
c = (Class)DEFINE_CLASS.invoke(loader, args);
} else if (DEFINE_CLASS_UNSAFE != null) {
Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), loader, protectionDomain };
c = (Class)DEFINE_CLASS_UNSAFE.invoke(UNSAFE, args);
} else {
throw new CodeGenerationException(THROWABLE);
}
// Force static initializers to run.
Class.forName(className, true, loader);
return c;
}

我们可以使用VM参数来指定Cglib使代理类文件落地。如下:

1
-Dcglib.debugLocation=E:\\WorkSpace\\helputils\\com\\cglib

这个参数也可以通过代码设置,如下:

1
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\WorkSpace\\helputils\\com\\cglib");

我们运行下Test,可以看到生成如下类。

upload successful

我们重点关注中间那个类,这个类继承RunSubject,这也印证了我们上面所说,Cglib是依靠继承目标类来实现代理的。

这个类代码比较多,我就不粘上来了,我们用图片看下它的几个关键部分。

upload successful

upload successful

由上面内容我们可以看到Cglib代理的几个步骤:

  1. 通过实现MethodInterceptor (extends Callback)接口(或者自己实现CallBack接口)创建自己回调类;
  2. 通过Enhancer类指定目标类为超类superClass,并指定我们上面的回调类;
  3. 通过反射机制实现对目标类的继承,创建代理类;
  4. 代理类在调用指定方法时,如果需要回调,会通过反射拿到回调类要执行的内容;如果没有回调类,会直接执行目标类指定方法。

总结

以上就是关于代理模式的全部内容,我们也分析了各种代理模式的一些特点及原理。

实际中主要常用的就是JDK动态代理和Cglib代理。

JDK动态代理,是Java自带的代理模式,无需依赖,也没有警告等信息,唯一缺点就是需要目标类实现接口,只能对实现接口的类进行代理。

Cglib代理,内部使用asm,直接修改字节码进行增强子类,也就是通过继承的方式进行代理,不关心目标类是否继承接口,但是无法处理final的类(无法被继承)。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

SakuraTears wechat
扫一扫关注我的公众号
您的支持就是我创作的动力!
0%