前言 代理(Proxy)是一种设计模式,提供了间接对目标对象进行访问的方式,即通过代理对象访问目标对象。
这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能。
这就符合了设计模式的开闭原则,即在对既有代码不改动的情况下进行功能的扩展。
正文 我们一般认为Java中有三种代理模式:静态代理、动态代理和Cglib代理。
其中Cglib代理需要借助cglib三方jar包实现。
我们来看下三种代理模式,以及它们的一些特点。
我们下面以代码来看下代理的例子。
我们现在有一个Subject
接口,同时有两个实现类RunSubject
和SingSubject
,接口中有一个方法doSomething
。
1 2 3 4 5 6 7 public interface Subject { 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
接口的实现类RunSubject
和SingSubject
,我们以代码来看下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+"方法调用之前记录日志----->" ); 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; } 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)
这个类。
可以看到它是protected final
的,理论上外部包是不能调用它的,那么Cglib是如何实现调用的呢。
打开Cglib的ReflectUtils
类,我们看到下图。
可以看到它通过Class.forName
拿到java.lang.ClassLoader
,然后拿到defineClass
方法,改变了其可访问性defineClass.setAccessible(true);
。
这也就是为什么JVM会发出警告的原因,因为正常情况下我们是不被允许访问此方法的(非法反射)。
还可以看到如果拿不到该方法(被限制后,抛出异常),那么它会尝试去拿sun.misc.Unsafe.defineClass
方法。
如果我们不想看到这个警告,可以添加 VM 参数来屏蔽它。
JDK动态代理源码分析 我们现在来分析下JDK动态代理是如何实现的,先看Proxy.newProxyInstance
方法。
可以看到Constructor> cons = getProxyConstructor(caller, loader, interfaces);
用来生成了构造器,而后通过newProxyInstance(caller, cons, h);
生成对象。
我们看一下getProxyConstructor
这个方法。
它分为只有一个接口和实现多个接口的两种处理逻辑,我们看其中一个就行,主要是new ProxyBuilder(ld, clv.key()).build()
这个方法,用来生成代理类。
在build
方法里,我们看到这个调用Class> proxyClass = defineProxyClass(module, interfaces);
,这就是生成代理类的方法。
继续跟踪defineProxyClass
方法,如下:
在这个方法中,我们可以看到生成代理类字节码的方法调用byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
跟踪generateProxyClass
方法,方法里有一个参数saveGeneratedFiles
用来是否保存生成的代理类。
正常情况下这个值是false,即不保存。
但是我们想看生成的代理类的话,由于ProxyGenerator.generateProxyClass
类及方法本身都是non-public
的,所以我们无法直接调用此方法生成代理类。
因此可以借助saveGeneratedFiles
参数。
观察代码saveGeneratedFiles
的定义。
1 2 3 4 5 private static final boolean saveGeneratedFiles = java.security.AccessController.doPrivileged( new GetBooleanAction( "jdk.proxy.ProxyGenerator.saveGeneratedFiles" )).booleanValue();
所以我们在 VM 变量里配置-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
就可以将生成的代理类保存到本地。
如上图,运行后生成的代理类在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
方法,实际调用代理类$Proxy0
的doSomething
方法。
而在实现Subject
接口方法的内部,通过反射调用了InvocationHandler
实现类的invoke
方法。
由上面内容可以看出,Java动态代理主要有以下几步:
通过实现InvocationHandler
接口创建自己的调用处理器; 通过为Proxy
类指定ClassLoader
对象和一组interface
来创建动态代理类; 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型; 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。 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()
方法。
其主要方法为Object result = super.create(key);
,用来创建代理类。其代码如下:
主要方法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
去生成代理类。
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); } 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,可以看到生成如下类。
我们重点关注中间那个类,这个类继承RunSubject
,这也印证了我们上面所说,Cglib是依靠继承目标类来实现代理的。
这个类代码比较多,我就不粘上来了,我们用图片看下它的几个关键部分。
由上面内容我们可以看到Cglib代理的几个步骤:
通过实现MethodInterceptor (extends Callback)
接口(或者自己实现CallBack
接口)创建自己回调类; 通过Enhancer
类指定目标类为超类superClass
,并指定我们上面的回调类; 通过反射机制实现对目标类的继承,创建代理类; 代理类在调用指定方法时,如果需要回调,会通过反射拿到回调类要执行的内容;如果没有回调类,会直接执行目标类指定方法。 总结 以上就是关于代理模式的全部内容,我们也分析了各种代理模式的一些特点及原理。
实际中主要常用的就是JDK动态代理和Cglib代理。
JDK动态代理,是Java自带的代理模式,无需依赖,也没有警告等信息,唯一缺点就是需要目标类实现接口,只能对实现接口的类进行代理。
Cglib代理,内部使用asm,直接修改字节码进行增强子类,也就是通过继承的方式进行代理,不关心目标类是否继承接口,但是无法处理final
的类(无法被继承)。