Each one
Hu: talk about some issues, less chat about doctrine
Foreword
Spring MVC
And MyBatis
as 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
MyBatis
when (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 MyBatis
when the question arises about this and also try out past @Param
comments, because I think a name twice asked me to write a bit superfluous (me too lazy).
And Spring MVC
humane treatment than it felt MyBatis
to 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
java
The user knows that .java
the file belongs to the source file, it needs to go through a javac
compiler to .class
bytecode files can be JVM
executed.
To .class
bytecode understand a little bit of a small partner should also know this: Java
at compile time to method, the default is to 不会
retain the method parameter names , so if we want to run from the .class
byte 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 MVC
time, Controller
the 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 MVC
even 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 MVC
means ParameterNameDiscoverer
to complete the method for obtaining the parameter name, and then complete the data package. On ParameterNameDiscoverer
its explanation, can first see: [small] Spring home Spring standard processing components large collection (ParameterNameDiscoverer, AutowireCandidateResolver, ResolvableType ...)
The Q describes ParameterNameDiscoverer
the basic use and ability to deliver, but not in-depth analysis. Then this article will analyze why Spring MVC
why 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: LocalVariableTable
andLineNumberTable
. 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 JVM
the implementation of the .class
file, and the line of the file and .java
the line corresponding to the source file is definitely not on, why was able to line number in .java
correspondence on file?
This is LineNumberTable
its role of:LineNumberTable属性存在于代码(字节码)属性中, 它建立了字节码偏移量到源代码行号之间的联系
LocalVariableTable
LocalVariableTable
Attribute 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 javac
and javap
commands to show you this: The
.java
source 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.java
compiled into .class
bytecode and then use javap -verbose MainTest2.class
to view the bytecode information is as follows:
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 : LineNumberTable
it 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.java
to compile, and then look at the byte code corresponding to the information below (note the difference and above):
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 -parameters
what is the difference? ?
Know -g
compilation parameters of small, hand to Java8
the new -parameters
people know some more. It and -g
parameters What difference does it make? ? ?
Seeing is believing, I prefer to put forward their own examples to illustrate the problem, .java
the 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 -parameters
to compile after the implementation of the results is as follows:
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 -parameters
byte code information compiled, allowing you to do the analysis and comparison:
== 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, Java8
Primal support: from java.lang.reflect.Parameter
can 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:
- Must Java8 or more (due java8 already very high penetration rate, so this better)
- Translation parameters must have
-parameters
(because the compiler dependent parameters, so the migration is not very friendly, this is more deadly)
Specify -parameters
compiler arguments ways:
- Manually compile command:
javac -parameters XXX.java
- IDE (with Idea for example) to compile:
- 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
+ javap
command
As examples may be used javac -g
compiled, then use javap
the 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 Java
byte 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 class
is 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 :
- idea is used by default javac compiler is compiled bytecode with
LocalVariableTable
the. But you can also turn it off, as shown below:
- 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):
ASM
:Java
Bytecode 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.Javassist
: Effects above. Compared toASM
it 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 -Proxy
Dynamic 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 ~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 AOP
As well asHibernate
to create a proxy object are usedCGLIB
Previous article has introduced the direct use CGLIB
of the API
operated bytecode / generate a proxy object, the article simply demonstrates directly at ASM
the frame to operate the examples:
ASM Example of use
First import asm
dependencies:
<!-- 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 ASM
tools methods getMethodParamNames(Method)
to get to any Method
of 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 MVC
Haoshi but it does not depend on -parameters
the parameters, nor on -g
the compiler parameters, because it is the means ASM
- to achieve
spring-core
There is a ParameterNameDiscoverer
is 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 MVC
its final relies DefaultParameterNameDiscoverer
to 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());
}
}
}
DefaultParameterNameDiscoverer
It is a manifestation of chain of responsibility pattern, relied add in the realization of class to handle, that is, two brothers:
StandardReflectionParameterNameDiscoverer
: depend on -parameters
to be effective (there is java version requires compiling and parameters required)
LocalVariableTableParameterNameDiscoverer
: Based ASM
achieve, no version and build parameters requirements ~
Note:
Spring
UseASM
no additional guide package, because the self-sufficiency:
MyBatis为何不好使?
The first to use the need to make this clear: MyBatis
is 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 ASM
also not help MyBatis
to simplify development?
Look at the example I gave, perhaps you will be able to understand and not blame MyBatis
it:
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
default
method andstatic
parameter names may be the normal method is to get to, there is little interest in the partnership can try their hands ~
As to ASM
why invalid interfaces , the fundamental reason I show you will see a bytecode clear:
because there is no way abstract method body, there is no local variables, naturally there is no local variable table, so even if ASM
can not get its variable name ~
Description: In
Java8
use the-parameter
parameter even in the interface, can directly obtain the reference to the name of the Method, the pair ofMyBatis
are 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 ASM
basic 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 ==