背景
之前开发x-apm的时候,自定义增强一个spring bean的时候,出现了一个奇怪的异常 – 找不到无参构造方法,导致bean初始化失败。原来是bytebuddy这个类库在增强类的时候,会自动增加一个synthetic的构造方法,导致spring无法找打正确的构造方法初始化。这里记录下synthetic方法和bridge方法。
synthetic方法
synthetic方法是什么呢?先来看个实际例子:
|
|
当你创建一个嵌套类(内部类)时,顶层类的私有属性和私有方法对内部类是可见的。然而jvm是如何处理这种情况的呢?jvm可不清楚什么是内部嵌套类,什么是顶层类。jvm对所有的类都一视同仁,它都认为是顶级类。所有类都会被编译成顶级类,而那些内部类编译完后会生成…$… class的类文件,如下javac编译:
|
|
当你创建内部类时,他会被编译成顶级类。那顶层类的私有属性和私有方法是如何被外部类访问的呢?
javac是这样解决这个问题的,对于任何private的字段,方法或者构造函数,如果它们也被其它顶层类所使用,就会生成一个synthetic方法。这些synthetic方法是用来访问最初的私有变量/方法/构造函数的。这些方法的生成也很智能:只有确实被外部类用到了,才会生成这样的方法
通过反编译SyntheticTest$A.class:
|
|
有个奇怪的方法SyntheticTest.access$000(this.this$0)
,这个就是java的synthetic方法。可以用java的反射再次验证这个问题:
|
|
输出:
|
|
可以看到access$000
这个方法,确实是一个synthetic方法
synthetic方法就是java编译器(例如javac)为了实现特定需求增加的方法,不存在源码中的
bridge方法
bridge方法又是什么呢?看个简单例子:
|
|
输出如下:
|
|
可以看到,多出来一个方法签名一致,返回类型为Object的bridge方法,这在java语言中是不合法的,不过在jvm中是允许的。那这个bridge方法到底作了什么呢?反编译看看:
|
|
输出如下:
|
|
可以看到,这个birdge不干别的,仅仅就是调用了原始的那个方法。所以这个方法到底有什么用,为什么需要bridge方法?看一下java手册的说明:
When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic method, called a bridge method, as part of the type erasure process. You normally don’t need to worry about bridge methods, but you might be puzzled if one appears in a stack trace.
如果一个类继承了一个范型类或者实现了一个范型接口, 那么编译器在编译这个类的时候就会生成一个叫做桥接方法的混合方法(混合方法简单的说就是由编译器生成的方法, 方法上有synthetic修饰符), 这个方法用于范型的类型安全处理,用户一般不需要关心桥接方法
其实是java为了泛型的向下兼容的一种手段。我们看下另一个例子:
|
|
这个类在泛型擦除后,变成如下形式:
|
|
子类的setData
方法签名和父类的已经不一致了。因此,MyNode.setData
方法其实已经不再重写(override)父类Node.setData
方法了。
为了解决这个问题,并且维持泛型类在泛型擦除后的多态性,java编译器会生成一个bridge方法