Author: Xiao Fu Ge blog: https://bugstack.cn

Precipitation, share, grow, let oneself and others can harvest! ๐Ÿ˜„

A call from late at night!

Why, your online system is streaking?

I was sleeping in the middle of the night on the weekend when I suddenly received a call from my boss โ˜Ž. “HURRIEDLY SEE WeChat, SEE WeChat, I SYSTEM OUT OF PROBLEM, WE ALL DON’T KNOW, RETURN GET USER FEEDBACK JUST KNOW!!” Late at night, get up, open the computer connected to the VPN, yawn, open the hazy eyes, check the system log, the original is the system hung, quickly restart recovery!

Although the reboot restored the system, it also reset the boss’s distorted expression. But how did the system fail? Because there was no monitoring system, and I did not know whether it was caused by too much traffic or by a program problem. Through the logs, I could only roughly estimate some labels that seemed to report to the boss. But the boss is not stupid, talk to talk, let all the system operating conditions are monitored out.

With your sleepy head dragging in your hands, you can’t think of any good way to hardcode time calculations on every method. The information is then collected and displayed in a monitoring page, which uses Apache Echarts, not to mention that if displayed in this way, it really looks good and works well.

  • But so hard coding is not called triad ah, this does not move our department brick code farmers tired fork! Besides, they wouldn’t think much of me for that.What an architect, to monitor the system, you have to hardcode it. Stupid!!
  • So think the whole of can’t sleep, have to look for information, report to the boss tomorrow!

In fact, the stable operation of an online system depends on its health, and this includes; A combined value for tuning amount, availability, duration of impact, and server performance. And in the case of abnormal problems in the system, the entire business method execution link can be captured and output. Incoming parameters, outgoing parameters, exception information, and so on. It also includes performance metrics for JVM, Redis, and MySQL to quickly locate and resolve problems.

So to do such a thing what to deal with the plan, in fact, the practice is still more, such as;

  1. The simplest and crudest way to do this is to hardcode the execution time and input and exception information into the method. However, such coding cost is too high, and after hard coding, a lot of regression tests are needed, which may bring certain risks to the system.What if someone’s hand shakes to copy and paste the wrong thing!
  2. It is better to choose a faceted approach to make a set of unified monitoring components. But it also requires hard coding, such as writing annotations, and it’s not cheap to maintain.
  3. In fact, there are a whole set of non-intrusion monitoring schemes on the market for such monitoring, such as; Google Dapper, Zipkin, etc., can all meet the requirements of monitoring system. They all collect system operation information based on probe technology and adopt the way of bytecode enhancement to analyze and monitor the running state.

OK, so this article will take you to try a few different ways to monitor the status of the system implementation ideas.

Second, preparation work

This article will implement different monitoring implementation code based on AOP and bytecode frameworks (ASM, Javassist, Byte-Buddy). The whole engineering structure is as follows:

MonitorDesign - CN - BugStack Middleware - AOP Heavy School Exercises - ASM Heavy School Exercises - CN - BugStack Middleware - ByteBuddy Heavy School Exercises - CN - BugStack Middleware - ByteBuddy Heavy School Exercises CN - BugStack-Middleware - Javassist Exercics - CN - BugStack-Middleware - Test Trash โ”€ Pom.xml
  • Source address: https://github.com/fuzhengwei/MonitorDesign
  • A brief introduction: AOP, ASM, ByteBuddy, and Javassist are four different implementations. Test is a simple test project based on SpringBoot.
  • Technologies used: SpringBoot, ASM, Byte-Buddy, Javassist

cn-bugstack-middleware-test

@RestController public class UserController { private Logger logger = LoggerFactory.getLogger(UserController.class); / * * * test: http://localhost:8081/api/queryUserInfo? userId=aaa */ @RequestMapping(path = "/api/queryUserInfo", Method = requestMethod. GET) public UserInfo queryUserInfo(@requestParam String UserId) {logger.info(" query user information, userId: {}", userId); Return new UserInfo(" + userId, 19, 14-0000, Dongli, Tianjin "); }}
  • The following kinds of monitoring code implementation, will be to monitorUserController#queryUserInfoThe method of implementation information, see how the various technologies are operating.

Third, use AOP to do a section monitoring

1. Engineering structure

Cn - bugstack - middleware - aop โ”” โ”€ โ”€ the SRC โ”œ โ”€ โ”€ the main โ”‚ โ”” โ”€ โ”€ Java โ”‚ โ”œ โ”€ โ”€ cn. Bugstack. Middleware. The monitor โ”‚ โ”‚ โ”œ โ”€ โ”€ the annotation โ”‚ โ”‚ โ”‚ โ”” โ”€ โ”€ DoMonitor. Java โ”‚ โ”‚ โ”œ โ”€ โ”€ the config โ”‚ โ”‚ โ”‚ โ”” โ”€ โ”€ MonitorAutoConfigure. Java โ”‚ โ”‚ โ”” โ”€ โ”€ DoJoinPoint. Java โ”‚ โ”” โ”€ โ”€ resources โ”‚ โ”” โ”€ โ”€ Meta-inf โ”‚ โ”” โ”€ โ”€ spring. Factories โ”” โ”€ โ”€ the test โ”” โ”€ โ”€ Java โ”” โ”€ โ”€ cn. Bugstack. Middleware. The monitor. The test โ”” โ”€ โ”€ ApiTest. Java

The monitoring system based on AOP implementation, the above project of the core logic is not complicated, its core point is the understanding and application of the section, and some configuration items need to be developed in accordance with the implementation way of SpringBoot.

