Why Spring MVC method available to the parameter name, and MyBatis has not? [Enjoy learning Spring MVC]

Each one

Hu: talk about some issues, less chat about doctrine

Foreword

Spring MVCAnd MyBatisas two of the most popular current framework, we usually develop are in use. If you think to go one step deeper, you should have this question:

  • In use Spring MVC, when you use without annotations, as long as the parameter name and the parameter key corresponding to the request, the value will automatically complete the package
  • In use MyBatiswhen (interface mode), when the parameter passing xml interface method in the SQL statement, 必须(当然不是100%的必须,特殊情况此处不做考虑)using @Param('')the specified key value, in SQL can only be taken into

I dare to believe that it is definitely not my own question, because I was the first time MyBatiswhen the question arises about this and also try out past @Paramcomments, because I think a name twice asked me to write a bit superfluous (me too lazy).
And Spring MVChumane treatment than it felt MyBatisto deal with this is simply weak burst. Incomprehensible for so long, and today I can finally explain this phenomenon, and to break its veil ~

Problems found

javaThe user knows that .javathe file belongs to the source file, it needs to go through a javaccompiler to .classbytecode files can be JVMexecuted.
To .classbytecode understand a little bit of a small partner should also know this: Javaat compile time to method, the default is to 不会retain the method parameter names , so if we want to run from the .classbyte code to get in direct method is the parameter name I can not do.

The following case, it is clear that obtaining less than the true parameter name myself:

public static void main(String[] args) throws NoSuchMethodException {
    Method method = Main.class.getMethod("test1", String.class, Integer.class);
    int parameterCount = method.getParameterCount();
    Parameter[] parameters = method.getParameters();

    // 打印输出:
    System.out.println("方法参数总数:" + parameterCount);
    Arrays.stream(parameters).forEach(p -> System.out.println(p.getType() + "----" + p.getName()));
}

Print content:

方法参数总数:2
class java.lang.String----arg0
class java.lang.Integer----arg1

From the results, we can not get to see the true method parameter names (acquired is meaningless arg0、arg1, etc.), this results in line with our theoretical knowledge as well as expected .

If you have some technical sensitivity, this time you should have this question: in the use of Spring MVCtime, Controllerthe method does not use the same annotation can automatically package ah, shaped like this:

@GetMapping("/test")
public Object test(String name, Integer age) {
    String value = name + "---" + age;
    System.out.println(value);
    return value;
}

Request:/test?name=fsx&age=18 . Console output:

fsx---18

From the results can be seen: the seemingly impossible case, Spring MVCeven to do (get to the method parameter names, and then complete the package), is not it a bit weird? ? ?

Look at this example (Spring MVC get the scene to restore the parameter name):

public static void main(String[] args) throws NoSuchMethodException {
    Method method = Main.class.getMethod("test1", String.class, Integer.class);
    MethodParameter nameParameter = new MethodParameter(method, 0);
    MethodParameter ageParameter = new MethodParameter(method, 1);

    // 打印输出:
    // 使用Parameter输出
    Parameter nameOriginParameter = nameParameter.getParameter();
    Parameter ageOriginParameter = ageParameter.getParameter();
    System.out.println("===================源生Parameter结果=====================");
    System.out.println(nameOriginParameter.getType() + "----" + nameOriginParameter.getName());
    System.out.println(ageOriginParameter.getType() + "----" + ageOriginParameter.getName());
    System.out.println("===================MethodParameter结果=====================");
    System.out.println(nameParameter.getParameterType() + "----" + nameParameter.getParameterName());
    System.out.println(ageParameter.getParameterType() + "----" + ageParameter.getParameterName());
    System.out.println("==============设置上ParameterNameDiscoverer后MethodParameter结果===============");
    ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    nameParameter.initParameterNameDiscovery(parameterNameDiscoverer);
    ageParameter.initParameterNameDiscovery(parameterNameDiscoverer);
    System.out.println(nameParameter.getParameterType() + "----" + nameParameter.getParameterName());
    System.out.println(ageParameter.getParameterType() + "----" + ageParameter.getParameterName());
}

