Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Working With Filters in Spring

DZone 's Guide to

Working With Filters in Spring

Check out this post to learn more about working with filters in Spring.

· Java Zone ·
Free Resource

In this article, I am going to talk about how to best implement filters over HTTP requests in Spring. That is, assuming we have a program listening in a URI, we can specify that we want to execute something before the requests are processed by the controller.

This is very useful if we want all the requests to meet a requirement, for example, that they must include a specific header.

As usual, the best way to understand the concept is by writing an example program and explaining it. The source of the program that I have written below is on my GitHub page.

In this program, there is a controller and listening REST requests in the class PrincipalCrontoller.java. This is the code for the class:

@RestController
public class PrincipalController {
@Autowired
SillyLog sillyLog;

@GetMapping("*")
public String entryOther(HttpServletRequest request,HttpServletResponse response)
{
sillyLog.debug("In entryOther");
if (response.getHeader("PROFE")!=null)
sillyLog.debug("Header contains PROFE: "+response.getHeader("PROFE"));
if (response.getHeader("CAKE")!=null)
sillyLog.debug("Header contains CAKE: "+response.getHeader("CAKE"));
return "returning by function entryOther\r\n"+
sillyLog.getMessage();
}
@GetMapping(value={"/","one"})
public String entryOne(HttpServletRequest request,HttpServletResponse response)
{
sillyLog.debug("In entryOne");
if (response.getHeader("PROFE")!=null)
{
sillyLog.debug("Header contains PROFE: "+response.getHeader("PROFE"));
return entryTwo(response);
}
return "returning by function entryOne\r\n"+
sillyLog.getMessage();
}
@GetMapping("two")
public String entryTwo(HttpServletResponse response)
{
sillyLog.debug("In entryTwo");
if (response.getHeader("PROFE")!=null)
sillyLog.debug("Header contains PROFE: "+response.getHeader("PROFE"));
return "returning by function entryTwo\r\n"+
sillyLog.getMessage();
}
@GetMapping("three")
public String entryThree()
{
sillyLog.debug("In entryThree");
return "returning by function entryThree\n"+
sillyLog.getMessage();
}
@GetMapping("redirected")
public String entryRedirect(HttpServletRequest request)
{
sillyLog.debug("In redirected");
return "returning by function entryRedirect\n"+
sillyLog.getMessage();
}
}


The entryOrderfunction will capture all GET requests, which the URIs aren't defined in the program. The entryOne function will be processing the GET requests to http://localhost:8080/one or http://localhost:8080/.

The SillyLog is a simple class where we add logs entries and then return them in the body response, so we will be able to see by where our request has gone.

In this application, it is defined as three filters: MyFilter.java, OtherFilter.java, and CakesFilter.java. The first one has preference over the second because of the parameter passed in the label @Order. I will speak more on the third filter later.

In the file MyFilter.java, we define our first filter as:

@Component
@Order(1)
public class MyFilter implements Filter{
@Autowired
SillyLog sillyLog;

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse  myResponse= (HttpServletResponse) response;
sillyLog.debug("Filter: URL"
+ " called: "+httpRequest.getRequestURL().toString());

if (httpRequest.getRequestURL().toString().endsWith("/one")){
myResponse.addHeader("PROFE", "FILTERED");
chain.doFilter(httpRequest, myResponse);
return;
}
        if (httpRequest.getRequestURL().toString().endsWith("/none")){   
            myResponse.setStatus(HttpStatus.BAD_GATEWAY.value());
    myResponse.getOutputStream().flush();
    myResponse.getOutputStream().println("-- I don't have any to tell you --");
            return; // No hago nada.
        }
if (httpRequest.getRequestURL().toString().endsWith("/redirect")){
myResponse.addHeader("PROFE", "REDIRECTED");
myResponse.sendRedirect("redirected");
chain.doFilter(httpRequest, myResponse);
return;
}
if (httpRequest.getRequestURL().toString().endsWith("/cancel")){
myResponse.addHeader("PROFE", "CANCEL");
myResponse.setStatus(HttpStatus.BAD_REQUEST.value());
myResponse.getOutputStream().flush();
myResponse.getOutputStream().println("-- Output by filter error --");
chain.doFilter(httpRequest, myResponse);
return;
}
chain.doFilter(request, response);
}
}


