Java中的bridge与synthetic方法

Java中的bridge与synthetic方法

背景:

之前在做一些通过java agent做字节码增强的事情,需求之一是通过增强日志库把每打印的一条日志前面加上当前链路的traceId,在我们增强了logback的MessageConverter这个类之后发现我们需要打印的日志都打印了两遍,看起来就像是convert方法被调用了两次。

使用javap反编译

由于做增强需要经常跟字节码打交道,于是我们用javap反编译了MessageConverter这个类,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

public java.lang.String convert(ch.qos.logback.classic.spi.ILoggingEvent);
descriptor: (Lch/qos/logback/classic/spi/ILoggingEvent;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: invokeinterface #2, 1 // InterfaceMethod ch/qos/logback/classic/spi/ILoggingEvent.getFormattedMessage:()Ljava/lang/String;
6: areturn
LineNumberTable:
line 26: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lch/qos/logback/classic/pattern/MessageConverter;
0 7 1 event Lch/qos/logback/classic/spi/ILoggingEvent;

public java.lang.String convert(java.lang.Object);
descriptor: (Ljava/lang/Object;)Ljava/lang/String;
flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #3 // class ch/qos/logback/classic/spi/ILoggingEvent
5: invokevirtual #4 // Method convert:(Lch/qos/logback/classic/spi/ILoggingEvent;)Ljava/lang/String;
8: areturn
LineNumberTable:
line 23: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lch/qos/logback/classic/pattern/MessageConverter;

结果确实有两个名叫convert的方法,但是第二个的入参是个Object而且checkcast之后直接调用了原本的第一个convert(ILoggingEvent)方法。注意下面这个convert的flags,比上面要多出两个:ACC_BRIDGE, ACC_SYNTHETIC。
这两个flag是干什么的?谁会用到这个方法?

synthetic 与 Bridge方法

简单来说,SYNTHETIC方法是编译器生成的,不会出现在源码中,一般在外部类访问内部类的成员变量时会生成类似access1这样的synthetic方法。示例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13

public class SyntheticMethodTest {

public static class A {
private A(){}
private int x;
}

public static void main(String[] args) {
A a = new A();
a.x = 2;
}
}

对于A的int x就会生成access$1 access$2这样的方法,并且modifier的标记是static跟synthetic。

还有一种情况就是上文出现的情况,我们先看下MessageConvert的继承结构如下:

1
2
3
4
public class MessageConverter extends ClassicConverter
abstract public class ClassicConverter extends DynamicConverter<ILoggingEvent>
abstract public class DynamicConverter<E> extends FormattingConverter<E> implements LifeCycle, ContextAware
abstract public class FormattingConverter<E> extends Converter<E>

注意Converter这个接口实际上是带泛型的,而我们通过这种方式来使用

Converter c=new MessageConverter();
c.convert("xxx");

由于java1.5之前没有泛型,这么写现在也是可以的。那这样java怎么找到对应的convert方法呢?

MessageConverter的convert方法里面接收的是ILogEvent而不是String,所以这时候javac给生成了一个方法入参是Object的同名方法,这个方法同时有两个flag:Bridge和Synthetic。

具体执行的内容就是checkcast,然后调用原有方法。checkcast用来确定可以类型转换到ILoggingEvent上,对于上面这个例子,c.convert(“xxx”)就会在运行时报ClassCastException。这类方法就叫做桥接方法,就会有Bridge的flag。