The thought and function of reflection
Every inverse has a positive, just like the Yin and Yang of the world, the 0 and 1 of the computer. The way of heaven has reincarnation, heaven… (You’ll be here blind bibi)
Before we learn about reflection, let’s learn what ortho is. One of the most common new ways that we use to instantiate objects is a form of orthophoto. If I needed to instantiate a HashMap, the code would look like this.
Map<Integer, Integer> map = new HashMap<>();
map.put(1.1);
Copy the code
One day, it turned out that this program was not suitable for storing key-value pairs in HashMap, preferring to store them in LinkedHashMap. After rewriting the code, it looks like this.
Map<Integer, Integer> map = new LinkedHashMap<>();
map.put(1.1);
Copy the code
If, one day, you find that the data is still suitable to store in HashMap, do you want to change the source code again?
Do you see the problem? Each time we changed a requirement, we had to redo the source code, compile it, package it, and restart the project on the JVM. All these steps down, very inefficient.
For scenarios where the requirements change frequently but not much, changing the source code frequently is definitely a no-no, and we can use a switch to determine which data structure to use when.
public Map<Integer, Integer> getMap(String param) {
Map<Integer, Integer> map = null;
if (param.equals("HashMap")) {
map = new HashMap<>();
} else if (param.equals("LinkedHashMap")) {
map = new LinkedHashMap<>();
} else if (param.equals("WeakHashMap")) {
map = new WeakHashMap<>();
}
return map;
}
Copy the code
Passing in the param parameter to determine which data structure to use can be used dynamically at project run time.
If you ever want to use TreeMap again, you still have to change the source code and recompile and execute it. That’s where reflection comes in.
We don’t know which data structure will be used until the code is running, and we only decide which data class to use when the program is running, whereas reflection can dynamically retrieve class information and call class methods while the program is running. By constructing class instances through reflection, the code evolves as follows.
public Map<Integer, Integer> getMap(String className) {
Class clazz = Class.forName(className);
Consructor con = clazz.getConstructor();
return (Map<Integer, Integer>) con.newInstance();
}
Copy the code
Whatever Map is used, as long as the Map interface is implemented, the full class name path can be passed into the method to obtain the corresponding Map instance. For example java.util.hashMap/java.util.LinkedhashMap ··· If you want to create other classes such as WeakHashMap, I do not need to modify the above source code.
Let’s review how to invoke reflection from a new object.
- When not using reflection, the object is constructed using the new method, which determines the type of the object at compile time.
- If the requirements change and another object needs to be constructed, the source code needs to be modified, which is very inelegant, so we use the
switch
To determine which object to construct at run timeChange the switchTo instantiate different data structures. - If there are other extended classes that might be used, they will be createdLots of branches, and do not know what other classes are used at coding time, what if later
Map
If there is one more collection class in the interfacexxxHashMap
You also have to create a branch, which leads to reflection: can be inThe runtime
To determine which data class to use, when switching classes, no need to modify the source code, compiler.
Chapter 1 Summary:
- The idea of reflection: Determine and parse the types of data classes as the program runs.
- The reflection of therole: in the
Compile time
For scenarios where you cannot determine which data class to use, passreflection
You can do it while the program is runningConstruct different data class instances.
The basic use of reflection
There are four main components of Java reflection:
Class
: Any Class running in memory is an instance object of that Class, and each Class object contains the originalAll the information. Remember a sentence, through the reflection to do anything, look for Class is right!Field
: describing a classattributeContains all information about the property, for exampleData type, property name, access modifier· · · · · ·Constructor
: describing a classA constructor, which contains all the information about the constructor, for exampleParameter type, parameter name, access modifier· · · · · ·Method
: describing a classAll the methods(including abstract methods), contains all the information about the method internally, andConstructor
Similar, except that Method hasReturn value typeBecause the constructor does not return a value.
I have summarized a brain map and put it below. If you use reflection, you can’t do without the four core classes. Only by understanding what information they provide and what function they serve can you use them easily.
As we study the basic use of reflection, I’ll use a SmallPineapple class as a template. First, let’s familiarize ourselves with the basic components of the class: properties, constructors, and methods
public class SmallPineapple {
public String name;
public int age;
private double weight; // Only you know your weight
public SmallPineapple(a) {}
public SmallPineapple(String name, int age) {
this.name = name;
this.age = age;
}
public void getInfo(a) {
System.out.print("["+ name + "Age is:" + age + "]"); }}Copy the code
There are many, many ways to use reflection. Common features include:
- Gets the Class object of a Class at run time
- Constructs an instantiated object of a class at run time
- Get all the information about a class at run time: variables, methods, constructors, annotations
Gets the Class object of the Class
In Java, each Class has its own Class object. When we write a.java file and compile it with Javac, we produce a bytecode file.class. The bytecode file contains all the information about the Class, such as properties, constructors, When the bytecode file is loaded into the virtual machine for execution, the Class object is generated in memory, which contains all the information inside the Class and can be accessed while the program is running.
There are three ways to obtain a Class object:
The name of the class. The class
: This is the only way to obtaincompile
A Class object can only be obtained if the Class type has already been declared
Class clazz = SmallPineapple.class;
Copy the code
Instance. GetClass ()
: Obtain the Class object of the instance by instantiating the object
SmallPineapple sp = new SmallPineapple();
Class clazz = sp.getClass();
Copy the code
Class.forName(className)
: passes the classFully qualified nameGets the Class object for this Class
Class clazz = Class.forName("com.bean.smallpineapple");
Copy the code
You take a Class object and you can do whatever you want with it: skin it (get its Class information), tell it what to do (call its methods), see through it (get its properties), and it has no privacy.
But in a program, you only have one Class object per Class, which means you only have this one slave. We tested it in the above three ways, and each Class object is printed the same in all three ways.
Class clazz1 = Class.forName("com.bean.SmallPineapple");
Class clazz2 = SmallPineapple.class;
SmallPineapple instance = new SmallPineapple();
Class clazz3 = instance.getClass();
System.out.println("Class.forName() == SmallPineapple.class:" + (clazz1 == clazz2));
System.out.println("Class.forName() == instance.getClass():" + (clazz1 == clazz3));
System.out.println("instance.getClass() == SmallPineapple.class:" + (clazz2 == clazz3));
Copy the code
The reason there is only one Class object in memory involves the parent delegation model of the JVM’s class-loading mechanism, which ensures that only one Class object is generated in memory for each Class when the program is loaded. I won’t go into detail here, but it can simply be understood that the JVM helps us ensure that there is at most one Class object per Class in memory.
Constructs the instantiated object of the class
There are two ways to construct an instance of a class using reflection:
- Class object call
newInstance()
methods
Class clazz = Class.forName("com.bean.SmallPineapple");
SmallPineapple smallPineapple = (SmallPineapple) clazz.newInstance();
smallPineapple.getInfo();
// [null age is: 0]
Copy the code
Even though SmallPineapple has explicitly defined the constructor, in an instance created through newInstance(), all property values are initial values of the corresponding type, because the newInstance() construct instance calls the default no-argument constructor.
- Constructor
newInstance()
methods
Class clazz = Class.forName("com.bean.SmallPineapple");
Constructor constructor = clazz.getConstructor(String.class, int.class);
constructor.setAccessible(true);
SmallPineapple smallPineapple2 = (SmallPineapple) constructor.newInstance("Little pineapple".21);
smallPineapple2.getInfo();
// [The age of the young pineapple is 21]
Copy the code
Through getConstructor (Object… The paramTypes) method specifies the Constructor that gets the specified parameter type. This Constructor calls newInstance(Object… If paramValues is passed to the constructor parameter, an instance can also be constructed, and the internal attributes have been assigned.
Calling newInstance() from a Class object takes the default parameterless constructor. If you want to construct an instance using an explicit constructor, you need to call getConstructor() from the Class to get the corresponding constructor in advance and instantiate the object using the constructor.
These apis are the most commonly encountered in development, of course, there are a lot of overloaded methods, due to the length of this article, and if we explain each method one by one, we will not remember, so when using it, it is enough to look in the class.
Gets all the information about a class
A Class object contains all the information about the Class. At compile time, the information we see is the variables, methods, and constructors of the Class, and most often at run time.
Get a variable in a class (Field)
- Field[] getFields() : Gets all the objects in the class
public
All variables that are modified - Field getField (String name) : according to the variable name for a variable in the class, the variable must be public
- Field[] getDeclaredFields() : Gets all variables in the class, but not inherited variables
- Field getDeclaredField(String name) : gets a variable in the class by name
Get the Method in the class
-
Method[] getMethods() : Gets all methods in a class that are decorated by public
-
Method getMethod(String name, Class…
paramTypes) : obtain the method by name and parameter type. The method must be modified by public -
Method[] getDeclaredMethods() : Gets all methods, but not inherited methods
-
Method getDeclaredMethod(String name, Class…
paramTypes) : obtain methods based on the name and parameter type. Failed to obtain inherited methods
Get the Constructor of the class
- Constuctor[] getConstructors() : Gets all the objects in the class
public
Modifier constructor - Constructor getConstructor(Class… <? > paramTypes) : according to
The parameter types
Gets a constructor in a class that must bepublic
modified - Constructor[] getDeclaredConstructors() : gets all constructors in the class
- Constructor getDeclaredConstructor(class… <? > paramTypes) : according to
The parameter types
Gets the corresponding constructor
Each function is divided into 2 classes using Declared:
Methods with Declared modifiers: you can get all variables, methods, and constructors contained within the class, but not inherited information
Methods without Declared modifiers: retrieve variables, methods, and constructors with public modifiers in the class, and obtain inherited information
If you want to get all the variables, methods, and constructors in your class (including inherited), you need to call getXXXs() and getDeclaredXXXs(), and store the variables, constructors, and methods with a Set, in case they get the same thing.
For example, to get SmallPineapple to get all the variables in the class, the code would look like this.
Class clazz = Class.forName("com.bean.SmallPineapple");
// Get public properties, including inheritance
Field[] fields1 = clazz.getFields();
// Get all properties, excluding inheritance
Field[] fields2 = clazz.getDeclaredFields();
// Aggregate all properties into a set
Set<Field> allFields = new HashSet<>();
allFields.addAll(Arrays.asList(fields1));
allFields.addAll(Arrays.asList(fields2));
Copy the code
I don’t know if you noticed an interesting thing, but if the properties of the parent class are protected, they are not available using reflection.
The scope of the protected modifier: it can only be accessed by the same package or subclass, and can be inherited to the subclass.
GetFields () can only get the value of the public property of the class;
GetDeclaredFields () can only retrieve all properties of the class, excluding inherited properties; There is no way to get a protected variable in a parent class, but it does exist in a child class.
To obtain annotations
Getting annotations is a separate twist because it is not a piece of information that is specific to Class objects. Every variable, Method, and Constructor can be decorated with annotations. So in reflection, Field, Constructor, and Method Class objects can call the following methods to get annotations annotated on them.
- Annotation[] getanannotations () : Returns all annotations on this object
- Annotation getAnnotation(Class annotaionClass) : pass in
Annotation type
Gets a specific annotation on the object - Annotation[] getdeclaredanannotations () : Returns all annotations to this object that are explicitly annotated
inheritance
Down note - Annotation getDeclaredAnnotation(Class annotationClass) : According to
Annotation type
To get a specific annotation on the objectinheritance
Down note
The @revector annotation can only be retrieved by reflection if it is annotated as RUNTIME. There are three strategies for saving @Revector:
SOURCE
: save only in ** source file (.java)**, that is, the annotation will only remain in the source file,This annotation is ignored by the compiler at compile time, such as the @override annotationCLASS
: stored in aBytecode files (.class)In, the annotations will follow the bytecode file along with compilation, butThe runtimeThe annotation will not be parsedRUNTIME
: Keep untilThe runtime.One of the most commonly used preservation strategiesAll the information for this annotation can be obtained at run time
In this example, the SmallPineapple class inherits from the abstract Pineapple class, with the @override annotation on the getInfo() method and the @transient annotation on the subclass. At run time, all annotations on the subclass Override method are retrieved. Only @Transient information can be obtained.
public abstract class Pineapple {
public abstract void getInfo(a);
}
public class SmallPineapple extends Pineapple {
@Transient
@Override
public void getInfo(a) {
System.out.print("The height and age of the little pineapple are :" + height + "cm ; " + age + "Age"); }}Copy the code
The Bootstrap class gets the annotation information on the getInfo() method in the SmallPineapple class:
public class Bootstrap {
/** * Determine the specific class object based on the full class name path passed in at runtime *@paramPath Specifies the full class name path */
public static void execute(String path) throws Exception {
Class obj = Class.forName(path);
Method method = obj.getMethod("getInfo");
Annotation[] annotations = method.getAnnotations();
for(Annotation annotation : annotations) { System.out.println(annotation.toString()); }}public static void main(String[] args) throws Exception {
execute("com.pineapple.SmallPineapple"); }}// @java.beans.Transient(value=true)
Copy the code
Methods are called by reflection
After obtaining an object of the Method class through reflection, it can be executed by calling the Invoke Method.
invoke(Oject obj, Object... args)
: parameter ` ` 1Specifies the object on which the method is called, parameter
2 ‘is the parameter list value of the method.
If the method called is static, parameter 1 simply needs to be passed null, because static methods are not related to an object, only to a class.
You can instantiate an object through reflection, then get the Method Method object, and call Invoke () to specify SmallPineapple’s getInfo() Method.
Class clazz = Class.forName("com.bean.SmallPineapple");
Constructor constructor = clazz.getConstructor(String.class, int.class);
constructor.setAccessible(true);
SmallPineapple sp = (SmallPineapple) constructor.newInstance("Little pineapple".21);
Method method = clazz.getMethod("getInfo");
if(method ! =null) {
method.invoke(sp, null);
}
// [The age of the young pineapple is 21]
Copy the code
Application scenarios of reflection
The following describes three common application scenarios of reflection:
- Spring instantiates objects: When the program starts, Spring reads the configuration file
applicationContext.xml
And parse out all the tags inside and instantiate them toIOC
In the container. - Reflection + Factory mode: Passes
reflection
Eliminate multiple branches in the factory. If you need to produce a new class, you don’t need to worry about the factory class. The factory class can handle all the new classes.reflection
Can make the program more robust. - JDBC Connection to database: When using JDBC to connect to a database, specify the connection to the database
Drive class
Use reflection to load the driver class
Spring’s IOC container
In Spring, it is common to write a context configuration file, ApplicationContext.xml, that contains the configuration for beans. The XML file is read at startup, all
tags are parsed out, and the object is instantiated into the IOC container.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="smallpineapple" class="com.bean.SmallPineapple">
<constructor-arg type="java.lang.String" value="Little pineapple"/>
<constructor-arg type="int" value="21"/>
</bean>
</beans>
Copy the code
After the above documents is defined through the ClassPathXmlApplicationContext loading the configuration file, the program starts, the Spring will all the bean is instantiated in the configuration file, in the IOC container, the IOC container is essentially a factory, The factory passes in the ID attribute of the
tag to get the corresponding instance.
public class Main {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
SmallPineapple smallPineapple = (SmallPineapple) ac.getBean("smallpineapple");
smallPineapple.getInfo(); // [The age of the young pineapple is 21]}}Copy the code
Spring has simplified the process of instantiating an object to reflect the steps of instantiating an object:
- Gets the constructor for the Class object
- The object is instantiated by calling newInstance() through the constructor
Spring, of course, does a lot of extra work when instantiating objects to make development easy and stable today.
In the following article, I will write a special article to explain how to use reflection to implement a simple version of IOC container. The principle of IOC container is very simple, as long as you master the thought of reflection and understand the common API of reflection, you can realize it. I can provide a simple idea: The use of HashMap to store all instances, with key representing the ID of the
tag and value storing the corresponding instance, corresponds to the fact that Spring IOC container-managed objects are singletonized by default.
Reflection + Abstract factory pattern
In the traditional factory model, if you need to produce new subclasses, you need to modify the factory class and add new branches in the factory class.
public class MapFactory {
public Map<Object, object> produceMap(String name) {
if ("HashMap".equals(name)) {
return new HashMap<>();
} else if ("TreeMap".equals(name)) {
return new TreeMap<>();
} / /...}}Copy the code
With the combination of reflection and the factory pattern, the factory class does not have to change anything when creating a new subclass. It can focus on the implementation of the subclass, and when the subclass is determined, the factory can produce the subclass.
The core idea of the Reflection + Abstract factory is:
- We get different Class objects at run time by passing in the fully qualified names of different subclasses, and call newInstance() to return different subclasses. Careful readers will notice that the concept of subclasses is mentioned, so the reflection + Abstract factory pattern is generally used when there are inheritance or interface implementation relationships.
For example, if we decide which Map structure to use at run time, we can instantiate a particular subclass by passing in the fully qualified name of a specific Map using reflection.
public class MapFactory {
/ * * *@paramClassName Fully qualified name of the class */
public Map<Object, Object> produceMap(String className) {
Class clazz = Class.forName(className);
Map<Object, Object> map = clazz.newInstance();
returnmap; }}Copy the code
ClassName can be specified as java.util.HashMap, java.util.TreeMap, and so on, depending on the business scenario.
JDBC loads the database driver class
When importing a third-party library, the JVM does not load the imported class. Instead, it waits until it is actually used to load the required class. For this reason, we can pass in the fully qualified name of the driver class to the JVM to load the class when we get a database connection.
public class DBConnectionUtil {
/** specifies the database driver class */
private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
public static Connection getConnection(a) {
Connection conn = null;
// Load the driver class
Class.forName(DRIVER_CLASS_NAME);
// Get the database connection object
conn = DriverManager.getConnection("JDBC: mysql: / /..."."root"."root");
returnconn; }}Copy the code
You’ll see this class a lot when you’re working on a SpringBoot project, but you’ll probably get so used to it that you don’t really care. I’m going to show you some common database configuration in Application.yML, and I think you’ll see the light.
The driver-class-name is similar to the driver class we loaded at the beginning. This is because the driver class is different due to the different versions of MySQL. This reflects the benefit of using reflection: you do not need to change the source code, but only load the configuration file to complete the replacement of the driver class.
A subsequent article will be devoted to detail the application scenarios of reflection, implementing a simple IOC container, and the benefits of implementing the factory pattern through reflection.
Here, you just need to understand the basic use of reflection and its ideas, and understand its main use scenarios.
The advantages and disadvantages of reflection
Advantages of reflection:
- Increased program flexibility: Flexibility to instantiate different objects in the face of changing requirements
However, you win some, you lose some, a technique can’t have all its advantages and disadvantages, and reflection also has two more subtle disadvantages:
- Break class encapsulation: You can force access to privately-decorated information
- Performance cost: Reflection requires far more checking and parsing steps than directly instantiating objects, calling methods, and accessing variables, and the JVM can’t optimize for them.
Increase the flexibility of the program
Instead of using SmallPineapple as an example, let’s take a closer look at development:
- Using reflection to connect to a database,The data source involved in the database. Everything in SpringBoot is more convention than configuration, wantCustom configurationWhen using
application.properties
The configuration file specifies the data source
Role 1 – Java designer: We design the DataSource interface, you other database vendors want developers to use your data sources to monitor the database, you have to implement my interface!
Role 2 – Database Vendor:
- MySQL database vendors: we provide a com. MySQL. Cj. JDBC. The MysqlDataSource data source, developers can use it to connect MySQL.
- Alibaba manufacturers: we provide a com. Alibaba. Druid. Pool. DruidDataSource data source, I am more cow force, this data source with page monitoring, slow SQL function such as logging, developers will quickly monitoring MySQL with it!
- Essentially a vendor: we provide a com. Microsoft. Essentially, the JDBC. SQLServerDataSource data source, if you want to practical SQL Server as the database, then use our this data source connection
Role 3 – Developer: We can specify using the DruidDataSource data source with the configuration file
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
Copy the code
Requirement change: One day, our boss came to us and told us that the Druid data source was not suitable for our current project. Let’s use MysqlDataSource. Then the programmer changed the configuration file, reloaded the configuration file, and restarted the project to complete the data source switch.
spring.datasource.type=com.mysql.cj.jdbc.MysqlDataSource
Copy the code
When changing the data source connected to the database, you only need to change the configuration file, without changing any code, because:
- Spring Boot encapsulates the configuration of the data source connected to the database, and uses reflection to adapt to various data sources.
Here is a brief source code analysis. DataSourceProperties = DataSourceProperties = DataSourceProperties = DataSourceProperties = DataSourceProperties = DataSourceProperties = DataSourceProperties = DataSourceProperties The specified data source operation is used when the database is connected and monitored.
private Class<? extends DataSource> type;
public void setType(Class<? extends DataSource> type) {
this.type = type;
}
Copy the code
The Class object specifies the generic DataSource upper bound. Let’s take a look at the Class diagram structure of each DataSource.
The figure above shows some of the data sources, and more than that, but as you can see, no matter which data source you specify, you only need to work with the configuration file without changing the source code, such is the flexibility of reflection!
Break the encapsulation of the class
One obvious feature of reflection is that it can retrieve variables, methods, and constructors in a class that are privately modified. This is a violation of object-oriented encapsulation, because being privately modified means that only the class is allowed to access them without exposing them, whereas setAccessable(true) can override the access modifier. The outside world can force access.
Remember the article on singleton patterns? We talked about reflection destroying hunky and slackty singletons, so we used enumerations to avoid being KO by reflection.
Back to the original starting point, SmallPineapple has a weight attribute that is modified by the private modifier in order to keep its weight unknown to the outside world.
public class SmallPineapple {
public String name;
public int age;
private double weight; // Only you know your weight
public SmallPineapple(String name, int age, double weight) {
this.name = name;
this.age = age;
this.weight = weight; }}Copy the code
Although the weight attribute is theoretically known only to the class itself, if reflected, the class will become as naked as if it were running naked in front of the reflection.
SmallPineapple sp = new SmallPineapple("Little pineapple".21."54.5");
Clazz clazz = Class.forName(sp.getClass());
Field weight = clazz.getDeclaredField("weight");
weight.setAccessable(true);
System.out.println("Peeping at the weight of the little pineapple:" + weight.get(sp));
// The weight of the little pineapple is 54.5 kg
Copy the code
Performance loss
When you directly new an object and call object methods and access properties, the compiler checks for accessibility ahead of time at compile time, and if you attempt incorrect access, the IDE will prompt for errors ahead of time, such as mismatched parameter passing types and illegal access to private properties and methods.
However, when using reflection to manipulate objects, the compiler cannot know in advance the type of the object, whether the access is valid, and whether the type of parameters passed is matched. Reflected code is examined, called, and returned from scratch only when it is called at runtime, and the JVM cannot optimize the reflected code.
Although reflection has the characteristics of performance loss, but we can not generalize, the use of reflection will degrade the performance of the idea, the reflection of the slow, need to call 100W times may be reflected in a few, dozens of calls, does not reflect the performance of reflection is poor. So do not blindly wear colored glasses to see reflection, in the process of single call reflection, performance loss can be negligible. If your program has high performance requirements, try not to use reflection.
Summary of reflection foundation article
- The idea of reflection: A reflection is like a mirror. It sees who it is, gets information about it, and even instantiates an object at runtime.
- The function of reflection: at run time to determine the instantiation of the object, so that the program is more robust, in the face of changing requirements, can do the maximum extent to do not modify the program source to deal with different scenarios, instantiation of different types of objects.
- The common application scenarios of reflection are
3
Spring’s IOC container. The reflection + factory pattern makes the factory class more stable. The driver class is loaded when JDBC connects to the database - The reflection of the
3
Features: increased program flexibility, broken class encapsulation, and performance cost
Hello, I am Cxuan. I have written four PDFS by hand, which are Java basic summary, HTTP core summary, computer basic knowledge, and operating system core summary respectively. I have sorted them into PDFS.