Java Reflection: Dynamic Class Loading and Invocation Tutorial

If you think the content of this blog is helpful or inspiring to you, please follow my blog to get the latest technical articles and tutorials as soon as possible. At the same time, you are also welcome to leave a message in the comment area to share your thoughts and suggestions. Thank you for your support!

1. Reflection overview

1.1 What is reflection?

Java reflection refers to the ability to dynamically obtain class information and operate on it when the program is running. In Java, each class has a Class object, which contains all the information of the class, including class name, parent class, methods, fields, and so on. Through reflection, we can obtain and manipulate this information at runtime without knowing the specific implementation of the class at compile time.

1.2 The role and advantages and disadvantages of reflection

Reflection has a wide range of applications in Java, such as:

  • Create objects dynamically
  • call method dynamically
  • dynamically loaded class
  • Get class information
  • Implementing proxies and AOP etc.

The advantage of reflection is that it is very flexible and can dynamically load and manipulate classes as needed at runtime. But reflection also has some disadvantages, such as:

  • Reflectively invoking a method is relatively slow because of the extra type checking and method invocation.
  • Reflection destroys the encapsulation of Java, and can access and modify the private members and methods of the class, resulting in security risks.
  • Reflection is complex to use and requires an understanding of the Java type system and the details of the reflection API.

1.3 Reflection-related classes and interfaces

The relevant classes and interfaces of Java reflection mainly include:

  • Class Class: Represents the type of a class or interface. Each object has a getClass() method to get its corresponding Class object.
  • Constructor class: Represents the constructor of a class.
  • Field class: Represents a field of a class.
  • Method class: Represents a method of a class.
  • Modifier class: Provides methods for accessing and modifying modifiers for classes, fields, and methods.

2. Dynamic class loading

2.1 The way of dynamically loading classes

Java can dynamically load classes in two ways:

  • Class.forName() method: This method loads the class according to the full path name of the class and returns the corresponding Class object. It should be noted that this method will throw a ClassNotFoundException exception, which needs to be caught or declared to be thrown.
  • ClassLoader class: ClassLoader is an abstract class used to load classes, Java provides a variety of implementations, such as URLClassLoader and AppClassLoader. The way to load classes using ClassLoader is more flexible, and classes can be loaded from different locations, such as the local file system, the network, and so on.

2.2 The difference between Class.forName() and ClassLoader

When a class is loaded using Class.forName(), the class is automatically initialized, including executing static code blocks and initializing static member variables. When using ClassLoader to load a class, you can control the initialization timing of the class, and it will only be initialized when the class needs to be used.

2.3 The difference between loading external classes and local classes

Classes in Java can be divided into two categories: external classes and local classes. External classes are class files stored on disk, while local classes are classes defined in the current program.

Loading an external class requires specifying the path of the class file, for example:

Class clazz = Class.forName("com.example.MyClass", true, ClassLoader.getSystemClassLoader());

The first parameter is the full path name of the class, the second parameter indicates whether to initialize, and the third parameter is ClassLoader.

To load a local class, you can directly use the Class object, for example:

MyClass myObj = new MyClass();
Class clazz = myObj.getClass();

Among them, myObj is a MyClass object, and the Class object of the object is obtained through the getClass() method.

3. Dynamic call method

3.1 Reflection to obtain class and method objects

Before calling a method using reflection, you need to obtain the corresponding class and method object. A class object can be obtained using the following methods of the Class class:

  • Class.forName(): Loads a class based on its full path name.
  • The getClass() method of the object: Get the class object of the object.
  • Class literal constants: Use class literal constants to get class objects, such as MyClass.class.

To get method objects you can use the following methods of the Class class:

  • getMethod(): Get the public method of the class.
  • getDeclaredMethod(): Get all methods of the class, including private methods.
  • getConstructor(): Get the constructor of the class.

These methods all need to pass in the method name and parameter list.

For example, the following code obtains the sayHello() method of the MyClass class:

Class clazz = MyClass.class;
Method method = clazz.getMethod("sayHello", String.class);

