3

我有一个动态模型,由 X 个单选、文本、选择、多选组成。这基本上就像后端的 EAV 数据库。

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

所以,我想使用典型的 Spring MVC 开发方式,通过ModelAttribute @Valid特性等来绑定表单对象。唯一的区别是模型对象在运行时是未知/未定义的。

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

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

4

2 回答 2

1

这是可能的,但不能使用 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"));
于 2016-11-12T23:52:08.520 回答
-1

为控制器类(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 /register:

@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 会自动将表单的字段值绑定到模型中的 backing 对象,因此我们可以通过这个 backing 对象访问用户输入的表单值,如下所示:

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

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

于 2016-11-11T22:29:30.840 回答