LINUX.ORG.RU

Создать класс-прокси в рантайме

 ,


0

1

Допустим, есть класс:

abstract class A {
    public abstract void foo(int i);
    public abstract void bar();
}

class B extends A {
    @Override
    public void foo(int i) { ... }
    
    @Override
    public void bar() { ... }
}

Я хочу запилить свой класс:

class C extends A {
    private final B b;
    C(B b) {
        this.b = b;
    }
    @Override
    public void foo(int i) {
        b.foo(i * 2);
    }
    @Override
    public void bar() {
        b.bar();
    }
}

То есть он по сути дела является обёрткой над классом B, которая модифицирует поведение одного из методов (точнее выполняет некоторую предобработку аргументов), а все остальные просто проксирует без изменений.

Разумеется, в реальности методов у класса B не 2, а больше.

Я хочу создать объект-прокси полностью в рантайме (с помощью, например, javassist). Более того, я даже знать не хочу, какие конкретно методы есть у класса A кроме того, в который я хочу влезть, но они должны правильно отпроксироваться.

Как такое запилить?

P. S.: Я понимаю, что полученный класс не будет полной заменой класса B, но мне это и не нужно - всё равно весь код работает с ним только как с классом A.

★★★★★

Последнее исправление: KivApple (всего исправлений: 1)

Mockito, например.

Я хочу создать объект-прокси полностью в рантайме

Если нужно полностью в рантайме, то, видимо, придётся проксировать все методы и в прокси классе в рантайме уже определять, передавать напрямую вызов, либо делать своё что-то.

orm-i-auga ★★★★★
()
		final B b = new B();
		A spy = spy(b);
		doAnswer(new Answer<Void>() {

			@Override
			public Void answer(InvocationOnMock invocation) throws Throwable {
				Object[] args = invocation.getArguments();
				int i = (Integer) args[0];
				b.foo(i * 2);
				return null;
			}

		}).when(spy).foo(anyInt());
		spy.foo(2);
orm-i-auga ★★★★★
()
Ответ на: комментарий от orm-i-auga

А насколько большой оверхед добавляется к вызовам тех методов, которые мне не интересны?

KivApple ★★★★★
() автор топика

Можно в javaagent'е такое провернуть, но уже загруженные классы он не может модифицировать емнип. Это можно обойти с кастомным classloader'ом.

Deleted
()

abstract class A {

а еслиб ты юзал интерфейсы то смогбы создать прокси тем что есть в жаве «из коробки»

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от orm-i-auga

Mockito *does not* delegate calls to the passed real instance, instead it actually creates a copy of it

Это не хорошо...

KivApple ★★★★★
() автор топика
Ответ на: комментарий от Deleted

Классы A и B написаны не мною. И вообще вся эта затея делается ради дикого костыля.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от Deleted

Насколько я помню, классы загружаются в момент первого обращения к ним. В принципе я могу провернуть все манипуляции до первого обращения к A и B. Но можно ли это сделать без javaagent (не хочу модифицировать classpath и создавать ещё один jar)?

Я с помощью javassist могу модифицировать методы класса. Затем могу вызвать метод toClass объекта CtClass. Но проблема в том, что не я создаю экземпляр класса B. Пробовал сделать свой ClassLoader и поставить его с помощью setContextClassLoader, но он почему-то обрабатывает не все классы, а только некоторые (того что мне нужно среди них нет). Если что A и B находятся в библиотеке, входящую в стандартную поставку и лежащую в lib/ext.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от KivApple

Можешь попробовать так — во время исполнения эту либу в classpath не добавляй, а загружай классы из нее своим classloader'ом. Тогда у тебя будет возможность байт-код модифицировать.

Deleted
()
Ответ на: комментарий от Deleted

Сделал вот такую вот штуку с помощью javassist:

private static Object createObjectProxy(Object realObject, Class realClass) throws Throwable {
		ClassPool pool = ClassPool.getDefault();
		CtClass cc = pool.makeClass(realClass.getName() + "_Proxy");
		cc.setSuperclass(pool.get(realClass.getName()));
		CtField realObjectField = new CtField(cc.getSuperclass(), "realObject", cc);
		realObjectField.setModifiers(Modifier.FINAL | Modifier.PRIVATE);
		cc.addField(realObjectField);
		CtField realClassField = new CtField(pool.get("java.lang.Class"), "realClass", cc);
		realClassField.setModifiers(Modifier.FINAL | Modifier.PRIVATE);
		cc.addField(realClassField);
		CtConstructor constructor = new CtConstructor(
				new CtClass[] { realObjectField.getType(), realClassField.getType() }, cc
		);
		constructor.setModifiers(Modifier.PUBLIC);
		constructor.setBody("{ realObject = $1; realClass = $2; }");
		cc.addConstructor(constructor);
		for (CtMethod method : cc.getSuperclass().getDeclaredMethods()) {
			if ((method.getModifiers() & Modifier.FINAL) != 0) continue;
			if ((method.getModifiers() & Modifier.STATIC) != 0) continue;
			CtMethod newMethod = new CtMethod(method.getReturnType(), method.getName(),
					method.getParameterTypes(), cc);
			newMethod.setModifiers(method.getModifiers() & ~(Modifier.NATIVE | Modifier.SYNCHRONIZED));
			newMethod.setExceptionTypes(method.getExceptionTypes());
			if (newMethod.getReturnType().equals(CtClass.voidType)) {
				if ((newMethod.getModifiers() & Modifier.PUBLIC) != 0) {
					newMethod.setBody("realObject." + method.getName() + "($$);");
				} else {
					newMethod.setBody("{ java.lang.reflect.Method method = realClass.getDeclaredMethod(\""
							+ method.getName() + "\", $sig);" + "method.setAccessible(true);"
							+ "method.invoke(this.realObject, $args); }");
				}
			} else {
				if ((newMethod.getModifiers() & Modifier.PUBLIC) != 0) {
					newMethod.setBody("return realObject." + method.getName() + "($$);");
				} else {
					newMethod.setBody("{ java.lang.reflect.Method method = realClass.getDeclaredMethod(\""
							+ method.getName() + "\", $sig);" + "method.setAccessible(true);"
							+ "java.lang.Object retVal = method.invoke(realObject, $args);"
							+ "return ($r) retVal; }");
				}
			}
			cc.addMethod(newMethod);
		}
		Class cls = cc.toClass();
		Constructor c = cls.getDeclaredConstructor(cls.getSuperclass(), Class.class);
		Object proxy = c.newInstance(realObject, realClass);
		for (Field field : realClass.getDeclaredFields()) {
			if ((field.getModifiers() & Modifier.STATIC) != 0) continue;
			if ((field.getModifiers() & Modifier.FINAL) != 0) continue;
			field.setAccessible(true);
			field.set(proxy, field.get(realObject));
		}
		return proxy;
	}

Используется как-то так:

B b = new B();
Object proxy = createObjectProxy(b, A.class);

И оно отлично работает.

KivApple ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.