Develop IDEA Plugin, introduce probe, and obtain and execute SQL based on bytecode stake

Posted by aaadispatch on Thu, 20 Jan 2022 15:53:02 +0100

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

Precipitate, share and grow, so that you and others can gain something! 😄

1, Foreword

One sided!

On the third day of January, Tolstoy said, "what a great writer is just writing his own one-sided". Not to mention me, not to mention us!

Although we don't write articles, we write requirements, code and comments. When we encounter problems that need to be discussed, they often become arguments. This is good, that is bad, what you use!

When you narrow the road, you will receive less new ideas, new ideas, new horizons, and very important income. Only by horizontal comparison, reference, leak detection and vacancy filling can you have more ideas in your mind, whether in writing code, financial management or life.

2, Demand purpose

When you use IntelliJ IDEA for development, do you always need to get and execute SQL statements and copy them for verification: select * from user where id =? AND name = ? You need to do it yourself? Replace the number with the input parameter value?

Of course, this demand is not big, and you can even solve it in other ways. Then this chapter will provide you with a new idea, which may be handled in a way you have almost never done before.

In the case of this chapter, we use the development capability based on IDEA Plugin to inject bytecode stake probe into the code based on the capability of Javaagent. Then, through the enhanced bytecode, get com mysql. jdbc. Preparedstatement - > executeinternal is the object during execution, so as to get the SQL statement that can be tested directly.

3, Case development

1. Engineering structure

guide-idea-plugin-probe
├── .gradle
├── probe-agent
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── cn.bugstack.guide.idea.plugin
│   │               ├── MonitorMethod.java
│   │               └── PreAgent.java
│   └── build.gradle
└── probe-plugin
│   └── src
│   │   └── main
│   │       ├── java
│   │       │    └── cn.bugstack.guide.idea.plugin
│   │       │        └── utils
│   │       │        │    └── PluginUtil.java
│   │       │        └── PerRun.java
│   │       └── resources
│   │           └── META-INF
│   │                └── plugin.xml
│    └── build.gradle
├── build.gradle    
└── gradle.properties

Source code: the official account: bugstack wormhole stack reply: idea can download all the IDEA plug-in development source code.

In this IDEA plug-in project, the project structure is divided into two parts:

  • Probe agent: probe module, which is used to compile and package, provide bytecode enhancement service, and use it for probe plugin module
  • Probe plugin: plug-in module, through Java The programpatcher loads the bytecode enhancement package, obtains and prints the SQL statements that perform database operations.

2. Bytecode enhanced SQL acquisition

The bytecode enhancement method here adopts the byte buddy bytecode framework, which is simpler to use. In the process of use, it can obtain the information you need like the interception method of AOP.

In addition, when gradle is packaged and built, the shadowJar module needs to be added to package the premain class. This part of the code can be viewed

2.1 probe inlet

cn.bugstack.guide.idea.plugin.PreAgent

//The JVM first attempts to call the following methods on the proxy class
public static void premain(String agentArgs, Instrumentation inst) {
    AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
        return builder
                .method(ElementMatchers.named("executeInternal")) // Intercept arbitrary methods
                .intercept(MethodDelegation.to(MonitorMethod.class)); // entrust
    };
    new AgentBuilder
            .Default()
            .type(ElementMatchers.nameStartsWith("com.mysql.jdbc.PreparedStatement"))
            .transform(transformer)
            .installOn(inst);
}
  • Through byte buddy configuration, the matching classes and methods are intercepted, because under this class and method, the complete SQL statements can be obtained and executed.

2.2 intercepting SQL

cn.bugstack.guide.idea.plugin.MonitorMethod

@RuntimeType
public static Object intercept(@This Object obj, @Origin Method method, @SuperCall Callable<?> callable, @AllArguments Object... args) throws Exception {
    try {
        return callable.call();
    } finally {
        String originalSql = (String) BeanUtil.getFieldValue(obj, "originalSql");
        String replaceSql = ReflectUtil.invoke(obj, "asSql");
        System.out.println("Database name: Mysql");
        System.out.println("thread  ID: " + Thread.currentThread().getId());
        System.out.println("Time:" + new Date());
        System.out.println("original SQL: \r\n" + originalSql);
        System.out.println("replace SQL: \r\n" + replaceSql);
    }
}
  • Intercepting method parameters is a configurable operation. For example, @ This Object obj is used to obtain the execution object of the current class, and @ origin method is used to obtain the execution method.
  • In the finally block, we can get the attribute information of the current class and the executed SQL through reflection, and print out.

2.3 compilation and packaging

Before testing and developing the IDEA Plugin, we need to package the bytecode enhanced code into a Jar package. In build gradle -> shadowJar

  • After packaging and compiling, you can see jar: probe-agent-1.0-snapshot-all under build - > LIBS Jar is used for bytecode enhancement.

2.4 test verification

Here, before the written bytecode enhancement component is used by the plug-in, a test verification can be done to avoid starting the plug-in every time.

unit testing

