⚠️ this article for nuggets community first contract article, not authorized to forbid reprint

I blog at bugstack.cn

Precipitate, share, grow, let oneself and others can have a harvest! 😄

A call from the late night!

What, your online system is streaking?

Weekend sleeping late at night, suddenly received the boss phone ☎ urged. “Quickly look at wechat, wechat, how the system out of the problem, we do not know, but also the user feedback to know!!” Get up in the middle of the night, open the computer to connect VPN, yawn, open the hazy eyes, check the system log, the original is the system hung up, hurriedly restart recovery!

While the reboot restored the system, it also reset the boss’s distorted expression. But how does the system hang? Because there is no monitoring system, and I do not know whether it is caused by too much traffic or because of a program problem. Through a piece of log, I can only roughly estimate some of them to report to the boss with similar labels. But the boss is not stupid, talk to talk, let all the system running status monitoring out.

Drag your sleepy head in your hands, can’t think of any good methods for a while, and hard code time-consuming calculations on each method. Then the information is collected in a unified, display to a monitoring page, monitoring page using Apache Echarts, not to say if displayed like this, it can really look good and useful.

  • But this hard coding is not called a thing ah, this is not our department to carry brick code farmers a lot of trouble! Besides, they would despise me for doing this.What an architect, to monitor the system, also have to hard code, stupid not!!
  • So one thought whole of can’t sleep, have to look for information, report to the boss tomorrow!

The stability of an online system depends on its operational health, which includes; A comprehensive value of the number of calls, availability, impact time, and server performance. And when abnormal problems occur in the system, the entire business method can be captured to execute the link and output; The incoming parameters, outgoing parameters, exception information, and so on. Also included are performance metrics for JVM, Redis, and Mysql to quickly locate and resolve problems.

So what is the solution to do such a thing? In fact, there are many ways to do it, such as;

  1. The simplest is to hard-code the method to receive execution time and input and exception information. However, such coding costs are too large, and hard coding requires a lot of regression testing, which may bring some risks to the system.In case someone’s hands shake to copy and paste the wrong!
  2. Can choose the aspect way to do a unified monitoring of the components, relatively good or some. But it also requires hard coding, such as writing annotations, and is not cheap to maintain.
  3. In fact, there are a whole set of non-intrusion monitoring schemes for such monitoring in the market, such as; Google Dapper, Zipkin, etc., can meet the requirements of monitoring system. They are based on probe technology, non-invasive, and adopt bytecode enhancement to collect system operation information for analysis and monitoring.

Ok, so this article will take you to try out a few different ways to monitor the running state of the system.

Two, preparation work

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

MonitorDesign ├── CN-Bugstack-Middleware - AOP ├── CN-Bugstack-Middleware - ASM ├── CN-Bugstack-Middleware - ByteBuddy ├─ ├── ungStack-Middleware - Javassist ├── UngStack-Middleware - Test ├─ pom.xmlCopy the code
  • Source address: github.com/fuzhengwei/…
  • Brief introduction: AOP, ASM, ByteBuddy, Javassist, are four different implementation schemes. Test is a simple test project based on SpringBoot.
  • Technology use: 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(Insect insect: "" + userId, 19."14-0000 vanke Pingxi Garden, Dongli District, Tianjin"); }}Copy the code
  • The following types of monitoring code will be implemented to monitorUserController#queryUserInfoMethod execution information, see how the various technologies operate.

Use AOP for aspect 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. JavaCopy the code

