JAVA hot update

introduction

Read this article first for knowledge reserve: JAVA Instrument

In this case, we will use Instrumentthe mechanism to implement a simple hot update case.

Overall, the steps are as follows:

  1. Create a package with premainmethods jar. This method regularly detects a file and performs hot updates.
  2. Parameters are used when starting a business class from the command line -javaagent, eg java -javaagent:jarpath[=选项] Main.

There are many cases on the Internet that use Mavenpackagingjar , but here I am talking about the pure command line approach.

Code

The structure is as follows

insert image description here
This is the compiled structure, let's start with the logic implementation.

1. Write a premainproxy class with methods

First declare a premainclass with methods. We set the class name as Agent: this class first starts a thread, and this thread will replace the class
after 25 seconds . Among them, reading the file into bytes and then replacing is relevant knowledge.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. Write business class

Main startup class:
Declare a named class wyw, Userand then print the properties of this class within 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());
        }
    }
}

The User class is defined as follows:

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

Create jarpackage, compile class

We first need to javaccompile the above implementation with:

PS C:\Users\QTZ\IdeaProjects\hotfix\src> javac .\com\wyw\User.java .\com\wyw\Agent.java .\com\wyw\Main.java

After execution, you can see that several files are generated class.

Then to create this jarpackage, you need to declare a list for packaging, in which you need to specify Premain-Classthe path, this list is named MANIFEST.MF:

Manifest-Version: 1.0
Premain-Class: com.wyw.Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

Then on the command line:

PS C:\Users\QTZ\IdeaProjects\hotfix\src> jar -c -f agent.jar -m .\com\wyw\MANIFEST.MF

A will be generated after execution agent.jar.

After these two basic steps are done, we start the class:

PS C:\Users\QTZ\IdeaProjects\hotfix\src> java -javaagent:agent.jar Main

The console will then produce output similar to this:

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

In the code logic of the case, the class will be replaced after 25 seconds, which means that we need to modify Userthe class and compile it during the period.
For example, we will Usermodify it to:

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

Then compile it:

PS C:\Users\QTZ\IdeaProjects\hotfix\src> javac .\com\wyw\User.java

You can see the console output as:

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

That is, the replacement is successful.

Note that we cannot add or delete a method through hot update

Replace the original Userwith the following method:

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

After recompiling the class, the following error will be reported when running:

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

Summarize

Usually we cannot add or delete a method through hot update, but we can modify the internal implementation of the method.
The case simply shows the relevant code logic, and fixed time and fixed classes are used for hot update and replacement.
Projected into a specific project, we need to implement our logic according to local conditions according to this logic, or formulate some kind of specification.

Here are some more of my thoughts.

when to check

Here I check at regular intervals, but in engineering, it should be modified to check at regular intervals.

Determine which classes need to be updated

Here I write directly to death. Other approaches include:

  • Use a file to store the paths that need to be hot-updated, and read this file.
  • Store the modification time of the current class and update it when the time changes.

Guess you like

Origin blog.csdn.net/sayWhat_sayHello/article/details/121083133
Recommended