美文网首页java架构经验分享
五分钟!带你重新了解自定义注解,看完还不懂算我输!

五分钟!带你重新了解自定义注解,看完还不懂算我输!

作者: 前程有光 | 来源:发表于2021-04-21 21:20 被阅读0次

    前言

    日常开发中用到了各式各样的注解,常用的注解@Override、@param、@Autowired、@Service等等,这些都是JDK或者Spring这类框架自带。在类,方法,变量,参数,包都可以用注解来注释。很多小伙伴可能还停留在使用层面,知道怎么用,但并不知道实现原理,更没亲自写过自定义注解运用在实际项目中解决问题。

    接下来聊聊注解的基础,再聊聊自定义注解在实际项目中的使用。

    注解作用

    1、生成文档,早期最常见的@return,@param

    2、在编译时进行检查,例如@Override,检查是否重写父类

    3、简化配置文件,使得代码更清晰

    什么是内置注解,元注解,自定义注解?

    内置注解

    @Override:作用在方法上,声明重写父类的方法

    @Deprecated:作用方法,属性,或者类上,标识已过时

    @SuppressWarings:用于抑制编译器警告,告知编译器,忽略它们产生了特殊警告

    @SafeVarargs:是jdk1.7引入的注解,作用抑制在使用泛型和可变参数搭配使用产生的编译器告警

    元注解:

    元注解是由JDK5.0开始提供的,不能更改,用于定义其他注解,也就是对我们自定义注解进行定义


    @Target:声明注解的作用范围可以是类,方法,方法参数变量等,也可以通过枚举类ElementType表达作用类型(可以查看源码)

    @Retention:声明注解保留时长的作用域,可以理解为运行环境,

    SOURCE(在源文件中有效)
    
    CLASS(源文件编译成Class类文件中有效)
    
    RUNTIME(在运行时有效)
    

    @Documented:文档化注解,作用可以被javadoc此类工具文档化

    @Inherited:声明此类能否被继承

    自定义注解

    /**
     * 元注解
     * public @interface 注解名称 {
     * 类型 属性名() default 默认值
     * }
     */
    //用来声明自定义注解作用范围,变量、方法、类、包...
    @Target({ElementType.METHOD, ElementType.TYPE})
    //声明注解在运行时有效
    @Retention(RetentionPolicy.RUNTIME)
    //文档化
    @Documented
    //此类能否被继承
    @Inherited
    public @interface TestAnnotation {
        String value() default "";
    }
    

    自定义注解的简单使用

    /**
     * 定义用户注解
     */
    @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface UserAnnontion {
        String name();
        int age() default 18;
        String[] familyMembers() default {};
    }
     
     
    /**
     * 方法注解
     */
    @User(name="阿杰",age = 18,familyMembers = {"xxx","xxx","xxx"})
    public void getUser() {
     
    }
    

    通过反射获取注解信息

    public class TestAnnotationReflex {
        public static void main(String[] args) {
            try {
                Class stuClass = Class.forName("com.example.demo.User");
                Method method = stuClass.getMethod("getUser");
                if(method.isAnnotationPresent(UserAnnontion.class)){
                    UserAnnontion userAnnontion = method.getAnnotation(UserAnnontion.class);
                    System.out.println("name: " + userAnnontion.name() 
                    + ", age: " + userAnnontion.age() + ", familyMembers: " + userAnnontion.familyMembers()[0]);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    以上的只是基础的知识,可能很多人还没搞明白自定义注解到底有什么作用,好像只是简单的注释说明,或者是通过反射获取注解信息。

    接下来,使用自定义注解结合Spring AOP来讲解一下平时工作中使用的场景,Spring AOP常用于拦截器,事务,日志,权限,AOP就不再讲解了,举个例子讲解一下日志记录。在实际项目中,在调试或者排查异常信息时,日志是否记录着请求入参信息,是否都是使用log.info("请求参数")这样的写法记录日志,如果每次都需要这样手写,很容易遗漏,并且重复代码也比较多,在这种场景下,可以做一个统一日志处理的方案,通过使用自定义注解和切面来实现这个功能。

    自定义注解+切面实现统一日志处理

    自定义日志注解

    /**
     * 自定义操作日志注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface OptLog {
        /**
         * 业务
         * @return
         */
        String business();
     
        /**
         * 操作类型,增删改查
         * @return
         */
        OptType optType();
    }
    

    声明日志切面组件

    import com.alibaba.fastjson.JSONObject;
    import com.example.demo.annotation.OptLog;
    import com.example.demo.annotation.OptType;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
    import org.springframework.stereotype.Component;
    import java.lang.reflect.Method;
     
    @Aspect
    @Component
    public class OptLogAspect {
     
        private static final Logger LOG = LoggerFactory.getLogger(OptLogAspect.class);
     
        /**
         * 声明切入点,凡是使用该注解都经过拦截
         */
        @Pointcut("@annotation(com.example.demo.annotation.OptLog)")
        public void OptLog() {
     
        }
     
        @Before("OptLog()")
        public void doOptLogBefore(JoinPoint proceedingJoinPoint) {
            LOG.info("前置通知, 在方法执行之前执行...");
        }
     
        @After("OptLog()")
        public void doOptLogAfter(JoinPoint proceedingJoinPoint) {
            LOG.info("后置通知, 在方法执行之后执行...");
        }
     
        @AfterReturning("OptLog()")
        public void doOptLogAfterReturning(JoinPoint proceedingJoinPoint) {
            LOG.info("返回通知, 在方法返回结果之后执行...");
        }
     
        @AfterThrowing("OptLog()")
        public void doOptLogAfterThrowing(JoinPoint proceedingJoinPoint) {
            LOG.info("异常通知, 在方法抛出异常之后执行...");
        }
     
        /**
         * 设置环绕通知,围绕着方法执行
         *
         * @param proceedingJoinPoint
         * @return
         */
        @Around("OptLog()")
        public Object optLogAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
            if (method == null) {
                return null;
            }
            // 获取方法名称
            String methodName = proceedingJoinPoint.getSignature().getName();
            LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
            // 请求参数名称
            String[] parameterNames = discoverer.getParameterNames(method);
            // 请求参数值
            Object[] paramValues = proceedingJoinPoint.getArgs();
     
            OptLog optLog = method.getAnnotation(OptLog.class);
            this.handle(optLog.optType(), optLog.business(), methodName, parameterNames, paramValues);
            return proceedingJoinPoint.proceed();
        }
     
        /**
         * 日志处理
         *
         * @param optType
         * @param business
         * @param methodName
         * @param parameterNames
         * @param paramValues
         */
        public void handle(OptType optType, String business, String methodName, 
                           String[] parameterNames, Object[] paramValues) {
            JSONObject jsonObject = new JSONObject();
            if (parameterNames != null && parameterNames.length > 0) {
                for (int i = 0; i < parameterNames.length; i++) {
                    jsonObject.put(parameterNames[i], paramValues[i]);
                }
            }
            LOG.info("optType:" + optType + ",business:" + business + ", methodName:" + methodName + ", params:" + jsonObject);
        }
     
    }
    

    控制层运行结果

    @RestController
    @RequestMapping("/user/")
    public class UserController {
     
        @OptLog(optType = OptType.CREATE,business = "用户信息")
        @RequestMapping("create")
        public String createUser(String userName,int age,String address) {
            System.out.println("方法执行中...");
            return "success";
        }
    }
    

    运行结果

    15:32:49.494 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [handle,91] - optType:CREATE,business:用户信息, methodName:createUser, params:{"address":"广州市","userName":"阿杰","age":18}
    15:32:49.494 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [doOptLogBefore,32] - 前置通知, 在方法执行之前执行...
    方法执行中...
    15:32:49.495 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [doOptLogAfterReturning,42] - 返回通知, 在方法返回结果之后执行...
    15:32:49.495 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [doOptLogAfter,37] - 后置通知, 在方法执行之后执行...
    

    最后

    在文章的最后作者为大家整理了很多资料!包括java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书等等!
    欢迎关注公众号:前程有光,领取!

    相关文章

      网友评论

        本文标题:五分钟!带你重新了解自定义注解,看完还不懂算我输!

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