Reputation: 85779
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.
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.
Upvotes: 16
Views: 20174
Reputation: 2961
You could consider using RequestBodyAdvice. See the javadocs. The HttpInputMessage object where you can access the http headers, is passed into the interface methods.
Upvotes: 0
Reputation: 85779
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:
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.
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.
Upvotes: 2
Reputation: 1094
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.
Upvotes: 12