Monitoring system based on AOP implementation, the core logic of the above project is not complex, its core point lies in the understanding and application of the aspect, and some configuration items need to be developed in accordance with the implementation of SpringBoot.

  • DoMonitor is a custom annotation. What it does is add this annotation and configure the necessary information on the monitoring interface for the method that needs to be used.
  • MonitorAutoConfigure, configured to work with SpringBoot YML files, handles some Bean initialization operations.
  • DoJoinPoint, the core part of the middleware, is responsible for intercepting and logic processing 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(a) default "";
   String desc(a) default "";

}
Copy the code
  • @ 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 the annotation of annotations, also known as meta-annotations. This annotation contains an input informationRetentionPolicy.RUNTIMEIn its comments, there is this description: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.What it means is that by adding this annotation, its information will be carried to the JVM runtime, and you can get the annotation information through reflection when you call a 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. Since the scope of the latter is larger than that of the former, retentionPolicy.Runtime is generally all you need.
  • @target is also a meta annotation that acts as a marker, and its name is what it means,The targetWhether our custom annotation DoWhiteList should be placed on a class, interface or method.ElementType provides a total of 10 target enumerations in JDK1.8, TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE, You can refer to your own custom annotation scope for Settings
  • The @domonitor custom annotation provides a description of the key and desc values for your monitoring methods. This mainly records the unique value configuration of your monitoring methods and a literal description of the monitoring methods.

3. Define section interceptors

cn.bugstack.middleware.monitor.DoJoinPoint

@Aspect
public class DoJoinPoint {

    @Pointcut("@annotation(cn.bugstack.middleware.monitor.annotation.DoMonitor)")
    public void aopPoint(a) {}@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("Monitoring - 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;
        returnjp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()); }}Copy the code
  • Use the @aspect annotation to define the Aspect class. This is a very common way to define a section.
  • @Pointcut("@annotation(cn.bugstack.middleware.monitor.annotation.DoMonitor)"), define the pointcut. There are many ways to find pointcuts in pointcuts, from specifying method names, to scoping expressions, and now through custom annotations. In general, in the middleware development, the custom annotation method is used more, because it can be used more flexibly in various business systems.
  • @Around("aopPoint() && @annotation(doMonitor)")The effect of this annotation is that when you call a method that has already been annotated with @domonitor, you will first go to the method enhanced at this pointcut. So at this point you can do some operation actions on the method, such as we need to do some method monitoring and log printing.
  • Finally, indoRouterMethod body to execute the methodjp.proceed();usetry finallyWrap it up and print out relevant monitoring information. Finally, the monitoring information can be sent to the server through asynchronous messages, and then the server processes the monitoring data and displays it to the monitoring page.

4. Initialize the aspect class

cn.bugstack.middleware.monitor.config.MonitorAutoConfigure

@Configuration
public class MonitorAutoConfigure {

    @Bean
    @ConditionalOnMissingBean
    public DoJoinPoint point(a){
        return newDoJoinPoint(); }}Copy the code
  • @Configuration, which can be counted as a component annotation, can be loaded when SpringBoot starts to create a Bean file.Because the @Configuration annotation has an @Component annotation
  • MonitorAutoConfigure can handle custom configuration information in YML and can also be used to initialize Bean objects, such as the DoJoinPoint aspect object instantiated here.

5. Run tests

5.1 Importing POM configuration

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

Configure monitoring registration in 5.2 Methods

@ DoMonitor (key = "cn. Bugstack. Middleware. UserController. QueryUserInfo", desc = "query user information")
@RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
public UserInfo queryUserInfo(@RequestParam String userId) {
    logger.info("Query user information, userId: {}", userId);
    return new UserInfo(Insect insect: "" + userId, 19."14-0000 vanke Pingxi Garden, Dongli District, Tianjin");
}
Copy the code
  • After introducing your own developed components through the POM, you can use custom annotations to intercept monitoring information.

5.3 Test Results

2021-07-04 23:21:10.710  INFO 19376 --- [nio-8081-exec-1] c.b.m.test.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: QueryUserInfo method time: 6ms monitoring -endCopy the code
  • By starting the SpringBoot program, open the URL address in a web page:http://localhost:8081/api/queryUserInfo?userId=aaaYou can see that the monitoring information can be printed to the console.
  • This configuration through custom annotations can solve some of the hard coding work, but if the method to add a lot of annotations, but also require some development work.

Let’s begin with a non-invasive approach to system monitoring using bytecode stubs. There are three components commonly used for bytecode stubs, including ASM, Javassit, and bytec-Buddy, and how they are used.