The OtherFilter class is a bit easier:

@Component
@Order(2)
public class OtherFilter implements Filter{
@Autowired
SillyLog sillyLog;

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
   HttpServletRequest httpRequest= (HttpServletRequest) request;
   HttpServletResponse  myResponse= (HttpServletResponse) response;
      sillyLog.debug("OtherFilter: URL"
      + " called: "+httpRequest.getRequestURL().toString());
   if (myResponse.getHeader("PROFE")!=null)
       sillyLog.debug("OtherFilter: Header contains PROFE: "+myResponse.getHeader("PROFE"));
   chain.doFilter(request, response);
   }
}


The first thing we have to do to define a general filter as it is tagged in the class with the label @Component. We should also implement the Filter interface. Our class could also extend from OncePerRequestFilter, which implements the interface Filter and adds some features. This is so that the filter is only executed once by request. In this example, we are going to simplify it to the maximum and we will directly implement the Filter interface.

The Filterinterface has three functions. void init(FilterConfig filterConfig) throws ServletException. This function will be executed by the web container, so it will be only executed once when the component is instantiated by Spring. void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOExceptionServletException. This function will be executed each time an HTTP request is received. We will be able to see the content of the HTTP request in the ServletRequest object and we can modify the answer in the ServletResponse object. 

FilterChain will be used to continue the flow of the request. void destroy()  is called by the Spring web container to indicate to the filter that it will stop being active.

As I mentioned earlier, the label @Order allows us to specify the order in which each filter will be executed. In this case, the MyFilter filter will run before the OtherFilter filter.

MyFilter class performs different actions depending on the URL called. OtherFilter only adds a log when it is executed.

In the example code, we use only the doFilter function. First, it converts the ServletResponse class to HttpServletResponse and then the ServletRequest class to HttpServletRequest. This is necessary in order to be able to access certain properties of objects that would not otherwise be available.

I'm going to explain, step by step, the different cases contemplated in the MyFilter class, depending on the URL invoked.

  • /one: We add a PROFE header with the FILTERED value to the response. It is important to emphasize that we can only modify the response if the request is unalterable. This is after we run the DoFilter function of the chain class so that the flow is continued. In this case, we will run the second filter, and then, it throws the entryOne function of the controller.

The first line is returned by the entryTwo function. The logs added are shown below. I recommend watching the source code if you don't understand where are going out so many lines.

> curl  -s http://localhost:8080/one
returning by function entryTwo
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/1 Filter: URL called: http://localhost:8080/one
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/2 OtherFilter: URL called: http://localhost:8080/one
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/3 OtherFilter: Header contains PROFE: FILTERED
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/4 In entryOne
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/5 Header contains PROFE: FILTERED
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/6 In entryTwo
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/7 Header contains PROFE: FILTERED


> curl  -s http://localhost:8080/one
returning by function entryTwo
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/1 Filter: URL called: http://localhost:8080/one
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/2 OtherFilter: URL called: http://localhost:8080/one
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/3 OtherFilter: Header contains PROFE: FILTERED
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/4 In entryOne
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/5 Header contains PROFE: FILTERED
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/6 In entryTwo
SillyLog: 15eb34c2-cfac-4a27-9450-b3b07f44cb50/7 Header contains PROFE: FILTERED

This is better, isn't it? Now, in the headers, it shows our value for PROFE and we can see the order to redirect to http://localhost:8080/redirected. Note that the HTTP code returned is 302 (redirect). If we say for the curl to follow the redirection, passing it the -L parameter, we will see what we expected.

