Reflexión de Java: Tutorial de invocación y carga dinámica de clases

Si cree que el contenido de este blog es útil o inspirador para usted, siga mi blog para obtener los últimos artículos técnicos y tutoriales lo antes posible. Al mismo tiempo, también puedes dejar un mensaje en el área de comentarios para compartir tus pensamientos y sugerencias. ¡Gracias por tu apoyo!

1. Panorama general de la reflexión

1.1 ¿Qué es la reflexión?

La reflexión de Java se refiere a la capacidad de obtener dinámicamente información de clase y operar con ella cuando el programa se está ejecutando. En Java, cada clase tiene un objeto Clase, que contiene toda la información de la clase, incluido el nombre de la clase, la clase principal, los métodos, los campos, etc. A través de la reflexión, podemos obtener y manipular esta información en tiempo de ejecución sin conocer la implementación específica de la clase en tiempo de compilación.

1.2 El papel y las ventajas y desventajas de la reflexión

Reflection tiene una amplia gama de aplicaciones en Java, tales como:

  • Crear objetos dinámicamente
  • llamar al método dinámicamente
  • clases cargadas dinámicamente
  • Obtener información de la clase
  • Implementación de proxies y AOP, etc.

La ventaja de la reflexión es que es muy flexible y puede cargar y manipular clases dinámicamente según sea necesario en tiempo de ejecución. Pero la reflexión también tiene algunas desventajas, como por ejemplo:

  • La invocación reflexiva de un método es relativamente lenta debido a la verificación de tipos adicional y la invocación de métodos.
  • Reflection destruye la encapsulación de Java y puede acceder y modificar los miembros y métodos privados de la clase, lo que genera riesgos de seguridad.
  • Reflection es complejo de usar y requiere una comprensión del sistema de tipos de Java y los detalles de la API de reflexión.

1.3 Clases e interfaces relacionadas con la reflexión

Las clases e interfaces relevantes de la reflexión de Java incluyen principalmente:

  • Clase Clase: Representa el tipo de clase o interfaz. Cada objeto tiene un método getClass() para obtener su objeto Clase correspondiente.
  • Clase constructora: Representa el constructor de una clase.
  • Clase de campo: Representa un campo de una clase.
  • Clase de método: representa un método de una clase.
  • Clase modificadora: proporciona métodos para acceder y modificar modificadores para clases, campos y métodos.

2. Carga dinámica de clases

2.1 La forma de cargar clases dinámicamente

Java puede cargar clases dinámicamente de dos maneras:

  • Método Class.forName(): este método carga la clase de acuerdo con el nombre de ruta completo de la clase y devuelve el objeto de clase correspondiente. Cabe señalar que este método generará una excepción ClassNotFoundException, que debe detectarse o declararse lanzada.
  • Clase ClassLoader: ClassLoader es una clase abstracta que se utiliza para cargar clases. Java proporciona una variedad de implementaciones, como URLClassLoader y AppClassLoader. La forma de cargar clases usando ClassLoader es más flexible y las clases se pueden cargar desde diferentes ubicaciones, como el sistema de archivos local, la red, etc.

2.2 La diferencia entre Class.forName() y ClassLoader

Cuando se carga una clase usando Class.forName(), la clase se inicializa automáticamente, incluida la ejecución de bloques de código estático y la inicialización de variables miembro estáticas. Cuando usa ClassLoader para cargar una clase, puede controlar el tiempo de inicialización de la clase y solo se inicializará cuando sea necesario usar la clase.

2.3 La diferencia entre cargar clases externas y clases locales

Las clases en Java se pueden dividir en dos categorías: clases externas y clases locales. Las clases externas son archivos de clases almacenados en el disco, mientras que las clases locales son clases definidas en el programa actual.

Cargar una clase externa requiere especificar la ruta del archivo de clase, por ejemplo:

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

El primer parámetro es el nombre de ruta completo de la clase, el segundo parámetro indica si se inicializa y el tercer parámetro es ClassLoader.

Para cargar una clase local, puedes usar directamente el objeto Clase, por ejemplo:

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

Entre ellos, myObj es un objeto MyClass, y el objeto Clase del objeto se obtiene mediante el método getClass ().

3. Método de llamada dinámica

3.1 Reflexión para obtener objetos de clase y método.

Antes de llamar a un método mediante la reflexión, debe obtener la clase y el objeto de método correspondientes. Se puede obtener un objeto de clase utilizando los siguientes métodos de la clase Class:

  • Class.forName(): carga una clase según su nombre de ruta completo.
  • El método getClass() del objeto: obtiene el objeto de clase del objeto.
  • Constantes literales de clase: utilice constantes literales de clase para obtener objetos de clase, como MyClass.class.