Four, ASM

ASM is a Java bytecode manipulation framework. It can be used to dynamically generate classes or enhance the functionality of existing classes. ASM can generate binary class files directly or dynamically change class behavior 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 in the class: class names, methods, attributes, and Java bytecode (instructions). After reading information from class files, ASM can change class behavior, analyze class information, and even generate new classes based on user requirements.

1. Start with a test

cn.bugstack.middleware.monitor.test.ApiTest

private static byte[] generate() {
    ClassWriter classWriter = new ClassWriter(0);
    // Define the object header; Version number, modifier, full class name, signature, superclass, implemented interface
    classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "cn/bugstack/demo/asm/AsmHelloWorld".null."java/lang/Object".null);
    // Add method; Modifiers, method names, descriptors, signatures, exceptions
    MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main"."([Ljava/lang/String;)V".null.null);
    // Execute the command; Getting static properties
    methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System"."out"."Ljava/io/PrintStream;");
    // Load constant
    methodVisitor.visitLdcInsn("Hello World ASM!");
    // Call the method
    methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream"."println"."(Ljava/lang/String;) V".false);
    / / return
    methodVisitor.visitInsn(Opcodes.RETURN);
    // Set the depth of the operand stack and the size of the local variables
    methodVisitor.visitMaxs(2.1);
    // The method ends
    methodVisitor.visitEnd();
    / / class
    classWriter.visitEnd();
    // Generate a byte array
    return classWriter.toByteArray();
}
Copy the code
  • This code is based on ASM to write HelloWorld, the whole process includes: define a class generation ClassWriter, set version, modifiers, full class name, signature, superclass, implement interface, in fact, that is the word; public class HelloWorld

  • Type descriptors:

    Java type 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 descriptors:

    Method declarations in the source file 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; Get static properties. The main thing is to get system.out

  • Load constant load constant, the output of our HelloWorld methodVisitor. VisitLdcInsn (” Hello World “);

  • Finally, you call the output method and set the null return. At the end, you set the depth of the operand stack and the size of the local variables.

  • It’s interesting to output a HelloWorld, although you might think it’s too hard to code and understand. However, you can install an ASM plugin in IDEA called The ASM Bytecode Outline to make it easier to see how normal code is handled using ASM.

  • In addition, the test result of the above code is mainly to generate a class file and output Hello World ASM! The results.

2. Monitor and design engineering structure

Cn - bugstack - middleware - asm └ ─ ─ the SRC ├ ─ ─ the main │ ├ ─ ─ Java │ │ └ ─ ─ cn. Bugstack. Middleware. The monitor │ │ ├ ─ ─ the config │ │ │ ├ ─ ─ │ ├── ProfilingAspect. Java │ ├─ ├─ ProfilingAspect. Java │ ├─ ├─ ProfilingAspect 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. JavaCopy the code

The above engineering structure uses THE ASM framework to enhance the system method, which is equivalent to the monitoring information before and after the method is hardcoded by the framework. However, this process is transferred to Java Agent #premain when the Java program starts.

  • MethodInfo is the definition of a method. It mainly describes the class name, method name, description, input parameter, and output parameter information.
  • ProfilingFilter is the profile information to be monitored. It mainly filters some methods that do not require bytecode enhancement, such as Main, hashCode, and Javax /
  • ProfilingAspect, ProfilingClassAdapter, ProfilingMethodVisitor, and ProfilingTransformer are mainly classes that perform bytecode interpolation and output monitoring results.
  • PreMain provides the entry point to the Javaagent, and the JVM first tries to call the PreMain method on the agent class.
  • Manifest.mf is the configuration information, mainly to find the premain-classPremain-Class: cn.bugstack.middleware.monitor.PreMain

3. Monitoring entries

cn.bugstack.middleware.monitor.PreMain

public class PreMain {