Output:

===================源生Parameter结果=====================
class java.lang.String----arg0
class java.lang.Integer----arg1
===================MethodParameter结果=====================
class java.lang.String----null
class java.lang.Integer----null
==============设置上ParameterNameDiscoverer后MethodParameter结果===============
class java.lang.String----name
class java.lang.Integer----age

From the results it can be seen: Spring MVCmeans ParameterNameDiscovererto complete the method for obtaining the parameter name, and then complete the data package. On ParameterNameDiscovererits explanation, can first see: [small] Spring home Spring standard processing components large collection (ParameterNameDiscoverer, AutowireCandidateResolver, ResolvableType ...)

The Q describes ParameterNameDiscovererthe basic use and ability to deliver, but not in-depth analysis. Then this article will analyze why Spring MVCwhy can correct analytic method parameter names to this problem, from the perspective of the bytecode-depth analysis of the reason -


For ease of understanding, the first two concepts briefly bytecode: LocalVariableTableandLineNumberTable . It said the two brothers often been out together, of course, is the focus of this article LocalVariableTable, but also to take this opportunity in passing LineNumberTable.

LineNumberTable

Have you ever been in doubt: online program throws an exception when the line number is displayed, why exactly is your source on that line do ? This is because there are questions about JVMthe implementation of the .classfile, and the line of the file and .javathe line corresponding to the source file is definitely not on, why was able to line number in .javacorrespondence on file?
This is LineNumberTableits role of:LineNumberTable属性存在于代码(字节码)属性中, 它建立了字节码偏移量到源代码行号之间的联系

LocalVariableTable

LocalVariableTableAttribute establish the correspondence between the method of local variables and local variables in the source code. This property is also present in the code (bytecode) and ~
can be seen from the name it: it is 局部变量a collection. It describes the local variable and the descriptor corresponding relationship between the source and the well .

Now I use javacand javapcommands to show you this: The
.javasource code is as follows:

package com.fsx.maintest;
public class MainTest2 {
    public String testArgName(String name,Integer age){
        return null;
    }
}

Description: Source I always write head, so please pay attention to the line number ~

Use javac MainTest2.javacompiled into .classbytecode and then use javap -verbose MainTest2.classto view the bytecode information is as follows:
Here Insert Picture Description
can be seen from the figure, exactly as I marked out a red line number and source code at the corresponding line number which answers the questions we above the : LineNumberTableit records the line number in the source code at.
Tips: here and no, and no, and no LocalVariableTable.

The same source, I use javac -g MainTest2.javato compile, and then look at the byte code corresponding to the information below (note the difference and above):
Here Insert Picture Description
Here one more LocalVariableTable, namely the local variable table, it will record the parameter name into the parameters of our approach. Since the record, so that we can get this name by analyzing bytecode information -

Description: javac debugging options mainly consists of three sub-options: lines,source,vars
If you do not compile with -g to retain only the source file and line number information; if it is compiled with -g to have a ~

And -parameterswhat is the difference? ?

Know -gcompilation parameters of small, hand to Java8the new -parameterspeople know some more. It and -gparameters What difference does it make? ? ?

Seeing is believing, I prefer to put forward their own examples to illustrate the problem, .javathe source code is as follows:

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class MainTest2 {

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = MainTest2.class.getMethod("testArgName", String.class, Integer.class);
        System.out.println("paramCount:" + method.getParameterCount());
        for (Parameter parameter : method.getParameters()) {
            System.out.println(parameter.getType().getName() + "-->" + parameter.getName());
        }
    }

    public String testArgName(String name, Integer age) {
        return null;
    }
}

The following were used javac、javac -g、javac -parametersto compile after the implementation of the results is as follows:
Here Insert Picture Description
from separately compiled, the results seen in Figure print run, and the result of the difference between them has been very clear, I will not pen and ink, doubt can give me a message.