Para obtener objetos de método, puede utilizar los siguientes métodos de la clase Class:

  • getMethod(): Obtiene el método público de la clase.
  • getDeclaredMethod(): obtiene todos los métodos de la clase, incluidos los métodos privados.
  • getConstructor(): Obtiene el constructor de la clase.

Todos estos métodos deben pasar el nombre del método y la lista de parámetros.

Por ejemplo, el siguiente código obtiene el método sayHello() de la clase MyClass:

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

3.2 Uso del método Method.invoke()

Después de obtener el objeto del método, puede utilizar el método invoke() de la clase Método para invocar el método. Este método necesita pasar una instancia de objeto (nula si es un método estático) y una lista de parámetros, y devuelve el valor de retorno del método.

Por ejemplo, el siguiente código llama al método sayHello() de la clase MyClass:

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

Donde obj es una instancia de MyClass, clazz es el objeto de clase de obj, método es el objeto Método del método sayHello(), "Mundo" es el parámetro del método y resultado es el valor de retorno del método.

Cuarto, crear objetos dinámicamente.

Además de llamar dinámicamente a métodos, la reflexión también se puede utilizar para crear objetos dinámicamente. El uso de la reflexión para crear objetos puede crear objetos según sea necesario en tiempo de ejecución, como crear objetos según el nombre de clase ingresado por el usuario.

4.1 Método de creación de objetos por reflexión.

La reflexión de Java proporciona las siguientes formas de crear objetos:

  • Llame al método newInstance() del objeto Clase: este método llamará al constructor sin argumentos de la clase para crear el objeto. Cabe señalar que si la clase no tiene un constructor sin parámetros, se generará una excepción InstantiationException.
  • Llame al método newInstance() del objeto Constructor: este método puede llamar a cualquier constructor para crear el objeto. Primero debe obtener el objeto Constructor y luego llamar al método newInstance(). Cabe señalar que si los parámetros entrantes no coinciden con la lista de parámetros del constructor, se generará una excepción IllegalArgumentException.

Por ejemplo, el siguiente código crea un objeto de clase MyClass usando el método newInstance() del objeto Class:

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

4.2 Método de creación de un objeto de matriz por reflexión

Además de crear objetos ordinarios, la reflexión también se puede utilizar para crear objetos de matriz. La reflexión de Java proporciona la clase Array para manipular objetos de matriz.

Los objetos Array se pueden crear usando el método newInstance() de la clase Array. Este método necesita pasar el tipo de elemento de la matriz y la longitud de la matriz.

Por ejemplo, el siguiente código crea una matriz String de longitud 10:

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

5. Aplicación práctica

Ahora que hemos comprendido los principios básicos y el uso de la reflexión de Java, veamos un ejemplo de aplicación práctica: el proxy dinámico.

5.1 ¿Qué es un proxy dinámico?

El proxy dinámico es un patrón de diseño común que puede mejorar la funcionalidad del código existente sin modificar el código original. Se implementa en base al mecanismo de reflexión de Java y generalmente se usa para implementar AOP (programación orientada a aspectos).

5.2 El principio del proxy dinámico

El proxy dinámico se implementa en función del mecanismo de reflexión de Java, que puede generar clases de proxy en tiempo de ejecución para mejorar las funciones del código existente.

Una clase de proxy suele ser una clase que implementa la interfaz de la clase de proxy, y sus llamadas a métodos se reenviarán a la clase de proxy para su ejecución. Al llamar al método de la clase proxy, la clase proxy obtendrá el método de la clase proxy a través del mecanismo de reflexión y llamará al método.

Java proporciona dos implementaciones de proxy dinámico: proxy dinámico basado en interfaz y proxy dinámico basado en clases. El proxy dinámico basado en interfaz se implementa utilizando la clase Proxy proporcionada por la biblioteca estándar de Java, y el proxy dinámico basado en clases se implementa utilizando una biblioteca de terceros, como CGLib.

5.3 Realización de proxy dinámico basado en interfaz

Los proxies dinámicos basados ​​en interfaz se implementan utilizando la clase Proxy proporcionada por la biblioteca estándar de Java. Las clases de proxy se pueden generar fácilmente usando esta clase.

Primero defina una interfaz:

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

Luego defina una clase de proxy:

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

Finalmente defina una clase de proxy:

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;
  }
}

Se puede ver que la clase proxy implementa la interfaz InvocationHandler y pasa el objeto proxy a través del método de construcción. En el método invoke () de la clase proxy, primero genera Antes, luego llama al método del objeto proxy y finalmente genera Después.

A continuación podemos usar el método estático newProxyInstance() de la clase Proxy para crear un objeto proxy:

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

Como puede ver, creamos una instancia de HelloImpl y luego usamos el método Proxy.newProxyInstance() para crear un objeto proxy. Este método debe pasar el cargador de clases, la interfaz implementada por el objeto proxy y el InvocationHandler del objeto proxy.

