Когда дело доходит до динамического прокси-сервера JDK, я думаю, что многие люди знакомы с ним. Однако мало кто может знать принцип реализации динамического прокси и как написать код для реализации функции динамического прокси. Далее давайте рассмотрим основные принципы динамического прокси JDK и то, как его имитировать с помощью Javassist.
Динамический прокси JDK
Пример
Ниже приведен пример hello world, основанный на динамическом прокси-сервере JDK, похожие версии можно увидеть во многих местах.
public class DynamicProxyTest {
interface IHello {
void sayHello();
}
static class Hello implements IHello {
@Override
public void sayHello() {
System.out.println("hello world");
}
}
static class DynamicProxy implements InvocationHandler {
Object originalObj;
Object bind(Object originalObj) {
this.originalObj = originalObj;
return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("pre method");
Object result = method.invoke(originalObj, args);
System.out.println("post method");
return result;
}
}
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
IHello hello = (IHello) new DynamicProxy().bind(new Hello());
hello.sayHello();
}
}
Сгенерировать исходный код класса агента
По настройке параметров
sun.misc.ProxyGenerator.saveGeneratedFiles имеет значение true, после выполнения основной функции мы получим копию$Proxy0.classфайл, который является прокси-классом для Hello.
После декомпиляции получаем$Proxy0Исходный код (с опущенным ненужным контентом) выглядит следующим образом:
final class $Proxy0 extends Proxy implements DynamicProxyTest.IHello {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final void sayHello() {
try {
this.h.invoke(this, m3, null);
return;
} 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("DynamicProxyTest$IHello").getMethod("sayHello", 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]);
} catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
} catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
Основной принцип реализации
- Динамически создавать прокси-класс, реализующий интерфейс IHello;
- Класс прокси наследует класс прокси и предоставляет переменную экземпляра InvocationHandler h для сохранения логики прокси (кроме того, прокси также предоставляет связанную логику обработки класса прокси);
- Прокси-класс объявляет ряд переменных класса Method, которые используются для сохранения методов отражения, связанных с интерфейсом;
- Прокси-класс реализует соответствующие методы интерфейса.Основная логика заключается в вызове метода вызова InvocationHandler и передаче трех параметров: текущего объекта прокси-класса, объекта метода отражения интерфейса и фактических параметров метода.
Javassist реализует динамический прокси JDK
Основной принцип динамического прокси JDK был кратко проанализирован ранее.Основная логика заключается в том, как создать класс динамического прокси, то есть Реализация метода java.lang.reflect.Proxy.newProxyInstance(загрузчик ClassLoader, интерфейсы Class>[], InvocationHandler h).
Далее мы шаг за шагом реализуем метод newProxyInstance через Javassist.
1. Определите интерфейс
Интерфейс в основном такой же, как у Proxy.newProxyInstance. Для простоты мы определяем здесь только параметр типа интерфейса Class> вместо массива.
public static Object newProxyInstance(ClassLoader loader, Class<?> interfaceClass, InvocationHandler h) {
...
}
2. Создайте динамический прокси-класс
Javassist может манипулировать исходным кодом с помощью простого Java API, так что можно динамически генерировать классы или изменять поведение классов без каких-либо знаний о байт-кодах Java.
Создайте прокси-класс с именем NewProxyClass.
ClassPool pool = ClassPool.getDefault();
CtClass proxyCc = pool.makeClass("NewProxyClass");
3. Добавьте переменную экземпляра InvocationHandler
Добавьте переменную экземпляра h типа InvocationHandler.
CtClass handlerCc = pool.get(InvocationHandler.class.getName());
/* 生成代码:private InvocationHandler h; */
CtField handlerField = new CtField(handlerCc, "h", proxyCc);
handlerField.setModifiers(AccessFlag.PRIVATE);
proxyCc.addField(handlerField);
4. Добавьте конструктор
Создайте конструктор с типом параметра InvocationHandler.
// 生成构造函数:public NewProxyClass(InvocationHandler h) { this.h = h; }
CtConstructor ctConstructor = new CtConstructor(new CtClass[] { handlerCc }, proxyCc);
ctConstructor.setBody("$0.h = $1;");
proxyCc.addConstructor(ctConstructor);
в,$0представляет это,$1Представляет первый параметр конструктора.
5. Реализуйте объявление интерфейса IHello
// 生成接口实现声明:public class NewProxyClass implements IHello
CtClass interfaceCc = pool.get(interfaceClass.getName());
proxyCc.addInterface(interfaceCc);
6. Реализуйте методы интерфейса, связанные с IHello
6.1 Методы обхода интерфейса
CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();
for (int i = 0; i < ctMethods.length; i++) {
// 核心逻辑在下方
}
6.2 Реализация прокси-метода
Поскольку прокси-класс вызывает метод вызова, ему необходимо передать объект метода отражения (метод) интерфейса, поэтому нам нужно добавить повторно используемую переменную класса метода для каждого метода.
6.2.1 Объявление и инициализация объекта метода отражения
/* 构造方法参数,如:new Class[] { String.class, Boolean.TYPE, Object.class } */
String classParamsStr = "new Class[0]";
if (ctMethods[i].getParameterTypes().length > 0) {
for (CtClass clazz : ctMethods[i].getParameterTypes()) {
classParamsStr = ((classParamsStr == "new Class[0]") ? clazz.getName() : classParamsStr + "," + clazz.getName()) + ".class";
}
classParamsStr = "new Class[] {" + classParamsStr + "}";
}
// 字段生成模板
String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";
// 根据模板生成方法及参数构造方法字段生成语句
String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), ctMethods[i].getName(), classParamsStr);
// 为代理类添加反射方法字段
CtField methodField = CtField.make(methodFieldBody, proxyCc);
proxyCc.addField(methodField);
С помощью приведенной выше логики аналогичный код будет сгенерирован следующим образом:
private static Method m0 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello", new Class[0]);
private static Method m1 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello2", new Class[] { Integer.TYPE });
private static Method m2 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello3", new Class[] { String.class, Boolean.TYPE, Object.class });
6.2.2 Реализация тела метода интерфейса
// invoke调用逻辑. 其中$args是实际方法传入的参数数组
String methodBody = "$0.h.invoke($0, " + methodFieldName + ", $args)";
// 如果方法有返回类型,则需要转换为相应类型后返回
if (CtPrimitiveType.voidType != ctMethods[i].getReturnType()) {
// 对8个基本类型进行转型
// 例如:((Integer)this.h.invoke(this, this.m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
if (ctMethods[i].getReturnType() instanceof CtPrimitiveType) {
CtPrimitiveType ctPrimitiveType = (CtPrimitiveType) ctMethods[i].getReturnType();
methodBody = "return ((" + ctPrimitiveType.getWrapperName() + ") " + methodBody + ")." + ctPrimitiveType.getGetMethodName() + "()";
}
// 对于非基本类型直接转型即可
else {
methodBody = "return (" + ctMethods[i].getReturnType().getName() + ") " + methodBody;
}
}
methodBody += ";";
/* 为代理类添加方法 */
CtMethod newMethod = new CtMethod(ctMethods[i].getReturnType(), ctMethods[i].getName(),
ctMethods[i].getParameterTypes(), proxyCc);
newMethod.setBody(methodBody);
proxyCc.addMethod(newMethod);
С помощью приведенной выше логики аналогичный код будет сгенерирован следующим образом:
public void sayHello() {
this.h.invoke(this, m0, new Object[0]);
}
public String sayHello2(int paramInt) {
return (String)this.h.invoke(this, m1, new Object[] { new Integer(paramInt) });
}
public int sayHello3(String paramString, boolean paramBoolean, Object paramObject) {
return ((Integer)this.h.invoke(this, m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
}
7. Сгенерируйте байт-код прокси-класса
Следующий оператор сгенерирует байт-код прокси-класса: D:/tmp/NewProxyClass.class.
proxyCc.writeFile("D:/tmp"); // 该步骤可选
8. Создайте прокси-объект
Наконец, сгенерируйте и верните прокси-класс, вызвав конструктор, созданный на шаге 3, и передав объект InvocationHandler.
Object proxy = proxyCc.toClass().getConstructor(InvocationHandler.class).newInstance(h);
return proxy;
полный код
public class ProxyFactory {
public static Object newProxyInstance(ClassLoader loader, Class<?> interfaceClass, InvocationHandler h) throws Throwable {
ClassPool pool = ClassPool.getDefault();
// 1.创建代理类:public class NewProxyClass
CtClass proxyCc = pool.makeClass("NewProxyClass");
/* 2.给代理类添加字段:private InvocationHandler h; */
CtClass handlerCc = pool.get(InvocationHandler.class.getName());
CtField handlerField = new CtField(handlerCc, "h", proxyCc); // CtField(CtClass fieldType, String fieldName, CtClass addToThisClass)
handlerField.setModifiers(AccessFlag.PRIVATE);
proxyCc.addField(handlerField);
/* 3.添加构造函数:public NewProxyClass(InvocationHandler h) { this.h = h; } */
CtConstructor ctConstructor = new CtConstructor(new CtClass[] { handlerCc }, proxyCc);
ctConstructor.setBody("$0.h = $1;"); // $0代表this, $1代表构造函数的第1个参数
proxyCc.addConstructor(ctConstructor);
/* 4.为代理类添加相应接口方法及实现 */
CtClass interfaceCc = pool.get(interfaceClass.getName());
// 4.1 为代理类添加接口:public class NewProxyClass implements IHello
proxyCc.addInterface(interfaceCc);
// 4.2 为代理类添加相应方法及实现
CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();
for (int i = 0; i < ctMethods.length; i++) {
String methodFieldName = "m" + i; // 新的方法名
// 4.2.1 为代理类添加反射方法字段
// 如:private static Method m1 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello2", new Class[] { Integer.TYPE });
/* 构造反射字段声明及赋值语句 */
String classParamsStr = "new Class[0]"; // 方法的多个参数类型以英文逗号分隔
if (ctMethods[i].getParameterTypes().length > 0) { // getParameterTypes获取方法参数类型列表
for (CtClass clazz : ctMethods[i].getParameterTypes()) {
classParamsStr = (("new Class[0]".equals(classParamsStr)) ? clazz.getName() : classParamsStr + "," + clazz.getName()) + ".class";
}
classParamsStr = "new Class[] {" + classParamsStr + "}";
}
String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";
String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), ctMethods[i].getName(), classParamsStr);
// 为代理类添加反射方法字段. CtField.make(String sourceCodeText, CtClass addToThisClass)
CtField methodField = CtField.make(methodFieldBody, proxyCc);
proxyCc.addField(methodField);
System.out.println("methodFieldBody: " + methodFieldBody);
/* 4.2.2 为方法添加方法体 */
/* 构造方法体. this.h.invoke(this, 反射字段名, 方法参数列表); */
String methodBody = "$0.h.invoke($0, " + methodFieldName + ", $args)";
// 如果方法有返回类型,则需要转换为相应类型后返回,因为invoke方法的返回类型为Object
if (CtPrimitiveType.voidType != ctMethods[i].getReturnType()) {
// 对8个基本类型进行转型
// 例如:((Integer)this.h.invoke(this, this.m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
if (ctMethods[i].getReturnType() instanceof CtPrimitiveType) {
CtPrimitiveType ctPrimitiveType = (CtPrimitiveType) ctMethods[i].getReturnType();
methodBody = "return ((" + ctPrimitiveType.getWrapperName() + ") " + methodBody + ")." + ctPrimitiveType.getGetMethodName() + "()";
} else { // 对于非基本类型直接转型即可
methodBody = "return (" + ctMethods[i].getReturnType().getName() + ") " + methodBody;
}
}
methodBody += ";";
/* 为代理类添加方法. CtMethod(CtClass returnType, String methodName, CtClass[] parameterTypes, CtClass addToThisClass) */
CtMethod newMethod = new CtMethod(ctMethods[i].getReturnType(), ctMethods[i].getName(),
ctMethods[i].getParameterTypes(), proxyCc);
newMethod.setBody(methodBody);
proxyCc.addMethod(newMethod);
System.out.println("Invoke method: " + methodBody);
}
proxyCc.writeFile("D:/tmp");
// 5.生成代理实例. 将入参InvocationHandler h设置到代理类的InvocationHandler h变量
@SuppressWarnings("unchecked")
Object proxy = proxyCc.toClass().getConstructor(InvocationHandler.class).newInstance(h);
return proxy;
}
}
public interface IHello {
int sayHello3(String a, boolean b, Object c);
}
public class Hello implements IHello {
@Override
public int sayHello3(String a, boolean b, Object c) {
String abc = a + b + c;
System.out.println("a + b + c=" + abc);
return abc.hashCode();
}
}
public class CustomHandler implements InvocationHandler {
private Object obj;
public CustomHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("pre method");
Object result = method.invoke(obj, args);
System.out.println("post method");
return result;
}
}
public class ProxyTest {
public static void main(String[] args) throws Throwable {
IHello hello = new Hello();
CustomHandler customHandler = new CustomHandler(hello);
IHello helloProxy = (IHello) ProxyFactory.newProxyInstance(hello.getClass().getClassLoader(), IHello.class, customHandler);
System.out.println();
System.out.println("a+false+Object=" + helloProxy.sayHello3("a", false, new Object()));
}
}
Результаты:
methodFieldBody: private static java.lang.reflect.Method m0=Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello3", new Class[] {java.lang.String.class,boolean.class,java.lang.Object.class});
Invoke method: return ((java.lang.Integer) $0.h.invoke(args)).intValue();
pre method
a + b + c=afalsejava.lang.Object@504bae78
post method
a+false+Object=-903110407
Ссылаться на
Официальный сайт Javassist:www.javassist.org/
Личный публичный аккаунт
Для получения дополнительных статей, пожалуйста, обратите внимание на общедоступный номер: Binary Road