用自定义注解做点什么

前言


你不一定听过注解,但你一定对@Override不陌生。

当我们重写父类方法的时候我们就看到了@Override。我们知道它表示父类方法被子类重写了。

现在告诉你,@Override就是一个注解。

也许你会疑惑注解是什么?
注解(annotation)是JDK5之后引进的新特性,是一种特殊的注释,之所以说它特殊是因为不同于普通注释(comment)能存在于源码,而且还能存在编译期跟运行期,会最终编译成一个.class文件,所以注解能有比普通注释更多的功能。

接下来,先入个门,然后通过实战来证明注解有多“猛”。

PS : 如果已经了解的小伙伴可自行跳到 自定义注解实战。

自定义注解入门


我们对于注解的认识大多数来源于标准注解(也称为内建注解)。

标准注解 表示的意义
@Override 用于标识该方法继承自超类 当父类的方法被删除或修改了,编译器会提示错误信息
@Deprecated 表示该类或者该方法已经不推荐使用 如果用户还是要使用,会生成编译的警告
@SuppressWarnings 用于忽略的编译器警告信息

Java不仅仅提供我们原有的注解使用,它还允许我们自定义注解。比如你可以像这样:

public @interface DoSomething {public String name() default "write";
}

这是最简单的注解声明。
尽管看上去像是接口的写法,但完全不是一回事。这一点要注意。
而使用注解也很简单,可以像这样:

@DoSomething(name = "walidake")//可以显式传值进来,此时name=walidake
public class UseAnnotation {}@DoSomething//如果不传值,则默认name=我们定义的默认值,即我们上面定义的"write"
public class UseAnnotation {}

需要注意的是当注解有value()方法时,不需要指明具体名称

public @interface DoSomething {public String value();public String name() default "write";
}@DoSomething("walidake")
public class UseAnnotation {}

然而“最简单的自定义注解”并没有特别的意义。所以,这时候我们需要引入一个元注解的概念。

我们需要知道这些概念:
“普通注解”只能用来注解“代码”,而**“元注解”只能用来注解 “普通注解”**。
自定义注解是“普通注解”

JDK5时支持的元注解有@Documented @Retention @Target @Inherited,接下来分别介绍它们修饰注解的效果。

@Documented
@interface DocumentedAnnotation{}@interface UnDocumentedAnnotation{}@DocumentedAnnotation
@UnDocumentedAnnotation
public class UseDocumentedAnnotation{}

打开小黑窗,运行javadoc UseDocumentedAnnotation.java

运行结果:

结论:可以看到,被@Documented修饰的注解会生成到javadoc中,如@DocumentedAnnotation。
而不被@Documented修饰的注解(@UnDocumentedAnnotation)不会生成到javadoc中。

注解的级别
@Retention可以设置注解的级别,分为三种,都有其特定的功能。
这个元注解是我们关注的重点,后面实战我们会用到。

注解级别 存在范围 主要用途
SOURCE 源码级别 注解只存在源码中 功能是与编译器交互,用于代码检测。 如@Override,@SuppressWarings。 额外效率损耗发生在编译时
CLASS 字节码级别 注解存在源码与字节码文件中 主要用于编译时生成额外的文件,如XML,Java文件等,但运行时无法获得。 这个级别需要添加JVM加载时候的代理(javaagent),使用代理来动态修改字节码文件
RUNTIME 运行时级别 注解存在源码,字节码与Java虚拟机中 主要用于运行时反射获取相关信息

限制注解使用的范围
注解默认可以修饰各种元素,而使用@Target可以限制注解的使用范围。

例如,可以限定注解只能修饰方法。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}

上面的代码将注解的使用范围限制在了方法上,而不能用来修饰类。

试着用@Override修饰类会得到“The annotation @Override is disallowed for this location”的错误。

@Target支持的范围(参见ElementType):

1) 类,接口,注解;
2) 属性域;
3) 方法;
4) 参数;
5) 构造函数;
6) 局部变量;
7) 注解类型;
8) 包

