alibaba / alibaba/one-java-agent

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

Open
#37 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
579
Forks
128
PR merge metrics
No merged PRs in 30d

Description

## 重现方式

写一个匹配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匹配的实现,避免加载类

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in SimpleInterfaceMatcher.match and trace the interface-matching path used during ClassFileTransformer#transform. Reproduce the DefaultListableBeanFactory and Invoker scenario, then verify that matching does not load interface or superclass classes while still recognizing inherited interfaces. Done means AbstractAutowireCapableBeanFactory remains available for transformer processing.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.