alibaba / alibaba/one-java-agent

匹配 interface的机制会提前加载类,导致 ClassFileTransformer#transform 丢失部分类

Aperta
#37 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
bug
Lingua principale
Java
Stelle
579
Fork
128
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Descrizione

## 重现方式

写一个匹配interface的`@Instrument`,比如

```java
@Instrument(Interface = "org.apache.dubbo.rpc.Invoker")
public abstract class Invoker {

/**
* invoke.
*
* @param invocation
* @return result
* @throws RpcException
*/
public Result invoke(Invocation invocation) throws RpcException {
DubboUtils.test(invocation);
System.err.println("invoker class: " + this.getClass().getName());
Result result = InstrumentApi.invokeOrigin();
System.err.println("result:" + result + ", invoker class: " + this.getClass().getName());
return result;
}

}
```

再写一个匹配abstract类的匹配,比如:

```java
@Instrument(Class = "org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory")
public class AbstractAutowireCapableBeanFactory {

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
return InstrumentApi.invokeOrigin();
}
```

那么在加载类 org.springframework.beans.factory.support.DefaultListableBeanFactory 时,会经过`Invoker`的判断

```java
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
```

对 `@Instrument(Interface = "org.apache.dubbo.rpc.Invoker")` 的匹配判断比较复杂,因为 interface 可能有多重继承

* **目前的实现比较简单,直接调用loader.loadClass来加载 interface了。**
* 导致结果是在 ClassFileTransformer#transform 处理 DefaultListableBeanFactory 时,又加载了 AbstractAutowireCapableBeanFactory ,所以后面 AbstractAutowireCapableBeanFactory 不会被 ClassFileTransformer#transform 处理了!
* 这里 jdk处理的逻辑不符合用户的想像

```java
public class SimpleInterfaceMatcher implements ClassMatcher {

@Override
public boolean match(ClassLoader loader, String className, Class classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
loader = ClassLoaderUtils.wrap(loader);

if (classBeingRedefined != null) { // 在retransform 时,可以直接判断
return match(classBeingRedefined);
} else {
// 读取出具体的 类名,还有父类名, 如果有匹配,则返回 true,没有,就返回 false
ClassReader reader = new ClassReader(classfileBuffer);
String clazzName = reader.getClassName();
String superName = reader.getSuperName();
String[] interfacesArray = reader.getInterfaces();

// 如果是接口,则没有需要处理的地方
if ((reader.getAccess() & Opcodes.ACC_INTERFACE) != 0) {
return false;
}

if (interfaces != null && interfaces.contains(clazzName.replace('/', '.'))) {
return true;
}

for (String i : interfacesArray) {
try {
Class interfaceClass = loader.loadClass(i.replace('/', '.'));
if (matchInterface(interfaceClass)) {
return true;
}
} catch (ClassNotFoundException e) {
// ignore
}
}
```

## 后续

* 需要重新实现 interface匹配的实现,避免加载类

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

Inizia da SimpleInterfaceMatcher.match e segui il percorso di corrispondenza delle interfacce utilizzato durante ClassFileTransformer#transform. Riproduci lo scenario con DefaultListableBeanFactory e Invoker, quindi verifica che la corrispondenza non carichi le classi delle interfacce o delle superclassi, continuando però a riconoscere le interfacce ereditate. Il lavoro è completato quando AbstractAutowireCapableBeanFactory rimane disponibile per l’elaborazione da parte del transformer.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
java
Ambito
devtools
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.