Preface
Recently, during the learning process, I discovered a problem. Abstract classes cannot construct the object through new without implementing all abstract methods, but abstract methods can have their own construction methods. This confused me. Since there is a construction method and it cannot be created through new, can abstract classes be instantiated when they have not become concrete classes?
In Java, abstract classes cannot be instantiated directly. However, this feature of abstract classes often becomes a troublesome obstacle. For example, if I want to use a dynamic proxy to give an abstract class the ability to execute abstract methods, there will be two difficulties: 1. The dynamic proxy can only create a proxy object that implements the interface, but not an object that inherits the abstract class. For this standard JVM there are some implementations, such as javassist, which can be used to accomplish this using bytecode tools (ProxyFactory).
If you want to construct an abstract class object in Android, I'm afraid there is only one new ClassName() {}
Or it is constructed after inheritance. However, both methods cannot be directly operated by their Class objects, which leads to some problems that cannot achieve the abstract capabilities we need.
Here is a detailed description of the scene mentioned in the first paragraph:
First, there is an interface file defined as follows (friends familiar with Android can see that this is an API configuration interface provided to Retrofit to generate proxy objects):
public interface RealApi { @GET("api1") Observable<String> api1(); @GET("api2") Observable<String> api2(); @GET("api3") Observable<String> api3(); //...Other methods}
Next, write an abstract class to implement only one of the methods of the interface (used to simulate interface data):
@MockApi public abstract class MockApi implements RealApi { Observable<String> api3() { return ("mock data"); } }
Then we need to have a tool, such as MockManager, that combines our existing RealApi object and MockApi class to construct a mixed object. When executing methods already defined in MockApi, it will directly execute them. When MockApi does not define the method, it will call the RealApi method. The call method is roughly:
RealApi api = (realApi, );
Through javassist, it is very simple to complete the above function. Create a ProxyFactory object, set its Superclass to MockApi, then filter the abstract method, and set the method handler to call the realApi object with the same name and parameter method. The code implementation will not be given here.
But on Android, the method of javassist will be thrown
Caused by: : can't load this type of class file at (:520) at (Native Method) at .toClass2(:182)
Similar exceptions. The reason is probably that the implementation and standards of virtual machines on Android are slightly different, so here we turn the direction to another direction of dynamic code generation, Annotation Processor.
If you use Annotation Processor to implement it, the idea is much simpler, but the process is still a bit tortuous:
First define an annotation to mark the abstract class that needs to be constructed
@Target() @Documented @Retention() public @interface MockApi { }
Processor obtains the element object of the class based on the annotation, which is a class-like object. Because class does not exist in the precompilation stage, it is used at this time
It is not possible to obtain the Class object required at runtime, but Element provides methods similar to Class reflection-related, and there are also differences between TypeElement and ExecutableElement. What are the abstract methods of the abstract class annotated using the Element object to analyze the annotated one, generate an implementation class (non-abstract) that inherits the class, and implement all abstract methods in the class. Because these abstract methods will not be actually used, they only need to be able to compile and pass. The way I chose is that each method body throws an exception, prompting that the method is an abstract method and cannot be called directly. The method of generating code can use some tools to simplify work, such as AutoProcessor and JavaPoet, which specifically implements the project code at the end of the reference text. The generated code is roughly like this:
// The generated class name is named with the suffix of the original class name + "$Impl" to avoid conflicts with other class names. This constraint is also used to reflect the class later.public final class MockApi$Impl extends MockApi { @Override public Observable<String> api1() { throw new IllegalStateException("api1() is an abstract method!"); } @Override public Observable<String> api2() { throw new IllegalStateException("api2() is an abstract method!"); } }
Reflect the implementation class based on the class name of the abstract class, and then call its constructor method based on the reflection to construct an implementation object.
// Obtain the object that generates the code constructprivate static <T> T getImplObject(Class<T> cls) { try { return (T) (() + "$Impl").newInstance(); } catch (Exception e) { return null; } }
Construct a dynamic proxy, pass in the real object of RealApi, and the implementation object of the abstract class constructed in the previous step, and determine which object is proxying its method behavior based on the definition in the abstract class: if there is a definition in the abstract class, that is, the method is not an abstract method, the implementation object of the abstract class will be executed; otherwise, it will be executed by the real object of the interface.
public static <Origin, Mock extends Origin> Origin build(final Origin origin, final Class<Mock> mockClass) { // If Mock Class is marked as closed, it will directly return to the real interface object if (!isEnable(mockClass)) { return origin; } final Mock mockObject = getImplObject(mockClass); Class<?> originClass = ().getInterfaces()[0]; return (Origin) ((), new Class[]{originClass}, new InvocationHandler() { @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { // Get the method of the same name in the defined abstract class to determine whether it has been implemented Method mockMethod = null; try { mockMethod = ((), ()); } catch (NoSuchMethodException ignored) { } if (mockMethod == null || (())) { return (origin, objects); } else { return (mockObject, objects); } } }); }
After completing the above work, you can use the build method to construct a proxy object that mixes real interfaces and abstract class methods as mentioned at the beginning. Although the class being called is hard-coded in nature, it is automatically generated by Annotation Processor without manual maintenance. In terms of use, it is basically the same as using Javassist implementation.
I used the method I belong to in this article to implement a tool that simulates retrofit requests (there is a link at the end of the article), but in essence, it can be used to implement many needs that require the construction of abstract classes, and more usage scenarios still need to be explored.
The source code implementation mentioned in the article can be used in the projectretrofit-mock-resultorLocal downloadFound in;
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.