How DWR cast incoming data and evade type erasure

2019-03-31 06:53发布

问题:

I would like to request for an item class of collection (kind of specific reflection). But regarding to type erasure it seems not possible and also regarding to some topics I've read here on stack. There are some workarounds (here), but I'm curious if somebody know how is it done for example by DWR:

http://directwebremoting.org/dwr/documentation/server/configuration/dwrxml/signatures.html

or in case that there is some better workaround it would be great.

Let's say we have something like:

public String foo(List<String> possibleFoos) {

and all I need to is find out that parameter possibleFoos is list of Strings, not just List

回答1:

While it's true that Java will erase types at runtime (thus turning List<String> into just List), in many cases it actually does store the generic type in runtime allowing you to restore the information lost to erasure. You can retrieve the actual generic types for these:

  • Method arguments (your case)
  • Method return types
  • Field types
  • Super classes/interfaces

This means if you simply have an object of type List, there's nothing you can do to get it's generic type (object.getClass() will get you List and that's it) - it's been permanently lost. But, if you're trying to figure out the generic type for a method argument or any of the above, you usually can by using reflection. As your case doesn't involve type variables or other complications, it's pretty straightforward to get the actual type:

ParameterizedType listArgument = (ParameterizedType) ClassDeclaringFoo.class.getMethod("foo", List.class).getGenericParameterTypes()[0];
Type listType = listArgument.getActualTypeArguments()[0]; //This will be a Class representing String, the type of the List

If you had a more parameters and a map instead:

public String foo(Object bar, Map<String, Number> possibleFoos) { ... }

The code would be similar:

ParameterizedType mapArgument = (ParameterizedType) ClassDeclaringFoo.class.getMethod("foo", Object.class, Map.class).getGenericParameterTypes()[1]; //1 is the index of the map argument
Type keyType = mapArgument.getActualTypeArguments()[0]; //This will be a Class representing String, the type of the Map key
Type valType = mapArgument.getActualTypeArguments()[1]; //This will be a Class representing Number, the type of the Map value

It's safe to assume this is what DWR is using as well, as the types are method arguments.

Similar methods are available for other listed cases:

  • Class.getMethod(...).getGenericReturnType() will get you the real return type
  • Class.getField(fieldName).getGenericType() will get you the real type of the field
  • Class.getGenericSuperClass() will get you the real super type
  • Class.getGenericInterfaces() will get you the real interface types

Equivalent methods exist allowing access to AnnotatedType (generic type plus annotations on the type usage) introduced in Java 8:

  • Class.getMethod(...).getAnnotatedParameterTypes()
  • Class.getMethod(...).getAnnotatedReturnType()
  • Class.getField(fieldName).getAnnotatedType()
  • Class.getAnnotatedSuperClass()
  • Class.getAnnotatedInterfaces()

Now, this is all dandy when your case as simple as in the example. But, imagine if your example looked like this:

public T foo(List<T> possibleFoos) {...}

In this case, getGenericParameterTypes()[0].getActualTypeArguments()[0] would give you T which is rather useless. To resolve what T stands for, you'd have to look into the class definition, and perhaps super classes, while keeping track of how the type variables are named in each class, as the names could be different in each.

To make working with generic type reflection easier, you can use a wonderful library called GenTyRef that does the hard work for you, and if you need support for AnnotatedTypes, you can use my fork called GeAnTyRef (both are in Maven Central). They also include a type factory, that allows you to construct instances of (Annotated)Type, which you can not easily do using normal Java API. There's also a handy super type token implementation allowing you to get an (Annotated)Type literal.

With those, you can do everything with generic types that Java allows without the hassle I explained above:

  • GenericTypeReflector#getExactParameterTypes( ... )
  • GenericTypeReflector#getExactReturnType( ... )
  • GenericTypeReflector#getExactFieldType( ... )
  • GenericTypeReflector#getExactSuperType( ... )

And many more operations, like figuring out if one Type is a super type of another (similar to Class#isAssignableFrom but for generic types), resolving specific type variables etc.