Adrninistrator / java-callgraph2

Programs for producing static call graphs for Java programs.
Apache License 2.0
194 stars 69 forks source link

关于@Resource type场景无法获取到SpringBeanTypeList #18

Closed john-70 closed 1 year ago

john-70 commented 1 year ago

该场景下,UseSpringBeanByAnnotationHandler.doGetSpringBeanName 直接将类型作为 beanName(fieldSpringBeanType变量)返回,但后续 defineSpringBeanByAnnotationHandler.getSpringBeanTypeList() 仅根据 beanName 从 stringBeanNameAndTypeMap 中获取typeList, stringBeanNameAndTypeMap中没有将type作为key的数据。此时未获取到准确的type

如: @Service public class Third1AuthService extends IAuthService { }

@Service public class Third2AuthService extends IAuthService { }

@Service public class Third3AuthService extends IAuthService { }

@Resource(type = Third1AuthService.class) private IAuthService third01AuthService;

Adrninistrator commented 1 year ago

看一下用的什么版本 @Resource(type = xxx.class) 这种形式是今年1月之后的版本支持的,测试类是https://github.com/Adrninistrator/java-all-call-graph/blob/main/java-all-call-graph/src/test/java/test/call_graph/spring/bean/use/TestSpringBeanC.java

可以在java-all-call-graph项目执行gradlew test_jar命令生成测试用的test.jar验证一下

john-70 commented 1 year ago

我只使用了callgraph2,最新的1.0.8

john-70 commented 1 year ago

看了下例子,目前猜测区别在于我例子中的父类是接口,你的是抽象类

Adrninistrator commented 1 year ago
    @Resource(type = SpringServiceImplC2.class)
    protected SpringInterfaceC springServiceC2B;

我把上面示例类里改成在接口上指定@Resource注解,还是能识别出来对应SpringServiceImplC2类的,是不是相关的class文件所在的jar包没有在配置文件里指定

john-70 commented 1 year ago

我发现了真正的区别,你的SpringServiceImplC2类上的@Repository填写了value,而我仅单纯使用了@Service。我尝试将你示例中SpringServiceImplC2上@Repository删除value值"test.call_graph.spring.bean.define.SpringServiceImplC2",就复现了我的状况。

Adrninistrator commented 1 year ago

我看一下,支持一下这种情况

john-70 commented 1 year ago

另外,我的尝试结果里,还有情况是未支持的。如:仅使用@Resource,未填写name或type的场景,spring正常会通过字段的name或字段声明定义的type去匹配bean。以及@Inject @Named注解。这些后续会考虑支持吗?

Adrninistrator commented 1 year ago

@Resource不指定name或者type的情况,和@Service的使用是类似的,这种情况下没有做特殊的处理,因为对应的接口一般只有一个实现类,接口的方法本身会关联到实现类对应的方法,所以可以不用把被调用字段的类型替换成实现类的类型

@Inject、@Named这些注解,如果是对字段注入,而且需要指定name的方式的话,也可以支持,构造函数和方法注入现在还没有处理

john-70 commented 1 year ago

不指定name的场景,并非只有一个实现类,而是取了field的name去匹配。可以理解为将注解的name偷懒配置为注解所在field的name,如同"约定大于配置"的味道

john-70 commented 1 year ago

@Autowired也有类似的体验,按照类型找到多个bean时,会注入符合字段name的bean

Adrninistrator commented 1 year ago

我看下哈,这几天有空了优化下

Adrninistrator commented 1 year ago

代码提交了,版本1.0.9

john-70 commented 1 year ago

@Inject + @Named 的效果预期应该与@Autowired+@Qualifier一致,但在我的测试,2者是不一致的

@Qualifier("noService")
@Autowired
private IService nameOnlyService1;

@Named("noService")
@Inject
private IService nameOnlyService3;

private void testNameOnly1() {
    nameOnlyService1.deal();
}

private void testNameOnly3() {
    nameOnlyService3.deal();
}