3.2 Use of Method.invoke() method

After obtaining the method object, you can use the invoke() method of the Method class to invoke the method. This method needs to pass in an object instance (null if it is a static method) and a parameter list, and returns the return value of the method.

For example, the following code calls the sayHello() method of the MyClass class:

MyClass obj = new MyClass();
Class clazz = obj.getClass();
Method method = clazz.getMethod("sayHello", String.class);
String result = (String) method.invoke(obj, "World");

Where obj is an instance of MyClass, clazz is the class object of obj, method is the Method object of the sayHello() method, "World" is the parameter of the method, and result is the return value of the method.

Fourth, dynamically create objects

In addition to dynamically calling methods, reflection can also be used to dynamically create objects. Using reflection to create objects can create objects as needed at runtime, such as creating objects based on the class name entered by the user.

4.1 Method of creating objects by reflection

Java reflection provides the following ways to create objects:

  • Call the newInstance() method of the Class object: This method will call the no-argument constructor of the class to create the object. It should be noted that if the class does not have a parameterless constructor, an InstantiationException will be thrown.
  • Call the newInstance() method of the Constructor object: This method can call any constructor to create the object. You need to get the Constructor object first, and then call the newInstance() method. It should be noted that if the incoming parameters do not match the parameter list of the constructor, an IllegalArgumentException will be thrown.

For example, the following code creates an object of class MyClass using the newInstance() method of the Class object:

Class clazz = MyClass.class;
MyClass obj = (MyClass) clazz.newInstance();

4.2 Method of creating an array object by reflection

In addition to creating ordinary objects, reflection can also be used to create array objects. Java reflection provides the Array class to manipulate array objects.

Array objects can be created using the newInstance() method of the Array class. This method needs to pass in the type of the array element and the length of the array.

For example, the following code creates a String array of length 10:

String[] strArr = (String[]) Array.newInstance(String.class, 10);

5. Practical application

Now that we have understood the basic principles and usage of Java reflection, let's look at an example of practical application: dynamic proxy.

5.1 What is a dynamic proxy

Dynamic proxy is a common design pattern that can enhance the functionality of existing code without modifying the original code. It is implemented based on Java's reflection mechanism and is usually used to implement AOP (aspect-oriented programming).

5.2 The principle of dynamic proxy

Dynamic proxy is implemented based on Java's reflection mechanism, which can generate proxy classes at runtime to enhance the functions of existing codes.

A proxy class is usually a class that implements the interface of the proxy class, and its method calls will be forwarded to the proxy class for execution. When calling the proxy class method, the proxy class will obtain the method of the proxy class through the reflection mechanism, and call the method.

Java provides two dynamic proxy implementations: interface-based dynamic proxy and class-based dynamic proxy. The interface-based dynamic proxy is implemented using the Proxy class provided by the Java standard library, and the class-based dynamic proxy is implemented using a third-party library, such as CGLib.

5.3 Realization of dynamic proxy based on interface

Interface-based dynamic proxies are implemented using the Proxy class provided by the Java standard library. Proxy classes can be easily generated using this class.

First define an interface:

public interface Hello {
  void sayHello(String name);
}

Then define a proxy class:

public class HelloImpl implements Hello {
  @Override
  public void sayHello(String name) {
    System.out.println("Hello, " + name + "!");
  }
}

Finally define a proxy class:

public class HelloProxy implements InvocationHandler {
  private Object target;

  public HelloProxy(Object target) {
      this.target = target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("Before");
      Object result = method.invoke(target, args);
      System.out.println("After");
      return result;
  }
}

It can be seen that the proxy class implements the InvocationHandler interface and passes in the proxy object through the construction method. In the invoke() method of the proxy class, first output Before, then call the method of the proxy object, and finally output After.

Next we can use the static method newProxyInstance() of the Proxy class to create a proxy object:

Hello hello = new HelloImpl();
Hello proxy = (Hello) Proxy.newProxyInstance(
  Hello.class.getClassLoader(),
    new Class[]{Hello.class},
    new HelloProxy(hello)
  );