    // The JVM first attempts 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 attempt to call it
    public static void premain(String agentArgs) {}}Copy the code
  • This is the fixed entry method class for the Javaagent technology, and the path to this class needs to be configured into manifest.mf.

4. Bytecode 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);
        returncw.toByteArray(); }}Copy the code
  • Use the ASM core classes ClassReader, ClassWriter, ClassVisitor to handle incoming class loaders, class names, bytecode, and so on, responsible for bytecode enhancement.
  • Here are the operations classes of ASM, ClassReader, ClassWriter, ClassVisitor, and articles on bytecode programming: ASM, Javassist, and the Bytec-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;     // Number of parameters
    private int startTimeIdentifier;        // Start time marker
    private int parameterIdentifier;        // Enter the parameter content tag
    private int methodId = -1;              // The method is globally unique
    private int currentLocal = 0;           // The current local variable value
    private final boolean isStaticMethod;   / / true; Static method, false; Nonstatic method
    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;
        // Check whether the method is static. If a non-static method has a local variable whose first value is this, and a static method has its first parameter
        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] [[ZCBSIFJD] {0, 2} {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)));
    }     

    / /... Some bytecode piling operations
}
Copy the code
  • Every method of every class is monitored when the program starts loading. The name of the class, the name of the method, the description of the parameters in and out of the method, and so on, can be obtained here.
  • In order not to waste performance by passing a parameter (method information) every time in the subsequent monitoring process, it is common to produce a global weight protection for each methodidBy thisidYou can query the corresponding method.
  • In addition, as you can see here, the input and output parameters of the method are described as a specified code,(II)Ljava/lang/String;In order for us to parse the parameter later, we need to disassemble the string.

6. Run tests

6.1 Configuring VM Parameters Javaagent

-javaagent:E:\itstack\git\github.com\MonitorDesign\cn-bugstack-middleware-asm\target\cn-bugstack-middleware-asm.jar
Copy the code
  • IDEA runtime configuration toVM options, the JAR package address is configured according to its own path.

6.2 Test Results

Monitor - Begin By ASM Cn. Bugstack. Middleware. Test. Interfaces. UserController $$$$8 f5a18ca EnhancerBySpringCGLIB. QueryUserInfo into arguments:nullInput parameter type: ["Ljava/lang/String;"[value] : ["aaa"] ginseng: Lcn bugstack/middleware/test/interfaces/dto/the UserInfo; Parameter [value] : {"address":"14-0000 vanke Pingxi Garden, Dongli District, Tianjin"."age":19."code":"0000"."info":"success"."name":Insect insect: "aaa"} take:54(s) Monitor - EndCopy the code
  • As you can see from running the test results, when you use ASM monitoring, you can operate in your code in a way that doesn’t require either hard coding or AOP. You can also monitor more complete method execution information, including input parameter types, input parameter values, and output parameter information, and output parameter values.
  • However, you may find it difficult to operate ASM, especially in some very complex coding logic, and may encounter various problems, so we will introduce some components based on ASM development, which can achieve the same function.

Five, the Javassist

Javassist is an open source class library for analyzing, editing, and creating Java bytecode. It was created by Shigeru Chiba in the Department of Mathematics and Computer Science at Tokyo Institute of Technology. It has joined the open source JBoss application Server project to implement a dynamic “AOP” framework for JBoss by using Javassist to manipulate bytecode.

1. Start with 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");

        // Attribute field
        CtField ctField = new CtField(CtClass.doubleType, "PI", ctClass);
        ctField.setModifiers(Modifier.PRIVATE + Modifier.STATIC + Modifier.FINAL);
        ctClass.addField(ctField, "3.14");

        // Method: Find the area of the circle
        CtMethod calculateCircularArea = new CtMethod(CtClass.doubleType, "calculateCircularArea".new CtClass[]{CtClass.doubleType}, ctClass);
        calculateCircularArea.setModifiers(Modifier.PUBLIC);
        calculateCircularArea.setBody("{return π * $1 * $1; }");
        ctClass.addMethod(calculateCircularArea);

        / / method; The sum of two Numbers
        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);
        Object obj_01 = method_calculateCircularArea.invoke(obj, 1.23);
        System.out.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); }}Copy the code
  • This is a process that uses Javassist generated classes and methods to find the area of a circle and abstract them and run the results. You can see that Javassist mainly uses methods like ClassPool, CtClass, CtField, CtMethod, and so on.
  • The test results mainly include that a class will be generated at the specified pathcn.bugstack.middleware.javassist.MathUtil, and output results in the console.

