primers

As you know, Java has three major features: encapsulation of inheritance for finer permissions, inheritance for polymorphism, and polymorphism for flexible reuse. We know that inheritance breaks encapsulation, but it doesn’t break it completely. Is there a more thorough way to break it? Yes, reflection! Reflection is inefficient because it works at run time. Why does it work at run time? Let’s start with an example:

Public class User {// create a private String name; public User(String name) { this.name = name; }} public void test(){// create a User object with name: Java User User = new User(" Java "); Class aclass = user.class; class aclass = user.class; // Get the declared Field :name Field nameField = aclass.getdeclAredField ("name"); // Allow access to a non-public field namefield.setaccessible (true); // Get the value of this field on the user Object Object nameValue = namefield.get (user); / / print System. Out. Println (nameValue); }Copy the code

Print result:

java
Process finished with exit code 0
Copy the code

This example demonstrates the use of reflection with only one point to remember: reflection works on.class objects. That is, if reflection is a function and its input is.class objects, where does the.class object come from? Answer: class loading!

Class loading mechanism

We’ve mentioned a little about class loading before in the JVM class loading mechanism, so let’s take a look at it again. JVM class loading process:

  • 1 Gets the binary byte stream that defines this class through the fully qualified class name.
  • Convert the static storage structure represented by the byte stream into the runtime data structure of the method area.
  • 3 generate a java.lang.Class object that represents the Class in memory, and use it as an entry point for various data of the Class.

In short, the JVM class loading mechanism receives a binary stream of:.class files and produces: Java.lang.class objects.

Wait a minute! The Class loading mechanism produces the object for reflection to work on, which is the Class object. After Class loading mechanism, the reflection can only work with the input (the Class object)Class loading works at run time, reflection works after class loading, so: reflection works at run time!That answers the question. So why does class loading work at runtime when Java isn’t a strongly typed static language?Yes, Java is a static language, but it has dynamic features introduced to support technologies such as polymorphism and JSP. For example:

A a = new B();
Copy the code

Where B is A subclass of A, this is polymorphism, only at run time, you know what type it is, this is the dynamic nature of Java, so the JVM class loading is at run time, although this reduces the efficiency to some extent, but the effect is huge, this is why reflection works at run time, Because it requires Class objects that are available only at run time.

So if the JVM classloading mechanism receives.class files and produces Java.lang. class objects, where does it receive.class files from? Someone already thought of it, javac! That is, compile!

The build process

As we know, writing a hello. Java file and then running the command javac hello. Java will generate a Hello.class file, which is a binary stream that the JVM needs for class loading! Javac represents the compilation process, run the command, you will find you to set the environment variable inside the relevant procedure, to compile, you can also directly in: com. Sun. View javac. Main. JavaCompiler find relevant code, take a look at this. How class files are generated, Roughly three steps:

  • 1 parse and fill the symbol table, this stage performs lexical analysis, syntax analysis, generate abstract syntax tree, etc.
  • Annotation processing, this phase focuses on annotations, is apt work phase, such as Arouter.
  • 3 analysis and bytecode generation, this stage to perform constant folding, decoding sugar and other operations.

As you can see, if a new file is generated during annotation processing, it jumps to the first step and continues “parsing and populating the symbol table” until no more Java files are generated. This is how Arouter works: by annotating processors and code templates, files are dynamically generated at compile time, some of which are discovered when we use arouter
G r o u p Group
App, ARouter
P r o v i d e r s Providers
That’s where you get files like apps.

After the compile phase, we have the.class file, which is what the JVM needs to load! You can view the contents of the.class file in the following steps:

  • Procedure 1 Run the vi hello. class command on the terminal to open the. Class file
  • 2 Run the %! XXD, converted to binary, you can view the relevant information
  • 3 Run the %! XXD -r, restore, and type :q! Exit can

Of course, you can also directly execute: javap -verbose Hello.class view.

The compile phase receives.java files and produces.class files.

Ok, let’s summarize:

  • We write.java files and input relevant code
  • The.class file is generated from the.java file after javac compilation
  • Java.lang.Class object is generated by the.Class file
  • We use these Class objects to perform various operations, such as reflection

Now that we know where the java.lang.Class file is coming from, let’s take a look at its API

Common Apis for reflection

Getting a Class object:

  • If you have an object, use object.getClass(); eg:
Class<? extends User> aClass = user.getClass();
Copy the code
  • If there is a Class, use class.class; eg:
Class aclass = User.class; Class<Void> voidClass = void.class; // Void also has class objectsCopy the code

It is worth noting that void also has a class object. Basic types (int,short) do not have a getClass() method, but do have.class objects whose wrapper classes have a getClass() method.

  • If you know the Class name, use class.forname (name) or classLoader.loadClass (name); eg:
Try {// load Class and initialize Class<? > aClass = Class.forName("com.company.bean.User"); } catch (ClassNotFoundException e) { e.printStackTrace(); } the try {/ / initialized only load but not this getSystemClassLoader (). The loadClass (" com.com pany. Bean. The User "); } catch (ClassNotFoundException e) { e.printStackTrace(); }Copy the code

Remember to handle a ClassNotFoundException that will be reported if no corresponding class can be found. In addition to loading the corresponding Class, class.forname (name) also initializes the Class, that is, executes the () function. In the Java layer, static blocks and static assignment statements are executed, whereas classloader.loadClass (name) only loads the Class. Do not do any other unnecessary action. Once we have the Class object, we can implement the various apis for reflection. There are two variations of the class.forname () method, which you can see in the documentation.

  • 1 Name Information
public static void getName() throws ClassNotFoundException { Class<? > aClass = Class.forName("java.lang.String"); Println (aclass.getPackage ().getName()); Println (aclass.getName ()); Println (aclass.getSimplename ()); Println (aclass.getCanonicalName ()); }Copy the code

Here are the results:

java.lang
java.lang.String
String
java.lang.String
Copy the code

One thing to note here is that getName() returns a real type inside Java, just like getCanonicalName() does, except for arrays.

  • 2 Field Information
public static void getFields() throws Exception { Class<? > aClass = Class.forName("java.util.ArrayList"); Field Field = aclass.getField ("size"); Field[] fields = aclass.getFields (); Aclass.getdeclaredfield ("DEFAULT_CAPACITY"); // getDeclaredField("DEFAULT_CAPACITY"); Aclass.getdeclaredfields (); aclass.getdeclAredFields (); aclass.getdeclAredFields (); Field.getname (); Field.isaccessible (); // Return whether the field can be read and written. Field.setaccessible (true); ArrayList<String> list = new ArrayList<>(); Field. Get (list); // Return the value of this field on the list object. Field. Set (list, 100); Annotations = field.getanannotations (); // Annotations = field.getanannotations (); // Annotations = field.getanannotations (); // Return the field modifier, which is a compound value similar to the Android MeasureSpec int Modifiers = field.getModifiers(); IsPublic (modifiers); // Is a final Modifier; / /... Other apis // return the type of the field, such as String name; Return java.lang.String Class<? > type = field.getType(); / /... Other apis}Copy the code

Here more API, not a list, you can find the official API

  • 3 Method Information
public static void getMethod() throws Exception { Class<? > aClass = Class.forName("java.util.ArrayList"); // getMethod = aclass.getMethod ("size", null); // getMethod("size", null); ArrayList<String> list = new ArrayList<>(); Method. Invoke (list,null); // Invoke (list,null); GetDeclaredMethod ("size",null); // getDeclaredMethod("size",null); // Get all public methods, including aclass.getMethods (); / / get all the way, including the public, but does not include the superclass aClass. GetDeclaredMethods (); }Copy the code

Invoke (Object obj, Object… Args) method, which means to execute the method with the first argument being the method’s calling object and the second argument being the argument the method received.

The trick here is that getXXX() returns all public values of this class and its parent class, which we simply call “parent-child”; GetDeclaredXXX () returns only those of this class, but includes non-public ones, which we call “owned” for short.

  • 4 Constructor information
public static void getConstructor() throws Exception { Class<? > aClass = Class.forName("java.util.ArrayList"); // Create an instance of the Class object with the method aclass.newinstance (); // All constructors, parent and child share aclass.getConstructors (); / / all the constructor, all aClass getDeclaredMethods (); AClass. GetConstructor (int. Class); // Constructor<? > constructor = aClass.getDeclaredConstructor(int.class); // The constructor creates the instance method, specifying an argument of 100 constructor.newinstance (100); }Copy the code
  • 5 Type Information
public static void getClassInfo() throws ClassNotFoundException { Class<? > aClass = Class.forName("java.util.ArrayList"); Aclass.getinterfaces (); // getInterfaces(); // Return all annotations. The father and son share aclass.getanannotations (); // Get the superclass aclass.getSuperClass (); Aclass.isarray (); Aclass.isinterface (); // Whether to enumerate aclass.isenum (); // Whether to enumerate aclass.isannotation (); // Is a member inner class aClass. IsMemberClass (); // Whether it is an anonymous inner class aclass.isanonymousClass (); // Is the local inner class aclass.isLocalClass (); }Copy the code
  • 6 Arrays and enumerations
public static void getArrayAndEnum() { int[] arr = new int[10]; Class<? extends int[]> aClass = arr.getClass(); // Get the element type of the array Class<? > componentType = aClass.getComponentType(); // Create an array object with an element type as the first argument, The second parameter for the Array length / / the Array object: Java. Lang. Reflect. The Array int [] a = (int []) Java lang. Reflect. Array. NewInstance (int. Class, 10); Object o = array.get (a, 5); Array. Set (a, 5, 100); Array.getlength (a); Class<Enum> enumClass = Enum.class; / / get all the enum constants enumClass. GetEnumConstants (); }Copy the code
  • 7 the generic
public static void getGeneral() { Class aClass = ArrayList.class; TypeVariable[] typeParameters = aclass.gettypeParameters (); Field field = null; Type genericType = field-.getGenericType (); Method method = null; / / method of pattern parameter method. The getGenericParameterTypes (); / / method paradigm return value method. GetGenericReturnType (); Method. GetExceptionTypes (); Constructor constructor = null; / / paradigm of constructor parameter constructor. GetGenericParameterTypes (); / / constructor paradigm exception constructor. GetGenericExceptionTypes (); }Copy the code

Now, some people here say, well, I thought Java had generic erasers, and if you erase everything, why do you have generics? Yes, Java generics are erased at compile time, but at the same time, a Signature is written to the.class file’s attribute_info table, which holds the generics’ Signature information. So Java generics are called “pseudo-generics.” To verify this, write a random generic class:

public class Hello<T> {
}
Copy the code

Javac Hello. Java gets the hello. class object, and then javap -verbose Hello.

As you can see, the Constant Pool has its Signature property in line 11, and the Constant Pool has its Signature property in line 12.

public class Hello {
}
Copy the code

As you can see, the Signature property is missing. This means that javac erases generics at compile time and saves the information in the constant pool Signature.

Use of reflection – plug-in

We know that an Activity is started by a Handler called mH in the ActivityThread. Let’s replace this Handler with a Hook:

  • 1. Define a Callback for this Handler:
private static class EvilCallback implements Handler.Callback { Handler mBase; public EvilCallback(Handler mBase) { this.mBase = mBase; } @override public Boolean handleMessage(@nonnull Message MSG) {Log. E ("HOOK", "block Message:" + MSG); mBase.handleMessage(msg); return true; }}Copy the code

This is a wrapper class that receives a Handler and prints a message in The handleMessage main.

  • 2 Then let’s replace the mCallback in ActivityThread’s mH with the one we created in the first step:
/** * Hook ActivityThread mCallback */ protected void hookHCallback() { // Obtain the sCurrentActivityThread object in the ActivityThread class, That is currently used ActivityThread Object Object sCurrentActivityThread = LReflect. GetField (" android. App. ActivityThread ", null, "sCurrentActivityThread"); / / get ActivityThread mH member variables in a class Handler mH = (Handler) LReflect. GetField (" android. App. ActivityThread ", sCurrentActivityThread, "mH"); // Replace mCallback (mH/sCurrentActivityThread) with lrefl.setfield (Handler. Class, mH, "mCallback") new EvilCallback(mH)); Public static Object getField(String className, Object obj, String className, Object obj) String fieldName) { try { Class claz = Class.forName(className); Field field = claz.getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); } catch (Exception e) { e.printStackTrace(); } return null; } public static void setField(Class claz, Object obj, String fieldName, Object filedValue) { try { Field field = claz.getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, filedValue); } catch (Exception e) { e.printStackTrace(); }}}Copy the code
  • OnCreate ();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    hookHCallback();
}
Copy the code

Then run the Activity to see the results:

E/BaseActivity: Intercepts messages: { id=46 when=-1ms what=159 obj=android.app.servertransaction.ClientTransaction@4962 target=android.app.ActivityThread$H } W/ActivityThread: handleWindowVisibility: no activity for token android.os.BinderProxy@7daf006 E/BaseActivity: { id=47 when=0 what=159 obj=android.app.servertransaction.ClientTransaction@fb13e219 target=android.app.ActivityThread$H } --------- beginning of system I/PhoneWindow: initSystemUIColor E/BaseActivity: { id=48 when=-87ms what=159 obj=android.app.servertransaction.ClientTransaction@8382 target=android.app.ActivityThread$H } E/BaseActivity: intercepts messages: {id = 78 when = 0 I = 149 obj = android OS. BinderProxy @ 7 daf006 target =. Android app. ActivityThread $H} E/BaseActivity: intercept the message: { id=82 when=-5ms what=159 obj=android.app.servertransaction.ClientTransaction@8363 target=android.app.ActivityThread$H } E/BaseActivity: intercepts messages: { id=83 when=-6ms what=159 obj=android.app.servertransaction.ClientTransaction@f043e0 Target = android. App. ActivityThread $H} E/BaseActivity: intercept the message: { id=88 when=-23ms what=159 obj=android.app.servertransaction.ClientTransaction@7fe0 target=android.app.ActivityThread$H } E/BaseActivity: intercepts messages: { id=90 when=-1ms what=119 obj=android.app.ActivityThread$ContextCleanupInfo@9540597 target=android.app.ActivityThread$H }Copy the code

Logging is the result of starting an Activity and then pressing the back key. As you can see, our hook has been successful. You can make various SAO operations here. If you are interested, you can search the source code according to what in the message to see what it does.

Reflection is very powerful and does not depend on specific classes, which can greatly reduce the coupling. However, since it takes effect at runtime, it affects the efficiency. Therefore, we generally use the interface when we can use the interface, and consider using APT technology (compile time) if we cannot use the interface.