当前位置:网站首页>Write a code hot deployment

Write a code hot deployment

2022-06-21 14:01:00 Funny 2233

The recent discovery javassist A very interesting class in ,HotSwapper, You can dynamically change a running class , So I want to write a code hot update gadget

Hot renewal effect ( Not only the hot change part, but also the original variable value is retained )
 Insert picture description here
Test1 Logic
 Insert picture description here
During the writing process, I had a whim and the original MakeR Combined with automatic update of resource files
MakeR:https://blog.csdn.net/weixin_44598449/article/details/118309945
Automatically update resource file effects
 Insert picture description here
Adding or deleting files in the specified resource directory will automatically update R class
Updated R class
 Insert picture description here
HotSwapper It's very easy to use

//  Virtual machine parameters must be specified :-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
HotSwapper swapper=new HotSwapper(8000);//  The ports here should be the same as those configured above address Agreement 
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// Compile code 
int result = compiler.run(null, null, null,"E:/IDEA/MyTest3/src/main/java/part15/TestQ.java");
System.out.println("result=" + result);
// Get bytecode and hot load 
byte[] bytes = Files.readAllBytes(Paths.get("E:\\IDEA\\MyTest3\\src\\main\\java\\part15\\TestQ.class"));
swapper.reload(TestQ.class.getName(),bytes);

The above program also relies on JDK\lib\tools.jar This jar package , You can introduce jar You can also rely on

<dependency>
    <groupId>com.sun</groupId>
    <artifactId>tools</artifactId>
    <version>1.8</version>
    <scope>system</scope><!-- Using environment variables -->
    <systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>

The code as a whole is very simple
Basically, by calling updateCode Update code , Determine whether the code file needs to be updated by the timestamp

@Slf4j
public class CodeUpdate {
    

    private Path basePath;
    private Map<String,Long> timeStampMap;
    private JavaCompiler compiler;
    private HotSwapper swapper;
    private List<UpdateListenner> listenners;
    private int updateCount;

    public CodeUpdate(String basePackage, int port) {
    
        initBasePath(basePackage);
        initTimeStampMap();
        initOther(port);
    }

    // -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
    private void initOther(int port){
    
        compiler = ToolProvider.getSystemJavaCompiler();
        try {
    
            swapper=new HotSwapper(port);
            log.info(" Successfully connected to {} port ",port);
        } catch (IOException|IllegalConnectorArgumentsException e) {
    
            e.printStackTrace();
            log.error(" Connection failed. Please configure virtual machine parameters :-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address={}",port);
        }
        listenners=new ArrayList<>();
    }

    public void addListenner(UpdateListenner listenner){
    
        listenners.add(listenner);
    }

    public void removeListenner(UpdateListenner listenner){
    
        listenners.remove(listenner);
    }

    private void initBasePath(String basePackage) {
    
        StringBuilder builder=new StringBuilder();
        builder.append(System.getProperty("user.dir")).append("/src/main/java");
        String[] items = basePackage.split("\\.");
        for (String item : items) {
    
            builder.append('/').append(item);
        }
        basePath=Paths.get(builder.toString());
    }

    @SneakyThrows
    private void initTimeStampMap(){
    
        timeStampMap=new LinkedHashMap<>();
        Files.walk(basePath).forEach(path -> {
    
            File file = path.toFile();
            if(file.getName().endsWith(".java")){
    
                timeStampMap.put(file.toString(),file.lastModified());
            }
        });
    }

    @SneakyThrows
    public void updateCode(){
    
        updateCount=0;
        Files.walk(basePath).forEach(path -> {
    
            File file = path.toFile();
            if(file.getName().endsWith(".java")){
    
                if(timeStampMap.getOrDefault(file.toString(),0L)<file.lastModified()){
    
                    timeStampMap.put(file.toString(),file.lastModified());
                    updateCode(file);
                    updateCount++;
                }
            }
        });
        listenners.forEach(listenner -> listenner.update(updateCount));
    }