Finalmente, llame al método sayHello () del objeto proxy y podrá ver que se generan Antes y Después, lo que indica que el proxy fue exitoso.

5.4 Realización de proxy dinámico basado en clases

Los proxies dinámicos basados ​​en clases se implementan mediante bibliotecas de terceros, como CGLib. CGLib es una biblioteca eficiente de generación de códigos de bytes que puede generar subclases de clases en tiempo de ejecución para implementar funciones de proxy.

Primero defina una clase:

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

Luego defina una clase de interceptor:

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;
  }
}

 Como puede ver, la clase interceptor implementa la interfaz MethodInterceptor y genera Antes y Después en el método intercept().

Luego use CGLib para crear un objeto proxy:

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

Como puede ver, creamos un objeto Enhancer y configuramos la clase principal y el interceptor del objeto proxy. Luego llame al método Enhancer.create() para generar un objeto proxy.

Finalmente, llame al método addUser () del objeto proxy y podrá ver que se muestran Antes y Después, lo que indica que el proxy fue exitoso.

6. Consideraciones de seguridad

6.1 Gerente de Seguridad y Reflexión

El administrador de seguridad es una característica importante de Java, que se utiliza para restringir ciertos comportamientos de los programas Java en tiempo de ejecución. Por ejemplo, el administrador de seguridad se puede utilizar para impedir que los programas Java accedan a los recursos del sistema o realicen ciertas operaciones confidenciales durante el tiempo de ejecución. El administrador de seguridad se puede configurar a través de un archivo de política de seguridad de Java para restringir el comportamiento del programa en tiempo de ejecución.

En Java, la reflexión es una característica muy poderosa que puede obtener dinámicamente información de clase, llamar a métodos y acceder a atributos en tiempo de ejecución. Debido a que la reflexión puede acceder a las propiedades y métodos privados de la clase, puede conllevar ciertos riesgos de seguridad. Para garantizar la seguridad del programa, Java proporciona un administrador de seguridad para limitar el uso de la reflexión.

En el administrador de seguridad, puede usar ​checkMemberAccess()​el método para verificar si tiene permiso para acceder a los miembros de la clase. Por ejemplo, el siguiente código demuestra cómo restringir el acceso a propiedades privadas en un administrador de seguridad:

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());

Como puede ver, en el código anterior, creamos un administrador de ​MySecurityManager​seguridad y ​System.setSecurityManager()​lo configuramos como el administrador de seguridad actual mediante el método. En ​checkMemberAccess()​el método , verificamos si el miembro al que se accede es un miembro público y lanzamos una excepción de seguridad si no lo es.

Al llamar a la propiedad privada de ​MyClass​la clase , dado que hemos configurado el administrador de seguridad, se lanzará una excepción de seguridad, garantizando así la seguridad del programa.

En resumen, en Java, la reflexión es una característica muy poderosa que puede obtener dinámicamente información de clase, llamar a métodos y acceder a atributos en tiempo de ejecución. Para garantizar la seguridad del programa, Java proporciona un administrador de seguridad para limitar el uso de la reflexión. Los desarrolladores pueden limitar el comportamiento del programa personalizando el administrador de seguridad para garantizar la seguridad del programa.

6.2 Cómo prevenir las vulnerabilidades de reflexión

A continuación se muestran algunos métodos utilizados habitualmente:

Usar administrador de seguridad

El uso de la reflexión se puede restringir mediante un administrador de seguridad. Los desarrolladores pueden personalizar el administrador de seguridad para restringir la reflexión, como prohibir el acceso a métodos y propiedades privados.

Utilice final para decorar clases y métodos.

El uso de final para decorar clases y métodos puede evitar que las clases se hereden y los métodos se anulen. Los métodos y propiedades privados no se pueden invocar mediante reflexión ya que no hay forma de crear subclases o anular métodos.

Utilice métodos seguros de creación de instancias de objetos

En Java, hay muchas formas de crear objetos mediante la reflexión, como ​newInstance()​,, etc.​getConstructor()​ ​newInstance()​Entre ellos, ​newInstance()​el método es uno de los más utilizados, pero también es uno de los más vulnerables. Los desarrolladores pueden utilizar métodos de creación de instancias de objetos más seguros, como el uso de métodos de fábrica, inyección de dependencia, etc., para crear objetos.

Control de acceso a métodos privados y propiedades de clases.

Al escribir código, puede restringir el acceso a métodos privados y propiedades de una clase mediante símbolos de control de acceso. Por ejemplo, establezca métodos y propiedades privados en ​private​, lo que evita que otras clases llamen a estos métodos y propiedades mediante la reflexión.