注解的继承
@Inherited可以让注解类似被“继承”一样。
通过使用@Inherited,可以让子类对象使用getAnnotations()获取父类@Inherited修饰的注解。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Inheritable{}@interface UnInheritable{}public class UseInheritedAnnotation{@UnInheritable@Inheritablepublic static class Super{}public static class Sub extends  Super {}public static void main(String... args){Super instance=new Sub();//result : [@com.walidake.annotation.util.Inheritable()]System.out.println(Arrays.toString(instance.getClass().getAnnotations()));}
}

我们干脆用@Documented查看类结构。发现:

这是不是恰恰证明了这种是伪继承的说法,而不是真正的继承。

自定义注解实战


引言
Java Web开发中,对框架的理解和掌握是必须的。而在使用大多数框架的过程中,一般有两种方式的配置,一种是基于xml的配置方式,一种是基于注解的方式。然而,越来越多的程序员(我)在开发过程中享受到注解带来的简便,并义无反顾地投身其中。

ORM框架,像Hibernate,Mybatis就提供了基于注解的配置方式。我们接下来就使用自定义注解实现袖珍版的Mybatis,袖珍版的Hibernate。

这很重要
说明:实战的代码会被文章末尾附上。而实际上在之前做袖珍版框架的时候并没有想到会拿来做自定义注解的Demo。因此给出的代码涉及了其他的一些技术,例如数据库连接池,动态代理等等,比较杂。
在这个篇幅我们只讨论关于自定义注解的问题,至于其他的技术后面会开多几篇博文阐述。(当然这么多前辈面前不敢造次,有个讨论学习的氛围是很好的~)

那么在自定义注解框架前,我们需要花点时间浏览以下几个和Annotation相关的方法。

方法名 用法
Annotation getAnnotation(Class annotationType) 获取注解在其上的annotationType
Annotation[] getAnnotations() 获取所有注解
isAnnotationPresent(Class annotationType) 判断当前元素是否被annotationType注解
Annotation[] getDeclareAnnotations() 与getAnnotations() 类似,但是不包括父类中被Inherited修饰的注解

Mybatis 自定义注解

本节目标:自定义注解实现Mybatis插入数据操作。
本节要求:细心观察使用自定义注解的步骤。

Step 1 : 声明自定义注解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Insert {public String value();
}

Step 2 : 在规定的注解使用范围内使用我们的注解

public interface UserMapper {@Insert("insert into user (name,password) values (?,?)")public void addUser(String name,String password);}

Step 3 : 通过method.getAnnotation(Insert.class).value()使用反射解析自定义注解,得到其中的sql语句

//检查是否被@Insert注解修饰
if (method.isAnnotationPresent(Insert.class)) {//检查sql语句是否合法//method.getAnnotation(Insert.class).value()取得@Insert注解value中的Sql语句sql = checkSql(method.getAnnotation(Insert.class).value(),Insert.class.getSimpleName());//具体的插入数据库操作insert(sql, parameters);
}

Step 4 : 根据实际场景调用Step 3的方法

UserMapper mapper = MethodProxyFactory.getBean(UserMapper.class);
mapper.addUser("walidake","665908");

运行结果:

以上节选自annotation中Mybatis部分。具体CRUD操作请看源码。

总结一下从上面学到的东西:
1.声明自定义注解,并限制适用范围(因为默认是通用)
2.规定范围内使用注解
3.isAnnotationPresent(Insert.class)检查注解,getAnnotation(Insert.class).value()取得注解内容
4.根据实际场景应用

Hibernate 自定义注解

本节目标:自定义注解使实体自动建表(即生成建表SQL语句)
本节要求:动手操作,把未给全的代码补齐。
本节规划:仿照Hibernate,我们大概会需要@Table,@Column,还有id,我们这里暂且声明为@PrimaryKey

仿照自定义Mybatis注解的步骤:

/*** 可根据需要自行定制功能*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {String name() default "";}@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {// 列名 默认为""String name() default "";// 长度 默认为255int length() default 255;// 是否为varchar 默认为trueboolean varchar() default true;// 是否为空 默认可为空boolean isNull() default true;
}/*** 有需要可以拆分成更小粒度* @author walidake**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface PrimaryKey {String name() default "";
}

完成Step 1,接下来是Step 2。

@Table
public class Person {@PrimaryKeyprivate int id;@Column(isNull = false, length = 20)private String username;...
}

Step 3,新建一个叫做SqlUtil的类,使用Class(实体类).isAnnotationPresent(Table.class)取到@Table注解的内容。

而我们如何取到@Column和@PrimaryKey的内容?
使用反射,我们可以很容易做到。

// 反射取得所有Field
Field[] fields = clazz.getDeclaredFields();
...
...
// 获取注解对象
column = fields[i].getAnnotation(Column.class);
// 设置访问私有变量
fields[i].setAccessible(true);
// 取得@Column的内容
columnName = "".equals(column.name()) ? fields[i].getName(): column.name();

反射的内容后面再写。(感觉每一篇都给自己挖了很多坑后面去填)

Step 4套入使用场景

String createSql = SqlUtil.createTable(clazz);
...
connection.createStatement().execute(createSql);

运行结果:

运行结果正确!

自此我们完成了实战模块的内容。当然关于Hibernate的CRUD也可以用同样的方法做到,更进一步还可以把二级缓存整合进来,实现自己的一个微型框架。尽管现有的框架已经很成熟了,但自己实现一遍还是能收获很多东西。

可以看出来,注解简化了我们的配置。每次使用注解只需要@注解名就可以了,就跟吃春药一样“爽”。不过由于使用了反射,后劲太“猛”,jvm无法对代码优化,影响了性能。这一点最后也会提及。

另外提一点,之前想格式化hibernate生成的SQL,做大量搜索后被告知“Hibernate 使用的是开源的语法解析工具 Antlr,需要进行 SQL 语法解析,将 SQL 语句整理成语法树”。也算一个坑吧~
不过后来找到一个除了建表SQL以外的格式化工具类,觉得还不错就也分享了。可以在源码中找到。

最后说点什么
可以发现我们使用运行时注解来搭建我们的袖珍版ORM框架,因为运行时注解来搭建框架相对容易而且适用性也比较广,搭建的框架使用起来也比较简单。但在此基础上因为需要用到反射,其效率性能相对不高。因此,多数Web应用使用运行时注解,而像Android等对效率性能要求较高的平台一般使用源码级别注解来搭建。下一节我们讨论怎么玩一玩源码级注解。

用自定义注解做点什么——自定义注解有什么用相关推荐

  1. 使用validate注解做校验以及自定义validate注解

    Springboot版本:2.3.1.RELEASE 引入依赖 <dependency><groupId>org.springframework.boot</groupI ...

  2. 自定义依赖注解无效_关于Apt注解实践与总结【包含20篇博客】

    超详细!安卓巴士开发者大会嘉宾及主题介绍 目录介绍 00.注解系列博客汇总 01.什么是apt 02.annotationProcessor和apt区别 03.项目目录结构 04.该案例作用 05.使 ...

  3. Spring 自定义注解,配置简单日志注解

    java在jdk1.5中引入了注解,spring框架也正好把java注解发挥得淋漓尽致. 下面会讲解Spring中自定义注解的简单流程,其中会涉及到spring框架中的AOP(面向切面编程)相关概念. ...

  4. 自定义注解以及通过aop实现注解横切(日志)

    Aop + 自定义注解实现日志记录 定义一个 Controller: @RestController @RequestMapping("/aop") public class Ao ...

  5. 注解提高篇:自定义注解处理器(APT)

    ## 0x01 继承AbstractProcessor抽象类 当定义好Annotation注解后,接下来就需要一个注解处理器来处理我们的自定义注解了.实现Java Annotation一般需要继承Ab ...

  6. java自定义注解为空值_java自定义注解

    1. Java注解(Annotation) Java注解是附加在代码中的一些元信息,用于一些工具在编译. 运行时进行解析和使用,起到说明.配置的功能. 注解相关类都包含在java.lang.annot ...

  7. 深入理解Java:注解(Annotation)自定义注解入门

    要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法. 元注解: 元注解的作用就是负责注解其他注解.Java5. ...

  8. 【Java注解系列】内置注解与AOP实现自定义注解

    Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制. Java 语言中的类.方法.变量.参数和包等都可以被标注.和 Javadoc 不同,Java 标注可 ...

  9. java注释和注解_深入理解JAVA注解(Annotation)以及自定义注解

    Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制.Java 语言中的类.方法.变量.参数和包等都可以被标注.注解可以看作是一种特殊的标记,在程序在编译或 ...

最新文章

  1. .h file not found
  2. python中的Xpath方法总结
  3. LoaderManager使用详解(一)---没有Loader之前的世界
  4. 【一针见血】 JavaScript this
  5. 阿里云助力1药网开辟疫情防控“第二战场”
  6. 需求规格说明书(备注:因不支持word复制,格式图片发生改变 ,故以文件方式又上传了一份pdf)...
  7. rxjs处理http请求超时
  8. 由Unity發佈到Google Play (Android Market)的步驟
  9. 云栖回顾|龙蜥论坛圆桌环节都有哪些精彩观点?
  10. 【数据分享】中国城市统计年鉴_2001-2021年
  11. 线程创建常用的四种方式
  12. pdf转图片 jpg png
  13. Ribbon界面图标可以直接用PNG做透明图标
  14. 图形学篇:多边形有效边表填充算法
  15. CAN总线和CANOpen协议栈总结
  16. 7-3 地下迷宫探索(30 分)(dfs)
  17. H5新增video标签的常用属性
  18. optics算法matlab实现,OPTICS聚类算法的matlab实现
  19. 计算机二级MS-office题目练习
  20. 周三直播 | PaddleGAN又开金手指,零门槛人像转卡通

热门文章

  1. java中map的遍历方法_Java中Map的三种遍历方式
  2. centos7镜像加速_docker 镜像加速CentOS7详细介绍
  3. java 执行顺序_Java代码执行顺序
  4. txt文本变为粗体_如何在PHP中使文本变为粗体?
  5. puppeteer执行js_使用Node.js和Puppeteer与表单和网页进行交互– 1
  6. c++ stl stack_C ++ STL中的stack :: push()函数
  7. synchronized底层是如何实现的?
  8. 面试官 | 如何优雅的设计Java 异常?
  9. 面试官 | JVM 为什么使用元空间替换了永久代?
  10. Debian11服务器系统默认不存在sudo命令的解决办法