SpringBoot 策略模式 + AOP 注解代理冲突问题

29 阅读2分钟

项目中使用了策略模式来根据不同的数据类型调用不同的处理策略。每个策略类实现了如下通用接口:

public interface SaveRawDataStrategy<T> {
    int saveRawData(List<T> data, String batchId);
}

策略实现类通过 @Component 注解注册为 Spring Bean,并在 Spring 启动时统一注入并放入 strategyMap<Class<?>, SaveRawDataStrategy> 中,按泛型参数分类。

@Component
public class XxxxStrategyImpl implements SaveRawDataStrategy<XXXFeatureDTO> {

    @Resource
    private XxxService service;

    @Override
    @LogInfo
    public int saveRawData(List<XXXFeatureDTO> data, String batchId) {
        return batchSaveUtil.batchSave(data, batchId, service::batchXxxSave);
    }
}

问题描述: 在某个策略类的方法上加上自定义注解 @LogInfo(配合 AOP 实现日志拦截)之后,该策略类未能成功注册进 strategyMap 中,导致获取不到具体的策略实现类,调用不到策略实现类中的方法。

在策略工厂中,通过下面的这个方法实现 bean 的注册:

@Autowired
public SaveRawDataStrategyFactory(List<SaveRawDataStrategy> strategies) {
	for (SaveRawDataStrategy<?> handler : strategies) {
		Class<?> dataClass = resolveGenericType(handler.getClass());
		if (dataClass != null) {
			strategyMap.put(dataClass, handler);
			log.info("注册 SaveDataStrategy:{} => {}", dataClass.getSimpleName(), handler.getClass().getSimpleName());
		} else {
			log.warn("未能识别泛型类型的处理器:{}", handler.getClass().getName());
		}
	}
}

通过日志发现了handler.getClass().getName()打印出来的是AxspaFeatureStrategyImpl$$EnhancerBySpringCGLIB$$xxx这样的实现类。这就说明了 Spring AOP 在方法上生成了 CGLIB 代理。

泛型解析失败的原因:关键的泛型解析就是Class<?> dataClass = resolveGenericType(handler.getClass());这个方法,方法如下:

private Class<?> resolveGenericType(Class<?> handlerClass) {
	Type[] genericInterfaces = handlerClass.getGenericInterfaces();
	for (Type type : genericInterfaces) {
		ParameterizedType parameterizedType = (ParameterizedType) type;
		Type rawType = parameterizedType.getRawType();
		if (rawType instanceof Class && SaveHospitalRawDataStrategy.class.isAssignableFrom((Class<?>) rawType)) {
			Type actualType = parameterizedType.getActualTypeArguments()[0];
			if (actualType instanceof Class<?>) {
				return (Class<?>) actualType;
			}
		}
		return null;
	}
}

泛型解析的方法通过 handler.getClass().getGenericInterfaces()获取泛型参数;

但是由于handler.getClass()返回的是代理类,代理类本身并不包含泛型信息,导致方法的返回值为 null,因此导致注册失败。

解决方案

将方法改为传入对象实例,而非 Class 类型,并通过 Spring 的 AopProxyUtils 获取原始类:

private Class<?> resolveGenericType(Object handler) {
    Class<?> targetClass = AopProxyUtils.ultimateTargetClass(handler); // 获取原始类

    for (Type type : targetClass.getGenericInterfaces()) {
        if (type instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) type;
            if (pt.getRawType() instanceof Class &&
                SaveHospitalRawDataStrategy.class.isAssignableFrom((Class<?>) pt.getRawType())) {
                return (Class<?>) pt.getActualTypeArguments()[0];
            }
        }
    }
    return null;
}

调用上面的方法也同样需要修改

@Autowired
public SaveHospitalRawDataStrategyFactory(List<SaveHospitalRawDataStrategy> strategies) {
	for (SaveHospitalRawDataStrategy<?> handler : strategies) {
		Class<?> dataClass = resolveGenericType(handler);
		if (dataClass != null) {
			strategyMap.put(dataClass, handler);
			log.info("注册 SaveHospitalRawDataStrategy:{} => {}", dataClass.getSimpleName(), handler.getClass().getSimpleName());
		} else {
			log.warn("未能识别泛型类型的处理器:{}", handler.getClass().getName());
		}
	}
}