您的位置:首页 > 编程语言 > Java开发

Java动态代理深度解析

2017-12-16 22:59 435 查看

Java动态代理深度解析

引言

说起动态代理,很多人可能都没有直接去使用过。但是只要用过Spring,那动态代理就是一个是个绕不过的坎,因为Spring的核心特性之一AOP就是基于动态代理来实现的,那么什么情况下需要用到动态代理呢?

场景

考虑这样一个教师的接口:

public interface Teacher {
void teach();
}


假设我们有一个TeacherChan的实现类,陈老师教的是摄影:

public class TeacherChan implements Teacher {

@Override
public void teach() {
System.out.println("大家好,我是陈老师,我教大家摄影!");
}

}


另外还有一个TeacherCang的实现类,苍老师教的是生物:

public class TeacherCang implements Teacher {

@Override
public void teach() {
System.out.println("大家好,我是苍老师,我教大家生物!");
}

}


不管是陈老师还是苍老师,只要实现了Teacher这个接口,给我们传道授业解惑,为了礼貌起见,我们总应该给人家问声好吧。而问好这件事不需要老师主动要求,可以交给代理来做,每次有老师来上课,代理自动做了问好这件事。而代理类又分为静态代理和动态代理,静态代理在编写代码时已经确定了要代理的类,只能代理单一的类型,在此略过,今天重点讲动态代理。

Java动态代理

Java动态代理创建代理类的方法为:

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)


其中ClassLoader是用来定义代理类的class文件的,用系统默认的就好,interfaces是要代理的接口,InvocationHandler是用来实际执行代理方法的接口,常用做法是实现该接口,并将需要代理的类实例对象传进去。

实现自己的方法执行器:

public class JdkDynamicProxy implements InvocationHandler {

private Object proxied;

public JdkDynamicProxy(Object object) {
this.proxied = object;
}

/**
* proxy为创建的代理类实例,method是本次被代理的方法,args是方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-------------------老师好[by jdk动态代理]-------------------");
// 执行代理方法
Object obj = method.invoke(proxied, args);
System.out.println("-------------------老师再见[by jdk动态代理]-------------------");
// 返回方法执行结果
return obj;
}

}


创建代理类对象并执行:

// 代理陈老师
Teacher proxy1 = (Teacher) Proxy.newProxyInstance(Teacher.class.getClassLoader(),
new Class[]{Teacher.class}, new JdkDynamicProxy(new TeacherChan()));
proxy1.teach();
// 代理苍老师
Teacher proxy2 = (Teacher) Proxy.newProxyInstance(Teacher.class.getClassLoader(),
new Class[]{Teacher.class}, new JdkDynamicProxy(new TeacherCang()));
proxy2.teach();


输出结果:

-------------------老师好[by jdk动态代理]-------------------
大家好,我是陈老师,我教大家摄影!
-------------------老师再见[by jdk动态代理]-------------------
-------------------老师好[by jdk动态代理]-------------------
大家好,我是苍老师,我教大家生物!
-------------------老师再见[by jdk动态代理]-------------------


实际上,Java会过滤掉接口所有final、native等方法,并为剩下的所有符合条件的方法生成代理方法。而且,熟悉Spring的朋友应该知道,Spring的AOP机制的实现不仅使用了Java的动态代理,而且还引入了CGLib。因为Java的动态代理只能代理接口,而不能代理原始的类。那么为什么Java不能代理类呢,答案是Java的单继承机制。

深入Java动态代理的实现

Java的动态代理是怎么实现的呢?其实很简单,就是
运行时生成一个代理类,该类实现了需要代理的接口,并返回这个代理类的实例对象给调用者
。调试进入Proxy.newProxyInstance()的方法内部,可以看到在Proxy内部生成class字节码的方法:

// 生成的代理类名前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 生成的代理类名序号
private static final AtomicLong nextUniqueNumber = new AtomicLong();

// 序号值加1
long num = nextUniqueNumber.getAndIncrement();
// 代理类名:$ProxyN
String proxyName = proxyPkg + proxyClassNamePrefix + num;

...

// 生成代理类的class字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);


不难看出,生成的代理类的类名是$ProxyN的形式,所以我们经常会看到$Proxy0这个类,就是动态代理在运行时生成的。

既然知道了动态代理是怎么生成代理类的了,那我们不妨把它生成的类打印出来看看,到底里面是怎么实现的。

// 调用Java生成字节码文件的方法
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
"com.test.$proxy0.class", new Class[]{Teacher.class}, Modifier.FINAL);
// 输出文件到本地
FileOutputStream out = new FileOutputStream(new File("/temp/$Proxy0.class"));
out.write(proxyClassFile);
out.flush();
out.close();


用java反编译软件打开生成的$Proxy0.class文件,内容如下:

package com.test.$proxy0;

import com.demos.java.basedemo.proxy.bean.Teacher;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class class extends Proxy
implements Teacher
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public class(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);
}
}

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 void teach()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
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") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.demos.java.basedemo.proxy.bean.Teacher").getMethod("teach", 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());
}
}
}


可以看出,代理类不仅代理了teach()这个方法,还代理了toString()、equals()和hashCode()方法,因为java中所有的类都是继承Object类的,所以自然有这些方法。其中还有一个地方要特别注意:
生成的代理类继承了Proxy这个类,因此它只能通过实现接口来代理其他的类
,现在知道为什么Java动态代理只能代理接口了。

既然都讲到这个份上了,当然不能不继续深入了。接下来是真正压轴的环节,实现自己的动态代理类。

手动实现动态代理

首先分析一下实现动态代理需要的步骤:

1.拼接java类实现,并生成class字节码
2.加载class字节码到JVM
3.实现自己的InvocationHandler处理器
4.对外提供接口


既然知道了Java动态代理的原理,我们不妨借鉴Java生成的class文件格式,同时去掉默认继承的Proxy,使得我们自己的动态代理既可以代理接口,也可以代理类。先写出代理类的格式(假设代理的是类TeacherChan):

public class $Proxy0 extends TeacherChan {

private InvocationHandler handler;

private static Method m0;

static {
try {
// 利用反射获取TeacherChan的teach()方法
m0 = TeacherChan.class.getMethod("teach", new Class[]{});
} catch (NoSuchMethodException ne) {
throw new NoSuchMethodError(ne.getMessage());
}
}

// 构造方法中传入代理类处理器
public $Proxy0(InvocationHandler handler) {
this.handler = handler;
}

public void teach() {
try {
// 收集teach()方法传入的参数,此处参数为空
Object[] args = new Object[]{};
// 执行代理类的teach()
Object result = handler.invoke(this, m0, args);
// 如果有返回值,此处要返回result
} catch (Error|RuntimeException e) {
throw e;
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}

}


好了,生成类的格式大概就是这样设计,实际编写代码时需要处理参数、返回值和异常的情况,略微有点繁琐。下面是动态类生成器:

public class MyProxyGenerator {

// 换行符
public static final String LINE_SEPARATOR = "\r\n";
// 动态代理类包名
public static final String PROXY_CLASS_PACKAGE = "com.demos.proxy";
// 动态代理类名前缀
public static final String PROXY_CLASS_NAME_PREFIX = "$Proxy";
// 动态代理类文件索引
public static final AtomicLong INDEX_GENERATOR = new AtomicLong();
// 动态代理生成文件临时目录
public static final String PROXY_CLASS_FILE_PATH = "/temp";

/**
* 生成代理类并加载到JVM
* @param clazz
* @param methods
* @throws Exception
*/
public static Class<?> generateAndLoadProxyClass(Class<?> clazz, Method[] methods) throws Exception {
long index = INDEX_GENERATOR.getAndIncrement();
// 代理类类名
String className = PROXY_CLASS_NAME_PREFIX + index;
String fileName = PROXY_CLASS_FILE_PATH + File.separator + className + ".java";
FileWriter writer = null;
try {
// 生成.java文件
writer = new FileWriter(new File(fileName));
writer.write(generateClassCode(PROXY_CLASS_PACKAGE, className, clazz, methods));
writer.flush();
// 编译.java文件
compileJavaFile(fileName);
// 加载class到JVM
String classPath = PROXY_CLASS_FILE_PATH + File.separator + className + ".class";
Class<?> proxyClass = MyClassLoader.getInstance().findClass(classPath, PROXY_CLASS_PACKAGE + "." + className);
return proxyClass;
} finally {
if (writer != null) {
writer.close();
}
}
}

/**
* 编译.java文件
* @param fileName
* @throws IOException
*/
private static void compileJavaFile(String fileName) throws IOException {
compileByTools(fileName);
//        compileByExec(fileName);
}

/**
* 使用Runtime执行javac命令
* 注意: 需要指定classpath, 否则找不到依赖的类
* 建议使用compileByTools()
* @param fileName
* @throws IOException
*/
@Deprecated
private static void compileByExec(String fileName) throws IOException {
// 获取当前的classpath
String classpath = MyProxyGenerator.class.getResource("/").getPath();
// 运行命令: javac -classpath ${classpath} ${filepath}
String command = "javac -classpath " + classpath + " " + fileName;
Process process = Runtime.getRuntime().exec(command);
// 等待执行, 并输出错误日志
try {
InputStream errorStream = process.getErrorStream();
InputStreamReader inputStreamReader = new InputStreamReader(errorStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
int exitVal = process.waitFor();
System.out.println("Process exitValue: " + exitVal);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

/**
* 使用JDK自带的JavaCompiler
* @param fileName
* @throws IOException
*/
private static void compileByTools(String fileName) throws IOException {
// 获取系统Java编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 获取标准文件管理器实例
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
try {
Iterable units = fileManager.getJavaFileObjects(fileName);
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
task.call();
} finally {
fileManager.close();
}
}

/**
* 拼接 class 代码片段
* @param packageName   代理类包名
* @param clazz         要代理的类型
* @return
*/
private static String generateClassCode(String packageName, String className, Class<?> clazz, Method[] methods) throws Exception {

StringBuilder classCodes = new StringBuilder();

/*--------------------包名和依赖 start--------------------*/
classCodes.append("package ").append(packageName).append(";").append(LINE_SEPARATOR);
classCodes.append(LINE_SEPARATOR);
classCodes.append("import java.lang.reflect.*;").append(LINE_SEPARATOR);
classCodes.append(LINE_SEPARATOR);
/*--------------------包名和依赖 start--------------------*/

/*--------------------类定义 start--------------------*/
classCodes.append("public class ").append(className);
if (clazz.isInterface()) {
classCodes.append(" implements ");
} else {
classCodes.append(" extends ");
}
classCodes.append(clazz.getName()).append(" {").append(LINE_SEPARATOR);
classCodes.append(LINE_SEPARATOR);
/*--------------------类定义 end--------------------*/

/*--------------------声明变量InvocationHandler start--------------------*/
classCodes.append("private InvocationHandler handler;").append(LINE_SEPARATOR);
classCodes.append(LINE_SEPARATOR);
/*--------------------声明变量InvocationHandler end--------------------*/

/*--------------------声明代理方法 start--------------------*/
for (int i = 0; i < methods.length; i++) {
classCodes.append("private static Method m").append(i).append(";").append(LINE_SEPARATOR);
}
classCodes.append(LINE_SEPARATOR);
/*--------------------声明代理方法 end--------------------*/

/*--------------------代理方法对象初始化 start--------------------*/
classCodes.append("static {").append(LINE_SEPARATOR);
classCodes.append("    ").append("try {").append(LINE_SEPARATOR);
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
classCodes.append("    ").append("    ").append("m").append(i).append(" = ").append(clazz.getName())
.append(".class.getMethod(\"").append(method.getName()).append("\", new Class[]{");
// 方法参数
Parameter[] params = method.getParameters();
if (params.length != 0) {
for (int j = 0; j < params.length; j++) {
if (j != 0) {
classCodes.append(", ");
}
Parameter param = params[j];
classCodes.append(param.getType().getName()).append(".class");
}
}
classCodes.append("});").append(LINE_SEPARATOR);
}
classCodes.append("    ").append("} catch (NoSuchMethodException ne) {").append(LINE_SEPARATOR);
classCodes.append("    ").append("    ").append("throw new NoSuchMethodError(ne.getMessage());").append(LINE_SEPARATOR);
classCodes.append("    ").append("}").append(LINE_SEPARATOR);
classCodes.append("}").append(LINE_SEPARATOR);
classCodes.append(LINE_SEPARATOR);
/*--------------------代理方法对象初始化 end--------------------*/

/*--------------------定义构造函数 start--------------------*/
classCodes.append("public ").append(className).append("(InvocationHandler handler) {").append(LINE_SEPARATOR);
classCodes.append("    ").append("this.handler = handler;").append(LINE_SEPARATOR);
classCodes.append("}").append(LINE_SEPARATOR);
classCodes.append(LINE_SEPARATOR);
/*--------------------定义构造函数 end--------------------*/

/*--------------------填充其他函数 start--------------------*/
classCodes.append(generateMethodCode(clazz, methods));
/*--------------------填充其他函数 end--------------------*/

// 类结束
classCodes.append("}").append(LINE_SEPARATOR);

return classCodes.toString();
}

/**
* 拼接 method 代码片段
* @param clazz
* @param methods
* @return
* @throws Exception
*/
private static String generateMethodCode(Class<?> clazz, Method[] methods) throws Exception {

StringBuilder methodCodes = new StringBuilder();

for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
// 返回类型
String returnType = method.getReturnType().getName();
// 参数列表
Parameter[] params = method.getParameters();
// 异常列表
Class<?>[] exceptionTypes = method.getExceptionTypes();

/*--------------------方法定义 start--------------------*/
methodCodes.append("public ").append(returnType).append(" ").append(method.getName());
methodCodes.append("(");
// 填充参数
if (params.length != 0) {
for (int j = 0; j < params.length; j++) {
if (j != 0) {
methodCodes.append(", ");
}
Parameter param = params[j];
methodCodes.append(param.getType().getName()).append(" ").append(param.getName());
}
}
methodCodes.append(")");
// 填充异常
if (exceptionTypes.length != 0) {
methodCodes.append(" throws ");
for (int j = 0; j < exceptionTypes.length; j++) {
if (j != 0) {
methodCodes.append(", ");
}
methodCodes.append(exceptionTypes[j].getName());
}
}
methodCodes.append(" {").append(LINE_SEPARATOR);
/*--------------------方法定义 end--------------------*/

/*--------------------方法体 start--------------------*/
methodCodes.append("    ").append("try {").append(LINE_SEPARATOR);
// 方法参数
methodCodes.append("    ").append("    ").append("Object[] args = new Object[]{");
if (params.length != 0) {
for (int j = 0; j < params.length; j++) {
if (j != 0) {
methodCodes.append(", ");
}
Parameter param = params[j];
methodCodes.append(param.getName());
}
}
methodCodes.append("};").append(LINE_SEPARATOR);
// 执行InvocationHandler.invoke()
methodCodes.append("    ").append("    ").append("Object result = handler.invoke(this, m").append(i)
.append(", args);").append(LINE_SEPARATOR);
// 返回结果
if (!"void".equals(returnType)) {
methodCodes.append("    ").append("    ").append("return (").append(returnType).append(") result;").append(LINE_SEPARATOR);
}
// 异常处理
methodCodes.append("    ").append("} catch (Error|RuntimeException");
for (Class<?> exceptionType : exceptionTypes) {
methodCodes.append("|").append(exceptionType.getName());
}
methodCodes.append(" e) {").append(LINE_SEPARATOR);
methodCodes.append("    ").append("    ").append("throw e;").append(LINE_SEPARATOR);
methodCodes.append("    ").append("} catch (Throwable t) {").append(LINE_SEPARATOR);
methodCodes.append("    ").append("    ").append("throw new UndeclaredThrowableException(t);").append(LINE_SEPARATOR);
methodCodes.append("    ").append("}").append(LINE_SEPARATOR);
/*--------------------方法体 end--------------------*/

// 方法结束
methodCodes.append("}").append(LINE_SEPARATOR).append(LINE_SEPARATOR);
}

return methodCodes.toString();
}

}


实际上只是拼接前面给出的代理类实现而已,代码量有点大,但并不难理解。

下一步,实现自己的类加载器,来加载生成的class字节码:

public class MyClassLoader extends ClassLoader {

private static MyClassLoader loader;

private MyClassLoader() {

}

public static MyClassLoader getInstance() {
if (loader == null) {
synchronized (MyClassLoader.class) {
// 得到锁首先检查loader是否已经存在, 避免重复创建
if (loader == null) {
loader = new MyClassLoader();
}
}
}
return loader;
}

/**
* 加载class文件,并返回类型对象
*
* @param filePath
* @param className
* @return
* @throws ClassNotFoundException
*/
public Class<?> findClass(String filePath, String className) throws ClassNotFoundException {
try {
// 读取指定class文件的字节码
byte[] classBytes = Files.readAllBytes(Paths.get(filePath));
// 加载类并返回class类型对象
Class<?> clazz = defineClass(className, classBytes, 0, classBytes.length);
return clazz;
} catch (IOException e) {
e.printStackTrace();
}
throw new ClassNotFoundException(className);
}

}


到这一步,复杂的工作基本做完了,接下来只剩下自定义处理器类和对外接口了

自定义处理器类与Java动态代理的方式相同:

public class MyInvocationHandler implements InvocationHandler {

private Object proxied;

public MyInvocationHandler(Object object) {
this.proxied = object;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-------------------老师好[by 自定义动态代理]-------------------");
Object obj = method.invoke(proxied, args);
System.out.println("-------------------老师再见[by 自定义动态代理]-------------------");
return obj;
}

}


对外接口:

public class MyDynamicProxy {

public static <T> T newProxyInstance(Class<T> clazz, InvocationHandler handler) throws Exception {
// 要代理的方法: public & !final
Method[] proxyMethods = Arrays.stream(clazz.getMethods())
.filter(method -> !Modifier.isFinal(method.getModifiers()))
.collect(Collectors.toList())
.toArray(new Method[0]);
// 生成的代理类
Class<?> proxyClass = MyProxyGenerator.generateAndLoadProxyClass(clazz, proxyMethods);
// 代理类的构造方法
Constructor c = proxyClass.getConstructor(InvocationHandler.class);
// 创建代理类对象
Object proxyObj = c.newInstance(handler);
return (T) proxyObj;
}

}


搞定,测试一下效果:

TeacherChan proxy1 = MyDynamicProxy.newProxyInstance(
TeacherChan.class, new MyInvocationHandler(new TeacherChan()));
proxy1.teach();

TeacherCang proxy2 = MyDynamicProxy.newProxyInstance(
TeacherCang.class, new MyInvocationHandler(new TeacherCang()));
proxy2.teach();


输出:

-------------------老师好[by 自定义动态代理]-------------------
大家好,我是陈老师,我教大家摄影!
-------------------老师再见[by 自定义动态代理]-------------------
-------------------老师好[by 自定义动态代理]-------------------
大家好,我是苍老师,我教大家生物!
-------------------老师再见[by 自定义动态代理]-------------------


完美!大功告成!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java aop