Technology for dynamically changing classes during runtime

Source: Internet
Author: User

FiAttach is used to input two parameters. One is the path of agent. jar, and the other is the path of the folder where the class file to be replaced during running is stored.
The program automatically detects the current Java application, attaches the agent. jar to the Virtual Machine Process, and dynamically replaces the class files in the folder with the new class to replace the class originally loaded in the virtual machine ).
Import java. io. IOException;
Import java. util. List;
Import com. sun. tools. attach. AgentInitializationException;
Import com. sun. tools. attach. AgentLoadException;
Import com. sun. tools. attach. AttachNotSupportedException;
Import com. sun. tools. attach. VirtualMachine;
Import com. sun. tools. attach. VirtualMachineDescriptor;

Public class FiAttach {
Public static void main (String [] args ){
List <VirtualMachineDescriptor> vmdList = VirtualMachine. list ();
If (args. length <2 ){
System. out. println ("Error! Run Command: java com. taobao. fi. FiAttach agentJarPath agentArgs ");
Return;
}

String agentJarPath = args [0];
String agentArgs = args [1];

System. out. println ("agentJarPath:" + agentJarPath );
System. out. println ("agentArgs:" + agentArgs );

For (VirtualMachineDescriptor vmd: vmdList ){
// Note: currently, only jboss and tomcat are supported. Otherwise, the result will be invalid!
// Vmd. displayName (): org. jboss. main-B 0.0.0.0-Djboss. server. home. dir =/home/admin/deploy /. default-Djboss. server. home. url = file:/home/admin/deploy /. default
If (vmd. displayName (). startsWith ("org. jboss. main ") | vmd. displayName (). startsWith ("org. apache. catalina. startup. bootstrap ")){
Try {
VirtualMachine vm = VirtualMachine. attach (vmd );
Vm. loadAgent (agentJarPath, agentArgs );
Vm. detach ();
} Catch (AttachNotSupportedException e ){
E. printStackTrace ();
} Catch (IOException e ){
E. printStackTrace ();
} Catch (AgentLoadException e ){
E. printStackTrace ();
} Catch (AgentInitializationException e ){
E. printStackTrace ();
}
}
}
}
}
During program compilation, JDK_HOME/lib/tools. jar is required.
The following describes the implementation of agent. jar, AgentMain. java: import java. util. Set;
Import java. util. HashSet;
Import java. io. File;
Import java. lang. instrument. Instrumentation;
Import java. lang. instrument. UnmodifiableClassException;

Public class AgentMain {

Private static Set <String> fiClsFileNames = new HashSet <String> ();

Private static Transformer transformer = new Transformer ();

// Identify whether fault injection has been performed before
Private static boolean hasFi = false;

Private static void updateClsFileNames (String fiClassFolderPath ){
FiClsFileNames. clear ();
File fiClassFolderFile = new File (fiClassFolderPath );
If (! FiClassFolderFile. isDirectory ()){
Return;
}

File [] fiClassFiles = fiClassFolderFile. listFiles ();
For (File fiClassFile: fiClassFiles ){
FiClsFileNames. add (fiClassFile. getName ());
}
}

// Determine whether it is a fault injection class or a fault injection class.
Private static boolean isPrevFiCls (String clsName ){
String clsFileName = clsName + ". class ";
Return fiClsFileNames. contains (clsFileName );
}

// Determine whether it is the class for fault injection (Note: before this, you need to call updateCurrClsFileNames ())
Private static boolean isWillingFiCls (String clsName ){
String clsFileName = clsName + ". class ";
Return fiClsFileNames. contains (clsFileName );
}

Public static void agentmain (String agentArgs, Instrumentation inst)
Throws ClassNotFoundException, UnmodifiableClassException,
InterruptedException {

System. out. println ("AgentMain: agentmain !! ");

Synchronized (AgentMain. class ){

String fiClsFolderPath = agentArgs;

If (hasFi ){
Inst. removeTransformer (transformer );

Class [] classes = inst. getAllLoadedClasses ();
For (Class cls: classes ){
System. out. println ("AgentMain: agentmain, recover class :"
+ Cls. getName ());
If (isPrevFiCls (cls. getName ())){
// Trigger the modification of the loaded class to restore the class
Inst. retransformClasses (cls );
}
}
}

UpdateClsFileNames (fiClsFolderPath );

Transformer. setFiClsFolderPath (fiClsFolderPath );
// There should be no thread security risks here, because attach actions are always manually triggered.
Transformer. setFiClsFileNames (fiClsFileNames );

// Add a converter
Inst. addTransformer (transformer, true );

// Modify the currently loaded class
Class [] classes = inst. getAllLoadedClasses ();
For (Class cls: classes ){
If (isWillingFiCls (cls. getName ())){
System. out
. Println ("AgentMain: agentmain, transform class :"
+ Cls. getName ());
Inst. retransformClasses (cls );
}
}

HasFi = true;
}
}
}
Transformer. java: import java. util. Set;
Import java. io. File;
Import java. io. FileInputStream;
Import java. io. IOException;
Import java. io. InputStream;
Import java. lang. instrument. ClassFileTransformer;
Import java. lang. instrument. IllegalClassFormatException;
Import java. security. ProtectionDomain;