// 实际类
@Service
public class NameFirstService implements IService {
    @Override
    public void deal() {
        isNameFirst();
    }
    private void isNameFirst(){}
}

// 干扰类
@Service
public class NameOnlyService3 implements IService {
    @Override
    public void deal() {
        isNameOnlyService3();
    }
    private void isNameOnlyService3(){}
}
13  org.testcase.ctrl.TestCtrl:testNameOnly1()  (_SPR_ACT_I)org.testcase.service.NameOnlyService:deal() 76  f   1   1
15  org.testcase.ctrl.TestCtrl:testNameOnly3()  (_SPR_ACT_I)org.testcase.service4.NameOnlyService3:deal()   84  f   1   1
Adrninistrator commented 1 year ago
@Named("noService")
@Inject
private IService nameOnlyService3;

这种写法是说有@Named及@Inject注解的时候,会优先使用@Named注解的value属性去找对应的Bean么 我晚上再优化一下

john-70 commented 1 year ago

是的,和@Autowired+@qualifier一样的效果

john-70 commented 1 year ago

另外 @Resource(type) 在有自定义beanName如@Service("xxService")时会失效,比较取巧的解决方式是将 DefineSpringBeanByAnnotationHandler.handleSpringComponentAnnotation() 新加的逻辑放在首行

// 修改前
    private void handleSpringComponentAnnotation(AnnotationEntry annotationEntry, String className) {
        // 若Component相关注解的value属性值非空,则作为Bean名称使用
        String valueAttributeValue = JavaCGAnnotationUtil.getAnnotationAttributeStringValue(annotationEntry, SpringAnnotationConstants.ANNOTATION_ATTRIBUTE_VALUE);
        if (StringUtils.isNotBlank(valueAttributeValue)) {
            stringBeanNameAndTypeMap.put(valueAttributeValue, Collections.singletonList(className));
            return;
        }

        // 若Component相关注解的value属性值为空
        // 将类名首字母小写作为Bean名称作用
        String simpleClassName = JavaCGUtil.getSimpleClassNameFromFull(className);
        String firstLetterLowerClassName = JavaCGUtil.getFirstLetterLowerClassName(simpleClassName);
        stringBeanNameAndTypeMap.put(firstLetterLowerClassName, Collections.singletonList(className));

        // 再将类名作为Bean名称作用(用来支持Bean使用@Service、@Repository等注解定义,使用@Resource的type属性注入对应Bean)
        stringBeanNameAndTypeMap.put(className, Collections.singletonList(className));
    }
// 修改后
    private void handleSpringComponentAnnotation(AnnotationEntry annotationEntry, String className) {
        // 再将类名作为Bean名称作用(用来支持Bean使用@Service、@Repository等注解定义,使用@Resource的type属性注入对应Bean)
        stringBeanNameAndTypeMap.put(className, Collections.singletonList(className));

        // 若Component相关注解的value属性值非空,则作为Bean名称使用
        String valueAttributeValue = JavaCGAnnotationUtil.getAnnotationAttributeStringValue(annotationEntry, SpringAnnotationConstants.ANNOTATION_ATTRIBUTE_VALUE);
        if (StringUtils.isNotBlank(valueAttributeValue)) {
            stringBeanNameAndTypeMap.put(valueAttributeValue, Collections.singletonList(className));
            return;
        }

        // 若Component相关注解的value属性值为空
        // 将类名首字母小写作为Bean名称作用
        String simpleClassName = JavaCGUtil.getSimpleClassNameFromFull(className);
        String firstLetterLowerClassName = JavaCGUtil.getFirstLetterLowerClassName(simpleClassName);
        stringBeanNameAndTypeMap.put(firstLetterLowerClassName, Collections.singletonList(className));
    }
Adrninistrator commented 1 year ago

更新版本了,1.0.10 上面两个问题优化了,再帮忙验证一下,感谢

john-70 commented 1 year ago

已验证,已解决