1. Overview
In general systems, when we do some important operations, such as logging in to the system, adding users, deleting users, etc., we need to persist these behaviors. In this article, we implement log insertion through custom annotations of Spring AOP and Java. This solution has less intrusion to the original business and is more flexible in realizing
2. Related class definitions of logs
We abstract the log into two classes: functional module and operation type
Use enumeration classes to define the function module type ModuleType, such as student and user module
public enum ModuleType { DEFAULT("1"), // Default value STUDENT("2"), // Student module TEACHER("3"); // User module private ModuleType(String index){ this.module = index; } private String module; public String getModule(){ return this.module; }}Use an enumeration class to define the type of the operation: EventType. Such as login, add, delete, update, delete, etc.
public enum EventType { DEFAULT("1", "default"), ADD("2", "add"), UPDATE("3", "update"), DELETE_SINGLE("4", "delete-single"), LOGIN("10","login"), LOGIN_OUT("11","login_out"); private EventType(String index, String name){ this.name = name; this.event = index; } private String event; private String name; public String getEvent(){ return this.event; } public String getName() { return name; }}3. Define log-related annotations
3.1. @LogEnable
Here we define the switch quantity of the log. Only when this value is true on the class, the log function in this class will be enabled.
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})public @interface LogEnable { /** * If true, the LogEvent below the class will be enabled, otherwise * @return */ boolean logEnable() default true;}3.2. @LogEvent
Definition of the log here. If this annotation is annotated on the class, this parameter is used as the default value for all methods of the class. If the annotation is on the method, it will only work for this method
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({java.lang.annotation.ElementType.METHOD, ElementType.TYPE})public @interface LogEvent { ModuleType module() default ModuleType.DEFAULT; // The module to which the log belongs EventType event() default EventType.DEFAULT; // Log event type String desc() default ""; // Description information}3.3. @LogKey
If this annotation is on a method, the parameters of the entire method are saved to the log in json format. If this annotation is annotated on both the method and the class, the annotation on the method overwrites the value on the class.
@Target({ElementType.FIELD,ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface LogKey { String keyName() default ""; // The name of the key boolean isUserId() default false; // Whether this field is the userId of this operation, here is omitted boolean isLog() default true; // Whether it is added to the log}4. Define log processing class
4.1. LogAdmModel
Define the class that holds log information
public class LogAdmModel { private Long id; private String userId; // Operate user private String userName; private String admModel; // Module private String admEvent; // Operation private Date createDate; // Operation content private String admOptContent; // Operation content private String desc; // Remark set/get omitted}4.2. ILogManager
Define the interface class ILogManager for log processing
We can store logs in the database, or send logs to open middleware, if redis, mq, etc. Each log processing class is an implementation class of this interface
public interface ILogManager { /** * Log processing module* @param paramLogAdmBean */ void dealLog(LogAdmModel paramLogAdmBean);}4.3. DBLogManager
ILogManager implementation class to store logs. Only simulated storage here
@Servicepublic class DBLogManager implements ILogManager { @Override public void dealLog(LogAdmModel paramLogAdmBean) { System.out.println("Store the log into the database, the log content is as follows: " + JSON.toJSONString(paramLogAdmBean)); }}5. AOP configuration
5.1. LogAspect defines AOP class
Annotate this class with @Aspect
Use @Pointcut to define the package and class methods to intercept
We use @Around to define the method
@Component@Aspectpublic class LogAspect { @Autowired private LogInfoGeneration logInfoGeneration; @Autowired private ILogManager logManager; @Pointcut("execution(* com.hry.spring.mvc.aop.log.service..*.*(..))") public void managerLogPoint() { } @Around("managerLogPoint()") public Object aroundManagerLogPoint(ProceedingJoinPoint jp) throws Throwable { …. } } aroundManagerLogPoint: The main business process of the main method
1. Check whether the class of the intercepting method is annotated by @LogEnable. If so, the log logic will be followed, otherwise normal logic will be executed.
2. Check whether the intercept method is @LogEvent. If so, the log logic will be followed, otherwise the normal logic will be executed.
3. Get the median value of @LogEvent based on the obtaining method and generate some parameters of the log. The value of @LogEvent is defined on the class as the default value
4. Call logInfoGeneration's processManagerLogMessage to fill other parameters in the log. We will talk about this method later.
5. Perform normal business calls
6. If the execution is successful, the logManager executes the log (we only record the logs of successful execution here, and you can also define the logs of failed recording)
@Around("managerLogPoint()") public Object aroundManagerLogPoint(ProceedingJoinPoint jp) throws Throwable { Class target = jp.getTarget().getClass(); // Get LogEnable LogEnable logEnable = (LogEnable) target.getAnnotation(LogEnable.class); if(logEnable == null || !logEnable.logEnable()){ return jp.proceed(); } // Get the LogEvent on the class as the default value LogEvent logEventClass = (LogEvent) target.getAnnotation(LogEvent.class); Method method = getInvokedMethod(jp); if(method == null){ return jp.proceed(); } // Get the LogEvent on the method LogEvent logEventMethod = method.getAnnotation(LogEvent.class); if(logEventMethod == null){ return jp.proceed(); } String optEvent = logEventMethod.event().getEvent(); String optModel = logEventMethod.module().getModule(); String desc = logEventMethod.desc(); if(logEventClass != null){ // If the value on the method is the default value, replace it with the global value. optEvent = optEvent.equals(EventType.DEFAULT) ? logEventClass.event().getEvent() : optEvent; optModel = optModel.equals(ModuleType.DEFAULT) ? logEventClass.module().getModule() : optModel; } LogAdmModel logBean = new LogAdmModel(); logBean.setAdmModel(optModel); logBean.setAdmEvent(optEvent); logBean.setDesc(desc); logBean.setCreateDate(new Date()); logInfoGeneration.processingManagerLogMessage(jp, logBean, method); Object returnObj = jp.proceed(); if(optEvent.equals(EventType.LOGIN)){ //TODO If it is logged in, you need to judge whether it is successful based on the return value. If it is successful, then add logs. The judgment here is relatively simple if(returnObj != null) { this.logManager.dealLog(logBean); } }else { this.logManager.dealLog(logBean); } return returnObj; } /** * Get the request method* * @param jp * @return */ public Method getInvokedMethod(JoinPoint jp) { // Parameters of calling method List classList = new ArrayList(); for (Object obj : jp.getArgs()) { classList.add(obj.getClass()); } Class[] argsCls = (Class[]) classList.toArray(new Class[0]); // The called method name String methodName = jp.getSignature().getName(); Method method = null; try { method = jp.getTarget().getClass().getMethod(methodName, argsCls); } catch (NoSuchMethodException e) { e.printStackTrace(); } return method; } }6. The solution to apply the above solution in practice
Here we simulate the business of students, and use the above annotations to apply them to them and intercept the logs.
6.1. IStudentService
Business interface class, execute general CRUD
public interface IStudentService { void deleteById(String id, String a); int save(StudentModel studentModel); void update(StudentModel studentModel); void queryById(String id);}6.2. StudentServiceImpl:
@LogEnable: @LogEvent defines all module methods on the startup log interception class. Other information on @LogEven defines the log. @Service@LogEnable // Start log interception @LogEvent(module = ModuleType.STUDENT)public class StudentServiceImpl implements IStudentService { @Override @LogEvent(event = EventType.DELETE_SINGLE, desc = "Delete Record") // Add log identifier public void deleteById(@LogKey(keyName = "id") String id, String a) { System.out.printf(this.getClass() + "deleteById id = " + id); } @Override @LogEvent(event = EventType.ADD, desc = "Save record") // Add log identifier public int save(StudentModel studentModel) { System.out.printf(this.getClass() + "save save = " + JSON.toJSONString(studentModel)); return 1; } @Override @LogEvent(event = EventType.UPDATE, desc = "Update record") // Add log identifier public void update(StudentModel studentModel) { System.out.printf(this.getClass() + "save update = " + JSON.toJSONString(studentModel)); } // No log identifier @Override public void queryById(String id) { System.out.printf(this.getClass() + "queryById id = " + id); }}Execute the test class and print the following information to indicate that our log annotation configuration is enabled:
Store the logs into the database, and the log content is as follows:
{"admEvent":"4","admModel":"1","admOptContent":"{/"id/":/"1/"}","createDate":1525779738111,"desc":"Delete record"}7. Code
See below for the detailed code above
Github code, please try to use tag v0.21, don't use master, because I can't guarantee that the master code will remain unchanged