This blog post is excerpted from bravo1988 on the question “how should Java learn to understand reflection?” The next answer! Orz

Just to ask what reflection is used for, there are actually two most commonly used:

  • Create instances based on class names (class names can be read from configuration files, not new, for decoupling);
  • Execute a Method with method.invoke.

But it’s not that hard to understand, it’s the reflection itself. If you’re interested, read on:

Because reflection itself is really abstract (the most abstract concept in Java is an understatement), I used a lot of metaphors when I wrote it. But metaphors can sometimes take the answer further. I was reading some articles about design patterns a while ago, and I used up all the metaphors. Sometimes it’s harder to understand metaphors than design patterns themselves… That’s the opposite. So, this time, can not use metaphors as far as possible, try to use the most realistic code to explain.

Main Contents:

  • How does the JVM build an instance
  • .class files
  • Class loader
  • Class Class
  • The reflection API

How does the JVM build an instance

The nouns I will use below and their corresponding relationships

  • Memory: that is, the JVM memory, the stack, heap, method area, etc are JVM memory, but only artificially partition;
  • .class files: these are called bytecode files, which are called.class files.

Suppose the main method has the following code:

Person p = new Person();
Copy the code

Many beginners would assume that the entire object creation process would look something like this:

javac Person.java
java Person
Copy the code

I can’t say it wrong, but it’s a little rough.

A slightly more detailed process might look like this:

Creating an instance with new and creating an instance with reflection does not circumvent the Class object.

In-depth study: JVM basics (iii) an object creation process

Summary JVM: an object creation process:

  1. Compile: compile. Java files into. Class bytecode files;
  2. Loading: The class information (.class file) is loaded into the JVM memory by the ClassLoader, and class objects are produced.
  3. Join: Default assignment (0 null) to static variables of the class;
  4. Initialization: Performs the actual assignment to a static variable of a class, executing a static code block;
  5. Object creation: When a new object is created, the JVM checks to see if the.class file has been loaded into memory and then performs steps 2, 3, 4, 5 as shown above.

.class files

Has anyone opened a.class file with an editor?

Let’s say I write a class:

Using the vim command to open the.class file, it looks like this in hexadecimal:

In a computer, the underlying storage of everything is 0101 code.

The.java source code is read by humans, while the.class bytecode is read by computers. Different meanings can be generated according to different rules of interpretation. It’s like, “Are you free this Sunday?” It’s important to have the right ending.

Similarly, the JVM has its own set of reading rules for.class files that we don’t have to worry about. In short, the 0101 code in its eyes is the same as the English source code in our eyes.


Class loader

At the beginning of our review of the object creation process, we learned that.class files are loaded by the class loader. About class loaders, if broken down, there are a lot of ways, you can see @ please call me program ape adult wrote terrible class loaders. But the core method is only loadClass(). Tell it the name of the class to load and it will load it for you:

