introdução
Leia este artigo primeiro para reserva de conhecimento: Instrumento JAVA
Nesse caso, usaremos Instrument
o mecanismo para implementar um caso simples de hot update.
No geral, as etapas são as seguintes:
- Crie um pacote com
premain
métodosjar
. Este método detecta regularmente um arquivo e executa atualizações dinâmicas. - Os parâmetros são usados ao iniciar uma classe de negócio a partir da linha de comando
-javaagent
, por exemplojava -javaagent:jarpath[=选项] Main
.
Existem muitos casos na Internet que usam Maven
empacotamentojar
, mas aqui estou falando sobre a abordagem de linha de comando pura.
Código
A estrutura é a seguinte
Esta é a estrutura compilada, vamos começar com a implementação da lógica.
1. Escreva uma premain
classe de proxy com métodos
Primeiro declare uma premain
classe com métodos. Definimos o nome da classe como Agent
: esta classe primeiro inicia um thread e este thread substituirá a classe
após 25 segundos . Entre eles, ler o arquivo em bytes e depois substituí-lo é um conhecimento 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. Escrever aula de negócios
Classe de inicialização principal:
declare uma classe nomeada e wyw
, User
em seguida, imprima as propriedades dessa classe em 50s.
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());
}
}
}
A classe User é definida da seguinte forma:
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;
}
}
Criar jar
pacote, compilar classe
Primeiro precisamos javac
compilar a implementação acima com:
PS C:\Users\QTZ\IdeaProjects\hotfix\src> javac .\com\wyw\User.java .\com\wyw\Agent.java .\com\wyw\Main.java
Após a execução, você pode ver que vários arquivos são gerados class
.
Então para criar este jar
pacote, você precisa declarar uma lista para empacotamento, na qual você precisa especificar Premain-Class
o caminho, esta lista é nomeada MANIFEST.MF
:
Manifest-Version: 1.0
Premain-Class: com.wyw.Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Então na linha de comando:
PS C:\Users\QTZ\IdeaProjects\hotfix\src> jar -c -f agent.jar -m .\com\wyw\MANIFEST.MF
Um será gerado após a execução agent.jar
.
Após essas duas etapas básicas serem concluídas, iniciamos a aula:
PS C:\Users\QTZ\IdeaProjects\hotfix\src> java -javaagent:agent.jar Main
O console produzirá uma saída semelhante 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
Na lógica do código do caso, a classe será substituída após 25 segundos, o que significa que precisamos modificar User
a classe e compilá-la durante o período.
Por exemplo, vamos User
modificá-lo para:
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;
}
}
Em seguida, compile-o:
PS C:\Users\QTZ\IdeaProjects\hotfix\src> javac .\com\wyw\User.java
Você pode ver a saída do console 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
Ou seja, a substituição é bem-sucedida.
Observe que não podemos adicionar ou excluir um método por meio de hot update
Substitua o original User
pelo seguinte 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: ";
}
}
Após recompilar a classe, o seguinte erro será relatado ao executar:
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
Normalmente, não podemos adicionar ou excluir um método por meio de hot update, mas podemos modificar a implementação interna do método.
O caso simplesmente mostra a lógica de código relevante, e o tempo fixo e as classes fixas são usadas para atualização e substituição a quente.
Projetado em um projeto específico, precisamos implementar nossa lógica de acordo com as condições locais de acordo com essa lógica, ou formular algum tipo de especificação.
Aqui estão mais alguns dos meus pensamentos.
quando verificar
Aqui eu verifico em intervalos regulares, mas na engenharia, deve ser modificado para verificar em intervalos regulares.
Determine quais classes precisam ser atualizadas
Aqui escrevo diretamente para a morte. Outras abordagens incluem:
- Use um arquivo para armazenar os caminhos que precisam ser atualizados e leia esse arquivo.
- Armazene o horário de modificação da aula atual e atualize-o quando o horário mudar.