    private void updateCode(File file) {
    
        int result = compiler.run(null, null, null,file.getAbsolutePath());
        String className = getClassName(file);
        if(result==0){
    
            byte[] bytes=getAndDelete(file);
            swapper.reload(className,bytes);
            log.info(" Hot update {} success !",className);
        }else {
    
            log.error(" compile {} Failure !",className);
        }
    }

    private String getClassName(File file) {
    
        String path = file.toString();
        String classPath = path.substring(path.indexOf("\\java\\") + 6, path.lastIndexOf('.'));
        return classPath.replaceAll("\\\\",".");
    }

    @SneakyThrows
    private byte[] getAndDelete(File file) {
    
        String name = file.getName();
        file=new File(file.getParent(),name.substring(0,name.lastIndexOf('.'))+".class");
        byte[] bytes = Files.readAllBytes(file.toPath());
        file.delete();
        return bytes;
    }

}

All that's left is to update the code and call updateCode Method , Actually, I prefer , hold updateCode Method is bound to a shortcut key , Apply updates manually
Use here WatchService Listen for file changes
Easy to use

WatchService watchService = FileSystems.getDefault().newWatchService();// Access to observation services 
// Register folder ( Only folders can be registered ) And specify listening events 
Paths.get(triggerDir).register(watchService,StandardWatchEventKinds.ENTRY_MODIFY);
/*  Supported file Events  StandardWatchEventKinds.OVERFLOW: Event lost or lost  StandardWatchEventKinds.ENTRY_CREATE: Create entities in the directory or rename the directory  StandardWatchEventKinds.ENTRY_MODIFY: Modification of entities in the directory  StandardWatchEventKinds.ENTRY_DELETE: Delete or rename entities in the directory  */
try {
    
    while (!Thread.interrupted()){
    
        WatchKey poll = watchService.take();// Block get events 
        //watchService.poll();// Non blocking access   No event returned null
        //watchService.poll(12, TimeUnit.SECONDS);// Specify the... With timeout 
        if(fileChange!=null)fileChange.change(poll.pollEvents());// must pollEvents Otherwise, the event will not disappear 
        poll.reset();// Reset for easy reuse 
    }
}finally {
    
    watchService.close();// Close the service 
}

The specific package is FileTrigger Listen for file changes

public class FileTrigger extends Thread{
    

    private String triggerDir;
    private WatchService watchService;
    private FileChange fileChange;

    @SneakyThrows
    public FileTrigger(String triggerDir) {
    
        this.triggerDir = triggerDir;
        watchService = FileSystems.getDefault().newWatchService();// Access to observation services 
    }

    public void setFileChange(FileChange fileChange) {
    
        this.fileChange = fileChange;
    }

    @SneakyThrows
    @Override
    public void run() {
    
        // Register folder ( Only folders can be registered ) And specify listening events 
        Paths.get(triggerDir).register(watchService,StandardWatchEventKinds.ENTRY_MODIFY,
                StandardWatchEventKinds.ENTRY_DELETE);
        try {
    
            while (!Thread.interrupted()){
    
                WatchKey poll = watchService.take();// Block get events 
                if(fileChange!=null)fileChange.change(poll.pollEvents());// must pollEvents Otherwise, the event will not disappear 
                poll.reset();// Reset for easy reuse 
            }
        }finally {
    
            watchService.close();
        }

    }

}

After that, I thought , Monitor file updates automatically R Just like Android
resources R class :https://blog.csdn.net/weixin_44598449/article/details/118309945
In fact, there is no code written here to directly modify the original MakeR Make it more suitable for multiple creation , And combine FileTrigger It's done.
After the bytecode is actually dynamically replaced, the original class file is not directly released
Here's how hot I am every second 100 Memory usage of secondary method area
[ Failed to transfer the external chain picture , The origin station may have anti-theft chain mechanism , It is suggested to save the pictures and upload them directly (img-uhU4Uwp4-1631950563802)(E:\ Text learning files \markdown\image\image-20210917085144145.png)]
Basically just 5s The method area starts from 2M Reached 30M, Then it started FullGC, But it is certainly not hotter per second during normal use 100 Secondary demand

原网站

版权声明
本文为[Funny 2233]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202221430106537.html