如何装饰所有请求从header中取值并添加到body参数中?

How to decorate all requests to take a value from header and add it in the body parameter?

背景

我正在使用 Spring MVC 创建 RESTful 服务。目前,我有以下控制器结构:

@RestController
@RequestMapping(path = "myEntity", produces="application/json; charset=UTF-8")
public class MyEntityController {

    @RequestMapping(path={ "", "/"} , method=RequestMethod.POST)
    public ResponseEntity<MyEntity> createMyEntity(
        @RequestBody MyEntity myEntity,
        @RequestHeader("X-Client-Name") String clientName) {
        myEntity.setClientName(clientName);
        //rest of method declaration...
    }

    @RequestMapping(path={ "/{id}"} , method=RequestMethod.PUT)
    public ResponseEntity<MyEntity> updateMyEntity(
        @PathVariable Long id,
        @RequestBody MyEntity myEntity,
        @RequestHeader("X-Client-Name") String clientName) {
        myEntity.setClientName(clientName);
        //rest of method declaration...
    }

    @RequestMapping(path={ "/{id}"} , method=RequestMethod.PATCH)
    public ResponseEntity<MyEntity> partialUpdateMyEntity(
        @PathVariable Long id,
        @RequestBody MyEntity myEntity,
        @RequestHeader("X-Client-Name") String clientName) {
        myEntity.setClientName(clientName);
        //rest of method declaration...
    }
}

如您所见,所有这三种方法都为 header @RequestHeader("X-Client-Name") String clientName 接收相同的参数,并以相同的方式将其应用于每个方法:myEntity.setClientName(clientName)。我将创建类似的控制器,对于 POST,PUT 和 PATCH 操作将包含几乎相同的代码,但用于其他实体。目前,大多数实体都设计为通过超级 class:

支持此字段
public class Entity {
    protected String clientName;
    //getters and setters ...
}
public class MyEntity extends Entity {
    //...
}

此外,我使用拦截器来验证是否为请求设置了 header。

问题

如何避免通过控制器 classes 和方法重复相同的代码?有没有一种干净的方法来实现它?还是我应该声明变量并在所有地方重复这些行?

西班牙社区也有人问过这个问题。这里是 the link.

我的建议是将 header 值存储在 Spring 拦截器或过滤器内的请求作用域 bean 中。然后你可以在任何你想要的地方自动装配这个bean - 服务或控制器并使用存储的客户端名称值。

代码示例:

public class ClientRequestInterceptor extends HandlerInterceptorAdapter {

    private Entity clientEntity;

    public ClientRequestInterceptor(Entity clientEntity) {
        this.clientEntity = clientEntity;
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        String clientName = request.getHeader("X-Client-Name");
        clientEntity.setClientName(clientName);
        return true;
    }
}

在您的配置文件中:

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(clientRequestInterceptor());
    }

    @Bean(name="clientEntity")
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public Entity clientEntity() {
        return new Entity();
    }

    @Bean
    public ClientRequestInterceptor clientRequestInterceptor() {
        return new ClientRequestInterceptor(clientEntity());
    }

}

然后,假设我们必须在我们的控制器中使用这个 bean:

@RestController
@RequestMapping(path = "myEntity", produces="application/json; charset=UTF-8")
public class MyEntityController {

    @Autowired
    private Entity clientEntity; // here you have the filled bean

    @RequestMapping(path={ "", "/"} , method=RequestMethod.POST)
    public ResponseEntity<MyEntity> createMyEntity(@RequestBody MyEntity myEntity) {
        myEntity.setClientName(clientEntity.getClientName());
        //rest of method declaration...
    }
    // rest of your class methods, without @RequestHeader parameters

}

我没有编译这段代码,如果有错误请指正。

我在西班牙网站上得到了一个有趣的答案(我也在那里发布了这个问题),基于这个答案,我可以生成适合这个需求的答案。这里是 my answer on SOes.


基于 @PaulVargas's answer 和@jasilva 的想法(在控制器中使用继承),我虽然针对这种情况提出了更强大的解决方案。设计由两部分组成:

  1. 为具有此行为的控制器定义一个超级 class。我称之为 class BaseController<E extends Entity> 因为 Entity 是几乎所有我的实体的超级 class (在问题中解释)。在此 class 中,我将检索 @RequestBody E entity 参数的值并将其分配给 @ModelAttribute 参数,如@PaulVargas 解释的那样。泛型的力量在这里有很大帮助。

  2. 我的控制器将扩展 BaseController<ProperEntity>,其中 ProperEntity 是正确的实体 class 我需要处理该控制器。然后,在方法中,我不会注入 @RequestBody@RequestHeader 参数,而是只注入 @ModelAttribute(如果需要)。

Aquí muestro el código para el diseño descrito:

//1.
public abstract class BaseController<E extends Entity> {

    @ModelAttribute("entity")
    public E populate(
            @RequestBody(required=false) E myEntity,
            @RequestHeader("X-Client-Name") String clientName) {
        if (myEntity != null) {
            myEntity.setCreatedBy(clientName);
        }
        return myEntity;
    }
}
//2.
@RestController
@RequestMapping(path = "myEntity", produces="application/json; charset=UTF-8")
public class MyEntityController extends BaseController<MyEntity> {

    @RequestMapping(path={ "", "/"} , method=RequestMethod.POST)
    public ResponseEntity<MyEntity> createMyEntity(
        @ModelAttribute("entity") MyEntity myEntity) {
        //rest of method declaration...
    }

    @RequestMapping(path={ "/{id}"} , method=RequestMethod.PUT)
    public ResponseEntity<MyEntity> updateMyEntity(
        @PathVariable Long id,
        @ModelAttribute("entity") MyEntity myEntity) {
        //rest of method declaration...
    }

    @RequestMapping(path={ "/{id}"} , method=RequestMethod.PATCH)
    public ResponseEntity<MyEntity> partialUpdateMyEntity(
        @PathVariable Long id,
        @ModelAttribute("entity") MyEntity myEntity) {
        //rest of method declaration...
    }    
}

这样,我就不需要在每个方法和控制器中重写那些代码行,实现我所要求的。

您可以考虑使用RequestBodyAdvice。见javadocs。 您可以访问 http headers 的 HttpInputMessage object 被传递到接口方法中。