带有Spring MVC的动态表单绑定表单对象CGLIB

时间:2016-11-11 22:19:22

标签: java spring-mvc cglib jsr

我有一个动态模型,包括无线电,文本,选择,选择多个的X编号。这基本上就像是后端的EAV数据库。

我需要使用N个字段来呈现此动态表单,然后验证提交的动态模型对象,然后使用字段定义的正则表达式对其进行验证。我希望通过JSR 303注释来执行此验证。

所以,我想通过ModelAttribute @Valid功能等方式使用典型的Spring MVC开发方式绑定表单对象。唯一的区别是模型对象未知/未定义,直到运行系统。

我倾向于使用CGLIB或类似的东西在运行时生成类,并使用特殊的taglib呈现它,然后使用反射以某种方式使用特殊验证来验证它。

这样的事情完全脱离了可能性吗?同样,我想做常规的Spring MVC控制器和模型,但是使用动态表单对象。

2 个答案:

答案 0 :(得分:0)

编写控制器类(RegisterController.java)的代码,如下所示:

File: src/main/java/net/codejava/spring/controller/RegisterController.java

package net.codejava.spring.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.codejava.spring.model.User;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping(value = "/register")
public class RegisterController {

    @RequestMapping(method = RequestMethod.GET)
    public String viewRegistration(Map<String, Object> model) {
        User userForm = new User();    
        model.put("userForm", userForm);

        List<String> professionList = new ArrayList<>();
        professionList.add("Developer");
        professionList.add("Designer");
        professionList.add("IT Manager");
        model.put("professionList", professionList);

        return "Registration";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String processRegistration(@ModelAttribute("userForm") User user,
            Map<String, Object> model) {

        // implement your own registration logic here...

        // for testing purpose:
        System.out.println("username: " + user.getUsername());
        System.out.println("password: " + user.getPassword());
        System.out.println("email: " + user.getEmail());
        System.out.println("birth date: " + user.getBirthDate());
        System.out.println("profession: " + user.getProfession());

        return "RegistrationSuccess";
    }
}

我们可以看到此控制器旨在处理请求URL /寄存器:

@RequestMapping(value = "/register")

我们分别实现了两个方法viewRegistration()和processRegistration()来处理GET和POST请求。 Spring中的编写处理程序方法非常灵活,因为我们可以自由选择自己的方法名称和必要的参数。让我们详细看一下上面控制器类的每个方法: viewRegistration():在这个方法中,我们创建一个模型对象,并使用键“userForm”将其放入模型映射中:

User userForm = new User();
model.put("userForm", userForm);

这将在指定对象与此方法返回的视图中的表单(即注册表单)之间创建绑定。请注意,键“userForm”必须与标记的commandName属性的值匹配。

另一个有趣的一点是,我们创建一个字符串列表并将其放入模型映射中,并使用“professionList”键:

List<String> professionList = new ArrayList<>();
professionList.add("Developer");
professionList.add("Designer");
professionList.add("IT Manager");
model.put("professionList", professionList);

此集合将由Registration.jsp页面中的标记使用,以便动态呈现行业下拉列表。 最后,此方法返回一个视图名称(“Registration”),该名称将映射到上面的注册表单页面。 processRegistration():此方法处理表单提交(通过POST请求)。这里的重要参数是:

@ModelAttribute("userForm") User user

这将使模型映射中存储在键“userForm”下的模型对象可用于方法体。同样,键“userForm”必须与标记的commandName属性的值匹配。 提交表单时,Spring会自动将表单的字段值绑定到模型中的后备对象,因此我们可以通过此后备对象访问用户输入的表单值,如下所示:

System.out.println("username: " + user.getUsername());

出于演示目的,此方法仅打印出User对象的详细信息,最后返回成功页面的视图名称(“RegistrationSuccess”)。

答案 1 :(得分:0)

这是可能的,但不适用于cglib。 Cglib是一个代理库,只允许您覆盖现有方法,但不能定义新的,以便将动态模型传递给Spring或任何其他bean验证库。

当然可以使用Javassist或Byte Buddy(披露:我是作者)生成这样的类,它允许您定义这样的元数据。例如,使用Byte Buddy,您可以定义动态类:

new ByteBuddy()
  .subclass(Object.class)
  .defineField("foo", String.class, Visibility.PUBLIC)
  .annotateField(AnnotationDescription.Builder.ofType(NotNull.class).build())
  .make();

使用此代码,您可以加载该类,为"foo"字段分配一个值,并将此bean对象提供给bean验证库。

与此同时,这种方法对我来说过于设计。如果您需要提供与您无法重新实现的特定JSR 303验证器的兼容性,我只会使用此解决方案。否则,您可能更愿意直接验证数据。

如果您的框架还需要getter和setter,您可以通过以下方式添加它们:

builder = builder
  .defineMethod("getFoo", String.class, Visibility.PUBLIC)
    .intercept(FieldAccessor.ofField("foo"));
  .defineMethod("setFoo", void.class, Visibility.PUBLIC)
    .withParameters(String.class)
    .intercept(FieldAccessor.ofField("foo"));