protected Class<? > loadClass(String name, Boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First check if the Class<? > c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); Try {// If not loaded, then follow the parent first hierarchical loading mechanism (called parent delegate mechanism) if (parent! = null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent Class loader} if (c == null) {findClass() long t1 = system.nanotime (); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } // Subclasses should override the method protected Class<? > findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }Copy the code

Loading a. Class file can be roughly divided into three steps:

  • Check whether it has been loaded. If it has been loaded, return directly to avoid repeated loading
  • If the class does not exist in the cache, load the.class file using the parent first loading mechanism
  • When both steps fail, call the findClass() method to load

Note that the ClassLoader class itself is abstract, and abstract classes cannot create objects through New. So its findClass() method is arbitrary, throwing an exception that you can’t call from a ClassLoader object anyway. That is, the findClass() method in the parent ClassLoader does not load the. Class file at all.

Instead, subclass overrides findClass() and writes custom load logic inside. Such as:

@Override public Class<? > findClass(String name) throws ClassNotFoundException {try {/* getClassData() reads the XXX. Class file from the specified location on the IO stream to get the byte array */ byte[] datas = getClassData(name); If (datas == null) {throw new ClassNotFoundException(" class not found: "+ name); Return defineClass(name, datas, 0, datas.length); return defineClass(name, datas, 0, datas.length); } catch (IOException e) { e.printStackTrace(); Throw new ClassNotFoundException(" class not found: "+ name); }}Copy the code

DefineClass () is a method defined by ClassLoader to create a corresponding class object from the.class file’s byte array byte[] b. We don’t know exactly how, because eventually it calls a native method:

Anyway, for now all we need to know about class loading is:

Class Class

The.class file has now been loaded into memory by the class loader, and the JVM has created the corresponding class object from its byte array.

Next, let’s take a look at Class objects, where we will step through the structure of the Class Class.

But before I look at the source code, I’d like to ask the smart ones: If you were the JDK source code designer, how would you design the Class Class?

Suppose you now have a BaseDto class

The above class contains at least the following information (in order) :

  • Permission modifier
  • The name of the class
  • Parameterized types (generic information)
  • interface
  • annotations
  • Fields (emphasis)
  • Constructor (emphasis)
  • Methods (key points)

Eventually this information will be represented as 0101 in the.class file:

The entire.class file ends up as an array of bytes [] B, whose constructors, methods, and other “components” are actually bytes.

So, I guess the Class field should at least look like this:

Ok, let’s see if the source code is as I expected:

(Field, method, constructor object)

(Annotated data)

(Generic information) and so on.

Furthermore, the JDK writes three separate classes for fields, methods, and constructors because they are too informative: Field, Method, and Constructor. Method = Method ();

In other words, the Class Class contains a lot of fields to represent the information in a.class file, such as the Class name, annotation, implementation interface, etc., but for fields, methods, constructors, etc., three classes are written to describe these important information in more detail. Each Class has a detailed mapping. The Class Class itself maintains references to all three objects (as an array of objects! Because a Class may have more than one Method, classes are saved with Method[] methods).

All information in the original UserController Class is “deconstructed” and stored in the Class. Among them, fields, methods and constructors are represented separately by objects such as Field and Method.

With the fields of the Class roughly in mind, let’s look at the methods of the Class Class.

  • The constructor

As you can see, the constructor of the Class Class is private, and you cannot manually new a Class object, only the JVM can create it. When the JVM constructs a Class object, it needs to pass in a Class loader and then have the load and create sequence we analyzed above.

  • Class.forname () method

Anyway, it’s the classloader.

  • newInstance()

That is, the underlying newInstance() is a call to newInstance() on an object constructed without arguments.

So, essentially, Class objects create instances through constructor objects. If you do not construct an object with empty arguments, you cannot use clazz.newinstance (). You must get other constructs with arguments and then call newInstance() of the constructs.


The reflection API

There are two main goals for reflection in daily development:

  • Create an instance
  • Reflection call method

The difficulty in creating an instance is that many people don’t know the underlying clazz.newinstance () or the newInstance() that calls the Contructor object. So, to call clazz.newinstance (), you must ensure that the class is written with a no-argument construct.

There are two difficulties with reflection calling methods that beginners may not understand.

Class (Field); Method (Constructor);

The Field, Method, and Constructor objects contain more detailed descriptions of fields, methods, and constructors:

OK, with that sorted out, let’s move on to the two difficulties with reflection calling methods.

  • Difficulty 1: Why do we need to pass in the Method name + the Class type of the parameter when obtaining Method from the Class object

Why pass name and ParameterType?

Because there are multiple methods in the.class file, such as:

Therefore, we must pass in the name of the Method to distinguish the Method and get the corresponding Method.

ParameterTypes is of Class type. When you call a method, you pass the name of the variable.

The answer is: we cannot distinguish methods by variable names

User getUser(String userName, int age);
User getUser(String mingzi, int nianling);
Copy the code

It’s not called overloading, it’s the same method. Only by parameter type.

I know, you’re also going to say, “Well, if the variable name doesn’t work, can I pass String, int?”

Sorry, these are primitive and reference types, and types cannot be passed. We can pass either values or objects (references). Class, int. Class are objects and are class objects.

In fact, when the Class object’s getMethod() Method is called, it internally loops through all methods and returns a unique Method based on the Method name and parameter type.

(Loop over all methods, matching by name and parameterType)

  • Method. Invoke (obj, args); Why pass in a target object when?

As analyzed above, after the.class file is loaded into memory via IO, the JDK creates at least four objects: class, Field, Method, and Constructor, which are all abstract representations of 0101010.

Take the Method object. What is it and how did it come about? As we have analyzed above, the Method object has many fields, such as name (Method name), returnType (return value type), and so on. That is, methods that we write in a.java file are “deconstructed” and stored in a Method object. So the object itself is a mapping of methods, one Method for each Method object.

As I mentioned in another article in this column, objects are designed to store data by nature. Method, as a behavior description, is common to all objects and does not belong to a particular object. Say you have two Instances of Person

Person p1 = new Person();
Person p2 = new Person();
Copy the code

Object P1 holds “HST” and 18, and object P2 holds “cxy” and 20. But whether p1 or P2, there’s always changeUser(), but it’s too wasteful to write a copy of each object. Since they are common behaviors, they can be extracted and shared in the method area.

But this raises a tricky question. Methods are shared. How does the JVM ensure that when P1 calls changeUser(), changeUser() doesn’t run to change P2’s data?

So the JVM has set up an implicit mechanism whereby every time an object calls a method, it implicitly passes an object parameter that is currently calling the method. Based on this object parameter, the method knows which object is currently calling the method!

Similarly, when reflection calls a method, it essentially wants the method to process data, so it must be told which object’s data to execute.

So, think of Method as a Method execution instruction, more like a Method executor that must be told what object (data) to execute.

Of course, if invoke is a static method, there is no need to pass in a concrete object. Static methods do not handle the data stored in the object.