Spring Web Service Response Filtering
For large projects, you must control the serialization of a service response.
Join the DZone community and get the full member experience.
Join For FreeIntroduction
Sometimes, mostly in large projects, there is a need to control the serialization of a service response — especially to filter fields of an object in response. For solving this type of issue, we have several solutions. Let’s take a look.
Issue
For example, we have an object named “User” that contains fields like: id, email, fullName, password, secretKey
. We have a task to return this object to a recipient with different fields depending on the role in the session. Also, we have two types of roles: admin and user:
- When the recipient has an “admin” role, we should return the full object with the fields:
id, email, fullName, password, secretKey.
- When a recipient has a “user” role, we should return an object partially with the fields:
email, fullName.
Standard Solutions
- Inherit two different classes with a different set of fields from the User class and call them like:
UserAdmin, UserSimple.
- Reset fields of the User class in response to the method depending on the role.
- Then, return the recipient to a serialized string with removed fields depending on role instead of User class.
Maybe this solution looks simple and justified, but it is not the best. In some situations, it will increase the number of similar classes or will lead to unpredictability of the service execution. But in all solutions, we need to create additional logic code.
JFilter Solution
This approach offers another solution. We can leave the service’s method response unchanged. But we need to somehow get the Spring message converter work. To let him know how to serialize an object and filter/exclude fields, the JFilter module is designed to solve this kind of issue.
Let’s look at the diagram.
As you can see, the JFilter module is embedded between Spring Web Service and Send response methods. The description of internal parts of the module include:
-
FilterAdvice
— is aControllerAdvice
component that handles all responses from the Spring Web Service -
FilterProvider
— is a component that attempts to find a suitable filter -
FilterConverter
— is a class that attempts to provide a suitable JSON or XML serializer - After serialization, the response will be written to the
HttpOutputMessage
body
Using
Before discussing filter types, we should know how to use them in your project. So, first of all, we need to import the repository:
<dependency>
<groupId>com.github.rkonovalov</groupId>
<artifactId>json-ignore</artifactId>
<version>1.0.8</version>
</dependency>
If you are using other automation tools, you can find a declaration by following this link.
The next step is enabling filter handling:
@ComponentScan({"com.jfilter.components"})
@EnableJsonFilter
This annotation should be declared in the Spring Web Service configuration component. The following code shows you how you can declare it:
@Configuration
@EnableWebMvc
@ComponentScan({"com.jfilter.components"})
@EnableJsonFilter
public class AppConfig extends WebMvcConfigurerAdapter {
}
After these two simple steps, we can start to explore filter types. But before that, we also need to define a sample Spring Rest component with the response method:
@RestController
public class SessionService {
@Autowired
private UserController userController;
@RequestMapping(value = "/users/signIn",
params = {"email", "password"}, method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
public User signIn(@RequestParam("email") String email, @RequestParam("password") String password) {
return userController.signInUser(email, password);
}
}
This method returns the object instance of the User class. There is a JSON serialized object that will be sent to the recipient.
{
"id": 10,
"email": "janedoe@gmail.com",
"fullName": "Jane Doe",
"password": "12345",
"secretKey": "54321",
"address": {
"id": 15,
"apartmentNumber": 22,
"street": {
"id": 155,
"streetName": "Bourbon Street",
"streetNumber": 15
}
}
}
That’s it. Now, we can declare the filter annotations.
Filters
Filters mainly analyze the Spring Service response for a filter annotation, and if the annotation is configured, it attempts to generate a list of filterable/exclusion fields from the response object. Let’s look at them a little closer.
Field Filter
This is a simple filter annotation where you can to define filterable fields of an object.
Annotation Example
@FieldFilterSetting(className = User.class, fields = {"id", "password", "secretKey"})
Code Example
@FieldFilterSetting(className = User.class, fields = {"id", "password", "secretKey"})
@RequestMapping(value = "/users/signIn",
params = {"email", "password"}, method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
public User signIn(@RequestParam("email") String email, @RequestParam("password") String password) {
return userController.signInUser(email, password);
}
Response Result
{
"email": "janedoe@gmail.com",
"fullName": "Jane Doe",
"address": {
"id": 15,
"apartmentNumber": 22,
"street": {
"id": 155,
"streetName": "Bourbon Street",
"streetNumber": 15
}
}
}
As you can see, fields id, password, and secretKey
have been successfully filtered from the response.
If you want to filter fields from a complex class that contains sub-classes with the same field names, you can filter them without qualifications for a certain class name.
Annotation Example
@FieldFilterSetting(fields = {"id", "password", "secretKey"})
Response Result
{
"email": "janedoe@gmail.com",
"fullName": "Jane Doe",
"address": {
"apartmentNumber": 22,
"street": {
"streetName": "Bourbon Street",
"streetNumber": 15
}
}
}
As you can see, all id
fields from all sub-objects have been filtered. Also, you can filter fields from sub-objects separately.
Annotation Example
@FieldFilterSetting(className = User.class, fields = {"id", "password", "secretKey"})
@FieldFilterSetting(className = Address.class, fields = {"apartmentNumber"})
@FieldFilterSetting(className = Street.class, fields = {"streetNumber"})
Response Result
{
"email": "janedoe@gmail.com",
"fullName": "Jane Doe",
"address": {
"id": 15,
"street": {
"id": 155,
"streetName": "Bourbon Street"
}
}
}
Session Strategy Filter
Sometimes, you need to filter fields depending on the recipient role (attribute in HTTP Session). As an example, you have two recipient roles: ADMIN and USER
USER Role Annotation Example
@SessionStrategy(attributeName = "ROLE", attributeValue = "USER", ignoreFields = {
@FieldFilterSetting(className = User.class, fields = {"id", "password"})
})
ADMIN Role Annotation Example
@SessionStrategy(attributeName = "ROLE", attributeValue = "ADMIN", ignoreFields = {
@FieldFilterSetting(className = User.class, fields = {"id"})
})
So, if the session has an attribute ROLE with value:
- USER — fields
id, password
will be removed from the response - ADMIN — the field
id
will be removed from the response
Thus, this filter gives flexibility and the possibility of selective filtering.
XML Schema-Based Filter
If you need to configure field filtration in a file, this filter can provide this feature.
Annotation Example
@FileFilterSetting(fileName = "filter_configuration.xml")
XML Configuration File
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE config PUBLIC
"-//json/json-ignore mapping DTD 1.0//EN"
"https://rkonovalov.github.io/json-ignore-schema-1.0.dtd">
<config>
<controller class-name="com.example.SessionService">
<strategy attribute-name="ROLE" attribute-value="USER">
<filter class="com.example.User">
<field name="password"/>
</filter>
</strategy>
</controller>
</config>
Description of XML Tags
config
— main tagcontroller
— in this TAG, you may set the class-name property. Set theService
class name where it is used in this configurationstrategy
— this TAG is similar to theSessionStrategy
annotationfilter
— this TAG is similar to theFieldFilterSetting
annotation
Note: configuration files could be changed after the application starts and the controller will correctly reload all changed files. Don’t worry about it!
Spring Controller Filtration
If you don’t want to add filter annotations to each Service Response, you can add an annotation on the whole Service Controller.
Annotation Example
@FileFilterSetting(fileName = "filter_configuration.xml")
@RestController
public class SessionService {
@Autowired
private UserController userController;
@RequestMapping(value = "/users/signIn",
params = {"email", "password"}, method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
public User signIn(@RequestParam("email") String email, @RequestParam("password") String password) {
return userController.signInUser(email, password);
}
}
You can declare all of the required filters.
Dynamic Filter
Using this type of filter, you can create your own filter with custom behavior. All dynamic filters should implement the DynamicFilterEvent.class
, and the filter should be annotated by theDynamicFilterComponent
annotation.
Dynamic Custom Filter Example
@DynamicFilterComponent
public class DemoIdFilter implements DynamicFilterEvent {
@Override
public FilterFields onGetFilterFields(MethodParameter methodParameter, RequestSession request) {
if(request.getSession().getAttribute("SOME_VALUE") != null) {
return new FilterFields(User.class, Arrays.asList("id", "password", "email"));
} else
return new FilterFields();
}
}
In this example, we created the DemoIdFilter
that attempts to find the attribute SOME_VALUE
in an Http Session. If the attribute exists, the event onGetFilterFields
returns a configured FilterFields
. As you can see, FilterFields
contains a filter for the User.class
. If the Response Body of the Spring Service contains an object instance of the User.class
, it will be filtered and the fields id, password, email
will be removed from the response.
Annotation Example
@DynamicFilter(DemoIdFilter.class)
@RequestMapping(value = "/users/signIn",
params = {"email", "password"}, method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
public User signIn(@RequestParam("email") String email, @RequestParam("password") String password) {
return userController.signInUser(email, password);
}
With this filter, you get huge space for Service Response customization.
Check out these links to learn more:
Conclusion
In the world of big data and complex systems, sometimes, we need to control and flexibly change data. Maybe this solution is not the best, but I hope it will save you some extra time. You can use it in your projects without the “rain dance” and still achieve your desired result.
Opinions expressed by DZone contributors are their own.
Comments