Spring Framework-IOC-Not Simple Generic (ResolvableType)

Contents of Spring Framework-IoC Series Articles

Generics are widely used in a variety of frameworks. They have a high exposure in everyday development or source reading, and it is necessary to understand them. Generic basic usage scenarios are relatively simple, but are often not easy to understand in complex scenarios

This paper first introduces the basic concepts of java generics, then lists some complex scenarios for using generics. Finally, it introduces the powerful and easy-to-use generic API in spring, hoping to help you understand generics more deeply, or make better use of them for common design in your daily development work.

Let's start with the java generics basics: Generics allow users to use type(class, interface) as a type parameter when defining classes, interfaces, and methods. As with method parameters, once a type parameter is defined, it can be referenced in the corresponding scope (class, interface, method). The difference is that the input to the method parameter is a value, and the input to the type parameter is a type

The generic terms involved in this passage explain:

Generic types: Include generic classes and generic interfaces, define formats: class name < T1, T2,..., Tn> {}

Generic method: Define format: <T1, T2,..., Tn> void test(T1 t1,T2 t2...) {}, both static and non-static methods support generics, note that generic parameters must precede method return types

Type parameter/type variable: T1, T2 included in'<>'in a generic type or method declaration. Is the type parameter (also known as type variable)

In addition, several terms used below are also explained here:

Parameterized type: Foo<String>is parameterized type

Type argument: Be careful not to be confused with type parameter, which is a parameter defined at the time of declaration, and type argument, which is an argument (the actual parameter passed in). For example, T in class Foo <T>{} is a type parameter, while String in Foo <String> is an argument.

Raw type: parameterized type is the original type after removing the type argument, and Foo in Foo<String>is the raw type

We won't expand on the basic usage scenarios for generics, but here are some slightly more complex scenarios

Scenario 1:

public void someMethod(Number n) { /* ... */ }
someMethod(new Integer(10));   // OK
someMethod(new Double(10.1));   // OK

List<Number> list = new ArrayList<Number>();
list.add(new Integer(10));   // OK
list.add(new Double(10.1));  // OK

There is no doubt that both scenarios are OK because Integer and Double are subtypes of Number, so let's consider what type of parameters can be passed in by this method:

public void test(List<Number> n) {}

Is List <Integer>, List <Double> OK? The answer is no, because List <Integer>, List <Double> are not subclasses of List <Number>, ArrayList <Number>, LinkedList <Number> are possible, they are subclasses of List <Number>

Scenario 2:

//Define a generic interface A
interface A<T>{}

You must have encountered each of these implementations:

//Mode 1
class B implements A<String>{}

//Mode 2
class B<T> implements A<T>{}

//Mode 3
class B<T extends User> implements A<T>{}

//Mode 4
class B<T> implements A<String>{}

//Mode 5
class B<T,E> implements A<T>{}

So what do they mean?

Mode 1: Subclass B specifies type argument String, at which point B is no longer a generic class, and when B is used, type argumentation is no longer required

Mode 2: Subclass B inherits the parent's type parameter intact, and when using B, any type argument can be specified

Mode 3: Subclass B redefines the type parameter inherited from the parent class, narrowing it down to be a User and its descendants. When using B, only type argument of User type (including descendants of User) can be specified.

Mode 4: Subclass B specifies the type argument from the parent class as a String and defines its own type parameter(T), which has nothing to do with the parent class

Mode 5: Subclass B inherits the type parameter(T) of the parent class and defines its own type parameter(E). When using B, two type argument s need to be specified

Scenario three:

The handle method of abstract template class A first cascades the input json into a T-type object, then handles it to the subclass

public abstract class A<T>{
    
    public void handle(String json){
        //Convert json to a T-type object and pass it to the subclass
        T t=fromJson(json);
        doHandle(t);
    }

    private T fromJson(String json) {
        //How?
    }

    protected abstract void doHandle(T t);
}

Consider how the fromJson method is implemented. When a json becomes an object, it must first get the corresponding Class. How? T.class Guess you can't?.. The correct solution is as follows, which you can interpret in conjunction with the above NOUN explanations:

public abstract class A<T>{

    public void handle(String json){
        T t=fromJson(json);
        doHandle(t);
    }