The generated class

public class MathUtil {
  private static final doublePI =3.14 D;

  public double calculateCircularArea(double var1) {
      return 3.14 D * var1 * var1;
  }

  public Double sumOfTwoNumbers(double var1, double var3) {
      return var1 + var3;
  }

  public MathUtil(a) {}}Copy the code

The test results

The circle area:4.750506Two Numbers and:3.0

Process finished with exit code 0
Copy the code

2. Monitor and design engineering structure

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. JavaCopy the code
  • The overall monitoring framework implemented using Javassist is very similar to the STRUCTURE of ASM, but most of the work of manipulating the bytecode is done by the Javassist framework, so the overall code structure looks simpler.

3. Monitoring method Pile insertion

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(! classNameSet.contains(currentClassName)) {// Promote the classes contained in classNameSet
                return null;
            }

            / / class
            CtClass ctClass = ClassPool.getDefault().get(currentClassName);
            String clazzName = ctClass.getName();

            // Obtain the method
            CtMethod ctMethod = ctClass.getDeclaredMethod("queryUserInfo");
            String methodName = ctMethod.getName();

            // Method information: methodinfo.getDescriptor ();
            MethodInfo methodInfo = ctMethod.getMethodInfo();

            // Method: input parameter information
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
            CtClass[] parameterTypes = ctMethod.getParameterTypes();

            booleanisStatic = (methodInfo.getAccessFlags() & AccessFlag.STATIC) ! =0;  // Determine whether the method is static
            int parameterSize = isStatic ? attr.tableLength() : attr.tableLength() - 1; // Value of the static type
            List<String> parameterNameList = new ArrayList<>(parameterSize);            // Enter the parameter name
            List<String> parameterTypeList = new ArrayList<>(parameterSize);            // Input parameter type
            StringBuilder parameters = new StringBuilder();                             // Parameter assembly; $1, $2... , $$can get all, but can't put the array initialization

            for (int i = 0; i < parameterSize; i++) {
                parameterNameList.add(attr.variableName(i + (isStatic ? 0 : 1))); // Static types remove the first this argument
                parameterTypeList.add(parameterTypes[i].getName());
                if (i + 1 == parameterSize) {
                    parameters.append("$").append(i + 1);
                } else {
                    parameters.append("$").append(i + 1).append(","); }}// Method: input information
            CtClass returnType = ctMethod.getReturnType();
            String returnTypeName = returnType.getName();

            // Method: generate method unique ID
            int idx = Monitor.generateMethodId(clazzName, methodName, parameterNameList, parameterTypeList, returnTypeName);

            // Define attributes
            ctMethod.addLocalVariable("startNanos", CtClass.longType);
            ctMethod.addLocalVariable("parameterValues", ClassPool.getDefault().get(Object[].class.getName()));

            // Method before strengthening
            ctMethod.insertBefore("{ startNanos = System.nanoTime(); parameterValues = new Object[]{" + parameters.toString() + "}; }");

            // Method after strengthening
            ctMethod.insertAfter("{ cn.bugstack.middleware.monitor.probe.Monitor.point(" + idx + ", startNanos, parameterValues, $_); }".false); // If the return type is not an object type, $_ needs to be cast

            / / method; Add the TryCatch
            ctMethod.addCatch("{ cn.bugstack.middleware.monitor.probe.Monitor.point(" + idx + ", $e); throw $e; }", ClassPool.getDefault().get("java.lang.Exception"));   // Add exception catching

            return ctClass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null; }}Copy the code
  • The overall monitoring approach is similar compared to the ASM implementation, so only the differences are shown here.
  • Through the operations of Javassist, we mainly implement oneClassFileTransformerThe transform method of the interface, in which the bytecode is fetched and processed accordingly.
  • The process includes getting the class, getting the method, getting the input parameter information, getting the output parameter information, generating a unique ID for the method, and then starting the before and after enhancement of the method, which is to add monitoring code to the method block.
  • Finally, bytecode information is returnedreturn ctClass.toBytecode();Now your newly added bytecode is ready to be loaded and processed by the program.

