注解都不会用,怎让Java对你动情

开发 后端
「注解」 也称为 元数据。为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。

[[344016]]

本文转载自微信公众号「小菜良记」,作者蔡不菜丶 。转载本文请联系小菜良记公众号。   

初识

“「注解」 也称为 元数据。为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。

注解是在 Java 1.5 之后引入的,它可以提供用来完整地描述程序中所需的信息,可以由编译器来测试和验证的格式,存储有关程序的额外信息。

注解的使用很简单,只需要和 @ 符号搭配。有些 Java 初学者常常会把 「注解」 和「注释」 混淆,但是两者的作用却大同小异,都是用来描述信息,不同的是 「注解」 描述的信息是给应用程序看的,而 「注释」 描述的信息是给开发人员看的。

初学者对「注解」的印象可能不深,「注解」也许不起眼但是处处可见。

最常见到的便是 @Override,表示当前的方法定义将覆盖父类的方法,如果拼写错误,或者方法签名对不上被覆盖的方法,编译器就会发出错误提示。

既然都说到@Override注解了,那仔细回忆下,脑子里估计就浮现出@SuppressWarnnings注解。还记得我最早见到这个注解的时候,还是Myeclipse提示我使用的,我也不管三七二十一,就给标注上了。后来才知道这个注解是用来关闭编译器对类、方法、成员变量、变量初始化的警告。

上面两个都说完了,那不妨再来一个,这个可能是最不常见的。那就是@Deprecated

 

不要因为这个类加了横线感到奇怪,那是因为@Deprecated的效果。它的具体功能就是用于标识方法或类,标识该类或方法已过时,建议不要使用。如果开发人员使用了注解为它的元素,那么编译器就会发出警告信息。

刚刚开局不久,我们就已经学到了三个注解的使用,虽然只是基本的,但接下来我们就通过这三个注解为导线通关打怪吧。

首关之程娲造注