    private T fromJson(String json) {
        //Gets the parameterized type of the parent class, A<User>. Explain "parameterized type" for corresponding nouns
        ParameterizedType parameterizedType=(ParameterizedType)this.getClass().getGenericSuperclass();
        //Gets its type arguments new Type[]{User.class}. Explain "type argument" with corresponding nouns
        Type[] typeArguments=parameterizedType.getActualTypeArguments();
        //Get the first type argument, User.class
        Class<T> clazzT=(Class<T>)typeArguments[0];
        return new Gson().fromJson(json,clazzT);
    }

    protected abstract void doHandle(T t);
}

public class B extends A<User>{
    @Override
    protected void doHandle(User user) {
        //do something
    }
}

Scenario 4:

Class C<T>{}, how do I get the corresponding type argument for T in C? The answer is that there is no way to get it. Scenario 3 is because type argumentation was determined during compilation (class B extends A<User>). In the current scenario, only type parameter has no type argument at compilation time, and processing for generics is done during compilation. It is not feasible to try to get generic information from runtime

Supplementary Notes:

jdk1.5 defines java.lang.reflect.Type, which is the common parent interface for all types in java, includes:

Class: Basic data type (int/short/boolean..) All running class and interface types, such as array type, enumeration type, are represented in Class

ParameterizedType: For example, MyService<User>

Type parameter/type variable: such as T

Generic array types, such as T[]

Spring provides a powerful generic parsing tool, ResolvableType: ResolvableType encapsulates a java. Lang.reflect. A Type object that provides access to information on a Type, such as the parent class (getSuperType()), the interface (getInterfaces()), and the generic parameters on a class (getGeneric(int...)). And the ability to resolve into classes (resolve())

ResolvableType can be constructed by field, method parameter, method return type, or class, corresponding to the static methods in ResolvableType: forField, forMethodParameter(Method, int), forMethodReturnType(Method), forClass(Class)

Here are a few examples of ResolvableType's capabilities and usability

HashMap<Integer, List<String>> myMap;

public void example1() throws NoSuchFieldException {
	//Construct a ResolvableType object from field
	ResolvableType t = ResolvableType.forField(getClass().getDeclaredField("myMap"));
	//Get the parent class and resolve to class->java. Util. AbstractMap
	System.out.println(t.getSuperType().resolve());
	//Get the first type argument and parse it into class->java. Lang.Integer
	System.out.println(t.getGeneric(0).resolve());
	//Ditto
	System.out.println(t.resolveGeneric(0));
	//Get the second type argument and parse it into class->java. Util. List, note: resolve returns raw type if type argument is still a generic type
	System.out.println(t.getGeneric(1).resolve());
	//Ditto
	System.out.println(t.resolveGeneric(1));
	//Get the first type argument of the list and resolve to class->java. Lang.String
	System.out.println(t.getGeneric(1).getGeneric(0).resolve());
	//Ditto
	System.out.println(t.resolveGeneric(1, 0));
}

//================================================================
public Map<String, List<Integer>> test(){return null;}

void example2() throws NoSuchMethodException {
	//Construct a ResolvableType object with method return type
	ResolvableType methodReturnType=ResolvableType.forMethodReturnType(getClass().getMethod("test"));
	//->java.lang.String
	System.out.println(methodReturnType.resolveGeneric(0));
	//->java.util.List
	System.out.println(methodReturnType.resolveGeneric(1));
	//->java.lang.Integer
	System.out.println(methodReturnType.resolveGeneric(1, 0));
}

//================================================================
//This is an example from the previous one, which is implemented using ResolvableType, and you can compare it with the previous implementation using jdk's own api
public abstract class A<T>{

    public void handle(String json){
        T t=fromJson(json);
        doHandle(t);
    }

    private T fromJson(String json) {
        //Construct a ResolvableType object through class and return it as the parent ResolvableType
        ResolvableType classResolvableType=ResolvableType.forClass(this.getClass()).as(A.class);
        //->User
        Class<T> clazzT=classResolvableType.resolveGeneric(0);
        return new Gson().fromJson(json,clazzT);
    }

    protected abstract void doHandle(T t);
}

public class B extends A<User>{
    @Override
    protected void doHandle(User user) {
        //do something
    }
}

Almost as you can imagine generic scenarios, ResolvableType can help you solve them, but more importantly, the solutions are all elegant, I hope you can master them

Keywords: Spring

Added by intenseone345 on Tue, 28 Dec 2021 12:47:40 +0200