美文网首页
Spring自定义注解

Spring自定义注解

作者: xbmchina | 来源:发表于2019-08-14 07:34 被阅读0次

    功能简介

    本文主要记录如何使用aop切面的方式来实现日志记录功能。

    主要记录的信息有: 操作人,方法名,参数,运行时间,操作类型(增删改查),详细描述,返回值。

    项目结构图

    代码实现

    1、maven依赖pom.xml

    主要是spring-boot-starter-aop的包引入。

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- spring-boot aop依赖配置引入 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.58</version>
            </dependency>
        </dependencies>
    

    2、AOP切点类

    这个是最主要的类,可以使用自定义注解或针对包名实现AOP增强。

    1)这里实现了对自定义注解的环绕增强切点,对使用了自定义注解的方法进行AOP切面处理;

    2)对方法运行时间进行监控;

    3)对方法名,参数名,参数值,对日志描述的优化处理;

    在方法上增加@Aspect 注解声明切面,使用@Pointcut 注解定义切点,标记方法。

    使用切点增强的时机注解:@Before,@Around,@AfterReturning,@AfterThrowing,@After

    
    @Aspect
    @Component
    public class LogAspect {
    
        /**
         * 此处的切点是注解的方式,也可以用包名的方式达到相同的效果
         * '@Pointcut("execution(* com.wwj.springboot.service.impl.*.*(..))")'
         */
        @Pointcut("@annotation(cn.xbmchina.myannotations.annotation.OperationLogDetail)")
        public void operationLog(){}
    
    
        /**
         * 环绕增强,相当于MethodInterceptor
         */
        @Around("operationLog()")
        public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
            Object res = null;
            long time = System.currentTimeMillis();
            try {
                res =  joinPoint.proceed();
                time = System.currentTimeMillis() - time;
                return res;
            } finally {
                try {
                    //方法执行完成后增加日志
                    addOperationLog(joinPoint,res,time);
                }catch (Exception e){
                    System.out.println("LogAspect 操作失败:" + e.getMessage());
                    e.printStackTrace();
                }
            }
        }
    
        private void addOperationLog(JoinPoint joinPoint, Object res, long time){
            MethodSignature signature = (MethodSignature)joinPoint.getSignature();
            OperationLog operationLog = new OperationLog();
            operationLog.setRunTime(time);
            operationLog.setReturnValue(JSON.toJSONString(res));
            operationLog.setId(UUID.randomUUID().toString());
            operationLog.setArgs(JSON.toJSONString(joinPoint.getArgs()));
            operationLog.setCreateTime(new Date());
            operationLog.setMethod(signature.getDeclaringTypeName() + "." + signature.getName());
            operationLog.setUserId("#{currentUserId}");
            operationLog.setUserName("#{currentUserName}");
            OperationLogDetail annotation = signature.getMethod().getAnnotation(OperationLogDetail.class);
            if(annotation != null){
                operationLog.setLevel(annotation.level());
                operationLog.setDescribe(getDetail(((MethodSignature)joinPoint.getSignature()).getParameterNames(),joinPoint.getArgs(),annotation));
                operationLog.setOperationType(annotation.operationType().getValue());
                operationLog.setOperationUnit(annotation.operationUnit().getValue());
            }
            //TODO 这里保存日志
            System.out.println("记录日志:" + operationLog.toString());
    //        operationLogService.insert(operationLog);
        }
    
        /**
         * 对当前登录用户和占位符处理
         * @param argNames 方法参数名称数组
         * @param args 方法参数数组
         * @param annotation 注解信息
         * @return 返回处理后的描述
         */
        private String getDetail(String[] argNames, Object[] args, OperationLogDetail annotation){
    
            Map<Object, Object> map = new HashMap<>(4);
            for(int i = 0;i < argNames.length;i++){
                map.put(argNames[i],args[i]);
            }
    
            String detail = annotation.detail();
            try {
                detail = "'" + "#{currentUserName}" + "'=》" + annotation.detail();
                for (Map.Entry<Object, Object> entry : map.entrySet()) {
                    Object k = entry.getKey();
                    Object v = entry.getValue();
                    detail = detail.replace("{{" + k + "}}", JSON.toJSONString(v));
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            return detail;
        }
    
        @Before("operationLog()")
        public void doBeforeAdvice(JoinPoint joinPoint){
            System.out.println("进入方法前执行.....");
    
        }
    
        /**
         * 处理完请求,返回内容
         * @param ret
         */
        @AfterReturning(returning = "ret", pointcut = "operationLog()")
        public void doAfterReturning(Object ret) {
            System.out.println("方法的返回值 : " + ret);
        }
    
        /**
         * 后置异常通知
         */
        @AfterThrowing("operationLog()")
        public void throwss(JoinPoint jp){
            System.out.println("方法异常时执行.....");
        }
    
    
        /**
         * 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
         */
        @After("operationLog()")
        public void after(JoinPoint jp){
            System.out.println("方法最后执行.....");
        }
    
    }
    

    3、自定义注解

    
    //@OperationLogDetail(detail = "通过手机号[{{tel}}]获取用户名",level = 3,operationUnit = OperationUnit.USER,operationType = OperationType.SELECT)
    @Documented
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface OperationLogDetail {
    
        /**
         * 方法描述,可使用占位符获取参数:{{tel}}
         */
        String detail() default "";
    
        /**
         * 日志等级:自己定,此处分为1-9
         */
        int level() default 0;
    
        /**
         * 操作类型(enum):主要是select,insert,update,delete
         */
        OperationType operationType() default OperationType.UNKNOWN;
    
        /**
         * 被操作的对象(此处使用enum):可以是任何对象,如表名(user),或者是工具(redis)
         */
        OperationUnit operationUnit() default OperationUnit.UNKNOWN;
    }
    

    4、注解用到的枚举类型

    OperationType的操作类型

    public enum OperationType {
        /**
         * 操作类型
         */
        UNKNOWN("unknown"),
        DELETE("delete"),
        SELECT("select"),
        UPDATE("update"),
        INSERT("insert");
    
        private String value;
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    
        OperationType(String s) {
            this.value = s;
        }
    }
    

    OperationUnit是被操作的单元

    public enum OperationUnit {
        /**
         * 被操作的单元
         */
        UNKNOWN("unknown"),
        USER("user"),
        EMPLOYEE("employee"),
        Redis("redis");
    
        private String value;
    
        OperationUnit(String value) {
            this.value = value;
        }
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    }
    

    5、日志记录对象

    
    public class OperationLog {
    
        private String id;
        private Date createTime;
        /**
         * 日志等级
         */
        private Integer level;
        /**
         * 被操作的对象
         */
        private String operationUnit;
        /**
         * 方法名
         */
        private String method;
        /**
         * 参数
         */
        private String args;
        /**
         * 操作人id
         */
        private String userId;
        /**
         * 操作人
         */
        private String userName;
        /**
         * 日志描述
         */
        private String describe;
        /**
         * 操作类型
         */
        private String operationType;
        /**
         * 方法运行时间
         */
        private Long runTime;
        /**
         * 方法返回值
         */
        private String returnValue;
    
        @Override
        public String toString() {
            return "OperationLog{" +
                    "id='" + id + '\'' +
                    ", createTime=" + createTime +
                    ", level=" + level +
                    ", operationUnit='" + operationUnit + '\'' +
                    ", method='" + method + '\'' +
                    ", args='" + args + '\'' +
                    ", userId='" + userId + '\'' +
                    ", userName='" + userName + '\'' +
                    ", describe='" + describe + '\'' +
                    ", operationType='" + operationType + '\'' +
                    ", runTime=" + runTime +
                    ", returnValue='" + returnValue + '\'' +
                    '}';
        }
    
        public Long getRunTime() {
            return runTime;
        }
    
        public void setRunTime(Long runTime) {
            this.runTime = runTime;
        }
    
        public String getReturnValue() {
            return returnValue;
        }
    
        public void setReturnValue(String returnValue) {
            this.returnValue = returnValue;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    
        public Integer getLevel() {
            return level;
        }
    
        public void setLevel(Integer level) {
            this.level = level;
        }
    
        public String getOperationUnit() {
            return operationUnit;
        }
    
        public void setOperationUnit(String operationUnit) {
            this.operationUnit = operationUnit;
        }
    
        public String getMethod() {
            return method;
        }
    
        public void setMethod(String method) {
            this.method = method;
        }
    
        public String getArgs() {
            return args;
        }
    
        public void setArgs(String args) {
            this.args = args;
        }
    
        public String getUserId() {
            return userId;
        }
    
        public void setUserId(String userId) {
            this.userId = userId;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getDescribe() {
            return describe;
        }
    
        public void setDescribe(String describe) {
            this.describe = describe;
        }
    
        public String getOperationType() {
            return operationType;
        }
    
        public void setOperationType(String operationType) {
            this.operationType = operationType;
        }
    }
    

    至此,一个关于日志的自定义注解已经实现了,下面我们就要使用一下该注解在实际中业务代码中。

    6、Controller业务控制类

    @Controller
    @RequestMapping("user")
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        /**
         * 访问路径 http://localhost:11000/user/findUserNameByTel?tel=1234567
         * @param tel 手机号
         * @return userName
         */
        @ResponseBody
        @RequestMapping("/findUserNameByTel")
        public String findUserNameByTel(@RequestParam("tel") String tel){
            return userService.findUserName(tel);
        }
    }
    

    7、服务类Service

    public interface UserService {
    
        /**
         * 获取用户信息
         * @return
         * @param tel
         */
        String findUserName(String tel);
    }
    
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @OperationLogDetail(detail = "通过手机号[{{tel}}]获取用户名",level = 3,operationUnit = OperationUnit.USER,operationType = OperationType.SELECT)
        @Override
        public String findUserName(String tel) {
            System.out.println("tel:" + tel);
            return "zhangsan";
        }
    }
    

    8、测试结果

    启动项目,在浏览器访问

    http://localhost:8080/user/findUserNameByTel?tel=231432535464
    

    最终结果如下图:

    总结

    自定义注解的步骤:
    1、模仿已存在的注解,写一个自定义的注解。
    2、使用AOP以该注解为切入点,对其前后等进行切面编程。

    最后

    如果对 Java、大数据感兴趣请长按二维码关注一波,我会努力带给你们价值。觉得对你哪怕有一丁点帮助的请帮忙点个赞或者转发哦。
    关注公众号【爱编码】,小编会一直更新文章的哦。

    相关文章

      网友评论

          本文标题:Spring自定义注解

          本文链接:https://www.haomeiwen.com/subject/wossjctx.html