Java.lang.reflect.proxy Retornando outro proxy da Invocation Results in ClassCastException na atribuição

StackOverflow https://stackoverflow.com/questions/2642700

Pergunta

Então, estou jogando com Geotools e pensei em proxy uma de suas classes de acesso de dados e rastrear como estava sendo usado em seu código.

Codifiquei um proxy dinâmico e enrolei um recurso (interface) nele e ele foi feliz. Então, eu queria ver alguns dos objetos transitivos retornados pelo recurso como a principal, já que a principal coisa que a empresa faz é retornar uma apresentação (recursos de recursos é análoga a um SQL DataSource e FeatureCollection para uma instrução SQL).

No meu InvocationHandler, acabei de passar a chamada para o objeto subjacente, imprimindo a classe de destino/método/args e resultado como eu fui, mas para chamadas que devolviam uma apresentação (outra interface), envolvi esse objeto no meu proxy (o A mesma classe, mas uma nova instância, não deveria importar?) E devolveu. BAM! Exceção de Classcast:

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

O código de chamada:

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

o proxy:

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);
}

}

É possível retornar dinamicamente proxies de interfaces a partir de uma interface proxiada ou estou fazendo algo errado? Felicidades!

Foi útil?

Solução

Class.getInterfaces () Retorna apenas as interfaces implementadas diretamente pela classe. Você precisa de um fechamento transitivo para optar por todas as interfaces.

ATUALIZAR

Exemplo:

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);
        }
    }
}

Você também pode precisar "desembrulhar" os proxies que são aprovados como argumentos.

Outras dicas

A solução da @Maurice-Perry funcionou muito bem para mim e eu votei nela, mas também queria ressaltar que existem implementações de bibliotecas do método necessário.

Acabei implementando esta solução com o método da biblioteca 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()]);
}

Funciona muito bem para esse segundo argumento mágico em newProxyInstance:

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

Há também uma abordagem de goiaba usando:

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

Mas então você tem que descobrir como se converter Set<TypeToken> para Class<?>[]. TRIVIAL talvez, se você é um buff de goiaba, mas o Apache está pronto para uso.

Ambos foram observados neste tópico relacionado, Obtenha todas as interfaces (derivadas) de uma classe.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top