In this post under MapStruct, I will show how to use decorator to implement pre and post mapping operations.
For our example, we will use the “Student” and “StudentDTO” pojo classes. The class structure is as shown below
Student
package package26;
public class Student {
private int id;
private String name;
private String className;
//Removed getter, setter, and toString for brevity
}
StudentDTO
package package26;
public class StudentDTO {
private int id;
private String name;
private String className;
//Removed getter, setter, and toString for brevity
}
In our example, we will be using “Student” as the source object and “StudentDTO” as the destination object. So basically we are mapping from “Student” to “StudentDTO” instance.
For our example, we will “StudentMapper” as our mapper interface.
As explained in my first post under MapStruct, MapStruct will internally generate a class that will implement this “StudentMapper” interface and provide logic to “getDTOFromModel” method.
Lets call this internally generated class as “StudentMapperImpl”.
We need to create a decorator class that will contain pre and post mapping logic and it will implement “StudentMapper” interface as shown below
StudentMapperDecorator
package package26;
public abstract class StudentMapperDecorator implements StudentMapper {
private StudentMapper studentMapper;
public StudentMapperDecorator(StudentMapper studentMapper) {
this.studentMapper = studentMapper;
}
@Override
public StudentDTO getDTOFromModel(Student student) {
System.out.println("Calling decorator before mapping");
StudentDTO studentDTO = studentMapper.getDTOFromModel(student);
System.out.println("Calling decorator after mapping");
return studentDTO;
}
}
As you can see from the above code, “StudentMapperDecorator” implements “StudentMapper” interface and it provide implementation for “getDTOFromModel” method.
The class will hold a reference to “StudentMapperImpl” instance in a private instance variable named “studentMapper”. Refer to line 4.
We need to create a constructor with one argument i.e, the constructor will take an instance of “StudentMapper” interface as an argument.
This constructor is called by MapStruct and MapStruct makes sure that it passes an instance of “StudentMapperImpl” as a parameter.
In the “getDTOFromModel” method, we are first printing a message to the console. This becomes our pre-mapping logic.
Then we are doing the actual mapping. The result is stored in “studentDTO” variable.
After mapping, we are printing a message to the console. This becomes our post-mapping logic.
The method at the end, returns “studentDTO” variable.
Below is the “StudentMapper” interface structure
StudentMapper
package package26;
import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
@Mapper
@DecoratedWith(StudentMapperDecorator.class)
public interface StudentMapper {
StudentDTO getDTOFromModel(Student student);
}
This is similar to mapper interface we showed in all my previous post with only one difference.
That is the addition of “@DecoratedWith” annotation. Refer to line 7.
This annotation will have the decorator class name “StudentMapperDecorator” as value.
This annotation is basically instructing MapStruct that we need to decorate the “StudentMapperImpl” instance with “StudentMapperDecorator” class.
Below is the complete main class showing how to use all these classes.
Main class
package package26;
import org.mapstruct.factory.Mappers;
public class Example26 {
public static void main(String[] args) {
StudentMapper studentMapper = Mappers.getMapper(StudentMapper.class);
Student student = new Student();
student.setClassName("X");
student.setName("John");
student.setId(1);
StudentDTO studentDTO = studentMapper.getDTOFromModel(student);
System.out.println(studentDTO);
}
}
In the above code at line 7, when we call “getMapper” and pass the “StudentMapper” interface class object. MapStruct will do the following
1) create an instance of “StudentMapperImpl”
2) create an instance of “StudentMapperDecorator” class containing a reference to the instance created in step 1.
3) returns the instance of “StudentMapperDecorator” as return value of “getMapper” method which is assigned to “studentMapper” variable declared at line 7 in the main class.
Calling “studentMapper.getDTOFromModel(student);” at line 12, will call the “getDTOFromModel” of “StudentMapperDecorator” class which will
1) performs the pre-mapping logic
2) internally calls “getDTOFromModel” of “StudentMapperImpl” instance
3) store the return value of the call
4) perform the post-mapping logic
5) return the value stored at step 3
Below is the output
Output
Calling decorator before mapping
Calling decorator after mapping
1:John:X
In this way we can use decorator to implement pre and post mapping logic.