spring boot 使用枚举策略实战踩坑

3,996 阅读2分钟

最近设计一个对外接口调用失败重试补推功能,因为对于很多接口的调用我准备采取策略模式去实现。思考了下,如果使用普通的策略模式,一个接口对应对个实现类,如果多一个类型的接口,那么就会多一个实现类,决定使用枚举策略模式。

失败接口实体

调用接口失败,将失败的调用的url,param,method等信息作为一条记录存入数据库中

public class HttpFailEntity {

    .
    .
    .
    
    private Integer apiType;

    private String url;

    private String param;
    
    .
    .
    .
    
}

定时器补推

通过定时器从库中获取调用外部接口失败的数据

List<HttpFailEntity> httpFailEntities;
//补推
httpFailEntities.forEach(httpFailEntity -> {
    PushHttpFailEnum.valueOf(httpFailEntity.getApiType()).push(httpFailEntity);
});

策略模式处理

使用枚举策略处理,避免多一个接口处理,新增一个实现类

public enum PushHttpFailEnum {

    /**
     * id -> 业务接口
     */
    ONE(1, "A接口") {

        private XXXXService xxxxService = SpringContextHolder.getBean(XXXXService.class);

        @Override
        public Map<String, Object> push(HttpFailEntity httpFailEntity) {
            // 补推操作
            xxxxService.XXXX();
        }
    },
    TWO(2, "B接口") {

        @Override
        public Map<String, Object> push(HttpFailEntity httpFailEntity) {
            // 补推操作
        }
    },
    .
    .
    .
    OTHER(9999, "") {
        @Override
        public Map<String, Object> push(HttpFailEntity httpFailEntity) {
            return null;
        }
    };

    /**
     * 补推
     *
     * @param httpFailEntity http推送失败类
     */
    public abstract Map<String, Object> push(HttpFailEntity httpFailEntity);

    /**
     * 状态值
     */
    @Getter
    private int value;

    /**
     * 状态表述
     */
    @Getter
    private String desc;

    PushHttpFailEnum(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }

    public static PushHttpFailEnum valueOf(int value) {
        return Arrays.stream(PushHttpFailEnum.values())
                .filter(a -> a.getValue() == value)
                .findFirst().orElse(PushHttpFailEnum.ERROR);
    }
}

在A接口的实现抽象方法中使用到 spring 容器中的Service类,一开始使用注解形式,运行得时候都是null

@Resource
private XXXXService xxxxService;

后来发现枚举类里面实现的抽象方法,本质就是匿名内部类,spring容器根本无法扫描到,所以也就无法把对象注入。最后使用SpringContextHolder的静态方法从spring容器中拿到对应的对象。

@Component
public class SpringContextHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

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

    public static ApplicationContext getApplicationContext() {
        assertApplicationContext();
        return applicationContext;
    }

    public static <T> T getBean(String beanName) {
        assertApplicationContext();
        return (T) applicationContext.getBean(beanName);
    }

    public static <T> T getBean(Class<T> requiredType) {
        assertApplicationContext();
        return applicationContext.getBean(requiredType);
    }

    private static void assertApplicationContext() {
        if (SpringContextHolder.applicationContext == null) {
            throw new RuntimeException("ApplicationContext属性为null,请检查是否注入了SpringContextHolder!");
        }
    }

}