@Validated注解与自定义注解的相互作用,导致获取注解属性时只能获取到代理信息,无法获取注解的值
最近项目中使用了策略工厂模式,但是在启动时循环获取注解中的属性的时候只获取到了被代理的信息,没有获取到属性信息,于是查了下相关资料
开始时代码如下所示,但是启动调试时发现
/** * 注解存在的的service实现类 */
//问题所在 @Validated @Slf4j @Service
//自定义的注解,主要时为了获取这个注解的value信息 @CalculationType(value = CalculationTypeEnum.DELETE_OEDER) public class DeleteCalculationImpl implements CalculationService { }
1 /** 2 * @description: 计算服务工厂 3 * @fileName: CalculationServiceFactory 4 */ 5 @Component 6 public class CalculationServiceFactory implements ApplicationContextAware { 7 8 /** 9 * 计算方式枚举 10 */ 11 private static final Map<CalculationTypeEnum, CalculationService> CALCULATION_SERVICE_MAPPING = new HashMap<>(); 12 13 /** 14 * 工厂方法获取计算服务实现 15 * 16 * @param calculationTypeCode 计算方式的内部编码 17 * @return 计算服务 18 */ 19 public static final CalculationService getCalculationService(String calculationTypeCode) throws InvalidParamException { 20 CalculationTypeEnum calculationTypeEnum = CalculationTypeEnum.getValue(calculationTypeCode); 21 if (calculationTypeEnum == null) { 22 throw new InvalidParamException(PeisAppErrorCodeEnum.INVALID_PARAM.getCode(),"无效的计算编码"); 23 } 24 CalculationService calculationService = CALCULATION_SERVICE_MAPPING.get(calculationTypeEnum); 25 if (calculationService == null) { 26 throw new InvalidParamException(PeisAppErrorCodeEnum.INVALID_PARAM.getCode(),"没有匹配的计算服务实现类"); 27 } 28 return calculationService; 29 } 30 31 /** 32 * 初始化计算枚举-计算服务的映射 33 * @param applicationContext 34 * @throws BeansException 35 */ 36 @Override 37 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 38 Map<String, Object> calculationServiceMap = applicationContext.getBeansWithAnnotation(CalculationType.class); 39 if (CollectionUtils.isEmpty(calculationServiceMap)) { 40 try { 41 throw new InvalidParamException(PeisAppErrorCodeEnum.INVALID_PARAM.getCode(),"因参数问题计算服务映射初始化失败"); 42 } catch (InvalidParamException e) { 43 e.printStackTrace(); 44 } 45 } 46 calculationServiceMap.forEach((key,bean) -> { 47 if (!(bean instanceof CalculationService)) { 48 try { 49 throw new InvalidParamException(PeisAppErrorCodeEnum.INVALID_PARAM.getCode(),"注解:" + CalculationType.class + ",只能用于" + CalculationService.class + "的实现类中"); 50 } catch (InvalidParamException e) { 51 e.printStackTrace(); 52 } 53 } 54 CalculationService calculationService = (CalculationService)bean;
//在这里getAnnotation获取到注解的属性值的为空
55 CalculationType annotation = calculationService.getClass().getAnnotation(CalculationType.class);
56 CALCULATION_SERVICE_MAPPING.put(annotation.value(),calculationService);
57 });
58 }
59 }
查询资料( https://blog.csdn.net/quintino/article/details/114833243 @ValidatedCGKIB动态代理导致无法判断自定义注解)发现@Validated 注解使用在类上的时候会对整个类的方法做校验,实际运行时,Spring 会通过代理(会根据配置使用不同的代理)生成原始类的子类。而这个自动生成的子类不会继承原始类的注解,故在处理逻辑时检测不到原始类的注解。
解决方法有两种
第一种、如果没必要使用@Validated对整个类的方法进行检查的化删除@Validated注解即可
第二种、如果非要使用该注解,我们可以通过代理类获取原始类然后在获取注解信息(参考资料: https://blog.csdn.net/lkforce/article/details/78126685 获得spring的指定目标对象,执行指定方法(JDK动态代理,cglib动态代理,Dubbo-Javassist代理))
第二种解决方案示例
1 /** 2 * @description: 计算服务工厂 3 * @fileName: CalculationServiceFactory 4 */ 5 @Component 6 7 public class CalculationServiceFactory implements ApplicationContextAware { 8 9 /** 10 * 支付方式枚举-支付服务的映射集合 11 */ 12 private static final Map<CalculationTypeEnum, CalculationService> CALCULATION_SERVICE_MAPPING = new HashMap<>(); 13 14 /** 15 * 工厂方法获取计算服务实现 16 * 17 * @param calculationTypeCode 计算方式的内部编码 18 * @return 计算服务 19 */ 20 public static final CalculationService getCalculationService(String calculationTypeCode) throws InvalidParamException { 21 CalculationTypeEnum calculationTypeEnum = CalculationTypeEnum.getValue(calculationTypeCode); 22 if (calculationTypeEnum == null) { 23 throw new InvalidParamException(PeisAppErrorCodeEnum.INVALID_PARAM.getCode(),"无效的计算编码"); 24 } 25 CalculationService calculationService = CALCULATION_SERVICE_MAPPING.get(calculationTypeEnum); 26 if (calculationService == null) { 27 throw new InvalidParamException(PeisAppErrorCodeEnum.INVALID_PARAM.getCode(),"没有匹配的计算服务实现类"); 28 } 29 return calculationService; 30 } 31 32 /** 33 * 初始化计算枚举-计算服务的映射 34 * @param applicationContext 35 * @throws BeansException 36 */ 37 @Override 38 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 39 Map<String, Object> calculationServiceMap = applicationContext.getBeansWithAnnotation(CalculationType.class); 40 if (CollectionUtils.isEmpty(calculationServiceMap)) { 41 //此处有两种实现CALCULATION_SERVICE_MAPPING初始化的方案 42 //1.第一种是通过calculationService类上的注解实现,第二种通过为calculationService类增加属性实现 43 //1.1注解实现方案中可能出现被动态代理的情况,此时需要把动态代理对象转换成实际对象,在获取注解中的属性信息。 44 Map<String, Object> payServiceMap = applicationContext.getBeansWithAnnotation(CalculationType.class); 45 payServiceMap.forEach((key,bean) -> { 46 Field h = null; 47 try { 48 throw new InvalidParamException(PeisAppErrorCodeEnum.INVALID_PARAM.getCode(),"因参数问题计算服务映射初始化失败"); 49 } catch (InvalidParamException e) { 50 h = bean.getClass().getSuperclass().getDeclaredField("h"); 51 h.setAccessible(true); 52 AopProxy aopProxy = (AopProxy) h.get(bean); 53 Field advised = aopProxy.getClass().getDeclaredField("advised"); 54 advised.setAccessible(true); 55 Object newObj = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget(); 56 CalculationType calculationType = newObj.getClass().getAnnotation(CalculationType.class); 57 CalculationService calculationService = (CalculationService)bean; 58 CALCULATION_SERVICE_MAPPING.put(CalculationTypeEnum.getValue(calculationType.value().getCode()), calculationService); 59 }catch (IllegalAccessException e) { 60 e.printStackTrace(); 61 } 62 } 63 calculationServiceMap.forEach((key,bean) -> { 64 if (!(bean instanceof CalculationService)) { 65 try { 66 throw new InvalidParamException(PeisAppErrorCodeEnum.INVALID_PARAM.getCode(),"注解:" + CalculationType.class + ",只能用于" + CalculationService.class + "的实现类中"); 67 } catch (InvalidParamException e) { 68 e.printStackTrace(); 69 } 70 }catch (NoSuchFieldException e) { 71 e.printStackTrace(); 72 } catch (Exception e) { 73 e.printStackTrace(); 74 } 75 CalculationService calculationService = (CalculationService)bean; 76 CalculationType annotation = calculationService.getClass().getAnnotation(CalculationType.class); 77 CALCULATION_SERVICE_MAPPING.put(annotation.value(),calculationService); 78 }); 79 //1.2通过增加属性实现比较简单,缺点是代码不简洁(这种方法是放弃使用自定义注解,直接在service上添加方法通过方法返回值获取属性,service在下面的代码块中展示) 80 Map<String, CalculationService> beans = applicationContext.getBeansOfType(CalculationService.class); 81 82 beans.entrySet().forEach(item->{ 83 84 CalculationService service = item.getValue(); 85 86 CalculationTypeEnum type = service.type(); 87 88 CALCULATION_SERVICE_MAPPING.put(type, service); 89 90 }); 91 92 93 94 } 95 }
1 public interface CalculationService { 2 3 /** 4 * 计算 5 * @param calculationDTO 6 * @return 7 */ 8 ResponseResult<CalculationDTO> calculation(@NotNull CalculationDTO calculationDTO); 9 10 /** 11 * 计算类型(通过预设的方法,避免使用自定义注解,也能达到相同的目的) 12 * @return 13 */ 14 CalculationTypeEnum type(); 15 }