  • Domonitor, is a custom annotation. It adds this annotation and configures the necessary information on the method monitoring interface that you want to use.
  • MonitorAutoConfigure, configured to use the SpringBoot YML file, handles initialization of some beans.
  • DojoinPoint, the core of the middleware, is responsible for the interception and logical processing of all methods that add custom annotations.

2. Define monitoring annotations

cn.bugstack.middleware.monitor.annotation.DoMonitor

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoMonitor {

   String key() default "";
   String desc() default "";

}
  • @ Retention (RetentionPolicy RUNTIME),Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
  • @Retention is an annotation of an annotation, also known as a meta-annotation. This annotation contains an input parameter informationRetentionPolicy.RUNTIMEIt is described in its notes as follows:Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.When you add this annotation, its information is carried to the JVM runtime, and you can retrieve the annotation information by reflection when you call the method. In addition, RetentionPolicy has two propertiesSOURCE,CLASSIn fact, these three enumerations officially correspond to the loading and running order of Java code, Java source file – >.class file -> memory bytecode. The latter is larger than the former, so in general you only need to use RetentionPolicy.Runtime.
  • @target is also a meta annotation that acts as a tag, and its annotation name is what it means,The targetThat is, whether our custom annotation DoWhiteList should be placed on a class, interface or method.In JDK1.8 elementType provides a total of 10 target enumerations, TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE You can set this by referring to the scope of your own custom annotations
  • The custom annotation @domonitor provides the monitor’s key and desc descriptions. This document records the unique value configuration of your monitor method and the literal description of the monitor method.

3. Define section interception

cn.bugstack.middleware.monitor.DoJoinPoint

@Aspect public class DoJoinPoint { @Pointcut("@annotation(cn.bugstack.middleware.monitor.annotation.DoMonitor)") public void aopPoint() { } @Around("aopPoint() && @annotation(doMonitor)") public Object doRouter(ProceedingJoinPoint jp, DoMonitor doMonitor) throws Throwable { long start = System.currentTimeMillis(); Method method = getMethod(jp); try { return jp.proceed(); } finally {System.out.println(" monitor -begin By AOP"); System.out.println(" monitor index: "+ domonitor.key ()); System.out.println(" Monitoring Description: "+ domonitor.desc ()); System.out.println(" Method Name: "+ Method.getName ()); System.out.println(" Method time: "+ (System.currentTimeMillis() -start) + "ms"); System.out.println(" monitor -end \r\n"); } } private Method getMethod(JoinPoint jp) throws NoSuchMethodException { Signature sig = jp.getSignature(); MethodSignature methodSignature = (MethodSignature) sig; return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()); }}
  • Define the Aspect class using the annotation @Aspect. This is a very common way to define a section.
  • @Pointcut("@annotation(cn.bugstack.middleware.monitor.annotation.DoMonitor)"), and define the pointcut. Pointcuts provide many ways to find pointcuts, from specifying method names, to scope-filtering expressions, and now through custom annotations. In general, in the development of middleware, the custom annotation method is used more, because it can be more flexible to apply to various business systems.
  • @Around("aopPoint() && @annotation(doMonitor)")The effect of this annotation is that when you call a method with a custom annotation @domonitor, you will enter the pointcut enhancement method first. So at this point you can do some action on the method, for example, we need to do some method monitoring and log printing, etc.
  • Finally, indoRouterMethod body to execute the methodjp.proceed();usetry finallyWrap and print relevant monitoring information. The acquisition of these monitoring information can finally be sent to the server through the way of asynchronous message, and then the server will process the monitoring data and display it to the monitoring page.

4. Initialize the section class

cn.bugstack.middleware.monitor.config.MonitorAutoConfigure

@Configuration public class MonitorAutoConfigure { @Bean @ConditionalOnMissingBean public DoJoinPoint point(){ return new DoJoinPoint(); }}
  • @Configuration, which can be used as a component annotation, can be loaded to create a Bean file when SpringBoot starts.Because the @Configuration annotation has an @Component annotation
  • MonitorAutoConfigure handles configuration information that is customized in YML and can be used to initialize Bean objects, such as the DojoinPoint facet object that we instantiated here.

5. Run tests

5.1 Introduction of POM configuration

<! -- Monitoring method: AOP --> <dependency> <groupId>cn.bugstack.middleware</groupId> <artifactId>cn-bugstack-middleware-aop</artifactId> < version > 1.0 - the SNAPSHOT < / version > < / dependency >

5.2 Configure monitoring registration on method

@DoMonitor(key = "cn.bugstack.middleware.UserController.queryUserInfo", Desc = "queryUserInfo") @RequestMapping(path = "/ API /queryUserInfo", Method = requestMethod. GET) public UserInfo queryUserInfo(@requestParam String UserId) {logger.info(" query user information, userId: {}", userId); Return new UserInfo(" + userId, 19, 14-0000, Dongli, Tianjin "); }
  • After introducing your own developed components through the POM, you can obtain monitoring information through custom annotations and intercepting methods.

5.3 Test results

