Case Study: Common Field Population
Adding Required Dependencies
To enable aspect-oriented programming in a Spring Boot project, include the following dependencies in your pom.xml:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
Defining a Custom Annotation
Create a custom annotation to mark methods that require automatic field population:
/**
* Custom annotation to indicate methods needing automatic field population.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldPopulation {
// Specifies the database operation type: UPDATE or INSERT
OperationType operation();
}
Creating an Aspect Class
Implement an aspect class to handle the logic for populating common fields:
/**
* Aspect class for automatic population of common fields.
*/
@Aspect
@Component
@Slf4j
public class FieldPopulationAspect {
/**
* Pointcut definition targeting methods in mapper classes annotated with @FieldPopulation.
*/
@Pointcut("execution(* com.example.mapper.*.*(..)) && @annotation(com.example.annotation.FieldPopulation)")
public void fieldPopulationPointcut() {}
/**
* Before advice to populate common fields before method execution.
*/
@Before("fieldPopulationPointcut()")
public void populateFields(JoinPoint joinPoint) {
log.info("Starting automatic field population...");
// Retrieve the method signature and annotation
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
FieldPopulation fieldPopulation = methodSignature.getMethod().getAnnotation(FieldPopulation.class);
OperationType operation = fieldPopulation.operation();
// Get the method arguments, assuming the first argument is the entity object
Object[] arguments = joinPoint.getArgs();
if (arguments == null || arguments.length == 0) {
return;
}
Object entity = arguments[0];
// Prepare data for population
LocalDateTime currentTime = LocalDateTime.now();
Long currentUserId = UserContext.getCurrentUserId();
// Populate fields based on operation type using reflection
try {
if (operation == OperationType.INSERT) {
// Set four common fields for INSERT operations
Method setCreationTime = entity.getClass().getDeclaredMethod(FieldConstants.SET_CREATION_TIME, LocalDateTime.class);
Method setCreator = entity.getClass().getDeclaredMethod(FieldConstants.SET_CREATOR, Long.class);
Method setModificationTime = entity.getClass().getDeclaredMethod(FieldConstants.SET_MODIFICATION_TIME, LocalDateTime.class);
Method setModifier = entity.getClass().getDeclaredMethod(FieldConstants.SET_MODIFIER, Long.class);
setCreationTime.invoke(entity, currentTime);
setCreator.invoke(entity, currentUserId);
setModificationTime.invoke(entity, currentTime);
setModifier.invoke(entity, currentUserId);
} else if (operation == OperationType.UPDATE) {
// Set two common fields for UPDATE operations
Method setModificationTime = entity.getClass().getDeclaredMethod(FieldConstants.SET_MODIFICATION_TIME, LocalDateTime.class);
Method setModifier = entity.getClass().getDeclaredMethod(FieldConstants.SET_MODIFIER, Long.class);
setModificationTime.invoke(entity, currentTime);
setModifier.invoke(entity, currentUserId);
}
} catch (Exception e) {
log.error("Error during field population: ", e);
}
}
}
Applying the Annottaion
Finally, annotate the methods in your mapper classes that require automatic field population with @FieldPopulation. For example:
@FieldPopulation(operation = OperationType.INSERT)
public void insertEntity(Entity entity) {
// Method implementation
}
This setup ensures that common fields like creation time, creator, modification time, and modifier are auotmatically populated based on the specified database operation type, reducing boilerplate code and improving maintainability.