Also attach a -parametersbyte code information compiled, allowing you to do the analysis and comparison:
Here Insert Picture Description


== acquisition method parameter name 3 ways to introduce ==

Although the Java compiler default parameter name will erase method of the case, but the above describes the relevant knowledge bytecode it shows that we still have a way to get the method of parameter names. Here are three scenarios for reference.

Method One: Use-parameters

The most straightforward way, Java8Primal support: from java.lang.reflect.Parametercan be obtained, so that the form:

public class MainTest2 {

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = MainTest2.class.getMethod("testArgName", String.class, Integer.class);
        System.out.println("paramCount:" + method.getParameterCount());
        for (Parameter parameter : method.getParameters()) {
            System.out.println(parameter.getType().getName() + "-->" + parameter.getName());
        }
    }

    public String testArgName(String name, Integer age) {
        return null;
    }
}

Output:

paramCount:2
java.lang.String-->name
java.lang.Integer-->age

Of course, it has two biggest drawbacks:

  1. Must Java8 or more (due java8 already very high penetration rate, so this better)
  2. Translation parameters must have -parameters(because the compiler dependent parameters, so the migration is not very friendly, this is more deadly)

Specify -parameterscompiler arguments ways:

  1. Manually compile command:javac -parameters XXX.java
  2. IDE (with Idea for example) to compile:
    Here Insert Picture Description
  3. Maven build: by compiling the plug designated to ensure the correctness of the migration project (recommended)
<!-- 编译环境在1.8编译 且附加编译参数:-parameters-->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
        <compilerArgs>
            <arg>-parameters</arg>
        </compilerArgs>
        <source>${java.version}</source>
        <target>${java.version}</target>
        <compilerVersion>${java.version}</compilerVersion>
        <encoding>${project.build.sourceEncoding}</encoding>
    </configuration>
</plugin>

Pros: Easy
Disadvantages: special designation -parameters, not too convenient ( of course use maven plugin editor to specify the program is relatively reliable and recommended )

Option Two: use the -g+ javapcommand

As examples may be used javac -gcompiled, then use javapthe acquired information bytecode, then their names out of the extracted parameters according to the format information (their own, their own, do it yourself)

This is tantamount to let you parse the http protocol in general, will you do it? ? ? So although this approach is a way, but it is clear that the use of inconvenience

Option Three: With ASM(recommended)

Speaking of ASM, at least for small partners name should be no stranger. It is a Javabyte code manipulation frame, it can be used to dynamically generate the class or classes of enhancement of both, it is possible to alter the behavior analysis of class information, a new class can be generated even according to user requirements.

For ASM, it Java classis described as a tree; using the "Visitor" mode (mode guide) to traverse the entire binary structure; event-driven approach to focus on such that the user need only be programmed significant portion (e.g. article focuses on the method parameters other non-interest), without having to know Java class file format of all the details .

----

ASM way , it is still based on the compiled bytecode of doing things, the so-called can not make bricks without straw , so it still must rely on the compile-time LocalVariableTable(-g parameters).

You might ask: I use the idea to compile / maven compiler are not themselves specify the -g parameter ah, why Ye make it? Your question is also my questions, I still can not figure out a more fundamental reason, but I can say the following two phenomena :

  1. idea is used by default javac compiler is compiled bytecode with LocalVariableTablethe. But you can also turn it off, as shown below:
    Here Insert Picture Description
  2. maven uses the default javac also compiled bytecode also with LocalVariableTable(but maven compile time compile command and parameters, I can not know. maven urge proficient students pointing ~)

----

