使用CDI Decorator装饰托管Validator时出现部署错误求助
问题描述
我有一个无状态Java EE 7 WebService,原本使用验证框架验证输入数据一切正常。希望将ConstraintViolationException转换为对客户端更友好的信息,认为CDI Decorator是理想方案。编写了装饰器代码后,部署时ValidationInterceptor报错无法找到Validator实现。虽通过在验证拦截器之前触发的拦截器实现了该功能,但觉得装饰器方案更简洁。主要熟悉Spring,对CDI不太了解,请问遗漏了什么?
错误信息
Exception while loading the app : CDI deployment failure:WELD-001408: Unsatisfied dependencies for type Validator with qualifiers @Default at injection point [UnbackedAnnotatedField] @Inject private org.hibernate.validator.cdi.internal.interceptor.ValidationInterceptor.validator at org.hibernate.validator.cdi.internal.interceptor.ValidationInterceptor.validator(ValidationInterceptor.java:0) WELD-001475: The following beans match by type, but none have matching qualifiers: - ValidatorBean [id=org.hibernate.validator.cdi.internal.ValidatorBean_hv] -- WELD-001408: Unsatisfied dependencies for type Validator with qualifiers @Default at injection point [UnbackedAnnotatedField] @Inject private org.hibernate.validator.cdi.internal.interceptor.ValidationInterceptor.validator at org.hibernate.validator.cdi.internal.interceptor.ValidationInterceptor.validator(ValidationInterceptor.java:0) WELD-001475: The following beans match by type, but none have matching qualifiers: - ValidatorBean [id=org.hibernate.validator.cdi.internal.ValidatorBean_hv] ]]
编写的装饰器代码
@Decorator public abstract class ValidationDecorator implements Validator { private static final Logger logger = Logger.getLogger(ValidationDecorator.class.getName()); //the predefine validator bean created by the container @Inject @Delegate private Validator validator; @Override public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) { logger.log(Level.INFO, () -> String.format("Validation decorator validating %s", object)); Set<ConstraintViolation<T>> violations = null; try { violations = validator.validate(object, groups); } catch (ConstraintViolationException ex) { try { // build a client friendly message SOAPFactory factory = SOAPFactory.newInstance(); SOAPFault fault = factory.createFault("A validation error occurred", new QName("ValidationError")); throw new SOAPFaultException(fault); } catch (SOAPException e) { e.printStackTrace(); } } return violations; } }
问题原因与修复方案
核心原因
Hibernate Validator提供的ValidatorBean并未使用@Default限定符,而是自带专属限定符@org.hibernate.validator.cdi.internal.ValidatorQualifier。你的装饰器尝试用@Default注入Validator,不仅自己匹配不到Bean,还覆盖了容器原生的Validator Bean,导致ValidationInterceptor也无法找到依赖。
修复步骤
使用正确限定符注入Delegate
修改装饰器的注入代码,指定Hibernate Validator的专属限定符:@Inject @Delegate @org.hibernate.validator.cdi.internal.ValidatorQualifier private Validator validator;若不想依赖内部API,也可以用
@Any结合Instance查找:@Inject @Delegate @Any private Instance<Validator> validatorInstance; private Validator getValidator() { return validatorInstance.select(org.hibernate.validator.cdi.internal.ValidatorQualifier.class).get(); }显式注册装饰器
在WEB-INF/beans.xml中添加装饰器声明(部分Java EE容器需要显式注册):<decorators> <class>com.yourpackage.ValidationDecorator</class> </decorators>修正异常处理逻辑
注意:validator.validate()本身不会抛出ConstraintViolationException,该异常由Java EE验证拦截器抛出。调整装饰器逻辑,在验证结果非空时抛出自定义SOAP异常:@Override public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) { logger.log(Level.INFO, () -> String.format("Validation decorator validating %s", object)); Set<ConstraintViolation<T>> violations = validator.validate(object, groups); if (!violations.isEmpty()) { try { SOAPFactory factory = SOAPFactory.newInstance(); SOAPFault fault = factory.createFault("输入验证失败:" + getViolationMessages(violations), new QName("ValidationError")); throw new SOAPFaultException(fault); } catch (SOAPException e) { logger.log(Level.SEVERE, "创建SOAP错误失败", e); throw new RuntimeException(e); } } return violations; } private <T> String getViolationMessages(Set<ConstraintViolation<T>> violations) { StringBuilder sb = new StringBuilder(); for (ConstraintViolation<T> violation : violations) { sb.append(violation.getMessage()).append("; "); } return sb.toString().trim(); }
更优替代方案:使用ExceptionMapper
对于JAX-WS WebService,用ExceptionMapper统一处理异常更简洁,无需修改验证逻辑:
@Provider public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> { @Override public Response toResponse(ConstraintViolationException exception) { try { SOAPFactory factory = SOAPFactory.newInstance(); SOAPFault fault = factory.createFault("输入验证失败", new QName("ValidationError")); // 添加具体错误详情 for (ConstraintViolation<?> violation : exception.getConstraintViolations()) { fault.addFaultDetail().addTextNode(violation.getMessage()); } return Response.status(Response.Status.BAD_REQUEST) .entity(new SOAPFaultException(fault)) .build(); } catch (SOAPException e) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("验证错误处理失败") .build(); } } }
该类会被JAX-WS容器自动发现,将ConstraintViolationException转换为友好的SOAP响应。
内容的提问来源于stack exchange,提问作者Alan Hay

