前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >开年第一个Bug,竟然是Java反射引起的,不会用就不要用啊喂!

开年第一个Bug,竟然是Java反射引起的,不会用就不要用啊喂!

作者头像
程序员小义
发布2025-01-13 12:40:21
发布2025-01-13 12:40:21
5100
代码可运行
举报
文章被收录于专栏:小义思小义思
运行总次数:0
代码可运行

大家好,我是小义,又来写技术文章了。开年本想着顺顺利利大展一番拳脚,可没想到,第一个 Bug 就给我来了个 “下马威”,而罪魁祸首,居然就是 Java 里那个让人又爱又恨的反射机制!今天就来和大家唠唠我这 “惨痛” 的经历,也顺便一起把这反射机制再好好捋捋。

下面结合代码,简单复现一下事情经过。小义想实现一个策略模式,需要把实现了某个父类的所有子类都放到一个Map中。首先我们自定义了一个注解,用于标识子类,即Map的key值。

代码语言:javascript
代码运行次数:0
复制
//自定义注解,用于标识客户端
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ClientType {
    String value() default "";
}

接着定义父类和子类,这里假设业务场景是要实现不同类型客户端的服务请求,有android安卓、harmony鸿蒙、apple苹果等等,分别继承BaseService父类,并且通过自定义注解@ClientType区分。

代码语言:javascript
代码运行次数:0
复制
//服务实现父类
public class BaseService {
    //...
}
代码语言:javascript
代码运行次数:0
复制
//安卓端服务类
@Service
@ClientType(value = "android")
public class AndroidService extends BaseService {
    //...
}
代码语言:javascript
代码运行次数:0
复制
//苹果端服务类
@Service
@ClientType(value = "apple")
public class AppleService  extends BaseService {
    //...
}
代码语言:javascript
代码运行次数:0
复制
//鸿蒙端服务类
@Service
@ClientType(value = "harmony")
public class HarmonyService  extends BaseService {

    @Transactional(rollbackFor = Exception.class)
    public String doService() {
        //...
        return "success";
    }
}

接下来是重中之重,利用Java反射遍历出所有子类,并转换为Map的形式缓存起来。 具体代码如下。

其中第三点注释的地方就是bug所在。细心的小伙伴可能发现了,HarmonyService和其他的继承了BeanService的Bean不同,它在处理业务代码时用到了事务,所以需要在方法上加上@Transactional事务注解。不加还没事,原本不加的时候,利用注释掉的那一行代码的getClass().getAnnotation()方法也能实现小义想要的效果,但加上后麻烦就来了。

在 Spring 中,给方法加上@Transactional注解后,通过 Spring 内部的 AOP 机制、对注解的解析以及代理对象的创建等一系列操作,就能使得该方法被 Spring 进行事务增强,从而方便地实现事务管理功能,保障了数据操作在事务控制下的一致性和完整性。也就是说Spring创建出了HarmonyService的代理对象,而这个对象在类上面是没有ClientType这个注解的。

代码语言:javascript
代码运行次数:0
复制
//服务管理器
@Component
public class ServiceManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    private Map<String, BaseService> clientServiceMap = new HashMap<>();

    @PostConstruct
    public void init() {
        //1、利用Spring应用上下文获取所有继承了BaseService的Bean实例,并将这些实例以BaseService类型的集合形式进行获取
        Collection<BaseService> values = applicationContext.getBeansOfType(BaseService.class).values();
        //2、遍历集合,取出带自定义注解的Bean
        for (BaseService handler : values) {
            //3、注释行在判断HarmonyService时,无法取到ClientType注解
            //ClientType annotation = (ClientType) AnnotationUtils.findAnnotation(printHandler.getClass(), ClientType.class);
            ClientType annotation = (ClientType) handler.getClass().getAnnotation(ClientType.class);
            if (null != annotation && null != annotation.value()) {
                //4、以ClientType的value为key,Bean示例为value保存至Map
                clientServiceMap.put(annotation.value(), handler);
            }
        }
    }

    /**
     * 获取服务
     */
    public BaseService getClientService(String clientType){
        return clientServiceMap.get(clientType);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

解决的方法也很简单,换成用AnnotationUtils工具类去获取注解就好了。 AnnotationUtils#findAnnotation方法会先在该实现类查找注解,如果没有找到,则递归地在父类中查找注解,直到找到注解或到达Object类为止。

代码语言:javascript
代码运行次数:0
复制
//org.springframework.core.annotation.AnnotationUtils#findAnnotation核心代码
public static <A extends Annotation> A findAnnotation(Class<?> clazz, @Nullable Class<A> annotationType) {
  //...
if (AnnotationFilter.PLAIN.matches(annotationType) ||
    AnnotationsScanner.hasPlainJavaAnnotationsOnly(clazz)) {
   A annotation = clazz.getDeclaredAnnotation(annotationType);
   if (annotation != null) {
    return annotation;
   }
   Class<?> superclass = clazz.getSuperclass();
   if (superclass == null || superclass == Object.class) {
    return null;
   }
   return findAnnotation(superclass, annotationType);
  }
  //...
 }

好了,bug解决完了,虽然只是替换简单的一行代码,但一不小心可能就有个大坑在等着你。我是小义,希望大家一起学习。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员小义 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档