proxy.sayHello("Alice");

As you can see, we create an instance of HelloImpl, and then use the Proxy.newProxyInstance() method to create a proxy object. This method needs to pass in the class loader, the interface implemented by the proxy object, and the InvocationHandler of the proxy object.

Finally, call the sayHello() method of the proxy object, and you can see that Before and After are output, indicating that the proxy is successful.

5.4 Realization of class-based dynamic proxy

Class-based dynamic proxies are implemented using third-party libraries, such as CGLib. CGLib is an efficient bytecode generation library that can generate subclasses of classes at runtime to implement proxy functions.

First define a class:

public class UserService {
  public void addUser(String name) {
  System.out.println("Add user: " + name);
  }
}

Then define an interceptor class:

public class UserServiceInterceptor implements MethodInterceptor {
  @Override
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    System.out.println("Before");
    Object result = proxy.invokeSuper(obj, args);
    System.out.println("After");
    return result;
  }
}

 As you can see, the interceptor class implements the MethodInterceptor interface, and outputs Before and After in the intercept() method.

Next use CGLib to create a proxy object:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new UserServiceInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.addUser("Alice");

As you can see, we created an Enhancer object and set the parent class and interceptor of the proxied object. Then call the enhancer.create() method to generate a proxy object.

Finally, call the addUser() method of the proxy object, and you can see that Before and After are output, indicating that the proxy is successful.

6. Security Considerations

6.1 Security Manager and Reflection

Security manager is an important feature in Java, which is used to restrict certain behaviors of Java programs at runtime. For example, the security manager can be used to restrict Java programs from accessing system resources or performing certain sensitive operations during runtime. The security manager can be configured through a Java security policy file to restrict program behavior at runtime.

In Java, reflection is a very powerful feature that can dynamically obtain class information, call methods and access attributes at runtime. Because reflection can access the private properties and methods of the class, it may bring certain security risks. In order to ensure the safety of the program, Java provides a security manager to limit the use of reflection.

In the security manager, you can use ​checkMemberAccess()​the method to check whether you have permission to access the members of the class. For example, the following code demonstrates how to restrict access to private properties in a security manager:

public class MySecurityManager extends SecurityManager {
    @Override
    public void checkMemberAccess(Class<?> clazz, int which) {
        if (which == Member.PUBLIC) {
            return;
        }
        if (clazz.getDeclaredFields().length > 0) {
            throw new SecurityException("Access to private fields not allowed!");
        }
    }
}

System.setSecurityManager(new MySecurityManager());

class MyClass {
    private int x;
    public void setX(int x) {
        this.x = x;
    }
    public int getX() {
        return x;
    }
}

MyClass obj = new MyClass();
Field field = obj.getClass().getDeclaredField("x");
field.setAccessible(true);
field.setInt(obj, 123);
System.out.println(obj.getX());

As you can see, in the above code, we created a security manager ​MySecurityManager​​​​​, and ​System.setSecurityManager()​set it as the current security manager through the​​​​ method. In ​checkMemberAccess()​the method , we check whether the accessed member is a public member, and throw a security exception if it is not.

When calling the private property of ​MyClass​the class , since we have set the security manager, a security exception will be thrown, thus ensuring the security of the program.

In short, in Java, reflection is a very powerful feature, which can dynamically obtain class information, call methods and access attributes at runtime. In order to ensure the safety of the program, Java provides a security manager to limit the use of reflection. Developers can limit the behavior of the program by customizing the security manager, so as to ensure the security of the program.

6.2 How to prevent reflection vulnerabilities

Here are a few commonly used methods:

Use Security Manager

The use of reflection can be restricted using a security manager. Developers can customize the security manager to restrict reflection, such as prohibiting access to private methods and properties.

Use final to decorate classes and methods

Using final to decorate classes and methods can prevent classes from being inherited and methods from being overridden. Private methods and properties cannot be invoked through reflection since there is no way to subclass or override methods.

Use safe object instantiation methods

