In this tutorial, you will learn how to define custom filters and specify their order of invocation in the Spring Boot application.
What is a custom filter?
Filters enable us to filter on either the request to a resource, the response from a resource, or both. Spring Boot provides a few options to register custom filters in the application. With custom filters, we can perform the following operations:
- Processing the request before it gets over to the controller
- Processing the response before it reaches the client
Defining Filters and their Invocation Order
We will be creating two filters:
- TransactionFilter – to start and commit transactions
- RequestResponseLoggingFilter – to log requests and responses
A filter class must implement the Filter interface, which requires overriding the init() and doFilter() methods.
For Spring to recognize a filter, we need to define it as a bean with the @Component annotation.
The @Order annotation specifies the order of execution of the filter.
Transaction Filter
The purpose of Transaction Filter is to log the request and response before starting and committing a transaction. We want this filter to be invoked first. Therefore we have set the order to 1.
The chain.doFilter(req, resp) call passes control to the filter with the next invocation order.
@Component @Order(1) public class TransactionFilter implements Filter { private final static Logger LOG = LoggerFactory.getLogger(TransactionFilter.class); @Override public void init(final FilterConfig filterConfig) { LOG.info("Initializing filter :{}", this); } @Override public void doFilter(final ServletRequest req, final ServletResponse resp, final FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; LOG.info("Starting Transaction for req :{}", request.getRequestURI()); chain.doFilter(req, resp); LOG.info("Committing Transaction for req :{}", request.getRequestURI()); } @Override public void destroy() { LOG.warn("Destructing filter :{}", this); } }
RequestResponseLogging Filter
The RequestResponseLogging filter is very similar to a TransactionFilter. We want RequestResponseLogging filter to be invoked after the TransactionFilter. Therefore, we have set the order to 2.
@Component @Order(2) public class RequestResponseLoggingFilter implements Filter { private static final Logger LOG = LoggerFactory.getLogger(RequestResponseLoggingFilter.class); @Override public void init(final FilterConfig filterConfig) { LOG.info("Initializing filter :{}", this); } @Override public void doFilter(final ServletRequest req, final ServletResponse resp, final FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse res = (HttpServletResponse) resp; LOG.info("Incoming Request {} : {}", request.getMethod(), request.getRequestURI()); chain.doFilter(req, resp); LOG.info("Incoming Response :{}", res.getContentType()); } @Override public void destroy() { LOG.warn("Destructing filter :{}", this); } }
Filtering on a specific URL Pattern
The filters we defined above are registered by default for all the URLs in our application. It may be useful to want a filter to apply only certain URL patterns. Spring allows us to define a Configuration class and register our filter using FilterRegistrationBean.
We register our filter with FilterRegistrationBean using the setFilter() method and can specify one or more URL patterns to filter using addUrlPatterns(). To set the filtration order, we use the setOrder() method on the FilterRegistrationBean itself.
@Configuration public class FilterConfig { // comment the @Component in the filter class definition to register only for a url pattern @Bean public FilterRegistrationBean<RequestResponseLoggingFilter> loggingFilter() { FilterRegistrationBean<RequestResponseLoggingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new RequestResponseLoggingFilter()); registrationBean.addUrlPatterns("/users/*"); registrationBean.setOrder(2); return registrationBean; } }
We can do the same for TransactionFilter if we want to.
A Quick Example of URL-based Filter
Let’s create a simple User endpoint and send an HTTP request to it. Before that, we will need a Plain Old Java Object (POJO) of User:
public class User { private String id; private String name; private String email; public User(String id, String name, String email) { super(); this.id = id; this.name = name; this.email = email; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
Now, we define our Rest Controller:
@RestController @RequestMapping("/users") public class UserController { private static final Logger LOG = LoggerFactory.getLogger(UserController.class); @GetMapping("") public List<User> getAllUsers() { LOG.info("Fetching all the users"); return Arrays.asList( new User(UUID.randomUUID().toString(), "User1", "[email protected]"), new User(UUID.randomUUID().toString(), "User2", "[email protected]"), new User(UUID.randomUUID().toString(), "User3", "[email protected]")); } }
Let’s observe the output:
07:14:38 INFO com.spring.demo.TransactionFilter - Starting Transaction for req :/users 07:14:38 INFO c.s.d.RequestResponseLoggingFilter - Incoming Request GET : /users ... 07:14:38 INFO c.s.d.RequestResponseLoggingFilter - Incoming Response :application/json;charset=UTF-8 07:14:38 INFO com.spring.demo.TransactionFilter - Committing Transaction for req :/users
This confirms the filters are invoked in the desired order.
Thanks for following the tutorial till the end. I hope that this adds to your knowledge.
Happy Learning!