15

Background

I'm creating RESTful services using Spring MVC. Currently, I have the following structure for a controller:

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

As you can see, all these three methods receive the same parameter for the header @RequestHeader("X-Client-Name") String clientName and applies it in the same way on each method: myEntity.setClientName(clientName). I will create similar controllers and for POST, PUT and PATCH operations will contain almost the same code but for other entities. Currently, most entities are designed to support this field vía a super class:

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

Also, I use an interceptor to verify that the header is set for requests.

Question

How can I avoid repeating the same code through controller classes and methods? Is there a clean way to achieve it? Or should I declare the variable and repeat those lines everywhere?

This question was also asked in the Spanish community. Here's the link.

Community
  • 1
  • 1
Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
  • are you fine with repeating @RequestHeader in every method (so you bother only about calling a setter) - or you want to avoid both of them? – AdamSkywalker Mar 29 '16 at 21:13
  • @AdamSkywalker I prefer to have a single place where I write this code rather than in several classes and methods :) – Luiggi Mendoza Mar 29 '16 at 21:13
  • Is that request header name "X-Client-Name" will be the same also for other entities? – Thomas Weglinski Mar 29 '16 at 22:17
  • As header, yes. As value for this header, it can have different values. This is used for a field to know which user did an action e.g. "X-Client-Name" can be "Luiggi Mendoza" on first request and then it can be "Tomas Weglinski" on a new request. – Luiggi Mendoza Mar 29 '16 at 22:20
  • There is no simple answer to your question and no Spring "standard" to do this. But I would personally consider using an interceptor or filter, where you can autowire bean of scope "request". This bean will contain the clientName string to fill out in the filter. Then you can autowire this bean later in the controller or service and use it as you want. – Thomas Weglinski Mar 29 '16 at 23:07
  • @ThomasWeglinski Filter and Interceptop won't work here since the `@RequestBody` parameter hasn't been parsed yet by Spring. I've already debugged this and found that the parameters are evaluated just before invoke the method on the controller. – Luiggi Mendoza Mar 30 '16 at 14:40
  • You are right about the request body, but I thought about storing in the interceptor only the header value in the request scoped bean. – Thomas Weglinski Mar 30 '16 at 16:37
  • @ThomasWeglinski maybe I'm misunderstanding your advice but I think I can store that value in a request scoped bean, then inject that bean as parameter in each method and execute the code in all methods, similar to what I'm doing here. If that's not what you meant, please provide an example. – Luiggi Mendoza Mar 30 '16 at 16:39

3 Answers3

12

My suggestion is to store the header value in the request scoped bean inside the Spring interceptor or filter. Then you may autowire this bean wherever you want - service or controller and use the stored client name value.

Code example:

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;
    }
}

In your configuration file:

@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());
    }

}

Then, lets assume we have to use this bean in our controller:

@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

}

I have not compiled this code, so correct me if I made some mistakes.

Thomas Weglinski
  • 1,094
  • 1
  • 10
  • 21
  • Did you noticed that `@RequestBody MyEntity myEntity` is a request parameter filled by the request body? I haven't tested the code yet, but I'm not sure that the parameter is being filled by Spring because now it's a managed bean (noticed by `WebConfig#clientEntity`) when in my case is a plain POJO. – Luiggi Mendoza Mar 30 '16 at 20:56
  • @LuiggiMendoza I've updated my answer, to show what I'm thinking of in exemplary controller method that you provided. – Thomas Weglinski Mar 30 '16 at 21:30
  • 1
    Using your approach, I still have to write this line: `myEntity.setClientName(clientEntity.getClientName());` in every method of every controller I need. – Luiggi Mendoza Mar 31 '16 at 15:00
  • Working great. Since I'm using a handler layer to process the request instead of doing it on controller, I have access to all the headers before reaching to the controller eliminating the need of adding header in each method signature. Thanks @ThomasWeglinski – PravyNandas May 04 '20 at 15:45
  • 1
    It doesn't work for me. When I autowire, i get the object with null fields @PravyNandas – RukaDo May 29 '20 at 19:22
  • 2
    WebMvcConfigurerAdapter is now deprecated, use WebMvcConfigurer instead. – user1123432 Oct 28 '20 at 14:59
  • I upvoted before testing. I am not sure how this solution can possibly work, since the beans are instantiated explicitly. This works around Spring's dependency injection – Hubert Grzeskowiak May 19 '21 at 08:23
2

I've got an interesting answer in the Spanish site (where I also posted this question) and based on that answer I could generate mine that adapts to this need. Here's my answer on SOes.


Based on @PaulVargas's answer and an idea from @jasilva (use inheritance in controller) I though on a stronger solution for this case. The design consists of two parts:

  1. Define a super class for controllers with this behavior. I call this class BaseController<E extends Entity> because Entity is the super class for almost al my entities (explained in the question). In this class I'll retrieve the value of @RequestBody E entity parameter and assign it into a @ModelAttribute parameter like @PaulVargas explains. Generics power helps a lot here.

  2. My controllers will extend BaseController<ProperEntity> where ProperEntity is the proper entity class I need to handle with that controller. Then, in the methods, instead of injecting @RequestBody and @RequestHeader parameters, I'll only inject the the @ModelAttribute (if needed).

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

In this way, I don't need to rewrite those lines of code in every method and controller, achieving what I've asked.

Community
  • 1
  • 1
Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
  • 1
    can you explain the order of method invocation? when populate method is called in this scenario? – AdamSkywalker May 05 '16 at 20:56
  • @AdamSkywalker `populate` method is called before calling the proper method that will handle the request. In this method, the request body will be read because it has a parameter `@RequestBody(required=false) E myEntity`. Then, it will generate a variable that will be stored as model attribute. Later, the method that handles the request is called e.g. `createMyEntity` or `updateMyEntity`. Since Spring already injected itself the `ModelAttribute`, it can retrieve it as parameter when declaring `@ModelAttribute("entity") MyEntity myEntity` and use it inside the method. – Luiggi Mendoza May 05 '16 at 21:00
  • I understood the ModelAttribute injection, but what makes the guarantee that populate method is called earlier than actual methods? – AdamSkywalker May 05 '16 at 21:07
  • @AdamSkywalker please check here [Spring MVC docs](http://docs.spring.io/autorepo/docs/spring/3.2.x/spring-framework-reference/html/mvc.html) (it refers to old 3.2 but works good for this concept), search **Using `@ModelAttribute` on a method** – Luiggi Mendoza May 05 '16 at 21:15
0

You could consider using RequestBodyAdvice. See the javadocs. The HttpInputMessage object where you can access the http headers, is passed into the interface methods.

maxhuang
  • 2,681
  • 1
  • 21
  • 26
  • Hi, Max. Can you advise any option to get request params/uri data? HttpInputMessage consists only headers and body... – kingoleg Mar 05 '19 at 11:13
  • Hi, you can look at HandlerMethodArgumentResolver interface. In the resolveArgument method, you can access the HttpServletRequest and HttpInputMessage from the NativeWebRequest param. This is how you do that: HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest); – maxhuang Mar 07 '19 at 02:52
  • Thanks, Max. I found that @ModelAttribute work perfect for my case – kingoleg Mar 09 '19 at 12:40