Весенние загрузки серии восемнадцать весной AOP + заметка комментарий

Spring Boot задняя часть GitHub Spring

1 Обзор

В общей системе, когда мы выполняем некоторые важные операции, такие как вход в систему, добавление пользователей, удаление пользователей и т. д., нам необходимо сохранять это поведение. В этой статье мы реализуем вставку журнала с помощью Spring AOP и пользовательских аннотаций Java. Это решение имеет низкое вмешательство в исходный бизнес и является более гибким в реализации.

2. Зарегистрируйте связанные определения классов

Мы абстрагируем журнал на следующие два класса: функциональный модуль и тип операции. Используйте класс перечисления, чтобы определить тип функционального модуля ModuleType, например, студенческий, пользовательский модуль

public enum ModuleType {
    DEFAULT("1"), // 默认值
    STUDENT("2"),// 学生模块
    TEACHER("3"); // 用户模块

    private ModuleType(String index){
        this.module = index;
    }
    private String module;
    public String getModule(){
        return this.module;
    }
}

Используйте класс перечисления, чтобы определить тип действия: EventType. Такие как вход в систему, добавление, удаление, обновление, удаление и т. д.

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. Определите аннотации, связанные с журналом

3.1. @LogEnable

Здесь мы определяем значение коммутатора журнала. Только если это значение верно в классе, функция журнала в этом классе включена.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface LogEnable {
    /**
     * 如果为true,则类下面的LogEvent启作用,否则忽略
     * @return
     */
    boolean logEnable() default true;
}

3.2. @LogEvent

Детали журнала определяются здесь. Если эта аннотация аннотирована в классе, этот параметр используется как значение по умолчанию для всех методов класса. Если аннотация относится к методу, она будет работать только с этим методом.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.METHOD, ElementType.TYPE})
public @interface LogEvent {
    ModuleType module() default ModuleType.DEFAULT; // 日志所属的模块
    EventType event() default EventType.DEFAULT; // 日志事件类型
    String desc() default  ""; // 描述信息
}

3.3. @LogKey

Если эта аннотация аннотируется в методе, параметры всего метода сохраняются в журнале в формате json. Если эта аннотация аннотирована как для метода, так и для класса, аннотация для метода переопределит значение для класса.

@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogKey {
     String keyName() default ""; // key的名称
     boolean isUserId() default false; // 此字段是否是本次操作的userId,这里略
     boolean isLog() default true; // 是否加入到日志中
}

4. Определите класс обработки журнала

4.1. LogAdmModel

Определите класс для сохранения информации журнала

public class LogAdmModel {
    private Long id;
    private String userId; // 操作用户
    private String userName;
    private String admModel; // 模块
    private String admEvent; // 操作
    private Date createDate; // 操作内容
    private String admOptContent; // 操作内容
    private String desc; // 备注
	set/get略
}

4.2. ILogManager

Интерфейсный класс ILogManager, определяющий обработку журнала. Мы можем сохранить журнал в базе данных или отправить журнал в открытое промежуточное ПО, такое как redis, mq и т. д. Каждый класс обработки журнала является классом реализации этого интерфейса.

public interface ILogManager {
    /**
     * 日志处理模块
     * @param paramLogAdmBean
     */
    void dealLog(LogAdmModel paramLogAdmBean);
}

4.3. DBLogManager

ILogManager реализует класс для хранения журналов. Здесь только хранилище симуляций

@Service
public class DBLogManager implements ILogManager {
    @Override
    public void dealLog(LogAdmModel paramLogAdmBean) {
        System.out.println("将日志存入数据库,日志内容如下: " + JSON.toJSONString(paramLogAdmBean));
    }
}

5. Конфигурация AOP

5.1. LogAspect определяет классы АОП

  • Аннотируйте этот класс с помощью @Aspect
  • Используйте @Pointcut для определения перехватываемых пакетов и методов класса.
  • Мы используем @Around для определения методов