4. Run tests

4.1 Configuring VM Parameters Javaagent

-javaagent:E:\itstack\git\github.com\MonitorDesign\cn-bugstack-middleware-javassist\target\cn-bugstack-middleware-javass ist.jarCopy the code
  • IDEA runtime configuration toVM options, the JAR package address is configured according to its own path.

4.2 Test Results

Monitor - Begin By Javassist method: Cn. Bugstack. Middleware. Test. Interfaces. UserController $$$$8 f5a18ca EnhancerBySpringCGLIB. QueryUserInfo into arguments:nullInput parameter type: ["Ljava/lang/String;"[value] : ["aaa"] ginseng: Lcn bugstack/middleware/test/interfaces/dto/the UserInfo; Parameter [value] : {"address":"14-0000 vanke Pingxi Garden, Dongli District, Tianjin"."age":19."code":"0000"."info":"success"."name":Insect insect: "aaa"} take:46(s) Monitor - EndCopy the code
  • From the test results and ASM do bytecode piling effect is the same, can be done to monitor the system execution information. But such a framework would make the development process simpler and more manageable.

Six, Byte – Buddy

Byte Buddy was awarded Duke’s Choice Award by Oracle in October 2015. Byte Buddy was commended for “tremendous innovations in Java technology.” We are very honored to receive this award and thank all the users who have helped Byte Buddy become a success, as well as everyone else. 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 utility that comes with the Java class library, Byte Buddy allows you to create arbitrary classes and is not limited to implementing the interface used to create the runtime agent. In addition, Byte Buddy provides a convenient API to use Java proxies or manually change classes during the build process.

  • You can easily manipulate bytecode and control classes and methods using simple apis without having to understand bytecode instructions.
  • Java 11 is 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 has some performance advantages over JDK dynamic proxy, Cglib, and Javassist.

1. Start with 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); }}Copy the code
  • This is a “Hello World!” generated using the ByteBuddy syntax Case, he runs a row,Hello World!, the whole code block core function is throughmethod(named("toString"))To findtoStringMethod, then through interceptionintercept, sets the return value of this method.FixedValue.value("Hello World!" ). There’s actually a basic way to get hereByte-buddy, and finally load, initialize, and invoke the output.

The test results

Hello World!

Process finished with exit code 0
Copy the code

2. Monitor and design engineering structure

Cn - bugstack - middleware - bytebuddy └ ─ ─ the SRC ├ ─ ─ the main │ ├ ─ ─ Java │ │ └ ─ ─ cn. Bugstack. Middleware. The monitor │ │ ├ ─ ─ │ ├ ─ premain.java │ ├ ─ Resources │ ├ ─ META_INF │ ├ ─ MANIFEST Cn. Bugstack. Middleware. The monitor. The test └ ─ ─ ApiTest. JavaCopy the code
  • This is one of my personal favorites because of its ease of operation and the ability to use bytecode enhanced operations just like normal business code. As you can see from the current engineering structure, there are fewer and fewer code classes.

3. Monitoring method Pile insertion

cn.bugstack.middleware.monitor.MonitorMethod

public class MonitorMethod {

