Java.lang.reflect.Proxy, возвращающий другой прокси из вызова, приводит к ClassCastException при назначении

Итак, я играю с геоинструментами и подумал, что могу проксировать один из их классов доступа к данным и отслеживать, как он используется в их коде.

Я написал динамический прокси-сервер и завернул в него FeatureSource (интерфейс), и все прошло успешно. Затем я хотел посмотреть на некоторые транзитивные объекты, возвращаемые featureSource, так как главное, что FeatureSource делает, это возвращает FeatureCollection (FeatureSource аналогичен sql DataSource, а featurecollection — оператору sql).

в моем обработчике вызовов я просто передал вызов базовому объекту, распечатав целевой класс/метод/аргументы и результат, когда я пошел, но для вызовов, которые возвращали FeatureCollection (другой интерфейс), я завернул этот объект в свой прокси ( тот же класс, но новый экземпляр, не должно ли это иметь значение, не так ли?) и вернул его. БАМ! Исключение класса:

java.lang.ClassCastException: $Proxy5 cannot be cast to org.geotools.feature.FeatureCollection  
    at $Proxy4.getFeatures(Unknown Source)  
    at MyClass.myTestMethod(MyClass.java:295)  

код вызова:

FeatureSource<SimpleFeatureType, SimpleFeature> featureSource = ... // create the FS
featureSource = (FeatureSource<SimpleFeatureType, SimpleFeature>) FeatureSourceProxy.newInstance(featureSource, features);
featureSource.getBounds();// ok
featureSource.getSupportedHints();// ok

DefaultQuery query1 = new DefaultQuery(DefaultQuery.ALL);
FeatureCollection<SimpleFeatureType, SimpleFeature> results = featureSource.getFeatures(query1); //<- explosion here

Прокси:

public class FeatureSourceProxy  implements java.lang.reflect.InvocationHandler {

private Object target;
private List<SimpleFeature> features;

public static Object newInstance(Object obj, List<SimpleFeature> features) {
return java.lang.reflect.Proxy.newProxyInstance(
    obj.getClass().getClassLoader(), 
    obj.getClass().getInterfaces(), 
    new FeatureSourceProxy(obj, features)
);
}

private FeatureSourceProxy(Object obj, List<SimpleFeature> features) {
this.target = obj;
this.features = features;
}

public Object invoke(Object proxy, Method m, Object[] args)throws Throwable{
Object result = null;
try {
    if("getFeatures".equals(m.getName())){ 
        result = interceptGetFeatures(m, args);
    }
    else{
        result = m.invoke(target, args);
    }
} 
catch (Exception e) {
    throw new RuntimeException("unexpected invocation exception: " +  e.getMessage(), e);
} 
return result;
}

private Object interceptGetFeatures(Method m, Object[] args) throws Exception{
    return newInstance(m.invoke(target, args), features);
}

}

Можно ли динамически возвращать прокси интерфейсов из проксируемого интерфейса или я что-то не так делаю? ваше здоровье!


person matao    schedule 15.04.2010    source источник


Ответы (2)


Class.getInterfaces() возвращает только интерфейсы, НЕПОСРЕДСТВЕННО реализованные классом. Вам нужно транзитивное закрытие, чтобы использовать все интерфейсы.

ОБНОВИТЬ

Пример:

private static Class<?>[] getInterfaces(Class<?> c) {
    List<Class<?>> result = new ArrayList<Class<?>>();
    if (c.isInterface()) {
        result.add(c);
    } else {
        do {
            addInterfaces(c, result);
            c = c.getSuperclass();
        } while (c != null);
    }
    for (int i = 0; i < result.size(); ++i) {
        addInterfaces(result.get(i), result);
    }
    return result.toArray(new Class<?>[result.size()]);
}

private static void addInterfaces(Class<?> c, List<Class<?>> list) {
    for (Class<?> intf: c.getInterfaces()) {
        if (!list.contains(intf)) {
            list.add(intf);
        }
    }
}

Вам также может понадобиться «развернуть» прокси, которые передаются в качестве аргументов.

person Maurice Perry    schedule 15.04.2010
comment
отлично работает!! вау, спасибо за это, оглядываясь назад, очевидно, что я должен был понять, по какой-то причине я думал, что getInterfaces() вернет все реализованные интерфейсы, а не только те, которые непосредственно реализованы классом. - person matao; 16.04.2010
comment
есть ли причина не использовать Set? - person TWiStErRob; 05.02.2019

Решение @maurice-perry отлично сработало для меня, и я проголосовал за него, но я также хотел указать, что существуют библиотечные реализации необходимого метода.

В итоге я реализовал это решение с помощью метода библиотеки Apache Commons ClassUtils.getAllInterfaces():

...
import org.apache.commons.lang3.ClassUtils;
...

private static Class<?>[] getAllInterfaces(Object object) {
    final List<Class<?>> interfaces =
        ClassUtils.getAllInterfaces(object.getClass());

    return interfaces.toArray(new Class<?>[interfaces.size()]);
}

Он отлично работает для этого волшебного второго аргумента в newProxyInstance:

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

Существует также подход Guava, использующий:

final Set<TypeToken> tt = TypeToken.of(cls).getTypes().interfaces();

Но тогда вам нужно выяснить, как преобразовать Set<TypeToken> в Class<?>[]. Возможно, тривиально, если вы любитель гуавы, но Apache готов к использованию.

Оба они были отмечены в этом связанном потоке, получить все (производные) интерфейсы класс.

person Jameson    schedule 16.09.2016