简介
在阅读MyBatis源码的时候看到了MapperAnnotationBuilder中调用了一个method.isBrige()方法,顺便看了一下这个issue#237。issue237传送门,顺便还看到了有一个isSynthetic方法。就顺便了解了一下这2个方法。
Brige
import java.lang.reflect.Method;
public class BrigeSynthetic {
interface GenericInterface<E>{
void function(E e);
}
class Brige implements GenericInterface<String>{
@Override
public void function(String e) {
}
}
public static void main(String[] args) {
Method[] methods = Brige.class.getDeclaredMethods();
for(Method method:methods){
System.out.println(method.toString() + " isBrige:" + method.isBridge() + " isSynthetic:" + method.isSynthetic());
}
}
}
我们先看一下输出的结果:
public void cn.freemethod.reflect.BrigeSynthetic$Brige.function(java.lang.String) isBrige:false isSynthetic:false
public void cn.freemethod.reflect.BrigeSynthetic$Brige.function(java.lang.Object) isBrige:true isSynthetic:true
我们知道Java的泛型是伪泛型,是一种语法糖,实际上在编译的时候就进行了泛型擦除。对于类和泛型方法我们知道可以指定泛型参数和类型推导完成替换为实际的类型。但是对于接口怎么办?
实际上对于接口泛型擦除使用的是Object类作为实际类型,这和如果一个泛型类不没有指定泛型参数所用的类型一样。
如上的:
interface GenericInterface<E>{
void function(E e);
}
泛型擦除之后,就变为:
interface GenericInterface{
void function(Object e);
}
又如:一个泛型类ArrayList,没有指定泛型参数:
ArrayList list = new ArrayList();
//警告:ArrayList is a raw type.
//References to generic type ArrayList<E> should be parameterized
实际上编译的时候是使用Object替换了ArrayList中的泛型参数的。
那么问题来了,根据上面的理论,我们开始的代码就变成了下面这样:
import java.lang.reflect.Method;
public class BrigeSynthetic {
interface GenericInterface{
void function(Object e);
}
class Brige implements GenericInterface{
@Override
public void function(String e) {
}
}
public static void main(String[] args) {
Method[] methods = Brige.class.getDeclaredMethods();
for(Method method:methods){
System.out.println(method.toString() + " isBrige:" + method.isBridge() + " isSynthetic:" + method.isSynthetic());
}
}
}
事实上上面的代码是通不过编译的,有2个问题,一是@Override必须在重写了父类的方法,二是Brige有没有实现的方法。归根结底一个问题,方法类型不匹配。上面的是方法重载,而不是重写。
那为什么使用泛型是没有问题呢?这就是我们要介绍的重点,Brige方法,编译器为我们生成了一个Brige方法。实际编译之后相等于下面这样的代码。
import java.lang.reflect.Method;
public class BrigeSynthetic {
interface GenericInterface{
void function(Object e);
}
class Brige implements GenericInterface{
public void function(String e) {
}
@Override
public void function(Object e) {
function((String)e);
}
}
public static void main(String[] args) {
Method[] methods = Brige.class.getDeclaredMethods();
for(Method method:methods){
System.out.println(method.toString() + " isBrige:" + method.isBridge() + " isSynthetic:" + method.isSynthetic());
}
}
}
其中,下面的方法就是Brige方法。
@Override
public void function(Object e) {
function((String)e);
}
我们可以反编译一下Brige类,看一下字节码验证一下:
使用javap命令,使用-v输出全部细节,重定向到文件中:
javap -v BrigeSynthetic$Brige.class >> brige.txt
Classfile /E:/studio/inspect/target/classes/cn/freemethod/reflect/BrigeSynthetic$Brige.class
MD5 checksum fde974f5a6da0b44dcadf5c53243f3da
Compiled from "BrigeSynthetic.java"
class cn.freemethod.reflect.BrigeSynthetic$Brige extends java.lang.Object implements cn.freemethod.reflect.BrigeSynthetic$GenericInterface<java.lang.String>
SourceFile: "BrigeSynthetic.java"
Signature: #33 // Ljava/lang/Object;Lcn/freemethod/reflect/BrigeSynthetic$GenericInterface<Ljava/lang/String;>;
InnerClasses:
#37= #1 of #35; //Brige=class cn/freemethod/reflect/BrigeSynthetic$Brige of class cn/freemethod/reflect/BrigeSynthetic
static #38= #5 of #35; //GenericInterface=class cn/freemethod/reflect/BrigeSynthetic$GenericInterface of class cn/freemethod/reflect/BrigeSynthetic
minor version: 0
major version: 51
flags: ACC_SUPER
Constant pool:
#1 = Class #2 // cn/freemethod/reflect/BrigeSynthetic$Brige
#2 = Utf8 cn/freemethod/reflect/BrigeSynthetic$Brige
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Class #6 // cn/freemethod/reflect/BrigeSynthetic$GenericInterface
#6 = Utf8 cn/freemethod/reflect/BrigeSynthetic$GenericInterface
#7 = Utf8 this$0
#8 = Utf8 Lcn/freemethod/reflect/BrigeSynthetic;
#9 = Utf8 <init>
#10 = Utf8 (Lcn/freemethod/reflect/BrigeSynthetic;)V
#11 = Utf8 Code
#12 = Fieldref #1.#13 // cn/freemethod/reflect/BrigeSynthetic$Brige.this$0:Lcn/freemethod/reflect/BrigeSynthetic;
#13 = NameAndType #7:#8 // this$0:Lcn/freemethod/reflect/BrigeSynthetic;
#14 = Methodref #3.#15 // java/lang/Object."<init>":()V
#15 = NameAndType #9:#16 // "<init>":()V
#16 = Utf8 ()V
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Lcn/freemethod/reflect/BrigeSynthetic$Brige;
#21 = Utf8 function
#22 = Utf8 (Ljava/lang/String;)V
#23 = Utf8 e
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 (Ljava/lang/Object;)V
#26 = Class #27 // java/lang/String
#27 = Utf8 java/lang/String
#28 = Methodref #1.#29 // cn/freemethod/reflect/BrigeSynthetic$Brige.function:(Ljava/lang/String;)V
#29 = NameAndType #21:#22 // function:(Ljava/lang/String;)V
#30 = Utf8 SourceFile
#31 = Utf8 BrigeSynthetic.java
#32 = Utf8 Signature
#33 = Utf8 Ljava/lang/Object;Lcn/freemethod/reflect/BrigeSynthetic$GenericInterface<Ljava/lang/String;>;
#34 = Utf8 InnerClasses
#35 = Class #36 // cn/freemethod/reflect/BrigeSynthetic
#36 = Utf8 cn/freemethod/reflect/BrigeSynthetic
#37 = Utf8 Brige
#38 = Utf8 GenericInterface
{
final cn.freemethod.reflect.BrigeSynthetic this$0;
flags: ACC_FINAL, ACC_SYNTHETIC
cn.freemethod.reflect.BrigeSynthetic$Brige(cn.freemethod.reflect.BrigeSynthetic);
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #12 // Field this$0:Lcn/freemethod/reflect/BrigeSynthetic;
5: aload_0
6: invokespecial #14 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcn/freemethod/reflect/BrigeSynthetic$Brige;
public void function(java.lang.String);
flags: ACC_PUBLIC
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 16: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcn/freemethod/reflect/BrigeSynthetic$Brige;
0 1 1 e Ljava/lang/String;
public void function(java.lang.Object);
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #26 // class java/lang/String
5: invokevirtual #28 // Method function:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
}
我们在最后可以看到有一个public void function(java.lang.Object)方法。并且调用了重载方法Method function:(Ljava/lang/String;)V。 其实在我们的反射输出中已经验证了这一点。
synthetic
至于什么是synthetic方法,前面其实都已经看到了一些。就是编译器添加的方法,这个说法可能不太准确,但是大概的意思就是这个。通过Javaassist,cglib添加的方法都不是synthetic方法。
下面看一个测试的例子:
import java.lang.reflect.Method;
//import javassist.CannotCompileException;
//import javassist.ClassPool;
//import javassist.CtClass;
//import javassist.CtMethod;
//import javassist.CtNewMethod;
//import javassist.NotFoundException;
public class OutClass {
private int out;
public void accessInnerClass() {
InnerClass innerClass = new InnerClass();
innerClass.in = 1;// 语句1
}
class InnerClass {
private int in;
public void printOut() {
System.out.println(out);// 语句2
}
public int getIn() {
return in;
}
}
public static void main(String[] args) {
// CtClass cc = null;
// try {
// cc = ClassPool.getDefault().get("cn.freemethod.reflect.DynamicClass");
// CtMethod m = CtNewMethod.make("public void xxx() { }", cc);
// cc.addMethod(m);
// } catch (NotFoundException | CannotCompileException e) {
// e.printStackTrace();
// }
Method[] methods = null;
methods = OutClass.class.getDeclaredMethods();
// try {
// methods = cc.toClass().getDeclaredMethods();
// } catch (SecurityException e) {
// e.printStackTrace();
// } catch (CannotCompileException e) {
// e.printStackTrace();
// }
System.out.println("----------OutClass-----------");
printMethod(methods);
methods = InnerClass.class.getDeclaredMethods();
System.out.println("----------InnerClass-----------");
printMethod(methods);
}
public static void printMethod(Method[] methods) {
for (Method method : methods) {
System.out.println(
method.toString() + " isBrige:" + method.isBridge() + " isSynthetic:" + method.isSynthetic());
}
}
}
输出结果:
----------OutClass-----------
public static void cn.freemethod.reflect.OutClass.main(java.lang.String[]) isBrige:false isSynthetic:false
public void cn.freemethod.reflect.OutClass.accessInnerClass() isBrige:false isSynthetic:false
static int cn.freemethod.reflect.OutClass.access$0(cn.freemethod.reflect.OutClass) isBrige:false isSynthetic:true
public static void cn.freemethod.reflect.OutClass.printMethod(java.lang.reflect.Method[]) isBrige:false isSynthetic:false
----------InnerClass-----------
static void cn.freemethod.reflect.OutClass$InnerClass.access$0(cn.freemethod.reflect.OutClass$InnerClass,int) isBrige:false isSynthetic:true
public void cn.freemethod.reflect.OutClass$InnerClass.printOut() isBrige:false isSynthetic:false
public int cn.freemethod.reflect.OutClass$InnerClass.getIn() isBrige:false isSynthetic:false
我们看到OutClass和InnerClass中都多了一个access$0这个静态方法,其实这是因为在我们外部类要访问内部类成员,内部类要访问外部类成员的结果。在我们标注语句1的地方访问了内部类的成员,其实是通过access$0这个静态方法传递一个内部类实例获取的。内部类也一样。
有兴趣的同学可以自己反编译一下OutClass和InnerClass看一下。
注释是部分是测试Javaassist动态添加的方法是不是synthetic方法。