curl -L -s http://localhost:8080/redirect
returning by function entryRedirect
SillyLog: dcfc8b09-84a4-40a1-a2d6-43340abdf50c/1 Filter: URL called: http://localhost:8080/redirected
SillyLog: dcfc8b09-84a4-40a1-a2d6-43340abdf50c/2 OtherFilter: URL called: http://localhost:8080/redirected
SillyLog: dcfc8b09-84a4-40a1-a2d6-43340abdf50c/3 In redirected
  • /none . I put the HTTP code to return the value BAD_GATEWAY and I write in the body "I don't have any to tell you". I don't run the doFilterfunction, therefore, the second filter will not be called, nor would it be passed to the controller.

> curl  -s http://localhost:8080/none
-- I don't have any to tell you --


  • /cancel: I put the HTTP code to return the value BAD_REQUEST and I write in the body "Output by filter error" . I run the doFilterfunction therefore the OtherFilter filter will be executed. After the entryOtherin the controller, it will be run.

curl  -s http://localhost:8080/cancel
-- Output by filter error --
returning by function entryOther
SillyLog: 1cf7f7f9-1a9b-46a0-9b97-b8d5caf734bd/1 Filter: URL called: http://localhost:8080/cancel
SillyLog: 1cf7f7f9-1a9b-46a0-9b97-b8d5caf734bd/2 OtherFilter: URL called: http://localhost:8080/cancel
SillyLog: 1cf7f7f9-1a9b-46a0-9b97-b8d5caf734bd/3 OtherFilter: Header contains PROFE: CANCEL
SillyLog: 1cf7f7f9-1a9b-46a0-9b97-b8d5caf734bd/4 In entryOther
SillyLog: 1cf7f7f9-1a9b-46a0-9b97-b8d5caf734bd/5 Header contains PROFE: CANCEL


  •  /otros: In any another call, the doFilter function of the chain class is executed in MyFilter. After the second filter is executed, finally, some function in the controller will take control.

> curl -L -s http://localhost:8080/three
returning by function entryThree
SillyLog: a2dd979f-4779-4e34-b8f6-cae814370426/1 Filter: URL called: http://localhost:8080/three
SillyLog: a2dd979f-4779-4e34-b8f6-cae814370426/2 OtherFilter: URL called: http://localhost:8080/three
SillyLog: a2dd979f-4779-4e34-b8f6-cae814370426/3 In entryThree


To specify that a filter is only active for certain URLs, you must explicitly register it and not mark the class with the @Component tag. In this example, in the FiltrosApplication class, we see the function where a filter is added:

@Bean
public FilterRegistrationBean<CakesFilter> cakesFilter()
{
   FilterRegistrationBean<CakesFilter> registrationBean = new FilterRegistrationBean<>();
   registrationBean.setFilter(new CakesFilter());
   registrationBean.addUrlPatterns("/cakes/*");
   return registrationBean;
}


The CakesFilter is the next:

@Order(3)
public class CakesFilter implements Filter{
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletResponse  myResponse= (HttpServletResponse) response;
    myResponse.addHeader("CAKE", "EATEN");
    chain.doFilter(request, response);
  }
}


When you make a request to a URL that starts with  /cakes/*you will see how the last filter is executed.

> curl  -s http://localhost:8080/cakes
returning by function entryOther
SillyLog: 41e2c9b9-f8d2-42cc-a017-08ea6089e646/1 Filter: URL called: http://localhost:8080/cakes
SillyLog: 41e2c9b9-f8d2-42cc-a017-08ea6089e646/2 OtherFilter: URL called: http://localhost:8080/cakes
SillyLog: 41e2c9b9-f8d2-42cc-a017-08ea6089e646/3 In entryOther
SillyLog: 41e2c9b9-f8d2-42cc-a017-08ea6089e646/4 Header contains CAKE: EATEN


Note: Because of Spring's way of managing its context variables, it is not possible to inject the SillyLog object with an @Autowired. If we inject it, we will see how the variable has the value null.

Don't forget to visit my page with more articles in Spanish about Spring. You can follow me in @chuchip.

Topics:
spring ,java ,filters ,rest api

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}