Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AbstractMethodError thrown within MethodValidationInterceptor [SPR-10644] #15272

Closed
spring-projects-issues opened this issue Jun 9, 2013 · 4 comments
Assignees
Labels
in: core type: bug
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Jun 9, 2013

Nick Williams opened SPR-10644 and commented

I've filed so many bugs since 4.M1 came out, I don't know whether y'all are gonna love me or hate me. :-)

I have the following interface and implementation:

@Validated
public interface EmployeeService
{
    public void saveEmployee(
            @NotNull(message = "{validate.employeeService.saveEmployee}")
            @Valid Employee employee
    );

    public Employee getEmployee(
            @Min(value = 1L,
                    message = "{validate.employeeService.getEmployee.id}") long id
    );

    @NotNull
    public List<Employee> getAllEmployees();
}
@Service
public class DefaultEmployeeService implements EmployeeService
{
    @Override
    public void saveEmployee(Employee employee)
    {
        // no-op
    }

    @Override
    public Employee getEmployee(long id)
    {
        return null;
    }

    @Override
    public List<Employee> getAllEmployees()
    {
        return null; // to force trigger a validation error
    }
}

I then have a simple controller that uses the service:

@Controller
public class EmployeeController
{
    @Inject EmployeeService employeeService;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String listEmployees(Map<String, Object> model)
    {
        model.put("employees", this.employeeService.getAllEmployees());
        return "employee/list";
    }
}

I defined a MethodValidationPostProcessor, which attaches a MethodValidationInterceptor to my DefaultEmployeeService methods. When I go to the handler method in the browser, I get this error:

java.lang.AbstractMethodError: org.springframework.validation.beanvalidation.LocalValidatorFactoryBean.forExecutables()Ljavax/validation/executable/ExecutableValidator;
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	java.lang.reflect.Method.invoke(Method.java:491)
	org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:191)
	org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:176)
	org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:118)
	org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
	com.sun.proxy.$Proxy36.getAllEmployees(Unknown Source)
	com.wrox.site.EmployeeController.listEmployees(EmployeeController.java:23)
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	java.lang.reflect.Method.invoke(Method.java:491)
	org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
	org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
	org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:747)
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:688)
	org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
	org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
	org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:946)
	org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:837)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
	org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:822)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:83)

This is because LocalValidatorFactoryBean (SpringValidatorAdapter) does not implement forExecutables. Here's what the JavaDoc has to say about that:

Note that Bean Validation 1.1's #forExecutables method isn't supported: We do not expect that method to be called by application code; consider MethodValidationInterceptor instead. If you really need programmatic #forExecutables access, inject this class as a ValidatorFactory and call getValidator() on it, then #forExecutables on the returned native Validator reference instead of directly on this class.

Well, I am using a MethodValidationInterceptor. MethodValidationInterceptor calls forExecutables, so I'm sure you can see the problem here. I managed to work around this by changing my post-processor definition from this:

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor()
{
    MethodValidationPostProcessor processor =
            new MethodValidationPostProcessor();
    processor.setValidator(this.localValidatorFactoryBean());
    return processor;
}

To this:

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor()
{
    MethodValidationPostProcessor processor =
            new MethodValidationPostProcessor();
    processor.setValidator(this.localValidatorFactoryBean().getValidator());
    return processor;
}

However, this is non-obvious and certainly not documented clearly.

I see no reason for the restriction documented on LocalValidatorFactoryBean regarding forExecutables. There's no reason that SpringValidatorAdapter can't implement this method. If the underlying Validator is BV 1.1, great. If it's not, the call to the method will throw an AbstractMethodError. But it already does that now, just always, even if the Validator is BV 1.1.

I'll send in a pull request shortly.


Affects: 4.0 M1

Referenced from: commits 7b2c74b, 8424974

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jun 11, 2013

Nick Williams commented

Hmmm. This wasn't as easy as I thought it was going to be. forExecutables returns a type that is also new to BV 1.1, so I can't just add a method that isn't @Override but has the same signature and return type. So, the way I see it, we have two options:

  1. Upgrade the library to BV 1.1, implement the method, and then document that the method is not supported and throws AbstractMethodError if you're using BV 1.0 instead of 1.1 (not a huge change from now, but makes configuration more obvious, so I'm for this one).

  2. Update the MethodValidationInterceptor (or would MethodValidationPostProcessor be better?) to detect if its Validator is a SpringValidatorAdapter and get the underlying Validator in that case. This would achieve the simplification of configuration without upgrading to BV 1.1. However, it feels like a hack to me. I'm open to this idea, and will submit a pull request if y'all think it's the best route to take.

Thoughts?

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jun 24, 2013

Phil Webb commented

I think we will want to retain BV 1.0 support at least with Spring 4.0 so option 2 would look better to me.

What about changing MethodValidationPostProcessor.setValidator

public void setValidator(Validator validator) {
	if(validator instanceof ValidatorFactory) {
		this.validator = ((ValidatorFactory) validator).getValidator();
	}
	else {
		this.validator = validator;
	}
}

Would that work?

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jun 24, 2013

Nick Williams commented

Agreed. I had already implemented this in my local repository and verified that it resolved the issue I was seeing. I have now pushed the commit and submitted a pull request.

#305

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jun 24, 2013

Phil Webb commented

Thanks, I have applied this with the minor change of checking for LocalValidatorFactoryBean rather than ValidatorFactory.

@spring-projects-issues spring-projects-issues added type: bug in: core labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 4.0 M2 milestone Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core type: bug
Projects
None yet
Development

No branches or pull requests

2 participants