本篇博文介绍的是JDK的动态代理,Java中动态代理不仅仅是JDK的动态代理还有CGLIB代理。
(JDK)动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
即,不直接调用目标对象而是通过代理对象调用。代理对象不直接生成,而是程序运行时根据需要动态生成!
代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。代理对象的生成,是利用JDK 的API,动态的在内存中构建代理对象。 动态代理也叫做:JDK 代理、接口代理。
JDK 实现代理只需要使用newProxyInstance
方法,但是该方法需要接收三个参数,完整的写法是:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
代理设计模式的原理:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
动态代理之所以被称为动态,是因为运行时才将它的类创建出来。代码开始执行时,还没有proxy类,它是根据需要从传入的接口集创建的。
【1】Proxy
专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。提供了许多用于创建动态代理类和动态代理对象的静态方法。
① static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
创建一个动态代理类所对应的Class对象。
② static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
直接创建一个动态代理对象。
其他方法如下图:
【2】InvocationHandler
InvocationHandler
是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类的invoke方法,由它决定处理。
InvocationHandler接口源码如下:
* //InvocationHandler是一个被代理实例的调用处理程序实现的接口。
* // 每一个代理实例都有一个相关联的invocation handler
* //当代理实例方法被调用时,代理会转发到相关联的invocation handler的invoke方法。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler
内部只有一个 invoke() 方法,当一个代理实例的方法被调用时,与代理实例相关联的invocationHhandler
的invoke
方法将会被调用。正是这个方法决定了怎么样处理代理传递过来的方法调用。
- proxy 代理对象
- method 代理对象调用的方法
- args 调用的方法中的参数
因为,Proxy 动态产生的代理对象会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
故而,总的运行流程为:获取代理实例
–>代理实例.方法
--> InvocationHandler.invoke()
--> 接口真正实现类的方法
。
InvocationHandler
根本就不是proxy,它只是一个帮助proxy的类,proxy会被调用转发给它处理。Proxy本身是利用静态的Proxy.newProxyInstance()
方法在运行时动态地创建的。
【3】动态代理步骤
① 父接口
父接口Human定义了两个方法:
interface Human {
void info();
void fly();
}
② 实现类SuperMan(被代理类)
// 被代理类
class SuperMan implements Human {
public void info() {
System.out.println("我是超人!我怕谁!");
}
public void fly() {
System.out.println("I believe I can fly!");
}
}
③ InvocationHandler实现类
也就是代理类的调用处理程序。
class MyInvocationHandler implements InvocationHandler {
// 被代理类对象的声明
Object obj;
// 动态的创建一个代理类的对象
public Object getProxyInstance()(Object obj){
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object returnVal = method.invoke(obj, args);
return returnVal;
}
}
④ 测试方法
public static void main(String[] args) {
//创建一个被代理类的对象
SuperMan man = new SuperMan();
//创建调用处理程序
MyInvocationHandler handler = new MyInvocationHandler();
//返回一个代理类的对象
Object obj = handler.getProxyInstance()(man);
System.out.println(obj.getClass());
//代理实例 强转
Human hu = (Human)obj;
//通过代理类的对象调用重写的抽象方法
hu.info();
System.out.println();
hu.fly();
}
测试结果如下:
// 这里表明拿到的是代理对象
class com.web.test.$Proxy0
我是超人!我怕谁!
I believe I can fly!
【4】JDK动态代理原理
① 关键入口代码
Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this);
根据类加载器,目标类的上行接口和InvocationHandler获取代理对象。这里有两个问题,第一如何获取?第二获取的代理对象是个什么样子?
先分析如何获取。
② 追踪源码到Proxy.newProxyInstance
* //loader 定义代理类的类加载器
* // interfaces 代理类需要实现的接口
* // h -- 用来转发/反射目标方法的handler
* //返回一个代理类的实例对象附带指定的代理类的invocation handler。
* //该代理类被指定的类加载器定义且实现了指定的接口。
*/
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
Objects.requireNonNull(h)
//...
//寻找或者产生代理类的class
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 拿到代理类的构造器--这一步很关键!
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//如果非public,setAccessible(true);--反射中常见操作
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 通过构造器反射获取代理类实例对象(InvocationHandler作为参数)
return cons.newInstance(new Object[]{h});
}
//...
}
过程很清晰(参考代码注释),继续细究下去。
③ Class<?> cl = getProxyClass0(loader, intfs)
做了什么?
源码如下:
//产生一个代理类 class。在调用该方法前必须调用checkProxyAccess 方法进行权限检查
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//如果代理类已经存在,则直接返回缓存中的copy;
//否则,通过ProxyClassFactory创建代理类
return proxyClassCache.get(loader, interfaces);
}
如果缓存中有,就直接返回proxy class
;否则就要通过ProxyClassFactory
获取。
这里的cache 是WeakCache
,暂且不去理会,继续看ProxyClassFactory。
Proxy
的静态内部类–Proxy$ProxyClassFactory
源码如下:
// 通过给定的类加载器和接口,产生、定义并返回代理类。
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// prefix for all proxy class names--代理类前缀
private static final String proxyClassNamePrefix = "$Proxy";
//代理类编号
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
//...省略代码
//定义代理类所在的包
String proxyPkg = null;
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
记录非公共代理接口的包,以便在同一包中定义代理类。
验证所有非公共代理接口都在同一个包中。
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
// 如果修饰符非public,则变为final
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
long num = nextUniqueNumber.getAndIncrement();
// 代理类的完整名字=包名+前缀+序号
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/* Generate the specified proxy class.*/
// 这里,产生指定的proxy class 字节数组!!!
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 通过一个native方法获取proxy class
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
// ... 省略代码
}
}
从ProxyClassFactory中下面的方法可以看到具体生成字节流的方法是ProxyGenerator.generateProxyClass(..).
。最后通过native方法生成Class对象。同时对class对象的包名称有一些规定比如命名为com.web.test$proxy0
。
要想得到字节码实例我们需要先下载这部分字节流,然后通过反编译得到java代码。
用ProxyGenerator.generateProxyClass(..)
方法生成字节流,然后写进硬盘.假设我把proxyName
定义为Human$ProxyCode
。
获取代理类代码如下:
public class TestProxyJava {
public static void generateClassFile(Class clazz,String proxyName) throws Exception
{
//根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName,new Class[]{clazz});
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
//保留到硬盘中
out = new FileOutputStream(paths+proxyName+".class");
out.write(classFile);
out.flush();
}
public static void main(String[] args) {
try {
generateClassFile(Human.class,"Human$ProxyCode");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
获取到的代理类源码如下:
public final class Human$ProxyCode extends Proxy implements Human{
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m0;
public Human$ProxyCode(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
throws
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//final fly
public final void fly()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void info()
throws
{
try
{
/调用InvokeHandler的invoke方法,并将具体方法传了进去
this.h.invoke(this, m4, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.web.test.Human").getMethod("fly", new Class[0]);
m4 = Class.forName("com.web.test.Human").getMethod("info", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
可以发现这个类extends Proxy
实现了我们需要代理的接口Human
,且他的构造函数确实是需要传递一个InvocationHandler
对象。
那么现在的情况就是我们的生成了一个代理类,这个代理类是我们需要代理的接口的实现类。我们的接口中定义的info
和fly
方法,在这个代理类中帮我们实现了,并且全部变成了final
的。同时覆盖了一些Object
类中的方法。
以info这个方法举例,方法中会调用InvocationHandler类中的invoke方法(也就是我们实现的逻辑的地方),同时把自己的Method对象,参数列表等传入进去。
再回头看上面的测试main方法:
public static void main(String[] args) {
SuperMan man = new SuperMan();//创建一个被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
Object obj = handler.bind(man);//返回一个代理类的对象
System.out.println(obj.getClass());
// 现在知道为什么可以强转了吧
Human hu = (Human)obj;
//通过代理类的对象调用重写的抽象方法
hu.info();
System.out.println();
hu.fly();
}
【5】动态代理与AOP
在软件业,AOP为Aspect Oriented Programming
的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理
实现程序功能的统一维护的一种技术。
AOP是OOP
的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等等。
简而言之,在不改变原有代码基础上进行功能增强!
添加HumanUtil模拟增强方法:
class HumanUtil {
public void method1() {
System.out.println("=======方法一=======");
}
public void method2() {
System.out.println("=======方法二=======");
}
}
修改MyInvocationHandler如下:
class MyInvocationHandler implements InvocationHandler {
// 被代理类对象的声明
Object obj;
// 动态的创建一个代理类的对象
public Object bind(Object obj){
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
HumanUtil h = new HumanUtil();
h.method1();
Object returnVal = method.invoke(obj, args);
h.method2();
return returnVal;
}
}
再次测试如下:
class com.web.test.$Proxy0
=======方法一=======
我是超人!我怕谁!
=======方法二=======
=======方法一=======
I believe I can fly!
=======方法二=======
使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理。
这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。
如下图所示:
【6】JDK动态代理总结
① 概率总结
① JDK动态代理只能代理有接口的类,并且只能代理接口方法,不能代理一般的类中自定义的方法
因为代理类是一个实现了被代理对象的上行接口的类,所以类必须有接口。而且不能代理被代理对象中自定义的方法!
② 提供了一个使用InvocationHandler作为参数的构造方法
在代理类中做一层包装,业务逻辑在invoke方法中实现。
③ 重写了Object类的equals、hashCode、toString
它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。
④ 在invoke方法中我们甚至可以不用Method.invoke方法调用实现类就返回
这种方式常常用在RPC框架中,在invoke方法中发起通信调用远端的接口等
② 动态代理在框架中的应用
① 在mybatis中的应用
如在MyBatis原理分析之获取SqlSession一文中获取executor 实例就应用了代理模式。在MyBatis原理分析之获取Mapper接口的代理对象也应用了java的动态代理思想。
③ 代理模式的几种变异体
① 远程代理
远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。
②虚拟代理
虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。
③ 缓存代理
缓存代理会维护之前创建的对象,当收到请求时,在可能的情况下返回缓存的对象。它也允许许多个客户共享结果,以减少计算或网络延迟。
④ 保护代理
保护代理可以根据客户的角色来决定是否允许客户访问特定的方法。所以保护代理可能只提供给客户部分接口。保护代理通常是由Java的动态代理技术实现。
⑤ 同步代理
在多线程的情况下为主题提供安全的访问。
⑥ 复杂隐藏代理
用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也称为外观代理(Facade Proxy),这不难理解。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。
【7】获取代理类class文件的简单方法
在【4】中通过ProxyGenerator.generateProxyClass(proxyName,new Class[]{clazz});
获取class文件,比较繁琐,这里介绍一种简单方式。
关键代码如下:
//生成$Proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
修改main方法如下:
public static void main(String[] args) {
//生成$Proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
SuperMan man = new SuperMan();//创建一个被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
Object obj = handler.bind(man);//返回一个代理类的对象
System.out.println(obj.getClass());
Human hu = (Human)obj;
hu.info();//通过代理类的对象调用重写的抽象方法
System.out.println();
hu.fly();
}
生成的$Proxy0
路径在项目根目录下com/web/test
–该包名为测试代码所在的包。
总结:
java动态代理是利用反射机制生成一个实现代理接口的代理类,在调用具体方法前调用内部InvokeHandler来处理,InvokeHandler将会调用invoke方法并处理目标实际方法。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
- 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
其他博客参考:
Cglib动态代理与Spring中动态代理思想