Episode: Acting on science (Proxy, CGLIB, Javassist, ASM):

  1. ASM: JavaBytecode manipulation framework open source. Manipulating the underlying level of the JVM assembler instruction level , which requires the user of the class structure and assembly instructions JVM have some knowledge, very demanding.
  2. Javassist: Effects above. Compared to ASMit it is characterized by simple operation, and can also speed (of course, no quick ASM). It is important: it does not require you to understand the JVM instructions / assembly instructions -
  3. ProxyDynamic Agent: dynamically generated (non-translated well in advance) proxy class: $Proxy0 extends Proxy implements MyInterface{ ... }, which determines that it can only be to the interface (or class implements interface) proxy, single inheritance also determines that it can not (abstract) proxy class ~
  4. CGLIB: Is a based ASM powerful, high-performance, high-quality bytecode generation library. It can be extended at runtime Java classes and implement Java interfaces.

    > Spring AOPAs well as Hibernateto create a proxy object are usedCGLIB

Previous article has introduced the direct use CGLIBof the APIoperated bytecode / generate a proxy object, the article simply demonstrates directly at ASMthe frame to operate the examples:

ASM Example of use

First import asmdependencies:

<!-- https://mvnrepository.com/artifact/asm/asm -->
<dependency>
    <groupId>asm</groupId>
    <artifactId>asm</artifactId>
    <version>3.3.1</version>
</dependency>

Description: asm has been upgraded to version 7.x, and GAV has changed. Since I am familiar point 3.x, so here I was a conservative, right ~

Based ASMtools methods getMethodParamNames(Method)to get to any Methodof the parameter name:

public class MainTest2 {

    // 拿到指定的Method的入参名们(返回数组,按照顺序返回)
    public static String[] getMethodParamNames(Method method) throws IOException {
        String methodName = method.getName();
        Class<?>[] methodParameterTypes = method.getParameterTypes();
        int methodParameterCount = methodParameterTypes.length;
        String className = method.getDeclaringClass().getName();
        boolean isStatic = Modifier.isStatic(method.getModifiers());
        String[] methodParametersNames = new String[methodParameterCount];


        // 使用org.objectweb.asm.ClassReader来读取到此方法
        ClassReader cr = new ClassReader(className);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);


        // 这一步是最红要的,开始visitor浏览了
        // ClassAdapter是org.objectweb.asm.ClassVisitor的子类~~~~
        cr.accept(new ClassAdapter(cw) {

            // 因为此处我们只关心对方法的浏览,因此此处只需要复写此方法即可
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                final Type[] argTypes = Type.getArgumentTypes(desc);

                // 只visitor方法名相同和参数类型都相同的方法~~~
                if (!methodName.equals(name) || !matchTypes(argTypes, methodParameterTypes)) {
                    return mv;
                }

                // 构造一个MethodVisitor返回 重写我们关心的方法visitLocalVariable~~~
                return new MethodAdapter(mv) {

                    //特别注意:如果是静态方法,第一个参数就是方法参数,非静态方法,则第一个参数是 this ,然后才是方法的参数
                    @Override
                    public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
                        // 处理静态方法与否~~
                        int methodParameterIndex = isStatic ? index : index - 1;
                        if (0 <= methodParameterIndex && methodParameterIndex < methodParameterCount) {
                            methodParametersNames[methodParameterIndex] = name;
                        }
                        super.visitLocalVariable(name, desc, signature, start, end, index);
                    }
                };
            }
        }, 0);
        return methodParametersNames;
    }

    /**
     * 比较参数是否一致
     */
    private static boolean matchTypes(Type[] types, Class<?>[] parameterTypes) {
        if (types.length != parameterTypes.length) {
            return false;
        }
        for (int i = 0; i < types.length; i++) {
            if (!Type.getType(parameterTypes[i]).equals(types[i])) {
                return false;
            }
        }
        return true;
    }
}

Run Case:

public class MainTest2 {
    
    // 使用工具方法获取Method的入参名字~~~
    public static void main(String[] args) throws SecurityException, NoSuchMethodException, IOException {
        Method method = MainTest2.class.getDeclaredMethod("testArgName", String.class, Integer.class);
        String[] methodParamNames = getMethodParamNames(method);

        // 打印输出
        System.out.println(StringUtils.arrayToCommaDelimitedString(methodParamNames));
    }

    private String testArgName(String name, Integer age) {
        return null;
    }
}

Output:

name,age

