springboot2.7 java8

问题

在使用工厂模式封装service时,需要通过service的class获取其类型注解,但是有些工厂类可以取到annotation注解,有些取不到
渠道注解:

/**
 * xxx渠道注解
 *
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XxxType {

    /**
     * 渠道的值为XxxTypeEnum枚举
     */
    XxxTypeEnum value();
}

enum:

/**
 * 枚举类
 */
@Getter
@AllArgsConstructor
public enum XxxTypeEnum {

    X1("X1", "渠道1"),
    X2("X2", "渠道2");

    private final String code;
    private final String message;

    public static XxxTypeEnum getEnumByCode(String code){
        for(XxxTypeEnum value:values()){
            if(StringUtils.equals(value.code, code)){
                return value;
            }
        }
        throw new CommonException("未知的XXX类型");
    }
}

工厂类:

@Component
public class XxxServiceFactory implements ApplicationContextAware {

    /**
     * xxx服务的映射集合
     */
    private static final Map<XxxTypeEnum, XxxService> SERVICE_MAP = new HashMap<>();

    /**
     * 工厂方法获取服务实现
     *
     * @param xxxType 渠道
     * @return 服务
     */
    public static XxxService getService(XxxTypeEnum xxxType) {
        XxxService service = SERVICE_MAP.get(xxxType);
        if (service == null) {
            throw new CommonException("没有匹配的服务实现类");
        }
        return service;
    }

    /**
     * 初始化渠道枚举-xxx服务的映射的映射
     *
     * @param applicationContext 应用上下文
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Object> serviceMap = applicationContext.getBeansWithAnnotation(XxxType.class);
        if (ObjectUtil.isEmpty(serviceMap)) {
            throw new CommonException("服务映射初始化失败");
        }
        serviceMap.forEach((key, bean) -> {
            if (!(bean instanceof XxxService)) {
                throw new CommonException("注解:" + XxxType.class + ",只能用于" + XxxService.class + "的实现类中");
            }
            XxxService service = (XxxService) bean;
            XxxType annotation = service.getClass().getAnnotation(XxxType.class);
            // annotation 有时为null
            SERVICE_MAP.put(annotation.value(), service);
        });
    }
}
public interface XxxService {

    void test();
    void doSomething();

}

渠道1服务实现类

/**
 * 渠道1xxx服务实现类
 */
@XxxType(XxxTypeEnum.X1)
@Service
@Slf4j
public class X1XxxServiceImpl implements XxxService {

    @Override
    public void test() {
        log.info("测试渠道1 test"); 
    }

    /**
     * 此方法需要事务包裹
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void doSomething() {
        log.info("测试渠道1 do something"); 
    }

}

渠道2服务实现类

/**
 * 渠道1xxx服务实现类
 */
@XxxType(XxxTypeEnum.X2)
@Service
@Slf4j
public class X2XxxServiceImpl implements XxxService {

    @Override
    public void test() {
        log.info("测试渠道2 test"); 
    }

    @Override
    public void doSomething() {
        log.info("测试渠道2 do something"); 
    }

}

解决

以上为部分代码,项目启动时,显示渠道1服务实现类的annotationnull,直接npe,找了半天,发现是因为渠道1内的doSomething方法添加事务注解,因为@Transactional也是基于aop的,所以此时拿到的bean是代理对象,而代理对象的方法是不会把原来父类中的方法的注解加上去的,所以为null,所以换了种方式

...
 /**
     * 初始化渠道枚举-xxx服务的映射的映射
     *
     * @param applicationContext 应用上下文
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Object> serviceMap = applicationContext.getBeansWithAnnotation(XxxType.class);
        if (ObjectUtil.isEmpty(serviceMap)) {
            throw new CommonException("服务映射初始化失败");
        }
        serviceMap.forEach((key, bean) -> {
            if (!(bean instanceof XxxService)) {
                throw new CommonException("注解:" + XxxType.class + ",只能用于" + XxxService.class + "的实现类中");
            }
            // XxxService service = (XxxService) bean;
            // XxxType annotation = service.getClass().getAnnotation(XxxType.class);
            // SERVICE_MAP.put(annotation.value(), service);
            List<Annotation> list = AnnotationUtil.scanClass(bean.getClass());
            list.stream().filter(annotation -> annotation instanceof XxxType).findFirst().ifPresent(annotation -> {
                XxxType xxxType = (XxxType) annotation;
                SERVICE_MAP.put(xxxType .value(), (XxxService) bean);
            });
        });
    }

这样就能正确的获得注解,并完成工厂类的初始化啦