Search code examples
javaspringinject

Inject request attribute from request to spring controller methods


I have some spring @RestControllers methods that I would like to inject with a value that comes with every request as a request attribute(containing the user) something like:

@RestController
@RequestMapping("/api/jobs")
public class JobsController {
     // Option 1 get user from request attribute as prop somehow
     private String userId = "user1";

    // Option 2 inject into method using aspect or something else
    @RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<Jobs>> getJobs() throws ResourceNotFoundException {
       // currentUser is injected 
       this.getJobs(currentUser);
}

I know I can do that:

@RestController
@RequestMapping("/api/jobs")
public class JobsController {

    @RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<Jobs>> getJobs(HttpServletRequest request) throws ResourceNotFoundException { 
       String currentUser = null;
       if (request.getAttribute("subject") != null) {
           currentUser = request.getAttribute("subject").toString();
       }
       this.getJobs(currentUser);
}

But that would require me to add this code at every method in my program, which seems to me, to be a really bad practice.
Is there a way to achieve what I want?

If the answer do require aspect, a code example will be much appreciated since I only read about it, but never actually did something with aspect.

Update
The code i suggested can be simplified using this:

@RestController
@RequestMapping("/api/jobs")
public class JobsController {

    @RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<Jobs>> getJobs(@Value("#{request.getAttribute('subject')}" String currentUser) throws ResourceNotFoundException { 
       this.getJobs(currentUser);
}

But still require me to add that parameter at every method. Can this parameter be injected to every method somehow?


Solution

  • You could use a Filter to populate a ThreadLocal<String> variable that stores that attribute:

    public class MyFilter implements Filter {
    
      @Override
      public void init(FilterConfig filterConfig) throws ServletException {}
    
      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        ContextHolder.setSubject(request.getAttribute('subject'));
        chain.doFilter(request, response);
      }
    
      @Override
      public void destroy() {
        ContextHolder.removeSubject();
      }
    }
    
    
    public class ContextHolder {
    
      private static final ThreadLocal<String> SUBJECT = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
          return "empty";
        }
      };
    
      public static void setSubject(String subject) {
        SUBJECT.set(subject);
      }
    
      public static String getSubject() {
        return SUBJECT.get();
      }
    
      public static void removeSubject() {
        SUBJECT.remove();
      }
    }
    

    The filter will be configured to intercept all requests and populate the SUBJECT variable. By using a ThreadLocal, you make sure that each thread has it's own subject value. You can now get that value anywhere in your application by calling ContextHolder.getSubject():

      @RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
      public ResponseEntity<List<Jobs>> getJobs(HttpServletRequest request) throws ResourceNotFoundException { 
        this.getJobs(ContextHolder.getSubject());
      }
    

    You will also have to register the Filter in the web.xml file:

    <filter>
        <filter-name>MyFilter</filter-name>
        <filter-class>com.MyFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MyFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    In case you had multiple attributes, you could use a ThreadLocal<Map<String, String>> variable instead.