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

998 阅读5分钟

前言

日常开发中用到了各式各样的注解,常用的注解@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架构实战电子书等等! 欢迎关注公众号:前程有光,领取!