Reflection mechanism in Catalina daemon

Introduction: Recently, I learned Tomcat and felt it necessary to review the Java reflection mechanism and deepen my understanding.

1, Review of reflexive knowledge

  this part refers to the explanation of java reflection in Li Xinghua's java se practice classic.

  reflection contains a concept of "anti", so to explain reflection, we must start from "positive". Generally speaking, when users use a class, they should first know the class, and then generate instantiated objects through the class, but "anti" refers to finding the class through the object.

package cn.mldn.demo;
class Person {}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Person per = new Person() ; // Positive operation
        System.out.println(per.getClass().getName());   // Contrary to
    }
}

  the above code uses a getClass() method, and then you can get the "package. Class" name of the object, which belongs to "anti". But in this "anti" operation, there is a getClass() as the start of all reflection operations.

  the parent class of Person is the Object class, and the getClass() method used above is the method defined in the Object class.

  get Class Object: public final Class <? > getclass(). All generics in the reflection are defined as?, and the return value is Object.

  the object returned by the getClass() method is an object of Class, so this Class is the source of all reflection operations. But there is another problem that needs to be explained before explaining its real use. Since Class is the source of all reflection operations, this Class must be the most important. If you want to obtain the instantiated object of this Class, there are at least four ways defined in Java:

Method 1: get it through getClass() method of Object class, basically not needed: because the new keyword has been used, it is not necessary to use reflection.

package cn.mldn.demo;
class Person {}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Person per = new Person() ; // Positive operation
        Class<?> cls = per.getClass() ; // Get Class object
        System.out.println(cls.getName());  // Contrary to
    }
}

Method 2: use class. Class to get it, and use it when learning Hibernate development in the future

package cn.mldn.demo;
class Person {}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Person.class ;   // Get Class object
        System.out.println(cls.getName());  // Contrary to
    }
}

Method 3: use a static method defined inside the Class, and use the most

  get Class object: public static Class <? > forname (string classname) throws classnotfoundexception;

package cn.mldn.demo;
class Person {}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person") ;   // Get Class object
        System.out.println(cls.getName());  // Contrary to
    }
}

Method 4: use class loader to load related classes to get class <? > objects:

package cn.mldn.demo;
class Person {}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        Class<?> cls = cl.loadClass("cn.mldn.demo.Person") ;   // Get Class object
        System.out.println(cls.getName());  // Contrary to
    }
}

  now that a new question has come up, what's the use of getting Class objects? Before the instantiation of an object, it always depends on the construction method and the keyword new. However, with the Class class object, there is now another object instantiation method:

  instantiate objects through reflection: public T newInstance() throws InstantiationException, IllegalAccessException;

**Example: * * instantiate an object by reflection

package cn.mldn.demo;
class Person {
    @Override
    public String toString() {
        return "Person Class Instance .";
    }
}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person") ;   // Get Class object
        Object obj = cls.newInstance() ;    // Instantiate the object as you would with the keyword new
        Person per = (Person) obj ; // Downward transformation
        System.out.println(per);
    }
}

  now we can find that for the instantiation of objects, in addition to the keyword new, there is another reflection mechanism operation, and this operation is more complex than the previous new, but what's the use?

  before the development mode of the program, it has been emphasized that the best way to reduce coupling is to use the interface, but even if the interface is used, the keyword new cannot escape, so in fact, new is the key culprit of coupling.

**Example: * * review the factory design pattern written before

package cn.mldn.demo;
interface Fruit {
    public void eat() ;
}
class Apple implements Fruit {
    public void eat() {
        System.out.println("Eat apples.");
    };
}
class Factory {
    public static Fruit getInstance(String className) {
        if ("apple".equals(className)){
            return new Apple() ;
        }
        return null ;
    }
}
public class FactoryDemo {
    public static void main(String[] args) {
        Fruit f = Factory.getInstance("apple") ;
        f.eat() ;
    }
}

  the above is the simplest factory design pattern written before, but there is one of the biggest problems in this factory design pattern: if the subclass of the interface is increased now, the factory class must be modified, which is the biggest problem it faces, and the key cause of this problem is new. If the keyword new is not used now, it will become What about the reflection mechanism?

  when the reflection mechanism instantiates an object, it actually only needs "package. Class", so modify the factory design pattern according to this operation.

package cn.mldn.demo;
interface Fruit {
    public void eat() ;
}
class Apple implements Fruit {
    public void eat() {
        System.out.println("Eat apples.");
    };
}
class Orange implements Fruit {
    public void eat() {
        System.out.println("Eat oranges.");
    };
}
class Factory {
    public static Fruit getInstance(String className) {
        Fruit f = null ;
        try {
            f = (Fruit) Class.forName(className).newInstance() ;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return f ;
    }
}
public class FactoryDemo {
        public static void main(String[] args) {
        Fruit f = Factory.getInstance("cn.mldn.demo.Orange") ;
        f.eat() ;
    }
}

  it is found that even if the subclass of the interface is added at this time, the factory class can still complete the instantiation operation of the object. This is the real factory class, which can respond to all changes. In terms of development alone, it has little to do with developers, but this is the lifeblood of its implementation for some framework technologies learned in the future. In terms of future program development, if a complete "package. Class" name needs to be passed in the process of operation, it is almost a reflection mechanism.

  the reflection mechanism provided by the above engineering mode is not complete either, because we can refer to all objects as Object type and call the Object's methods through the reflected methods. Let's take a look at the reflection mechanism implementation principle used in Catalina daemon, the Servlet container in Tomcat.

2, Reflections used in CatalinaDaemon instance object construction

    ClassLoader catalinaLoader = null;//Get an instance object reference variable first
    catalinaLoader = createClassLoader("server", commonLoader);//Get an example of Catalina class loader
	/**
	* The following two methods are used to set the class loader to be used by default in specific situations
	*/
    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);

	
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");//Load a Class through the Class loader and return the Class object
    
    Object startupInstance = startupClass.getConstructor().newInstance();//Get the constructor through the Class object, and then call the newInstance() method, which is equivalent to calling the parameterless constructor to return an instance object
    
    /**
    * The following methods are used to set the parent loader
    */
	String methodName = "setParentClassLoader"; 
	Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
	
	Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
     
	method.invoke(startupInstance, paramValues);
    /**
    * The following method is used to get a Catalina instance Object and use the Object class variable to reference it
    */	
    catalinaDaemon = startupInstance;


  why reflection is used for method calls:

  here we use the method of proof to explain the reason. Suppose we use the general method: instance.method0() to call the method through the instance object (assuming we don't know that we can use reflection to solve it).

  as mentioned above, we get a Catalina instance Object and use the Object Class variable to reference it. Then, if you don't make a downward transformation, you can only call the method of the parent Object in the compiler, which obviously can't meet the requirements. To avoid this, we can use casts, or downturns. However, this leads to another problem. Before we introduced the reflection mechanism to get the Class Object and the instance Object referenced by the Object step by step. The purpose is not to use the specified type directly in the code, which is conducive to code decoupling and easy to modify later. Using cast destroys all of the above efforts.

  so in fact, all method calls of catalinaDaemon instance objects are implemented through reflection. For example, some codes mentioned in the previous code block:

	Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
     
	method.invoke(startupInstance, paramValues);

Attach personal blog address: https://jiangweijia.xyz

104 original articles published, 18 praised, 10000 visitors+
Private letter follow

Tags: Java Tomcat Hibernate Apache

Posted on Fri, 10 Jan 2020 09:20:43 -0500 by flumpy