ProxyingHandlerMethod ArgumentResolver干扰数据绑定



我有一个处理验证的web处理程序。当我添加数据jpa依赖项时,验证将停止工作。

问题出在ProxyingHandlerMethodArgumentResolver上。datajpaStarter将解析程序添加到解析程序列表的头部,并在稍后的列表中再次添加。将创建一个不更新参数的模型属性注释中引用的模型属性对象的代理。

我的解决方案是从冲突解决程序列表的头部删除冲突解决程序,但稍后将其保留在列表中。解析程序仍然可以被引用,但在我的自定义解析程序之后。

我认为,当我稍后使用datajpa中的更多功能时,这个解决方案会引起问题。你能提出另一种让原始代码工作的方法吗?

详细信息:

以下代码在添加数据依赖项之前工作。我为模型属性使用了一个接口。据我所知,模型属性参数用于绑定到具有该名称的模型属性(如果存在(,并在模型中不存在该名称的情况下创建一个新实例。由于";dataBad";在模型中,我不希望数据绑定创建新实例,所以我可以使用接口。

@Controller
@RequestMapping("/ControllerBad")
@SessionAttributes("dataBad")
public class ControllerBad {

@ModelAttribute("dataBad")
public RequestDataRequired modelData() {
return new RequestDataRequiredSingle();
}

@PostMapping(params="confirmButton")
public String confirmMethod(
@Valid @ModelAttribute("dataBad") RequestDataRequired dataBad,
BindingResult errors
) 
{
if (errors.hasErrors()) {
return "edit";
}
return "redirect:ControllerBad?confirmButton=Confirm";
}

这是正确的。请求参数被复制到模型属性"中;dataBad";。

接下来,我想添加持久性,所以我在pom文件中添加了spring-boot-starter数据jpa和mysql-connector java

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

我将数据库的属性添加到应用程序属性中

spring.datasource.url=jdbc:mysql://localhost:3306/baz
spring.datasource.username=foo
spring.datasource.password=bar

我没有创建任何实体类。我有一个绑定到表单的类,但我没有添加实体的注释。在这一点上,我只想将表单中的数据获取到模型中的bean中。这是表单数据对象的接口。

public interface RequestDataRequired {
@NotNull(message = "cannot be empty")
@Pattern(regexp = "(?i)red|green|blue",
message = "must be red, green, or blue")
public String getColor();
public void setColor(String color);
}

其他什么都没有改变。当我运行新版本时,验证失败,因为color属性为null。

如果我使用接口的实现,那么它就可以工作。我想让它与接口一起工作,因为实现类的名称将出现在控制器中的可能位置,而不仅仅是模型属性方法中。

@Valid @ModelAttribute("dataBad") RequestDataRequiredSingle dataBad

我可以让它与会话属性接口和模型属性接口一起工作,但这需要复制请求参数和错误的重复工作。

@PostMapping(params="confirmSessionModelButton")
public String confirmSessionModelMethod(
Model model,
@SessionAttribute RequestDataRequired dataBad,
@Valid @ModelAttribute RequestDataRequired dataModel,
BindingResult errors
) 
{
BeanUtils.copyProperties(dataModel, dataBad);
if (errors.hasErrors()) {
model.addAttribute(BindingResult.class.getName() + ".dataBad", errors);
return viewLocation("edit");
}
return "redirect:ControllerBad?confirmButton=Confirm";
}

经过一些实验,我发现datajpa添加了四个新的参数解析器。ProxyingHandlerMethodArgumentResolver包含了两次:一次位于解析程序列表的顶部,另一次位于我自己的自定义解析程序之后。

将为接口创建一个代理对象,并将请求参数复制到代理中。代理不会更新参数的模型属性注释中引用的模型属性对象。代理对象在请求处理程序中与请求数据一起可用,但会话属性不会更新。

由于代理解析程序位于列表的第一位,因此不会调用任何自定义解析程序。

如果我将代理解析程序从参数解析程序列表的开头删除,但将其保留在列表的后面,那么我可以像以前一样运行代码。

public class WebConfig implements WebMvcConfigurer  {

@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@PostConstruct
public void init() {
List<HandlerMethodArgumentResolver> argumentResolvers = 
requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> newList = argumentResolvers.subList(1, argumentResolvers.size());
requestMappingHandlerAdapter.setArgumentResolvers(newList);
}
}

我现在对这个解决方案很满意,但我认为我会在数据jpa中破坏一些稍后需要的东西。

有人能提出一种不同的方法来获得以前的行为吗?即用请求数据更新模型属性,并仅在模型属性不在模型中时创建模型属性的新实例?

我找到了这个问题的简单解决方案。Datajpa使用为接口创建代理的投影,这就是我遇到的问题。然而datajpa还支持DTO,DTO是看起来像接口的类。

@Component
public class RequestDataRequiredDTO implements RequestDataRequired {
private String color;
@Override
public String getColor() {
return color;
}
@Override
public void setColor(String color) {
this.color = color;
}
}

我必须做两件事。首先,我必须在模型属性参数中使用对DTO的引用,数据jpa将在不使用代理的情况下投影到其中。我仍然在其他地方使用普通接口,我甚至从接口扩展了DTO。

@PostMapping(params="confirmButton")
public String confirmMethod(
@Valid @ModelAttribute("dataBad") RequestDataRequiredDTO dataBad,
BindingResult errors
) 
{
if (errors.hasErrors()) {
return viewLocation("edit");
}
return "redirect:ControllerBad?confirmButton=Confirm";
}

其次,我必须定义一个从模型中的实际类到DTO类型的转换器。

@Component
public class ClassToDTOConverter 
implements Converter<RequestDataRequiredSingle, RequestDataRequiredDTO> {
@Override
public RequestDataRequiredDTO convert(RequestDataRequiredSingle source) {
RequestDataRequiredDTO target = new RequestDataRequiredDTO();
target.setColor(source.getColor());
return target;
}

}

最新更新