如何装饰所有请求从头部获取值并将其添加到body参数?

时间:2016-03-29 21:07:03

标签: java spring rest spring-mvc http-headers

背景

我正在使用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...
    }
}

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

public class Entity {
    protected String clientName;
    //getters and setters ...
}
public class MyEntity extends Entity {
    //...
}

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

问题

如何通过控制器类和方法避免重复相同的代码?有没有一个干净的方法来实现它?或者我应该声明变量并在任何地方重复这些行吗?

西班牙社区也提出了这个问题。这是the link

3 个答案:

答案 0 :(得分:2)

我的建议是将标头值存储在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

}

我没有编译这段代码,所以如果我犯了一些错误,请纠正我。

答案 1 :(得分:2)

我在西班牙语网站(我也发布了这个问题)中得到了一个有趣的答案,根据这个答案,我可以生成适合这种需要的答案。这是my answer on SOes

基于@PaulVargas's answer和来自@jasilva的想法(在控制器中使用继承)我虽然对这种情况有更强的解决方案。设计包括两部分:

  1. 使用此行为为控制器定义超类。我将此类称为BaseController<E extends Entity>,因为Entity几乎是我的实体的超类(在问题中解释)。在这个课程中,我将检索@RequestBody E entity参数的值,并将其分配到@ModelAttribute参数,如@PaulVargas解释。泛型能量在这里有很大帮助。

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

  3. Aquímuestroelcódigoparaeldiseñodescrito:

    //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...
        }    
    }
    

    通过这种方式,我不需要在每个方法和控制器中重写这些代码行,从而实现我所要求的。

答案 2 :(得分:0)

您可以考虑使用RequestBodyAdvice。请参阅javadocs。 您可以访问http标头的HttpInputMessage对象将传递给接口方法。