博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Boot系列十八 Spring AOP + 注解实现统一注解
阅读量:5875 次
发布时间:2019-06-19

本文共 7689 字,大约阅读时间需要 25 分钟。

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

这里我们定义日志的开关量,类上只有这个值为true,这个类中日志功能才开启

@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)@Documentedpublic @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实现类,将日志入库。这里只模拟入库

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

5. AOP的配置

5.1. LogAspect定义AOP类

  • 使用@Aspect注解此类
  • 使用@Pointcut定义要拦截的包及类方法
  • 我们使用@Around定义方法
@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:主方法的主要业务流程 1. 检查拦截方法的类是否被@LogEnable注解,如果是,则走日志逻辑,否则执行正常的逻辑 2. 检查拦截方法是否被@LogEvent,如果是,则走日志逻辑,否则执行正常的逻辑 3. 根据获取方法上获取@LogEvent 中值,生成日志的部分参数。其中定义在类上@LogEvent 的值做为默认值 4. 调用logInfoGeneration的processingManagerLogMessage填充日志中其它的参数,做个方法我们后面再讲 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. 代码

以上的详细的代码见下面

转载地址:http://yckix.baihongyu.com/

你可能感兴趣的文章
修改校准申请遇到的问题
查看>>
Linux 进程中 Stop, Park, Freeze【转】
查看>>
文件缓存
查看>>
远程协助
查看>>
Scrum实施日记 - 一切从零开始
查看>>
关于存储过程实例
查看>>
配置错误定义了重复的“system.web.extensions/scripting/scriptResourceHandler” 解决办法...
查看>>
AIX 7.1 install python
查看>>
PHP盛宴——经常使用函数集锦
查看>>
重写 Ext.form.field 扩展功能
查看>>
Linux下的搜索查找命令的详解(locate)
查看>>
福利丨所有AI安全的讲座里,这可能是最实用的一场
查看>>
开发完第一版前端性能监控系统后的总结(无代码)
查看>>
Python多版本情况下四种快速进入交互式命令行的操作技巧
查看>>
MySQL查询优化
查看>>
【Redis源码分析】如何在Redis中查找大key
查看>>
关于链接文件的探讨
查看>>
android app启动过程(转)
查看>>
Linux—源码包安装
查看>>
JDK8中ArrayList的工作原理剖析
查看>>