    @RuntimeType
    public static Object intercept(@Origin Method method, @SuperCallCallable<? > 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 input parameters: + method.getParameterCount());
            for (int i = 0; i < method.getParameterCount(); i++) {
                System.out.println(Enter parameter Idx: + (i + 1) + "Type:" + method.getParameterTypes()[i].getTypeName() + "Content:" + args[i]);
            }
            System.out.println("Input type:" + method.getReturnType().getName());
            System.out.println("Result of participation:" + resObj);
            System.out.println("Method time:" + (System.currentTimeMillis() - start) + "ms");
            System.out.println("Monitor - End\r\n"); }}}Copy the code
  • @Origin, which is used to intercept the original method so that the relevant information in the method can be obtained.
  • The information in this part is relatively complete, especially the number and type of parameters, which can be output in the subsequent processing of parameters.

Commonly used annotations

Byte Buddy provides a number of other annotations in addition to the above annotations used to get method execution information. The following;

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

Common Core apis

  1. ByteBuddy

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

    • Utility classes that provide a set of element matches (named/any/nameEndsWith, etc.)
    • ElementMatcher(provides a way to matches types, methods, fields, annotations, similar to Predicate)
    • Junction performs and/ OR operations on multiple ElementMatcher
  3. DynamicType

    (Dynamic typing, the beginning of all bytecode operations, is very noteworthy.)

    • The bytecode that is created dynamically has not Unloaded into the virtual machine yet and needs to be Unloaded by the classloader.
    • Loaded(after being Loaded into the JVM, the Class representation is resolved)
    • Default(Default implementation of DynamicType, complete relevant practical operations)
  4. `Implementation

    (Used to provide implementation of dynamic methods)

    • Method calls return fixed values
    • Delegation method calls in two ways: static method calls in Class and instance method calls in object
  5. Builder

    (used to create DynamicType, related interface and implementation to be explained later)

    • MethodDefinition
    • FieldDefinition
    • AbstractBase

4. Configure entry methods

cn.bugstack.middleware.monitor.PreMain

public class PreMain {

    // The JVM first attempts 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")) // Intercept any method
                    .intercept(MethodDelegation.to(MonitorMethod.class)); / / to entrust
        };

        new AgentBuilder
                .Default()
                .type(ElementMatchers.nameStartsWith(agentArgs))  // Specify the class to intercept "cn.bugstack.demo.test"
                .transform(transformer)
                .installOn(inst);
    }

    // If the proxy class does not implement the above method, then the JVM will attempt to call it
    public static void premain(String agentArgs) {}}Copy the code
  • The premain method delegates to the implemented MonitorMethod and sets an intercepting method in the method, which can also be used on the classpath.

5. Run tests

5.1 Configuring VM Parameters Javaagent

-javaagent:E:\itstack\git\github.com\MonitorDesign\cn-bugstack-middleware-bytebuddy\target\cn-bugstack-middleware-bytebu ddy.jarCopy the code
  • IDEA runtime configuration toVM options, the JAR package address is configured according to its own path.

5.2 Test Results

QueryUserInfo Specifies the number of parameters to be entered.1The independence Idx refs:1Type: Java. Lang. String content: aaa reference types: cn. Bugstack. Middleware. Test. Interfaces. The dtos. The UserInfo and results: Cn. Bugstack. Middleware. Test. Interfaces. Dto. @ 214 b199c method time-consuming: 1 ms monitoring - EndCopy the code
  • Bytes-buddy is the simplest and most convenient bytecode framework for our entire testing process, and it is very easy to expand information. The whole process is as simple as using AOP in the first place, but meets the need 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 very broad, but probably not usually seen, because it is used in conjunction with other frameworks as support services. There are many other technologies like this, such as Javassit, Cglib, Jacoco, and so on.
  • Javassist is used heavily in some of the components in full-link monitoring, which can either handle bytecode enhancement in an encoded manner or as ASM does.
  • Byt-buddy is a very convenient framework that is becoming more and more widely used and has the lowest learning difficulty of the several frameworks. In addition to the case introduction in this chapter, you can also visit the website:https://bytebuddy.net“To learn more aboutByte BuddyThe content of the.
  • All source code for this section has been uploaded to GitHub: github.com/fuzhengwei/…