Java动态代理
基于jdk1.8.0_172
1、定义一个公共接口Greeting.java:
package aop;
public interface Greeting {
void sayHello(String name);
}
2、定义真实业务逻辑类GreetingImpl.java,实现Greeting接口,重写sayHello方法:
package aop.demo3;
import aop.Greeting;
public class GreetingImpl implements Greeting {
public void sayHello(String name) {
System.out.println("Hello! " + name);
}
}
3、定义代理类JDKDynamicProxy的逻辑,实现InvocationHandler接口,重写invoke方法,在invoke中添加额外逻辑before()和after(),并调用真实业务方法method.invoke(target,args);
调用Proxy.newProxyInstance方法生成代理类对象,客户端实际使用的是这个对象:
package aop.demo3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKDynamicProxy implements InvocationHandler {
private Object target;
public JDKDynamicProxy(Object target) {
this.target = target;
}
//生成代理对象,传入真实业务类的classloader、真实业务类实现的接口、InvocationHandler
@SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
//增强真实业务类的调用
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
private void before() {
System.out.println("Before");
}
private void after() {
System.out.println("After");
}
}
4、客户端使用动态代理,通过构造器注入真实业务类对象,调用getProxy方法获得代理类对象,调用代理方法sayHello:
package aop.demo3;
import aop.Greeting;
/**
* 3. JDK 动态代理
*/
public class Client {
public static void main(String[] args) {
Greeting service = new GreetingImpl();
Greeting greeting = new JDKDynamicProxy(service).getProxy();
greeting.sayHello("Jack");
}
}
5、执行结果:
下面看看Java动态代理的内部原理:
代理类的对象生成过程在Proxy.newProxyInstance方法中,主要四个步骤:
1、从缓存中获取指定代理类,传入加载真实业务类的类加载器和真实业务类实现的接口
/*
* 查找或生成指定代理类
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
2、ProxyGenerator.generateProxyClass生成代理类的字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
内部调用generateClassFile()生成字节码的byte数组
final byte[] var4 = var3.generateClassFile();
3、调用defineClass0这个native方法加载字节码,返回Class对象
defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
4、最后调用Constructor.newInstance方法生成对应代理类的对象
final Constructor<?> cons = cl.getConstructor(constructorParams);
//省略部分代码
return cons.newInstance(new Object[]{h});
以上过程生成了一个代理真实业务类的Class对象,并最终调用newInstance方法生成了一个代理对象,这个生成字节码的过程是在内存中进行的,为了进一步研究这个代理类,我们把它输出来,反编译看一下第2步ProxyGenerator.generateProxyClass生成的字节码是什么样的内容。
在Client.java中加入下面的内容:
byte[] classFile = ProxyGenerator.generateProxyClass("com.sun.proxy.$Proxy1", service.getClass().getInterfaces());
FileOutputStream out = new FileOutputStream("com.sun.proxy.$Proxy1.class");
out.write(classFile);
out.flush();
文件会生成在项目根目录:
用反编译工具看这个.class文件,内容如下:
package com.sun.proxy.$Proxy;
import aop.Greeting;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy1
extends Proxy
implements Greeting
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy1(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
{
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);
}
}
public final void sayHello(String paramString)
{
try
{
this.h.invoke(this, m3, new Object[] { paramString });
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
{
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()
{
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("aop.Greeting").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
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());
}
}
}
分析上面的代码:
1、生成的$Proxy1继承自Proxy类,并实现了Greeting接口;
2、重写了Object对象的equals、toString、hashCode三个方法;
3、最关键的实现了Greeting接口的sayHello方法,客户端执行sayHello方法,实际上是调用这个代理类的this.h.invoke(this, m3, new Object[] { paramString });,h.invoke就是我们实现的InvocationHandler.invoke方法。传入了当前代理对象、当前执行的sayHello方法、参数。
存在的问题:
Java动态代理只能代理接口,不能代理没有接口的类。(为什么只能代理接口?从Proxy.newProxyInstance可以看出,这个方法是生成代理类对象用的,第二个参数要求传递真实业务类的接口,并且最终生成的代理类和真实业务类实现了同样的接口。)
假如我想要增强一个没有接口的类,怎么办呢?CGLib类库可以代理没有接口的类。
参考文章及案例代码:
https://www.jianshu.com/p/a1d094fc6c00 参考了狼哥文章的行文逻辑和写作思路,主要是跟着敲了一遍加深了印象
https://my.oschina.net/huangyong/blog/161338 参考了代码