The expected effect of the composite, using ASM get what we expect real method parameter names (do not specify any parameters compiled oh). ASM-based way, even if you are Java8 following versions are able to get to normal, because it does not rely on the compiler parameters ~~~

== With these basics, then the book The True Story, to explain the text of the first questions: ==

Spring MVC为何好使?

First to be clear on the use of : Spring MVCHaoshi but it does not depend on -parametersthe parameters, nor on -gthe compiler parameters, because it is the means ASM- to achieve

spring-coreThere is a ParameterNameDiscovereris used to obtain the parameter name, the subbing layer asm resolution, but the interface method parameter names can not be obtained , i.e., only non-method parameter name interface class can.
From the text of the first example can be seen in Spring MVCits final relies DefaultParameterNameDiscovererto help to get into the parameter name, look at this piece of code:

// @since 4.0
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {

    public DefaultParameterNameDiscoverer() {
        if (!GraalDetector.inImageCode()) {
            if (KotlinDetector.isKotlinReflectPresent()) {
                addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
            }
            addDiscoverer(new StandardReflectionParameterNameDiscoverer());
            addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
        }
    }
}

DefaultParameterNameDiscovererIt is a manifestation of chain of responsibility pattern, relied add in the realization of class to handle, that is, two brothers:
StandardReflectionParameterNameDiscoverer: depend on -parametersto be effective (there is java version requires compiling and parameters required)
LocalVariableTableParameterNameDiscoverer: Based ASMachieve, no version and build parameters requirements ~

Note: SpringUse ASMno additional guide package, because the self-sufficiency:
Here Insert Picture Description

MyBatis为何不好使?

The first to use the need to make this clear: MyBatisis through the interface and then generate a proxy class with the SQL statement to bind to achieve.

Since there is strong ASM, then the question arises: Is it ASMalso not help MyBatisto simplify development?
Look at the example I gave, perhaps you will be able to understand and not blame MyBatisit:

public class MainTest2 {
    
    // 使用工具方法获取Method的入参名字~~~
    public static void main(String[] args) throws SecurityException, NoSuchMethodException, IOException {
        Method method = MainTest2.class.getDeclaredMethod("testArgName", String.class, Integer.class);
        String[] methodParamNames = getMethodParamNames(method);

        // 打印输出
        System.out.println(StringUtils.arrayToCommaDelimitedString(methodParamNames));
    }
}

// 接口方法
interface MyDemoInterface{
    String testArgName(String name, Integer age);
}

Output:

null,null

Even as strong as can be seen ASM, is there a way to get directly to the wood-shaped interface parameter names.
This can be understood, because the interface method is not practical method, its parameter name is the implementation class will be covered, so little interface method parameter name meaning ~

Tips: on the interface defaultmethod and staticparameter names may be the normal method is to get to, there is little interest in the partnership can try their hands ~

As to ASMwhy invalid interfaces , the fundamental reason I show you will see a bytecode clear:
Here Insert Picture Description
because there is no way abstract method body, there is no local variables, naturally there is no local variable table, so even if ASMcan not get its variable name ~

Description: In Java8use the -parameterparameter even in the interface, can directly obtain the reference to the name of the Method, the pair of MyBatisare useful. Of course, in order to ensure compatibility, personal recommendations obediently use @Param annotation to specify it ~

So far, I have reason to believe that small partners are like me, completely understand why the Spring MVC can do, but can not MyBatis this question it ~ ~ ~

to sum up

There may also be analyzed in this long troubled by your question (questions such as the title) paper deep into bytecode, the hope of answering questions for you. Also introduces the ASMbasic usage, perhaps you understand other subsequent framework will be helpful -

== If for Spring, SpringBoot, MyBatis source code analysis and other interested can add me wx: fsx641385712, manually invite you to take off into a group ==
== If for Spring, SpringBoot, MyBatis source code analysis and other interested can add me wx : fsx641385712, manually invite you into the group took off ==

Guess you like

Origin www.cnblogs.com/fangshixiang/p/11532689.html
Recommended