The 2021-07-04 23:21:10. 19376-710 the INFO [nio - 8081 - exec - 1] C.B.M.T est. Interfaces. UserController: Query the user information, userId: aaa monitoring - Begin By AOP monitoring indexes: cn. Bugstack. Middleware. UserController. QueryUserInfo monitoring description: query information By the user name: The queryUserInfo method takes time: 6ms monitor-end
  • Open the URL in a Web page by launching the SpringBoot program:http://localhost:8081/api/queryUserInfo?userId=aaaYou can see that you are ready to print the monitoring information to the console.
  • This configuration through custom annotations can solve a certain amount of hard coding work, but if a large number of annotations are added to the method, it also needs some development work.

Next, we’ll start with a non-invasive approach to system monitoring using bytecode pins. There are three commonly used components for bytecode pins: ASM, Javassit, and Byte-Buddy. Next, we’ll discuss how they are used.

Four, ASM

ASM is a Java bytecode manipulation framework. It can be used to dynamically generate classes or to enhance the functionality of existing classes. ASM can either generate binary class files directly or change the class behavior dynamically before the class is loaded into the Java virtual machine. Java classes are stored in strictly formatted.class files that have enough metadata to parse all the elements of the class: the class name, methods, attributes, and Java bytecode (instructions). After ASM reads information from a class file, it can change the behavior of the class, analyze the class information, and even generate new classes based on user requests.

1. Take a test

cn.bugstack.middleware.monitor.test.ApiTest

