Java reflection mechanism
Overview of reflection
Java reflection refers to the mechanism by which a program obtains structural information of a class during runtime and dynamically creates objects, accesses fields, calls methods, or calls constructors.
Normally, the code is clear at compile time which type to use. Using reflection, a program can decide which class to load at run time based on the class name or configuration file, so reflection is often used in frameworks, dependency injection, object mapping, and common tool classes.
Loading of classes and Class objects
When the JVM loads a class, it creates a unique Class object for that type. The Class object holds structural information such as class names, fields, methods, constructors, and annotations, and is an entry for using reflection.
Get Class objects
Obtained through Class.forName()
try {
Class<?> personClass = Class.forName("com.hyxy.Person");
System.out.println(personClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
This method loads the class through the full class name, which is suitable for scenarios where the class name comes from the configuration file or the target type cannot be directly referenced at compile time.
Obtained through class literals
Class<Person> personClass = Person.class;
When the target type is already known at compile time, it is most straightforward to use 类型.class, and ClassNotFoundException will not be thrown.
Get through objects
Person person = new Person();
Class<?> personClass = person.getClass();
getClass() can be called when you already hold an object but need to obtain its runtime type.
public static void printType(Object object) {
Class<?> objectClass = object.getClass();
System.out.println(objectClass.getName());
}
Common methods for Class
| Method | Action |
|---|---|
getName() | Get the complete class name, including the package name |
getSimpleName() | Get the class name |
getDeclaredField(String name) | Gets the field for the specified name in the current class |
getDeclaredFields() | Get all fields for the current class declaration |
getDeclaredMethod(String name, Class<?>... parameterTypes) | Obtaining method based on method name and parameter type |
getDeclaredMethods() | All ways to obtain current class declarations |
getDeclaredConstructor(Class<?>... parameterTypes) | Obtain constructor based on parameter type |
getDeclaredConstructors() | Obtain all constructors for current class declarations |
Class.newInstance() is outdated. When creating an object, you should first obtain the constructor and then call the constructor’s newInstance().
Object object = personClass.getDeclaredConstructor().newInstance();
Using Field Operation Fields
Each Field object represents a field declared in the class. Through Field, you can obtain the field name and field type, and read or assign values to the fields of the specified object.
Common methods are as follows:
| Method | Action |
|---|---|
getName() | Get field name |
getType() | Get Field Type |
setAccessible(true) | allows reflection access to non-public fields |
set(Object object, Object value) | Assign |
get(Object object) | Read the field value of the specified object |
The following example creates a Student object and modifies its name field through reflection:
package com.hyxy;
import java.lang.reflect.Field;
public class FieldTest {
public static void main(String[] args) {
try {
Class<?> studentClass = Class.forName("com.hyxy.Student");
Object student = studentClass.getDeclaredConstructor().newInstance();
Field nameField = studentClass.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(student, "Tom");
Object name = nameField.get(student);
System.out.println(name);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}getDeclaredField() can obtain the fields declared by the current class, including private fields, but will not automatically look up the parent class. You usually need to call setAccessible(true) when accessing private fields.
Call methods using Method
Each Method object represents a method in the class. When calling invoke(), the first parameter is the object to which the method belongs, and the subsequent parameters are the arguments that need to be passed in to call the method.
package com.hyxy;
import java.lang.reflect.Method;
public class MethodTest {
public static void main(String[] args) {
try {
Class<?> studentClass = Class.forName("com.hyxy.Student");
Object student = studentClass.getDeclaredConstructor().newInstance();
Method method1 = studentClass.getDeclaredMethod("study");
Method method2 = studentClass.getDeclaredMethod(
"study",
String.class,
int.class
);
method1.setAccessible(true);
method2.setAccessible(true);
method1.invoke(student);
method2.invoke(student, "Java", 24);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}When looking for overloaded methods, you must provide not only the method name, but also an accurate list of parameter types. During calling a method, exceptions thrown inside the target method will be wrapped as InvocationTargetException.
For static methods, the first parameter when calling invoke() can be passed to null.
method.invoke(null, "参数");
Creating objects using Constructor
Each Constructor object represents a constructor in the class. You can obtain the specified constructor through the parameter type, and then call newInstance() to create the object.
package com.hyxy;
import java.lang.reflect.Constructor;
public class ConstructorTest {
public static void main(String[] args) {
try {
Class<Student> studentClass = Student.class;
Constructor<Student> constructor = studentClass.getDeclaredConstructor(
String.class,
int.class,
int.class
);
constructor.setAccessible(true);
Student student = constructor.newInstance("Tom", 20, 110011);
System.out.println(
student.getName() + "," +
student.getAge() + "," +
student.getNo()
);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}Reflection implements universal DAO
basic ideas
Traditional DAOs usually write add, delete, modify, and check codes for each table separately. The universal DAO can read the class name and field information of entity classes through reflection, and then dynamically generate SQL.
A simple agreement is:
- The entity class name corresponds to the table name.
- Entity class field names correspond to column names.
- The primary key field name is passed in by the caller.
- Self-added primary keys are not passed in as a placeholder parameter when inserted.
In real projects, class names and table names, field names and column names are often not exactly the same, so the mapping is usually completed in conjunction with annotations.
Examples of common new methods
package com.hyxy.dao;
import org.apache.commons.dbutils.QueryRunner;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class CommonDao {
public int insert(Object entity, String primaryKeyName, boolean autoIncrement) {
Class<?> entityClass = entity.getClass();
String tableName = entityClass.getSimpleName();
Field[] fields = entityClass.getDeclaredFields();
List<String> columnNames = new ArrayList<>();
List<String> placeholders = new ArrayList<>();
List<Object> parameterValues = new ArrayList<>();
for (Field field : fields) {
String fieldName = field.getName();
if (autoIncrement && fieldName.equals(primaryKeyName)) {
continue;
}
field.setAccessible(true);
columnNames.add(fieldName);
placeholders.add("?");
try {
parameterValues.add(field.get(entity));
} catch (IllegalAccessException e) {
throw new RuntimeException("读取字段失败:" + fieldName, e);
}
}
String sql = "insert into " + tableName +
"(" + String.join(",", columnNames) + ") values(" +
String.join(",", placeholders) + ")";
QueryRunner queryRunner = new QueryRunner();
try (Connection connection = DBUtil.getConnection()) {
return queryRunner.update(
connection,
sql,
parameterValues.toArray()
);
} catch (SQLException e) {
throw new RuntimeException("执行新增操作失败", e);
}
}
}This example shows the basic combination of reflection and JDBC encapsulation, but still has the following limitations:
- The entity class name must be consistent with the table name.
- The field name must be consistent with the column name.
- Field order relies on reflection return results and should not be used as a fixed database order.
- Real projects also need to deal with primary keys, inherited fields, static fields, transactions, and exception regimes.
- Table names and column names cannot use precompiled placeholders, so they must come from trusted mapping information and cannot be directly entered by user.
Considerations for use of reflection
Reflection improves program dynamics, but it also reduces code readability and bypasses some compile-time type checking. The following principles should be followed when using:
- When it can be called directly, ordinary Java code is preferred.
- Perform type and parameter verification on the reflection entrance.
- Don’t treat unverified user input directly as class names, method names, table names, or column names.
- Carry out unified processing of
ReflectiveOperationException. - In modular Java projects, access to non-public members may also be restricted by module access rules.
If you enjoyed this, leave a comment~