In Java, there are many ways to create objects using reflection, such as ​newInstance()​​​,​​, ​getConstructor()​and ​newInstance()​​​etc. Among them, ​newInstance()​the method is one of the most commonly used methods, but it is also one of the most vulnerable methods. Developers can use safer object instantiation methods, such as using factory methods, dependency injection, etc. to create objects.

Access control to private methods and properties of classes

When writing code, you can restrict access to private methods and properties of a class through access control symbols. For example, set private methods and properties to ​private​, which prevents other classes from calling these methods and properties through reflection.

In short, developers need to take some measures, such as using security managers, using final to decorate classes and methods, using safe object instantiation methods, and access control to private methods and attributes of classes. At the same time, it is also necessary to strengthen security awareness when writing code, pay attention to the existence of security holes, and deal with them accordingly.

7. Analysis of reflection-related classes and interfaces

The Java reflection mechanism provides a set of important classes and interfaces, such as Class, Method, Constructor, AccessibleObject, Field, and Modifier, which can be used to obtain information about Java classes, access methods, fields, and constructors of Java classes, etc. Understanding the usage of these classes and interfaces allows us to use the Java reflection mechanism more flexibly.

class class

The Class class is the core class of the Java reflection mechanism, which represents the runtime state of a class or interface. This class provides many useful methods, such as newInstance(), getMethods(), getFields(), getConstructors(), etc., which can be used to obtain class instances, all methods, all attributes, all constructors, and other information, as well as type Judgment and other operations.

Field class

The Field class represents a member variable in a Java class, and it provides some useful methods, such as get(), set(), and getType(), etc., which can be used to obtain, set, and determine the type of the member variable.

Method class

The Method class represents a method in a Java class, which provides some useful methods, such as getName(), getReturnType(), invoke(), isAccessible(), etc., which can be used to obtain the method name, return type, and execute the method And determine whether the method is accessible and other operations.

Constructor class

The Constructor class represents a construction method in a Java class, which provides some useful methods, such as newInstance(), getName(), getParameterTypes(), isAccessible(), etc., which can be used to create an instance of a class and obtain the parameters of the construction method Type and determine whether the construction method is accessible and other operations.

AccessibleObject类

The AccessibleObject class is an abstract base class in the Java reflection mechanism, which implements the access control of the reflection object. This class provides two useful methods, namely isAccessible() and setAccessible(), which are used to determine whether the object is accessible and to set whether the object is accessible.

Field class

The Field class represents a field in a Java class, which provides some useful methods, such as getName(), getType(), get(), set(), etc., which can be used to get the name, type, value and set the field value and other information.

Modifier class

The Modifier class provides a set of constants and static methods commonly used in reflection operations. For example, the constants PUBLIC, PRIVATE, PROTECTED, FINAL, STATIC, etc. can be used to represent the access control modifiers of classes, methods, and fields; and the static methods isPublic(), isPrivate(), isProtected(), isFinal(), isStatic() etc. can be used to determine whether a class, method, or field has a specific access control modifier.

8. Summary

This article introduces the basic principles and usage of Java reflection, and realizes a practical application through dynamic proxy. Reflection can dynamically obtain class information and call methods at runtime, which is one of the very important features in Java. Through reflection, we can create objects, call methods, and obtain information such as properties and annotations at runtime. At the same time, reflection can also realize dynamic proxy and help us realize some special functions.

When using reflection, you need to pay attention, because reflection will create objects, get attributes, and call methods at runtime, so it will bring additional performance overhead. Therefore, reflection needs to be used with caution in scenarios with high performance requirements. At the same time, since reflection can access the private properties and methods of the class, it may also bring certain security risks and needs to be used with caution.

In short, reflection is one of the very powerful features in Java. Mastering the basic usage and principles of reflection can help us develop and debug Java better.

If you think the content of this blog is helpful or inspiring to you, please follow my blog to get the latest technical articles and tutorials as soon as possible. At the same time, you are also welcome to leave a message in the comment area to share your thoughts and suggestions. Thank you for your support!

Guess you like

Origin blog.csdn.net/bairo007/article/details/132544927