Public class Transformer implements ClassFileTransformer {

Private String fiClsFolderPath;

Private Set <String> fiClsFileNames = null;

Public String getFiClsFolderPath (){
Return fiClsFolderPath;
}

Public void setFiClsFolderPath (String fiClsFolderPath ){
This. fiClsFolderPath = fiClsFolderPath;
}

Public Set <String> getFiClsFileNames (){
Return fiClsFileNames;
}

Public void setFiClsFileNames (Set <String> fiClsFileNames ){
This. fiClsFileNames = fiClsFileNames;
}

Private boolean isFiCls (String clsName ){
String clsFileName = clsName + ". class ";
Return fiClsFileNames. contains (clsFileName );
}

Public static byte [] getBytesFromFile (String fileName ){
System. out. println ("[Transformer]: getBytesFromFile:" + fileName );
Try {
// Precondition
File file = new File (fileName );
InputStream is = new FileInputStream (file );
Long length = file. length ();
Byte [] bytes = new byte [(int) length];

// Read in the bytes
Int offset = 0;
Int numRead = 0;
While (offset <bytes. length
& (NumRead = is. read (bytes, offset, bytes. length-offset)> = 0 ){
Offset + = numRead;
}

If (offset <bytes. length ){
Throw new IOException ("cocould not completely read file"
+ File. getName ());
}
Is. close ();
Return bytes;
} Catch (Exception e ){
System. out. println ("error occurs in _ ClassTransformer! "
+ E. getClass (). getName ());
Return null;
}
}

@ Override
Public byte [] transform (ClassLoader loader, String className,
Class <?> ClassBeingRedefined, ProtectionDomain protectionDomain,
Byte [] classfileBuffer) throws IllegalClassFormatException {
System. out. println ("transform:" + className );

// If it is not the class for fault injection, null is returned directly, meaning no conversion is performed.
If (! IsFiCls (className. replace ("/","."))){
Return null;
}

Return getBytesFromFile (fiClsFolderPath + File. separator
+ ClassName. replace ("/", ".") + ". class ");
}
}
The name format of the class file name, for example, com. taobao. A. class.
Here, if the converted class changes to the class) must depend on a class as Class B), you can place the source code of this Class B to the agent project and package it with agent. jar. After the Virtual Machine loads agent. jar, it also loads the class. In this way, the converted class can also access Class B.
Note: In order to compress the agent, you need to create a new file MANIFEST. MF in the META-INF folder under the source directory, the content is as follows: Manifest-Version: 1.0
Agent-Class: com. taobao. fi. AgentMain
Can-Redefine-Classes: false
Can-Retransform-Classes: false
Boot-Class-Path: fiagent. jar
Note that this file is space-sensitive. No extra spaces are allowed for each row. Otherwise, the packaged agent. jar is not recognized by the virtual opportunity.
When using eclipse to export jar packages, remember to use the MANIFEST. MF file under the project source code directory.
If you want to restore to the original class, you only need to delete the class under the class folder, and then re-Execute FiAttach.
This article is my summary of the Java fault injection testing tool for the reference of colleagues in the industry. Digress, such as btrace is also based on this principle.
This article is complete.

This article is from "recalling the past ..." Blog. For more information, contact the author!

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.