public class ApiTest {

    public static void main(String[] args) throws Exception {

        String URL = "jdbc:mysql://127.0.0.1:3306/itstack?characterEncoding=utf-8";
        String USER = "root";
        String PASSWORD = "123456";
        Class.forName("com.mysql.jdbc.Driver");

        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
        String sql="SELECT * FROM USER WHERE id = ? AND name = ?";
        PreparedStatement statement = conn.prepareStatement(sql);
        statement.setLong(1,1L);
        statement.setString(2,"Xie plane");
        ResultSet rs = statement.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("name") + " " + rs.getString("address"));
        }

    }

}
  • VM options: - javaagent: your path \ LIBS \ probe-agent-1.0-snapshot-all jar
  • Note that during the test run, you need to configure VM options for ApiTest to print the intercepted SQL information

test result

original SQL: 
SELECT * FROM USER WHERE id = ? AND name = ?
replace SQL: 
SELECT * FROM USER WHERE id = 1 AND name = 'Xie plane'
Xie plane Beijing.Daxing District.Tongming Lake Park
  • OK, so we can intercept the SQL statements that can be copied and executed. Next, we'll deal with the IDEA Plugin.

3. Introduce probe Jar through plug-in development

Next, we will copy the developed bytecode enhancement jar package to LIBS (you can create it yourself) in the IDEA Plugin plug-in development module, and then in plugin XML configuration loads the implementation fileTree(dir: 'libs', includes: ['*jar ']) so that you can find the jar package in the program and configure it in the program.

3.1 copy jar to libs

3.2 build.gradle configuration loading

dependencies {
    implementation fileTree(dir: 'libs', includes: ['*jar'])
}
  • The way of loading file tree is introduced through implementation fileTree to load our configured Jar into the program running.

3.3 introducing Java agent into the program

cn.bugstack.guide.idea.plugin.PerRun

public class PerRun extends JavaProgramPatcher {

    @Override
    public void patchJavaParameters(Executor executor, RunProfile configuration, JavaParameters javaParameters) {

        RunConfiguration runConfiguration = (RunConfiguration) configuration;
        ParametersList vmParametersList = javaParameters.getVMParametersList();
        vmParametersList.addParametersString("-javaagent:" + agentCoreJarPath);
        vmParametersList.addNotEmptyProperty("guide-idea-plugin-probe.projectId", runConfiguration.getProject().getLocationHash());

    }

}
  • By inheriting the JavaProgramPatcher class, we implement the patchJavaParameters method, and configure our own - javaagent package to be loaded through the configuration attribute.
  • In this way, the function of intercepting and printing SQL will be executed when the plug-in is installed and the code is run through the IDEA.

3.4 plugin.xml add configuration

<extensions defaultExtensionNs="com.intellij">
    <!-- Add your extensions here -->
    <java.programPatcher implementation="cn.bugstack.guide.idea.plugin.PerRun"/>
</extensions>
  • After that, you also need to configure the developed loading class to Java Programpatcher so that when the program runs, it can be loaded into.

4, Test verification

  • Prepare a project with database operation. What you need is JDBC. If it is other, you need to expand it yourself
  • After starting the plug-in, open your project, run unit tests, and view the print area

Launch plug-in

  • If you are downloading new code, you can start it in probe plugin - > tasks - > IntelliJ - > runide.

unit testing

@Test
public void test_update(){
    User user = new User();
    user.setId(1L);
    user.setName("Xie plane");
    user.setAge(18);
    user.setAddress("Beijing.Daxing District.Yizhuang Economic Development Zone");
    userDao.update(user);
}

test result

22:30:55.593 [main] DEBUG cn.bugstack.test.demo.infrastructure.dao.UserDao.update[143] - ==>  Preparing: UPDATE user SET name=?,age=?,address=? WHERE id=? 
22:30:55.625 [main] DEBUG cn.bugstack.test.demo.infrastructure.dao.UserDao.update[143] - ==> Parameters: Xie plane(String), 18(Integer), Beijing.Daxing District.Yizhuang Economic Development Zone(String), 1(Long)
Database name: Mysql
 thread  ID: 1
 original SQL: 
UPDATE user SET name=?,age=?,address=?
        WHERE id=?
replace SQL: 
UPDATE user SET name='Xie plane',age=18,address='Beijing.Daxing District.Yizhuang Economic Development Zone'
        WHERE id=1
  • As can be seen from the test results, we can get the SQL statements directly used for test and verification, so we don't have to modify the SQL with question marks after copying.

5, Summary

  • First of all, in this chapter, we initially try to use the multi module method to create a project, which can better maintain the code modules required under various projects. You can also try creating multi module projects using gradle
  • This article is only an introduction to the use of bytecode stake enhancement. This technology can also be applied to more scenarios and develop various tools to improve R & D efficiency.
  • Learn how additional Jar packages are loaded into the project, and how to make javaagent introduce its own developed probe components through configuration.

6, Series recommendation

Topics: Java github Spring Back-end Programmer