introducción
Lea este artículo primero para la reserva de conocimiento: Instrumento JAVA
En este caso, utilizaremos Instrument
el mecanismo para implementar un caso simple de actualización en caliente.
En general, los pasos son los siguientes:
- Crear un paquete con
premain
métodosjar
. Este método detecta regularmente un archivo y realiza actualizaciones activas. - Los parámetros se utilizan cuando se inicia la clase empresarial desde la línea de comandos
-javaagent
, pjava -javaagent:jarpath[=选项] Main
.
Hay muchos casos en Internet que usan Maven
empaquetadojar
, pero aquí estoy hablando del enfoque de línea de comando puro.
Código
La estructura es la siguiente
Esta es la estructura compilada, comencemos con la implementación lógica.
1. Escribe una premain
clase proxy con métodos
Primero declara una premain
clase con métodos. Establecemos el nombre de la clase como Agent
: esta clase primero inicia un hilo, y este hilo reemplazará la clase
después de 25 segundos . Entre ellos, leer el archivo en bytes y luego reemplazarlo es un conocimiento relevante.User
Instrument
package com.wyw;
import java.io.File;
import java.io.FileInputStream;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.util.concurrent.TimeUnit;
public class Agent {
public static void premain(String arg, Instrumentation instrumentation) {
System.out.println("premain starting ~");
Thread thread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
Class<?> person = Class.forName("com.wyw.User");
File file = new File("C:\\Users\\QTZ\\IdeaProjects\\hotfix\\src\\com\\wyw\\User.class");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = fileInputStream.readAllBytes();
instrumentation.redefineClasses(new ClassDefinition(person, bytes));
} catch (Exception e) {
e.printStackTrace();
}
});
thread.start();
}
}
2. Escribe clase ejecutiva
Clase de inicio principal:
declare una clase con nombre wyw
y User
luego imprima las propiedades de esta clase dentro de los 50 segundos.
package com.wyw;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException {
User user = new User("wyw");
int i = 0;
while (i < 50) {
TimeUnit.SECONDS.sleep(1);
System.out.println(i++ + ": " + user.getName());
}
}
}
La clase de usuario se define de la siguiente manera:
package com.wyw;
public class User {
private static final String DEFAULT_PREFIX = "User: ";
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return DEFAULT_PREFIX + name;
}
}
Crear jar
paquete, compilar clase
Primero necesitamos javac
compilar la implementación anterior con:
PS C:\Users\QTZ\IdeaProjects\hotfix\src> javac .\com\wyw\User.java .\com\wyw\Agent.java .\com\wyw\Main.java
Después de la ejecución, puede ver que se generan varios archivos class
.
Luego, para crear este jar
paquete, debe declarar una lista para empaquetar, en la que debe especificar Premain-Class
la ruta, esta lista se llama MANIFEST.MF
:
Manifest-Version: 1.0
Premain-Class: com.wyw.Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Luego en la línea de comando:
PS C:\Users\QTZ\IdeaProjects\hotfix\src> jar -c -f agent.jar -m .\com\wyw\MANIFEST.MF
Se generará un después de la ejecución agent.jar
.
Después de estos dos pasos básicos, comenzamos la clase:
PS C:\Users\QTZ\IdeaProjects\hotfix\src> java -javaagent:agent.jar Main
La consola entonces producirá una salida similar a esta:
PS C:\Users\QTZ\IdeaProjects\hotfix\src> java -javaagent:agent.jar com.wyw.Main
premain starting ~
0: User: wyw
1: User: wyw
2: User: wyw
3: User: wyw
4: User: wyw
En la lógica del código del caso, la clase se reemplazará después de 25 segundos, lo que significa que debemos modificar User
la clase y compilarla durante el período.
Por ejemplo, User
lo modificaremos a:
package com.wyw;
public class User {
private static final String DEFAULT_PREFIX = "!!!User: ";
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return DEFAULT_PREFIX + name;
}
}
Luego compílalo:
PS C:\Users\QTZ\IdeaProjects\hotfix\src> javac .\com\wyw\User.java
Puede ver la salida de la consola como:
17: User: wyw
18: User: wyw
19: User: wyw
20: User: wyw
21: User: wyw
22: User: wyw
23: User: wyw
24: !!!User: wyw
25: !!!User: wyw
26: !!!User: wyw
27: !!!User: wyw
28: !!!User: wyw
29: !!!User: wyw
Es decir, el reemplazo es exitoso.
Tenga en cuenta que no podemos agregar o eliminar un método a través de la actualización en caliente
Reemplace el original User
con el siguiente método:
package com.wyw;
public class User {
private static final String DEFAULT_PREFIX = "User: ";
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return addMethod() + DEFAULT_PREFIX + name;
}
private String addMethod() {
return "Add: ";
}
}
Después de volver a compilar la clase, se informará el siguiente error al ejecutar:
20: User: wyw
21: User: wyw
22: User: wyw
23: User: wyw
java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
at java.instrument/sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
at java.instrument/sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:193)
at com.wyw.Agent.lambda$premain$0(Agent.java:28)
at java.base/java.lang.Thread.run(Thread.java:834)
24: User: wyw
25: User: wyw
Resumir
Por lo general, no podemos agregar o eliminar un método a través de una actualización activa, pero podemos modificar la implementación interna del método.
El caso simplemente muestra la lógica del código relevante, y el tiempo fijo y las clases fijas se utilizan para la actualización y el reemplazo en caliente.
Proyectado en un proyecto específico, necesitamos implementar nuestra lógica de acuerdo con las condiciones locales de acuerdo con esta lógica, o formular algún tipo de especificación.
Aquí hay algunos más de mis pensamientos.
cuando comprobar
Aquí verifico a intervalos regulares, pero en ingeniería, debe modificarse para verificar a intervalos regulares.
Determinar qué clases necesitan ser actualizadas
Aquí escribo directamente a la muerte. Otros enfoques incluyen:
- Utilice un archivo para almacenar las rutas que deben actualizarse en caliente y lea este archivo.
- Almacena la hora de modificación de la clase actual y actualízala cuando cambie la hora.