En resumen, los desarrolladores deben tomar algunas medidas, como usar administradores de seguridad, usar final para decorar clases y métodos, usar métodos seguros de instanciación de objetos y control de acceso a métodos privados y atributos de clases. Al mismo tiempo, también es necesario fortalecer la conciencia de seguridad al escribir código, prestar atención a la existencia de vulnerabilidades de seguridad y abordarlas en consecuencia.

7. Análisis de clases e interfaces relacionadas con la reflexión.

El mecanismo de reflexión de Java proporciona un conjunto de clases e interfaces importantes, como clase, método, constructor, objeto accesible, campo y modificador, que se pueden utilizar para obtener información sobre clases de Java, métodos de acceso, campos y constructores de clases de Java. etc. Comprender el uso de estas clases e interfaces nos permite utilizar el mecanismo de reflexión de Java de manera más flexible.

clase clase

La clase Class es la clase principal del mecanismo de reflexión de Java y representa el estado de ejecución de una clase o interfaz. Esta clase proporciona muchos métodos útiles, como newInstance(), getMethods(), getFields(), getConstructors(), etc., que se pueden utilizar para obtener instancias de clase, todos los métodos, todos los atributos, todos los constructores y otra información. así como tipo Sentencia y otras operaciones.

clase de campo

La clase Field representa una variable miembro en una clase Java y proporciona algunos métodos útiles, como get(), set() y getType(), etc., que se pueden utilizar para obtener, establecer y determinar el tipo. de la variable miembro.

Clase de método

La clase Método representa un método en una clase Java, que proporciona algunos métodos útiles, como getName(), getReturnType(), invoke(), isAccessible(), etc., que se pueden utilizar para obtener el nombre del método y el tipo de retorno. y ejecutar el método Y determinar si el método es accesible y otras operaciones.

clase constructora

La clase Constructor representa un método de construcción en una clase Java, que proporciona algunos métodos útiles, como newInstance(), getName(), getParameterTypes(), isAccessible(), etc., que se pueden utilizar para crear una instancia de una clase. y obtener los parámetros del método de construcción Escriba y determine si el método de construcción es accesible y otras operaciones.

Objeto Accesible类

La clase AccessibleObject es una clase base abstracta en el mecanismo de reflexión de Java, que implementa el control de acceso del objeto de reflexión. Esta clase proporciona dos métodos útiles, a saber, isAccessible() y setAccessible(), que se utilizan para determinar si el objeto es accesible y establecer si el objeto es accesible.

clase de campo

La clase Field representa un campo en una clase Java, que proporciona algunos métodos útiles, como getName(), getType(), get(), set(), etc., que se pueden usar para obtener el nombre, tipo y valor. y establezca el valor del campo y otra información.

Clase modificadora

La clase Modificador proporciona un conjunto de constantes y métodos estáticos comúnmente utilizados en operaciones de reflexión. Por ejemplo, las constantes PUBLIC, PRIVATE, PROTECTED, FINAL, STATIC, etc. se pueden utilizar para representar los modificadores de control de acceso de clases, métodos y campos, y los métodos estáticos isPublic(), isPrivate(), isProtected(), isFinal(), isStatic(), etc. se pueden utilizar para determinar si una clase, método o campo tiene un modificador de control de acceso específico.

8. Resumen

Este artículo presenta los principios básicos y el uso de la reflexión de Java y realiza una aplicación práctica a través de un proxy dinámico. Reflection puede obtener dinámicamente información de clase y llamar a métodos en tiempo de ejecución, que es una de las características más importantes de Java. A través de la reflexión, podemos crear objetos, llamar a métodos y obtener información como propiedades y anotaciones en tiempo de ejecución. Al mismo tiempo, la reflexión también puede realizar un proxy dinámico y ayudarnos a realizar algunas funciones especiales.

Al utilizar la reflexión, debe prestar atención, porque la reflexión creará objetos, obtendrá atributos y llamará métodos en tiempo de ejecución, por lo que generará una sobrecarga de rendimiento adicional. Por lo tanto, la reflexión debe utilizarse con precaución en escenarios con requisitos de alto rendimiento. Al mismo tiempo, dado que la reflexión puede acceder a las propiedades y métodos privados de la clase, también puede traer ciertos riesgos de seguridad y debe usarse con precaución.

En resumen, la reflexión es una de las características más poderosas de Java. Dominar el uso básico y los principios de la reflexión puede ayudarnos a desarrollar y depurar mejor Java.

Si cree que el contenido de este blog es útil o inspirador para usted, siga mi blog para obtener los últimos artículos técnicos y tutoriales lo antes posible. Al mismo tiempo, también puedes dejar un mensaje en el área de comentarios para compartir tus pensamientos y sugerencias. ¡Gracias por tu apoyo!

Supongo que te gusta

Origin blog.csdn.net/bairo007/article/details/132544927
Recomendado
Clasificación