注解一旦构造出来,就享有编译器的类型检查保护。让我们先看下一组代码热热身:

  1. public class TestService { 
  2.  
  3.     @MyAnnotation 
  4.     public void runTset() { 
  5.         System.out.println("annotation test"); 
  6.     } 
  7.  

不要纳闷,Java 中确实没有一个注解名为MyAnnotation,但这个怎么来的呢,就是我们自己造的。

那么关子卖完了,接下来就来揭秘注解的制造:

  1. @Target(ElementType.METHOD) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface MyAnnotation { 

这样子,一个简单的注解就新鲜出炉了。需要注意的是这可不是一个接口,需要留言interface前面还有一个@,这个标识要是漏掉了,那可以天差地别。

细心的小伙伴可能注意到了,定义的注解头上怎么还有注解。

 

这就是接下来要讲到,敲黑板,注意看!

元注解来帮忙

在定义注解时,会需要一些元注解。上面出现了两个,分别是@Target和@Retention.

其中@Target用来定义你的注解将应用于什么地方(例如一个方法或一个域),@Retention用来定义该注解在哪一个级别可用,在源代码中(「SOURCE」),类文件中(「CLASS」)或者运行时(「RUNTIME」)。Java 提供了四种元注解,如下:

名称 用处
「@Target」 标识该注解可以用于什么地方。其中 ElementType 参数包括:
1. CONSTARUCTOR:构造器的声明
2. FIELD:域声明(包括enum实例)
3. LOCAL_VARIABLE:局部变量声明
4. METHOD:方法声明
5. PACKAGE:包声明
6. TYPE:类、接口(包括注解类型)或enum 声明
「@Retention」 表示需要在什么级别保存该注解信息,其中RetentionPolicy参数包括:
1.SOURCE:注解将被编译器丢弃
2.CLASS:注解在 class 文件中可用,但会被 VM 丢弃
3. RUNTIME:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息
「@Documented」 将此注解包含在 JavaDoc 中
「@Inherited」 允许子类继承父类的注解

注解也分类

我们在上面示例中创建了一个 @MyAnnotation 注解。看起来很简单,没什么内容,因此这种注解我们也称为 「标记注解」,注解也分几类:

  • 标记注解:注解内部没有属性。使用方式:「@注解名」
  1. //定义 
  2. @Target(ElementType.METHOD) 
  3. @Retention(RetentionPolicy.RUNTIME) 
  4. public @interface MyAnnotation { 
  • 单值注解:注解内部只有一个属性。使用方式:「@注解名(key = value)」
  1. //定义 
  2. @Target(ElementType.METHOD) 
  3. @Retention(RetentionPolicy.RUNTIME) 
  4. public @interface SingleValueAnnotation { 
  5.     String name(); 
  6. //使用 
  7. @SingleValueAnnotation(name = "test"
  8. public void singleTest() {} 
  • 多值注解:注解内部有过个属性。使用方式:「@注解名(key = value, key = value, ...)」
  1. //定义 
  2. @Target(ElementType.METHOD) 
  3. @Retention(RetentionPolicy.RUNTIME) 
  4. public @interface MultiValueAnnotation { 
  5.     String name(); 
  6.     int num(); 
  7. //使用 
  8. @MultiValueAnnotation(name = "test", num = 1) 
  9. public void multiTest() {} 

值也有默认

当我们使用的不是标记注解时,如果在使用注解的时候不给注解中的属性赋上值,那么编译器就会报错,提示我们需要赋值。

 

这样子是很不方便,有时候我们并没有使用到或值是固定的不想重复写,那么这个时候就需要借助「default」关键字来帮忙我们解决这种问题。

  1. @Target(ElementType.METHOD) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface MultiValueAnnotation { 
  4.      
  5.     String name(); 
  6.      
  7.     int num() default 0; 

我们在属性上使用了 default 关键字来声明 num 属性的默认值为 0 ,这样子我们在使用上述那个注解的时候就可以不用手动给num赋值了。

次关之造器解注

注解具有让编译器进行编译检查的作用,但是如果没有用来读取注解的工具,那注解也不会比注释更有用,起码注释可以让开发人员更直观的看到此段代码的用处。

重回反射想要创建与使用 「注解处理器」,我们还需要借助反射机制来构造这类工具。以下是简单的例子:

  1. public class AnnotationHandle { 
  2.  
  3.     public static void track(Class<?> c) { 
  4.         for (Method m : c.getDeclaredMethods()) { 
  5.             MultiValueAnnotation annotation = m.getAnnotation(MultiValueAnnotation.class); 
  6.             if (annotation != null) { 
  7.                 System.out.println("name:" + annotation.name() + 
  8.                         "\n num:" + annotation.num()); 
  9.             } 
  10.         } 
  11.     } 
  12.  
  13.     public static void main(String[] args) { 
  14.         track(TestService.class); 
  15.     } 
  16.  
  17. /*  OUTPUT
  18.   name:test 
  19.    num:0 
  20. */ 

在上述例子中我们用到了两个反射的方法:getDeclaredMethods()和getAnnotation()。

其中getDeclaredMethods() 用来返回该类的所有方法,getAnnotation()用来获取指定类型的注解对象。如果方法上没有该注解则会返回 「null」 值。

注解元素可用类型

上述@MultiValueAnnotation注解中我们定义了 String类型的 「name」 和 int类型的 「num」,除此之外我们还可以使用其他类型如下:

  • 「基本类型」(「int、float、boolean等」)
  • 「String」
  • 「Class」
  • 「enum」
  • 「Annotation」
  • 「以上类型的数组」

如果使用了上面以外的其他类型,那么编译器就会报错。而且要注意的是,「也不能使用基本类型的包装类型」

默认值的限制

上述例子中我们也看到了,我们可以在使用注解的时候给注解属性赋值,也可以在定义注解的时候给注解一个默认值,但是这两者都说明了一件事:「那就是,注解元素不能有不确定的值,要么具有默认值,要么在使用注解时提供元素的值」

基本元素不存在null值,因此对于非基本类型的元素,无论是在使用中声明,还是在定义时声明, 「都不能将 null 值作为其值」。因此在实际开发中,我们往往会定义一些特殊值作为不存在的标识,例如 「负数」 或 「空字符串」

三关之运注帷幄

在前面两关中,我们学会了定义注解和创建注解处理器。接下来我们就要来更加深入掌握注解!

注解也能嵌套

在修饰注解元素的时候我们看到可以使用Annotation来修饰,估计看到那的时候会觉得有点奇怪。在这里就来为你来揭秘。

先来看一组注解:

@Constraints

  1. @Target(ElementType.FIELD) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface Constraints { 
  4.     boolean primaryKey() default false
  5.     boolean unique() default false

@SQLString

  1. @Target(ElementType.FIELD) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface SQLString { 
  4.     String name() default ""
  5.     Constraints constraints() default @Constraints; 

我们在@SQLString注解中使用Constraints注解元素,并将默认值设为@Constraints。这个时候Constraints中的值都是@Constraints注解中定义的默认值,如果我们要使用自定义的话,做法如下:

  1. @Target(ElementType.FIELD) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface SQLString { 
  4.     String name() default ""
  5.     Constraints constraints() default @Constraints(primaryKey = true); 

这样子我们就可以使用自己定义的「value」

注解不支持继承

我们不能使用extends来继承某个@interface,但是可以通过嵌套的方式来解决这一烦恼。

AOP与注解的搭配

「AOP」 在当今开发中我们并不陌生,那么 「AOP」 和 「注解」 能产生什么化学反应呢,请看以下代码:

@ApiLog:

  1. @Retention(RetentionPolicy.RUNTIME) 
  2. @Target({ElementType.METHOD, ElementType.TYPE}) 
  3. public @interface ApiLog { 
  4.     /** 
  5.      * 接口名称 
  6.      */ 
  7.     String name(); 

使用:

  1. @GetMapping(value = "/getConfig"
  2. @ApiLog(name = "获取系统相关配置"
  3. public Result getConfig() throws Exception { 
  4.     return sendOK(SystemService.getConfig(type)); 

Aop使用:

  1. @Aspect 
  2. @Component 
  3. public class SysLogAspect { 
  4.     @Autowired 
  5.     private LogService logService; 
  6.      
  7.     @Pointcut("@annotation(cbuc.life.annotation.ApiLog)"
  8.     public void logPointCut() {         
  9.     } 
  10.  
  11.     @Around("logPointCut()"
  12.     public Object around(ProceedingJoinPoint point) throws Throwable { 
  13.         long beginTime = System.currentTimeMillis(); 
  14.         //执行方法 
  15.         Object result = point.proceed(); 
  16.         //执行时长(毫秒) 
  17.         long time = System.currentTimeMillis() - beginTime; 
  18.         //保存日志 
  19.         saveSysLog(point, time); 
  20.         return result; 
  21.     } 
  22.  
  23.     private void saveSysLog(ProceedingJoinPoint joinPoint, long time) { 
  24.         MethodSignature signature = (MethodSignature) joinPoint.getSignature(); 
  25.         Method method = signature.getMethod(); 
  26.  
  27.         LogEntity log = new LogEntity(); 
  28.         ApiLog apiLog = method.getAnnotation(ApiLog.class); 
  29.         if(apiLog != null){ 
  30.             //注解上的描述 
  31.             log.setMethodDescribe(syslog.value()); 
  32.         } 
  33.  
  34.         //请求的方法名 
  35.         String className = joinPoint.getTarget().getClass().getName(); 
  36.         String methodName = signature.getName(); 
  37.         log.setMethod(className + "." + methodName + "()"); 
  38.  
  39.         //请求的参数 
  40.         Object[] args = joinPoint.getArgs(); 
  41.         String params = JSON.toJSONString(args[0]); 
  42.         log.setParams(params); 
  43.  
  44.         //获取request 
  45.         HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 
  46.  
  47.         //设置IP地址 
  48.         log.setIp(ServletUtil.getIpAddress(request)); 
  49.  
  50.         //用户名 
  51.         String username = LoginInfo.getUsername(); 
  52.         log.setUsername(username); 
  53.  
  54.         //保存系统日志 
  55.         logService.save(log); 
  56.     } 

 

通过以上代码,我们可以在期望记录日志的方法上增加@ApiLog注解,该方法的动作就会被记录进日志表,不管方法叫什么名字,类在什么位置,都可以轻松的解决,而且没有代码入侵!

 

责任编辑:武晓燕 来源: 小菜良记
相关推荐

2020-08-26 14:40:38

explainMySQL数据库

2021-03-16 15:12:57

CompletableFuture机制java

2015-03-16 11:33:16

程序员代码bug

2021-01-28 09:40:33

运维监控工具软件

2020-05-14 08:59:28

API网关性能

2020-09-01 14:17:03

WindowsDefender微软

2022-02-22 08:25:51

typeScript泛型概念泛型使用

2019-12-26 09:56:34

Java多线程内部锁

2022-03-27 22:07:35

元宇宙虚拟人IBM

2019-09-03 09:30:46

ss 命令SocketLinux

2020-03-06 10:25:10

注解Java代码

2021-07-07 06:54:37

网页Selenium浏览器

2017-02-08 19:49:03

内存SSDDRAM

2023-05-16 07:15:11

架构模型对象

2020-10-30 07:09:52

Lombok架构师

2020-12-18 09:45:33

DockerLinux命令

2020-08-03 07:54:33

神经网络通用近似定理机器学习

2020-11-09 09:03:35

高并发多线程ThreadLocal

2022-12-26 18:53:00

MQ宕机仓储服务

2021-09-08 17:27:54

神经网络AI算法
点赞
收藏

51CTO技术栈公众号