private static byte[] generate() { ClassWriter classWriter = new ClassWriter(0); // Define the object header; The version number, the modifier, all class names, signature, and the parent class, implementation of the interface classWriter. Visit (Opcodes. V1_7 Opcodes. ACC_PUBLIC, "cn/bugstack/demo/asm/AsmHelloWorld", null, "java/lang/Object", null); // add method; MethodVisitor = ClassWriter. VisitMethod (opcodes.acc_public + opcodes.acc_static) MethodVisitor = ClassWriter. VisitMethod (opcodes.acc_public + opcodes.acc_static) "Main", "([Ljava/lang/String;) V", null, null); / / instruction execution; static attributes methodVisitor. VisitFieldInsn (Opcodes GETSTATIC, "java/lang/System", "out", "Ljava/IO/PrintStream;"); / / the load constant load constant methodVisitor. VisitLdcInsn (" Hello World ASM! "); / / call methods methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", False); // Returns methodVisitInSn (opCodes. RETURN); // Sets the depth of the operand stack and the size of the local variable methodVisitMaxs (2, // The class completes ClassWriter.visitEnd (); // Generates the byte array return ClassWriter.toByteArray ();}
  • This code is based on the ASM written HelloWorld, the whole process includes: define a class generation ClassWriter, set the version, modifier, full class name, signature, superclass, implementation of the interface, in fact, is that sentence;public class HelloWorld
  • Type descriptor:

    | | type Java type descriptor | | : — — — — — — — — – | : — — — — — — — — — — — — — — — — — — – | | Boolean | Z | | char C | | | byte | B | | short | S | | int | I | | float | F | | long | J | | double | D | | Object | Ljava/lang/Object; | | int[] | [I | | Object[][] | [[Ljava/lang/Object; |

  • Method descriptor:

    | | in the source file method statement method descriptor | | : — — — — — — — — — — — — — — — — — — — — — — – | : — — — — — — — — — — — — — — — — — — — — – | | void m (int I, float f) | (IF)V | | int m(Object o) | (Ljava/lang/Object;) I | | int[] m(int i, String s) | (ILjava/lang/String;) [I | | Object m(int[] i) | ([I)Ljava/lang/Object; |

  • Execute instructions; Gets the static property. The main thing is to get System.Out
  • Load constant, output our HelloWorldmethodVisitor.visitLdcInsn("Hello World");
  • Finally, you call the output method and set the null return, while at the end you set the depth of the operand stack and the size of the local variable.
  • So I’m going to print aHelloWorldIt’s kind of interesting, although you might think it’s really hard to code, and it’s really hard to understand. However, you can install an ASM Bytecode Outline plugin for ASM in IDEA, which can make it easier to see how a normal code is handled in the way that ASM is used.
  • In addition, the test results of this section of code above are mainly generating a class file and outputHello World ASM!The results.

2. Monitoring and designing engineering structures

Cn - bugstack - middleware - asm โ”” โ”€ โ”€ the SRC โ”œ โ”€ โ”€ the main โ”‚ โ”œ โ”€ โ”€ Java โ”‚ โ”‚ โ”” โ”€ โ”€ cn. Bugstack. Middleware. The monitor โ”‚ โ”‚ โ”œ โ”€ โ”€ the config โ”‚ โ”‚ โ”‚ โ”œ โ”€ โ”€ MethodInfo. Java โ”‚ โ”‚ โ”‚ โ”” โ”€ โ”€ ProfilingFilter. Java โ”‚ โ”‚ โ”œ โ”€ โ”€ the probe โ”‚ โ”‚ โ”‚ โ”œ โ”€ โ”€ ProfilingAspect. Java โ”‚ โ”‚ โ”‚ โ”œ โ”€ โ”€ ProfilingClassAdapter. Java โ”‚ โ”‚ โ”‚ โ”œ โ”€ โ”€ ProfilingMethodVisitor. Java โ”‚ โ”‚ โ”‚ โ”” โ”€ โ”€ ProfilingTransformer. Java โ”‚ โ”‚ โ”” โ”€ โ”€ PreMain. Java โ”‚ โ”” โ”€ โ”€ resources โ”‚ โ”” โ”€ โ”€ META_INF โ”‚ โ”” โ”€ โ”€ the MANIFEST. The MF โ”” โ”€ โ”€ the test โ”” โ”€ โ”€ Java โ”” โ”€ โ”€ cn. Bugstack. Middleware. The monitor. The test โ”” โ”€ โ”€ ApiTest. Java

The above engineering structure is to use ASM framework to enhance the operation of the system method, which is equivalent to the implementation of the monitoring information before and after hard-coding the method by the framework. However, this process moves to JavaAgent# premain when the Java program starts.

  • MethodInfo is the definition of a method that describes the class name, method name, description, incoming and outgoing parameter information.
  • ProfilingFilter is the configuration information for monitoring, mainly filtering methods that do not require bytecode enhancement operations, such as Main, HashCode, javax/, and so on
  • The ProfilingAspect, ProfilingClassAdapter, ProfilingMethodVisitor, and ProfilingTransformer are the classes that perform the bytecode instrumentation operations and output the monitoring results.
  • Premain provides an entry point to the JavaAgent, and the JVM first attempts to call the Premain method on the proxy class.
  • MANIFEST.MF is the configuration information for finding the premain-classPremain-Class: cn.bugstack.middleware.monitor.PreMain

3. Monitor class entry

cn.bugstack.middleware.monitor.PreMain

Public class PreMain {/ / JVM first try to call the following method on the proxy class public static void PreMain (String agentArgs, Instrumentation inst) { inst.addTransformer(new ProfilingTransformer()); } // If the proxy class does not implement the above method, then the JVM will try to call the method public static void preMain (String agentArgs) {}}
  • This is the fixed entry method class for JavaAgent technology, and you also need to configure the path of this class into MANIFEST.MF.

4. Bytecode method processing

cn.bugstack.middleware.monitor.probe.ProfilingTransformer

public class ProfilingTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        try {
            if (ProfilingFilter.isNotNeedInject(className)) {
                return classfileBuffer;
            }
            return getBytes(loader, className, classfileBuffer);
        } catch (Throwable e) {
            System.out.println(e.getMessage());
        }
        return classfileBuffer;
    }

    private byte[] getBytes(ClassLoader loader, String className, byte[] classfileBuffer) {
        ClassReader cr = new ClassReader(classfileBuffer);
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new ProfilingClassAdapter(cw, className);
        cr.accept(cv, ClassReader.EXPAND_FRAMES);
        return cw.toByteArray();
    }

}
  • The ASM core classes ClassReader, ClassWriter, ClassVisitor are used to handle incoming classloaders, class names, bytecodes, and so on, and are responsible for bytecode enhancement operations.
  • This is mainly about ASM operation classes, ClassReader, ClassWriter, ClassVisitor, and articles on bytecode programming: ASM, Javassist, and Byte-Bu series

5. Bytecode method parsing

cn.bugstack.middleware.monitor.probe.ProfilingMethodVisitor

public class ProfilingMethodVisitor extends AdviceAdapter {

    private List<String> parameterTypeList = new ArrayList<>();
    private int parameterTypeCount = 0;     // ๅ‚ๆ•ฐไธชๆ•ฐ
    private int startTimeIdentifier;        // ๅฏๅŠจๆ—ถ้—ดๆ ‡่ฎฐ
    private int parameterIdentifier;        // ๅ…ฅๅ‚ๅ†…ๅฎนๆ ‡่ฎฐ
    private int methodId = -1;              // ๆ–นๆณ•ๅ…จๅฑ€ๅ”ฏไธ€ๆ ‡่ฎฐ
    private int currentLocal = 0;           // ๅฝ“ๅ‰ๅฑ€้ƒจๅ˜้‡ๅ€ผ
    private final boolean isStaticMethod;   // true๏ผ›้™ๆ€ๆ–นๆณ•๏ผŒfalse๏ผ›้ž้™ๆ€ๆ–นๆณ•
    private final String className;

    protected ProfilingMethodVisitor(int access, String methodName, String desc, MethodVisitor mv, String className, String fullClassName, String simpleClassName) {
        super(ASM5, mv, access, methodName, desc);
        this.className = className;
        // ๅˆคๆ–ญๆ˜ฏๅฆไธบ้™ๆ€ๆ–นๆณ•๏ผŒ้ž้™ๆ€ๆ–นๆณ•ไธญๅฑ€้ƒจๅ˜้‡็ฌฌไธ€ไธชๅ€ผๆ˜ฏthis๏ผŒ้™ๆ€ๆ–นๆณ•ๆ˜ฏ็ฌฌไธ€ไธชๅ…ฅๅ‚ๅ‚ๆ•ฐ
        isStaticMethod = 0 != (access & ACC_STATIC);
        //(String var1,Object var2,String var3,int var4,long var5,int[] var6,Object[][] var7,Req var8)=="(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;IJ[I[[Ljava/lang/Object;Lorg/itstack/test/Req;)V"
        Matcher matcher = Pattern.compile("(L.*?;|\\[{0,2}L.*?;|[ZCBSIFJD]|\\[{0,2}[ZCBSIFJD]{1})").matcher(desc.substring(0, desc.lastIndexOf(')') + 1));
        while (matcher.find()) {
            parameterTypeList.add(matcher.group(1));
        }
        parameterTypeCount = parameterTypeList.size();
        methodId = ProfilingAspect.generateMethodId(new MethodInfo(fullClassName, simpleClassName, methodName, desc, parameterTypeList, desc.substring(desc.lastIndexOf(')') + 1)));
    }     

    //... ไธ€ไบ›ๅญ—่Š‚็ ๆ’ๆกฉๆ“ไฝœ 
}
  • Every method of each class is monitored when the program starts loading. The name of the class, the name of the method, the description of the method’s input and exit parameters, and so on, can be obtained here.
  • In order to avoid wasting performance by passing arguments (method information) in subsequent monitoring processes, a global load-proof is typically produced for each methodid, through thisidYou can query for the corresponding method.
  • In addition, you can see here that the method’s incoming and outgoing arguments are described as a specified code,(II)Ljava/lang/String;In order for us to parse the parameters later, we need to break this string apart.

6. Run tests

6.1 Configure VM parameters JavaAgent

-javaagent:E:\itstack\git\github.com\MonitorDesign\cn-bugstack-middleware-asm\target\cn-bugstack-middleware-asm.jar
  • The IDEA runtime is configured toVM optionsThe JAR package address is configured according to its own path.

6.2 Test results

Monitoring - Begin By ASM method: Cn. Bugstack. Middleware. Test. Interfaces. UserController $$$$8 f5a18ca EnhancerBySpringCGLIB. QueryUserInfo into arguments: null into the parameter type: ["Ljava/lang/String;"] The number [] : (" aaa ") and: Lcn bugstack/middleware/test/interfaces/dto/the UserInfo; And [value] : {" address ":" tianjin dong li vanke enjoy XiYuan 14-0000 ", "age" : 19, "code" : "0000", "info" : "success", "name" : "insect insect: aaa"} time-consuming: 54 monitoring - End (s)
  • As you can see from the results of the run tests, with ASM monitoring, you can operate in code in a way that requires neither hard coding nor AOP. You can also monitor more complete method execution information, including input type, input value, and output parameter information and output parameter value.
  • However, you may find it troublesome to operate ASM, especially in some very complex coding logic, may encounter a variety of problems, so next we will introduce some components based on ASM development, these components can also achieve the same function.

Five, the Javassist

Javassist is an open source library for analyzing, editing, and creating Java bytecode. Was created by Shigeru Chiba of the Department of Mathematics and Computer Science at the Tokyo Institute of Technology. It has been added to the open source JBoss application server project to implement a dynamic “AOP” framework for JBoss by using Javassist for bytecode manipulation.

1. Take a test

cn.bugstack.middleware.monitor.test.ApiTest

public class ApiTest { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("cn.bugstack.middleware.javassist.MathUtil"); // ctField ctField = new ctField (ctClass.doubleType, "ฯ€", ctClass); ctField.setModifiers(Modifier.PRIVATE + Modifier.STATIC + Modifier.FINAL); CtClass. AddField (ctField, "3.14"); / / methods: CalculateCircularArea ctMethod CalculateCircularArea = new ctMethod (ctClass.DoubleType, "CalculateCircularArea ", new CtClass[]{CtClass.doubleType}, ctClass); calculateCircularArea.setModifiers(Modifier.PUBLIC); CalculateCircularArea. SetBody (" {return PI * * $1 $1; } "); ctClass.addMethod(calculateCircularArea); / / method; CtMethod sumofTwonumbers = new ctMethod (pool.get(double.class.getName ()), "sumofTwonumbers ", new CtClass[]{CtClass.doubleType, CtClass.doubleType}, ctClass); sumOfTwoNumbers.setModifiers(Modifier.PUBLIC); sumOfTwoNumbers.setBody("{return Double.valueOf($1 + $2); } "); ctClass.addMethod(sumOfTwoNumbers); // Output the contents of the class ctClass.writeFile(); // Test call Class clazz = ctClass.toClass(); Object obj = clazz.newInstance(); Method method_calculateCircularArea = clazz.getDeclaredMethod("calculateCircularArea", double.class); The Object obj_01 = method_calculateCircularArea. Invoke (obj, 1.23); Println (" area of circle: "+ obj_01); Method method_sumOfTwoNumbers = clazz.getDeclaredMethod("sumOfTwoNumbers", double.class, double.class); Object obj_02 = method_sumOfTwoNumbers.invoke(obj, 1, 2); System.out.println(" two numbers and: "+ obj_02); }}
  • This is a process of finding the circle area and abstract classes and methods generated by Javassist and running the results. As you can see, Javassist is mainly the use of ClassPool, CTClass, CTField, CTMethod, and so on.
  • The test results mainly consist of generating a class under the specified pathcn.bugstack.middleware.javassist.MathUtilIt also outputs the results from the console.

The generated class

Public class Mathutil {private static final double ฯ€ = 3.14d; Public double calculateCircularArea(double var1) {return 3.14d * var1 * var1; } public Double sumOfTwoNumbers(double var1, double var3) { return var1 + var3; } public MathUtil() { } }

The test results

Circle area: 4.750506 double and: 3.0 Process finished with exit code 0

2. Monitoring and designing engineering structures

Cn - bugstack - middleware - javassist โ”” โ”€ โ”€ the SRC โ”œ โ”€ โ”€ the main โ”‚ โ”œ โ”€ โ”€ Java โ”‚ โ”‚ โ”” โ”€ โ”€ cn. Bugstack. Middleware. The monitor โ”‚ โ”‚ โ”œ โ”€ โ”€ the config โ”‚ โ”‚ โ”‚ โ”” โ”€ โ”€ MethodDescription. Java โ”‚ โ”‚ โ”œ โ”€ โ”€ the probe โ”‚ โ”‚ โ”‚ โ”œ โ”€ โ”€ the Monitor. The Java โ”‚ โ”‚ โ”‚ โ”” โ”€ โ”€ MyMonitorTransformer. Java โ”‚ โ”‚ โ”” โ”€ โ”€ PreMain. Java โ”‚ โ”” โ”€ โ”€ resources โ”‚ โ”” โ”€ โ”€ META_INF โ”‚ โ”” โ”€ โ”€ the MANIFEST. The MF โ”” โ”€ โ”€ the test โ”” โ”€ โ”€ Java โ”” โ”€ โ”€ cn. Bugstack. Middleware. The monitor. The test โ”” โ”€ โ”€ ApiTest. Java
  • The overall monitoring framework implemented with Javassist looks very similar to the structure of ASM, but most of the work on the bytecode is handled by the Javassist framework, so the overall code structure looks simpler.

3. Monitoring method Piling

cn.bugstack.middleware.monitor.probe.MyMonitorTransformer

public class MyMonitorTransformer implements ClassFileTransformer { private static final Set<String> classNameSet = new HashSet<>(); static { classNameSet.add("cn.bugstack.middleware.test.interfaces.UserController"); } @Override public byte[] transform(ClassLoader loader, String className, Class<? > classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { try { String currentClassName = className.replaceAll("/", "."); if (! Contains (currentClassName)) {return null; Contains (currentClassName); } // Get class ctClass = ClassPool.getDefault(). Get (currentClassName); String clazzName = ctClass.getName(); / / get methods CtMethod CtMethod = ctClass. GetDeclaredMethod (" queryUserInfo "); String methodName = ctMethod.getName(); // methodInfo: methodInfo.getDescriptor(); MethodInfo methodInfo = ctMethod.getMethodInfo(); / / methods: the reference information CodeAttribute CodeAttribute = methodInfo. GetCodeAttribute (); LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); CtClass[] parameterTypes = ctMethod.getParameterTypes(); boolean isStatic = (methodInfo.getAccessFlags() & AccessFlag.STATIC) ! = 0; // static method int parameterSize = isStatic? attr.tableLength() : attr.tableLength() - 1; ParameterNameList = new ArrayList< ParameterSize; // ArrayList< ParameterSize; // ParameterTypeList <String bb0 ParameterTypeList = new ArrayList<>(ParameterSize); StringBuilder parameters = new StringBuilder(); // Parameter assembly; $1, $2... For (int I = 0; $int I = 0; i < parameterSize; i++) { parameterNameList.add(attr.variableName(i + (isStatic ? 0:1))); ParameterTypeList.add (ParameterTypes [I].getName()); if (i + 1 == parameterSize) { parameters.append("$").append(i + 1); } else { parameters.append("$").append(i + 1).append(","); }} getReturnType(); ctClass ReturnType = ctMethod.getReturnType(); String returnTypeName = returnType.getName(); / / methods: Generate Method Unique ID int idx = Monitor. GenerateMethoId (ClazzName, MethodName, ParameterNameList, ParameterTypeList, returnTypeName); / / define attributes ctMethod. AddLocalVariable (" startNanos ", CtClass. LongType); ctMethod.addLocalVariable("parameterValues", ClassPool.getDefault().get(Object[].class.getName())); // Enforce ctMethod.insertBefore("{startNanos = System.nanoTime(); parameterValues = new Object[]{" + parameters.toString() + "}; } "); / / method after strengthening ctMethod. InsertAfter (" {cn. Bugstack. Middleware. The monitor. The probe. The monitor. The point (" + + "independence idx, startNanos, parameterValues, $_); }", false); // If the return type is not an object type, $_ needs to be cast. Add TryCatch ctMethod. AddCatch (" {cn. Bugstack. Middleware. The monitor. The probe. The monitor. The point (" + + "independence idx, $e); throw $e; }", ClassPool.getDefault().get("java.lang.Exception")); // add exception catch return ctClass.toBytecode(); } catch (Exception e) { e.printStackTrace(); } return null; }}
  • The overall monitoring approach is similar compared to the ASM implementation, so the differences are only shown here.
  • Through Javassist’s operations, the main thing is to implement oneClassFileTransformerThe transform method of the interface, where the bytecode is retrieved and processed accordingly.
  • The process includes getting the class, getting the method, getting the input information, getting the parameter information, generating a unique ID for the method, and then starting the method before and after enhancement, which is to add the monitoring code in the method block.
  • Finally, the bytecode information is returnedreturn ctClass.toBytecode();Now your new bytecode is ready to be loaded by the program.

4. Run tests

4.1 Configure VM parameters JavaAgent

-javaagent:E:\itstack\git\github.com\MonitorDesign\cn-bugstack-middleware-javassist\target\cn-bugstack-middleware-javass ist.jar
  • The IDEA runtime is configured toVM optionsThe JAR package address is configured according to its own path.

4.2 Test results

Monitoring - Begin By Javassist method: Cn. Bugstack. Middleware. Test. Interfaces. UserController $$$$8 f5a18ca EnhancerBySpringCGLIB. QueryUserInfo into arguments: null into the parameter type: ["Ljava/lang/String;"] The number [] : (" aaa ") and: Lcn bugstack/middleware/test/interfaces/dto/the UserInfo; And [value] : {" address ":" tianjin dong li vanke enjoy XiYuan 14-0000 ", "age" : 19, "code" : "0000", "info" : "success", "name" : "insect insect: aaa"} time-consuming: 46 monitor - End (s)
  • From the test results, it can be seen that the effect of ASM is the same as that of bytecode piling, which can monitor the system execution information. But such a framework would make the development process simpler and more manageable.

Six, Byte – Buddy

In October 2015, Byte Buddy was awarded the Duke’s Choice Award by Oracle. The award recognizes Byte Buddy’s “tremendous innovations in Java technology.” We are very honored to receive this award and thank all the users and everyone else who have helped Byte Buddy succeed. We really appreciate it!

Byte Buddy is a code generation and manipulation library for creating and modifying Java classes while a Java application is running, without the help of a compiler. In addition to the code generation utilities that come with the Java class library, Byte Buddy allows you to create arbitrary classes and is not limited to implementing interfaces for creating run-time proxies. In addition, Byte Buddy provides a convenient API for using Java proxies or manually changing classes during the build process.

  • You can easily manipulate bytecode, control classes, and methods using simple APIs without understanding bytecode instructions.
  • Java 11 is already supported, the library is lightweight and depends only on the visitor API of the Java bytecode parser library ASM, which itself does not require any other dependencies.
  • Byte Buddy offers some performance advantages over JDK dynamic proxies, CGLIB, and Javassist.

1. Take a test

cn.bugstack.middleware.monitor.test.ApiTest

public class ApiTest {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        String helloWorld = new ByteBuddy()
                .subclass(Object.class)
                .method(named("toString"))
                .intercept(FixedValue.value("Hello World!"))
                .make()
                .load(ApiTest.class.getClassLoader())
                .getLoaded()
                .newInstance()
                .toString();

        System.out.println(helloWorld);
    }

}
  • This is a “Hello World!” generated using ByteBuddy syntax. Case, the result of his run is one row,Hello World!, the core function of the whole code block is throughmethod(named("toString"))To findtoStringMethod, and then by interceptioninterceptSet the return value of this method.FixedValue.value("Hello World!" ). There’s actually a basic way to get thereByte-buddy, and finally load, initialize, and invoke the output.

The test results

Hello World!

Process finished with exit code 0

2. Monitoring and designing engineering structures

Cn - bugstack - middleware - bytebuddy โ”” โ”€ โ”€ the SRC โ”œ โ”€ โ”€ the main โ”‚ โ”œ โ”€ โ”€ Java โ”‚ โ”‚ โ”” โ”€ โ”€ cn. Bugstack. Middleware. The monitor โ”‚ โ”‚ โ”œ โ”€ โ”€ MonitorMethod โ”‚ โ”œ โ”€โ”€ promen.java โ”‚ โ”œ โ”€โ”€ promen.java โ”€ promen.java โ”€ promen.java โ”€ promen.java โ”€ promen.java โ”€ promen.java โ”€ promen.java โ”€ promen.java โ”€ promen.java โ”€ promen.java โ”€ promen.java Cn. Bugstack. Middleware. The monitor. The test โ”” โ”€ โ”€ ApiTest. Java
  • This is one of my personal favorites because of the ease of operation and the ability to use bytecode enhanced operations as if they were normal business code. As you can see from the current engineering structure, the number of code classes is getting smaller and smaller.

3. Monitoring method Piling

cn.bugstack.middleware.monitor.MonitorMethod

public class MonitorMethod { @RuntimeType public static Object intercept(@Origin Method method, @SuperCall Callable<? > callable, @AllArguments Object[] args) throws Exception { long start = System.currentTimeMillis(); Object resObj = null; try { resObj = callable.call(); return resObj; } finally {system.out. println(" monitor -begin By byte-buddy "); System.out.println(" Method Name: "+ Method.getName ()); System.out.println(" Number of arguments: "+ Method.getParameterCount ()); for (int i = 0; i < method.getParameterCount(); I ++) {System.out.println(" ID: "+ (I + 1) +");} " + args[i]); } System.out.println(" Output type: "+ Method.getReturnType ().getName()); System.out.println(" Output result: "+ resObj); System.out.println(" Method time: "+ (System.currentTimeMillis() -start) + "ms"); System.out.println(" monitor -end \r\n"); }}}
  • @OriginIs used to intercept the original method so that the relevant information in the method can be retrieved.
  • This part of the information is relatively complete, especially the number and type of parameters are obtained, so that the subsequent processing of parameters can be carried out in a loop output.

Common Notes

In addition to the annotations used above to obtain method execution information, Byte Buddy provides many other annotations. The following;

annotations instructions
@Argument Bind a single parameter
@AllArguments Bind an array of all parameters
@This The dynamically generated object that is currently intercepted
@Super Object that is the parent of the dynamically generated object that is currently being intercepted
@Origin You can bind to the following types of parameters: The return value of toString() of the dynamic Class int is the modifier of the dynamic Method
@DefaultCall Call the default method instead of Super’s method
@SuperCall Method used to call the parent class version
@Super Injects an object of the parent type, which can be an interface, and calls any of its methods
@RuntimeType Can be used on return values, parameters, and to prompt ByteBuddy to disable strict type checking
@Empty Default value for type of injection parameter
@StubValue Inject a stub value. For methods that return a reference and void, inject null; For methods that return primitive types, inject 0
@FieldValue Injects the value of a field of the intercepted object
@Morph Similar to @superCall, but allows you to specify invocation parameters

Common Core APIs

  1. ByteBuddy

    • Streaming API entry class
    • Provide Subclassing/rewrite the bytecode Redefining/Rebasing way
    • All operations depend on DynamicType.Builder to create an immutable object
  2. ElementMatchers(ElementMatcher)

    • Provides a set of tool classes for element matching (named/any/nameEndsWith, etc.)
    • ElementMatcher(matches types, methods, fields, and annotations, similar to Predicate)
    • The Junction performs an AND/OR operation on multiple elementMatchers
  3. DynamicType

    Dynamic typing, the beginning of all bytecode operations, is of great concern.

    • Unloaded(Dynamically created bytecode has not yet been loaded into the virtual machine and needs to be loaded by the class loader)
    • Loaded(resolves the Class representation after it has been Loaded into the JVM)
    • Default(the Default implementation of DynamicType, complete the actual operation)
  4. `Implementation

    (used to provide the implementation of dynamic methods)

    • FixedValue(method call returns a FixedValue)
    • MethodDelegation(Method Call Delegation, which supports two methods: Class static method calls and object instance method method calls)
  5. Builder

    DynamicType is used to create a DynamicType interface, and its implementation will be explained later.

    • MethodDefinition
    • FieldDefinition
    • AbstractBase

4. Configure the entry method

cn.bugstack.middleware.monitor.PreMain

Public class PreMain {/ / JVM first try to call the following method on the proxy class public static void PreMain (String agentArgs, Instrumentation inst) { AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, JavaModule) -> {return builder. Method (ElementMatchers. Named ("queryUserInfo")) // Block any method .intercept(MethodDelegation.to(MonitorMethod.class)); / / authorized}; New AgentBuilder. Default (). The type (ElementMatchers. NameStartsWith (agentArgs)) / / specifies the class need to intercept "cn. Bugstack. Demo. Test" .transform(transformer) .installOn(inst); } // If the proxy class does not implement the above method, then the JVM will try to call the method public static void preMain (String agentArgs) {}}
  • The preMain method delegate to the implemented monitorMethod, but also sets an intercepting method in the method, which goes to the classpath, and so on.

5. Run tests

5.1 Configure VM parameters JavaAgent

-javaagent:E:\itstack\git\github.com\MonitorDesign\cn-bugstack-middleware-bytebuddy\target\cn-bugstack-middleware-bytebu ddy.jar
  • The IDEA runtime is configured toVM optionsThe JAR package address is configured according to its own path.

5.2 Test Results

Monitor -begin By byte-buddy method Name: queryUserInfo Number of parameters: 1 Input Idx: 1 Type: java.lang.string Content: aaa Output parameter Type: Cn. Bugstack. Middleware. Test. Interfaces. The dtos. The UserInfo and results: cn. Bugstack. Middleware. Test. Interfaces. The dto. @ 214 b199c method time-consuming: 1ms monitoring - End
  • Byte-Buddy was the simplest and most convenient of the several bytecode frameworks we used throughout the testing process, and it was also very easy to expand information. The whole process is as simple as using AOP in the first place, but meets the requirements for non-invasive monitoring.
  • So when using a bytecode framework, consider using byte-buddy, a very useful bytecode framework.

Seven,

  • The application of ASM bytecode programming is quite extensive, but it may not be commonly seen, because it is used in conjunction with other frameworks as a supporting service. There are many other technologies like Javassit, CGLIB, Jacoco, and so on.
  • Javassist is used extensively in some components in full-link monitoring, either to manipulate bytecode enhancement in an encoded manner or to handle it as ASM does.
  • Byte-Buddy is a very convenient framework that is becoming more and more widely used, and one of the least difficult to get started with. In addition to the case introduction in this chapter, you can also visit the official website:https://bytebuddy.net, to learn more aboutByte BuddyThe content of the.
  • All the source code has been uploaded to the GitHub:https://github.com/fuzhengwei/MonitorDesign in this chapter

VIII. Series Recommendations

  • Before graduation, I wrote 200,000 lines of code, which made me the face bully in the eyes of my classmates.
  • I’m going to introduce you to Spring Ringing!
  • SpringBoot Middleware Design and Development, this time teach you how to build a rocket!
  • A technical book of Internet practical cases “Re-learning Java Design Patterns”
  • “JAVA Face Book” PDF, 417 pages 115,000 words, complete and released!