@Component
@Aspect
public 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: основной бизнес-процесс основного метода1. Проверьте, будет ли класс перехваченного метода аннотироваться с @Logranable, если это так, следите за логикой журнала, в противном случае выполните обычную логику 2. Проверьте, является ли перехваченный метод @Logevent, если это так, пройдите через логику журнала, в противном случае выполните обычную логику 3. Получите значение в @Logevent в соответствии с методом получения и генерируйте некоторые параметры журнала. Значение @Logevent, определенного в классе, используется в качестве значения по умолчанию 4. Позвоните в ProcessingManagerLogmessage Loginfenpeneration, чтобы заполнить другие параметры в журнале, и мы поговорим об этом позже. 5. Выполните обычные бизнес-звонки 6. Если выполнение успешно, LogManager выполняет обработку журнала (мы записываем только успешный журнал выполнения здесь, вы также можете определить неудачный журнал)

	    @Around("managerLogPoint()")
	    public Object aroundManagerLogPoint(ProceedingJoinPoint jp) throws Throwable {
	
	        Class target = jp.getTarget().getClass();
	        // 获取LogEnable
	        LogEnable logEnable = (LogEnable) target.getAnnotation(LogEnable.class);
	        if(logEnable == null || !logEnable.logEnable()){
	            return jp.proceed();
	        }
	
	        // 获取类上的LogEvent做为默认值
	        LogEvent logEventClass = (LogEvent) target.getAnnotation(LogEvent.class);
	        Method method = getInvokedMethod(jp);
	        if(method == null){
	            return jp.proceed();
	        }
	
	        // 获取方法上的LogEvent
	        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){
	            // 如果方法上的值为默认值,则使用全局的值进行替换
	            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(returnObj != null) {
	                this.logManager.dealLog(logBean);
	            }
	        }else {
	            this.logManager.dealLog(logBean);
	        }
	        return returnObj;
	    }
	
	    /**
	     * 获取请求方法
	     *
	     * @param jp
	     * @return
	     */
	    public Method getInvokedMethod(JoinPoint jp) {
	        // 调用方法的参数
	        List classList = new ArrayList();
	        for (Object obj : jp.getArgs()) {
	            classList.add(obj.getClass());
	        }
	        Class[] argsCls = (Class[]) classList.toArray(new Class[0]);
	
	        // 被调用方法名称
	        String methodName = jp.getSignature().getName();
	        Method method = null;
	        try {
	            method = jp.getTarget().getClass().getMethod(methodName, argsCls);
	        } catch (NoSuchMethodException e) {
	            e.printStackTrace();
	        }
	        return method;
	    }
	}

6. Схема применения вышеописанной схемы на практике

Здесь мы моделируем бизнес, управляемый студентами, применяем к нему приведенные выше аннотации и перехватываем журналы.

6.1. IStudentService

Класс бизнес-интерфейса, который выполняет общий 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 в классе определяет все модули
  • Другая информация о методе определения журнала @LogEven
@Service
@LogEnable // 启动日志拦截
@LogEvent(module = ModuleType.STUDENT)
public class StudentServiceImpl implements IStudentService {
    @Override
    @LogEvent(event = EventType.DELETE_SINGLE, desc = "删除记录") // 添加日志标识
    public void deleteById(@LogKey(keyName = "id") String id, String a) {
        System.out.printf(this.getClass() +  "deleteById  id = " + id);
    }

    @Override
    @LogEvent(event = EventType.ADD, desc = "保存记录") // 添加日志标识
    public int save(StudentModel studentModel) {
        System.out.printf(this.getClass() +  "save  save = " + JSON.toJSONString(studentModel));
        return 1;
    }

    @Override
    @LogEvent(event = EventType.UPDATE, desc = "更新记录") // 添加日志标识
    public void update(StudentModel studentModel) {
        System.out.printf(this.getClass() +  "save  update = " + JSON.toJSONString(studentModel));
    }

    // 没有日志标识
    @Override
    public void queryById(String id) {
        System.out.printf(this.getClass() +  "queryById  id = " + id);
    }
}

Выполните тестовый класс и распечатайте следующую информацию, указывающую, что наша конфигурация аннотации журнала включена:

将日志存入数据库,日志内容如下: {"admEvent":"4","admModel":"1","admOptContent":"{\"id\":\"1\"}","createDate":1525779738111,"desc":"删除记录"}

7. Код

Подробный код выше показан нижекод github, попробуйте использовать тег v0.21, не используйте мастер, потому что я не могу гарантировать, что мастер-код останется неизменным