DEV Community

Shilpa Gore
Shilpa Gore

Posted on

(Mis-)Use Spring proxy magic to inject Http request into business layer - should you?

A few weeks ago, while reviewing a Spring Boot codebase, I came across a service that injected HttpServletRequest as a field into a singleton bean. This bean is called from service layer to construct and publish domain events, where events needed information from the incoming request. The code “worked,” and at first glance even looked convenient and served the purpose. But the deeper I looked, the more uneasy I felt. A request-scoped concern was quietly living inside a singleton bean, and a transport-layer abstraction had leaked straight into business logic. That small design choice opened the door to a larger conversation about how easily Spring’s magic can blur architectural boundaries if we’re not careful.

@Component
public class EventFactory {
   @Autowired
   private HttpServletRequest request;

   public void prepareAndSendEvent(){
     OrderEvent ordEvent = new OrderEvent;
     ordEvent.sentClientIP(request.getRemoteAddr());
   } 
}
Enter fullscreen mode Exit fullscreen mode

Why does this feel off?
Any body familiar with the Spring bean's scope can easily tell that this is a singleton bean. Singleton beans are created and initialised only once, right during the startup phase. As the name suggests, the same object is used for serving every http request that comes in. On the other hand, HttpServletRequest is a request-scoped bean and a new object of it is instantiated for every incoming Http request. The natural question that needs to be answered is - how is request field variable initialised when during startup no http requests are expected? Will this even work without mixing up information between different Http requests? This where Spring magic steps in - Spring injects proxy as a placeholder for request-scoped beans. This is not a flaw, rather a powerful tool that Spring relies on to manage the request processing.

Bridging difference in scope
Spring during bean initialisation field realises that it cannot bind any object to request variable(in the code snipped above). But, it is familiar with HttpServletRequest being a request scoped variable - for managing lifecycle of a http request - whose current value is stored in the ThreadLocal(Scoped variable in future versions) until the thread created for the request processing is alive. Back to our code, Spring creates a proxy method and initialises the return value of that method to request variable. When the the business logic access value from request, the proxy method gets the actual value from the current HttpServletRequest. This is Spring's usual behaviour to deal with difference in scope of a bean and injected bean(s).

Why this is a smell?
Spring with its one request per thread execution model and different bean annotations such as "@Component", "@Service", "@Repository" makes the design a three layered architecture with stateless singleton beans encapsulating business logic a breeze. This separation of concerns in terms of controller layer where HttpRequests land and go no further, service layer which receives a DTO(Data Transfer Object) constructed out of Http Request body, Request header and Session Object. Service layer applies business logic and calls gateway layer/ repository layer further. In the same manner, the client responses end at gateway layer. DTO is created out of the received response for service layer, if needed.

It is the developer(s) responsibility to maintain and enforce the separation of concern in the application. But in this case, because Spring makes it possible to inject request scoped bean into a singleton bean through the mechanism of scoped proxies, it is tempting to (mis)use this magic. Since it works flawlessly many developers may argue for it. However, the price that we pay is the silent degradation of clean code and compromise of layered architecture for very little convenience.

What problems can this cause?
Apart from the smells mentioned above this approach can pose challenges to maintainability and testing strategy of the application.

  • Falls apart in asynchronous execution
    The reason why the use of current http request in the method that prepares and sends the event works is that it is performed synchronously, in the same thread where the ThreadLocal created by Spring is available for real-time object resolution. It is not uncommon, that in the lifetime of an application - for speeding up response time - such a method comes to be executed asynchronously. In such a case, the code works but the ThreadLocal variable is not available as asynchronous execution is performed on a different thread outside of the current context. As a result the execution will be break with IllegalStateException.

  • Testing complexity increases
    Write unit tests for EventFactory gets complex because HttpServletRequest has to be mocked. Mocked HttpServletRequest is required to be mocked only while testing the controller beans in a Spring Boot application. With this approach, focus shifts from unit tests to mocking HttpServletRequest resulting in complex tests.

Recommended approach
The best approach to make information from HTTP request to be made available in business layers, is context extract and method injection. The required information is extracted into a DTO and send as a method parameter to the business layer beans.

@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;

    @GetMapping
    public List<OrderDto> getOrders(
            HttpServletRequest request,
            @RequestHeader("X-Tenant-Id") String tenantId) {

        String userAgent = request.getHeader("User-Agent");
        String clientIp = request.getRemoteAddr();

        RequestContext ctx = new RequestContext(tenantId, clientIp);

        return orderService.getOrders(ctx);
    }
}
Enter fullscreen mode Exit fullscreen mode
@Component
public class EventFactory {
   public void prepareAndSendEvent(EventInfo eventInfo, RequestContext requestContext){
   // requestContext object has the needed information 
}

}
Enter fullscreen mode Exit fullscreen mode

TL;DR
Injecting Request-Scoped HttpServletRequest into business layer works due to Spring magic, but it comes with a hidden cost and risk. It is the developer's responsibility to avoid using it when a cleaner alternative is available.

Top comments (0)