框架基础:深入理解Java注解类型(@Annotation)
注解的概念
注解的官方定义
首先看看官方对注解的描述:
An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.
翻译:
注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。
通过官方描述得出以下结论:
注解是一种元数据形式。即注解是属于java的一种数据类型,和类、接口、数组、枚举类似。
注解用来修饰,类、方法、变量、参数、包。
注解不会对所修饰的代码产生直接的影响。
注解的使用范围
继续看看官方对它的使用范围的描述:
Annotations have a number of uses, among them:Information for the complier - Annotations can be used by the compiler to detect errors or suppress warnings.Compiler-time and deployment-time processing - Software tools can process annotation information to generate code, XML files, and so forth.Runtime processing - Some annotations are available to be examined at runtime.
翻译:
注解又许多用法,其中有:为编译器提供信息 - 注解能被编译器检测到错误或抑制警告。编译时和部署时的处理 - 软件工具能处理注解信息从而生成代码,XML文件等等。运行时的处理 - 有些注解在运行时能被检测到。
##2 如何自定义注解
基于上一节,已对注解有了一个基本的认识:注解其实就是一种标记,可以在程序代码中的关键节点(类、方法、变量、参数、包)上打上这些标记,然后程序在编译时或运行时可以检测到这些标记从而执行一些特殊操作。因此可以得出自定义注解使用的基本流程:
第一步,定义注解——相当于定义标记;
第二步,配置注解——把标记打在需要用到的程序代码中;
第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作。
基本语法
注解类型的声明部分:
注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface
。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
public @interface CherryAnnotation {
}
注解类型的实现部分:
根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。咱们来看看其语法:
public @interface CherryAnnotation {
public String name();
int age() default 18;
int[] array();
}
定义注解类型元素时需要注意如下几点:
访问修饰符必须为public,不写默认为public;
该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
default代表默认值,值必须和第2点定义的类型一致;
如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
常用的元注解
一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!
元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。我们为大家一个个来做介绍。
@Target
@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。它使用一个枚举类型定义如下:
public enum ElementType {
/** 类,接口(包括注解类型)或枚举的声明 */
TYPE, /** 属性的声明 */
FIELD, /** 方法的声明 */
METHOD, /** 方法形式参数声明 */
PARAMETER, /** 构造方法的声明 */
CONSTRUCTOR, /** 局部变量声明 */
LOCAL_VARIABLE, /** 注解类型声明 */
ANNOTATION_TYPE, /** 包的声明 */
PACKAGE
}
//@CherryAnnotation被限定只能使用在类、接口或方法上面
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface CherryAnnotation {
String name();
int age() default 18;
int[] array();
}
@Retention
@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。
注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。同样使用了RetentionPolicy枚举类型定义了三个阶段:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* (注解将被编译器忽略掉)
*/
SOURCE, /**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
*/
CLASS, /**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
* (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
我们再详解一下:
如果一个注解被定义为RetentionPolicy.SOURCE,则它将被限定在Java源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到;
如果一个注解被定义为RetentionPolicy.CLASS,则它将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到;
如果一个注解被定义为RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME;
自定义注解
在具体的Java类上使用注解
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface CherryAnnotation {
String name();
int age() default 18;
int[] score();
}
public class Student {
@CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77})
public void study(int times){
for(int i = 0; i < times; i++){
System.out.println("Good Good Study, Day Day Up!");
}
}
}
简单分析下:
CherryAnnotation的@Target定义为ElementType.METHOD,那么它书写的位置应该在方法定义的上方,即:public void study(int times)之上;
由于我们在CherryAnnotation中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上(),并且在()内以“元素名=元素值“的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用“,”号分割;
注解与反射机制
为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口,它简要含义如下:
Class:类的Class对象定义
Constructor:代表类的构造器定义
Field:代表类的成员变量定义
Method:代表类的方法定义
Package:代表类的包定义
下面是AnnotatedElement中相关的API方法,以上5个类都实现以下的方法
返回值 | 方法名称 | 说明 |
<A extends Annotation> | getAnnotation(Class<A> annotationClass) | 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。 |
Annotation[] | getAnnotations() | 返回此元素上存在的所有注解,包括从父类继承的 |
boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。 |
Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组 |
简单案例演示如下:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA {
}
package com.zejian.annotationdemo; import java.lang.annotation.Annotation;
import java.util.Arrays; @DocumentA
class A{ } //继承了A类
@DocumentB
public class DocumentDemo extends A{ public static void main(String... args){ Class<?> clazz = DocumentDemo.class;
//根据指定注解类型获取该注解
DocumentA documentA=clazz.getAnnotation(DocumentA.class);
System.out.println("A:"+documentA); //获取该元素上的所有注解,包含从父类继承
Annotation[] an= clazz.getAnnotations();
System.out.println("an:"+ Arrays.toString(an));
//获取该元素上的所有注解,但不包含继承!
Annotation[] an2=clazz.getDeclaredAnnotations();
System.out.println("an2:"+ Arrays.toString(an2)); //判断注解DocumentA是否在该元素上
boolean b=clazz.isAnnotationPresent(DocumentA.class);
System.out.println("b:"+b); }
}
执行结果:
A:@com.zejian.annotationdemo.DocumentA()
an:[@com.zejian.annotationdemo.DocumentA(), @com.zejian.annotationdemo.DocumentB()]
an2:@com.zejian.annotationdemo.DocumentB()
b:true
通过反射获取上面我们自定义注解
public class TestAnnotation {
public static void main(String[] args){
try {
//获取Student的Class对象
Class stuClass = Class.forName("pojos.Student"); //说明一下,这里形参不能写成Integer.class,应写为int.class
Method stuMethod = stuClass.getMethod("study",int.class); if(stuMethod.isAnnotationPresent(CherryAnnotation.class)){
System.out.println("Student类上配置了CherryAnnotation注解!");
//获取该元素上指定类型的注解
CherryAnnotation cherryAnnotation = stuMethod.getAnnotation(CherryAnnotation.class);
System.out.println("name: " + cherryAnnotation.name() + ", age: " + cherryAnnotation.age()
+ ", score: " + cherryAnnotation.score()[0]);
}else{
System.out.println("Student类上没有配置CherryAnnotation注解!");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
运行时注解处理器
了解完注解与反射的相关API后,现在通过一个实例(该例子是博主改编自《Tinking in Java》)来演示利用运行时注解来组装数据库SQL的构建语句的过程
/**
* Created by ChenHao on 2019/6/14.
* 表注解
*/
@Target(ElementType.TYPE)//只能应用于类上
@Retention(RetentionPolicy.RUNTIME)//保存到运行时
public @interface DBTable {
String name() default "";
} /**
* Created by ChenHao on 2019/6/14.
* 注解Integer类型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
//该字段对应数据库表列名
String name() default "";
//嵌套注解
Constraints constraint() default @Constraints;
} /**
* Created by ChenHao on 2019/6/14.
* 注解String类型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString { //对应数据库表的列名
String name() default ""; //列类型分配的长度,如varchar(30)的30
int value() default 0; Constraints constraint() default @Constraints;
} /**
* Created by ChenHao on 2019/6/14.
* 约束注解
*/ @Target(ElementType.FIELD)//只能应用在字段上
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
//判断是否作为主键约束
boolean primaryKey() default false;
//判断是否允许为null
boolean allowNull() default false;
//判断是否唯一
boolean unique() default false;
} /**
* Created by ChenHao on 2019/6/14.
* 数据库表Member对应实例类bean
*/
@DBTable(name = "MEMBER")
public class Member {
//主键ID
@SQLString(name = "ID",value = 50, constraint = @Constraints(primaryKey = true))
private String id; @SQLString(name = "NAME" , value = 30)
private String name; @SQLInteger(name = "AGE")
private int age; @SQLString(name = "DESCRIPTION" ,value = 150 , constraint = @Constraints(allowNull = true))
private String description;//个人描述 //省略set get.....
}
上述定义4个注解,分别是@DBTable(用于类上)、@Constraints(用于字段上)、 @SQLString(用于字段上)、@SQLString(用于字段上)并在Member类中使用这些注解,这些注解的作用的是用于帮助注解处理器生成创建数据库表MEMBER的构建语句,在这里有点需要注意的是,我们使用了嵌套注解@Constraints,该注解主要用于判断字段是否为null或者字段是否唯一。必须清楚认识到上述提供的注解生命周期必须为@Retention(RetentionPolicy.RUNTIME),即运行时,这样才可以使用反射机制获取其信息。有了上述注解和使用,剩余的就是编写上述的注解处理器了,前面我们聊了很多注解,其处理器要么是Java自身已提供、要么是框架已提供的,我们自己都没有涉及到注解处理器的编写,但上述定义处理SQL的注解,其处理器必须由我们自己编写了,如下
package com.chenHao.annotationdemo;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List; /**
* Created by chenhao on 2019/6/14.
* 运行时注解处理器,构造表创建语句
*/
public class TableCreator { public static String createTableSql(String className) throws ClassNotFoundException {
Class<?> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);
//如果没有表注解,直接返回
if(dbTable == null) {
System.out.println(
"No DBTable annotations in class " + className);
return null;
}
String tableName = dbTable.name();
// If the name is empty, use the Class name:
if(tableName.length() < 1)
tableName = cl.getName().toUpperCase();
List<String> columnDefs = new ArrayList<String>();
//通过Class类API获取到所有成员字段
for(Field field : cl.getDeclaredFields()) {
String columnName = null;
//获取字段上的注解
Annotation[] anns = field.getDeclaredAnnotations();
if(anns.length < 1)
continue; // Not a db table column //判断注解类型
if(anns[0] instanceof SQLInteger) {
SQLInteger sInt = (SQLInteger) anns[0];
//获取字段对应列名称,如果没有就是使用字段名称替代
if(sInt.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sInt.name();
//构建语句
columnDefs.add(columnName + " INT" +
getConstraints(sInt.constraint()));
}
//判断String类型
if(anns[0] instanceof SQLString) {
SQLString sString = (SQLString) anns[0];
// Use field name if name not specified.
if(sString.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sString.name();
columnDefs.add(columnName + " VARCHAR(" +
sString.value() + ")" +
getConstraints(sString.constraint()));
} }
//数据库表构建语句
StringBuilder createCommand = new StringBuilder(
"CREATE TABLE " + tableName + "(");
for(String columnDef : columnDefs)
createCommand.append("\n " + columnDef + ","); // Remove trailing comma
String tableCreate = createCommand.substring(
0, createCommand.length() - 1) + ");";
return tableCreate;
} /**
* 判断该字段是否有其他约束
* @param con
* @return
*/
private static String getConstraints(Constraints con) {
String constraints = "";
if(!con.allowNull())
constraints += " NOT NULL";
if(con.primaryKey())
constraints += " PRIMARY KEY";
if(con.unique())
constraints += " UNIQUE";
return constraints;
} public static void main(String[] args) throws Exception {
String[] arg={"com.zejian.annotationdemo.Member"};
for(String className : arg) {
System.out.println("Table Creation SQL for " +
className + " is :\n" + createTableSql(className));
}
}
}
输出结果:
Table Creation SQL for com.zejian.annotationdemo.Member is :
CREATE TABLE MEMBER(
ID VARCHAR(50) NOT NULL PRIMARY KEY,
NAME VARCHAR(30) NOT NULL,
AGE INT NOT NULL,
DESCRIPTION VARCHAR(150)
);
如果对反射比较熟悉的同学,上述代码就相对简单了,我们通过传递Member的全路径后通过Class.forName()方法获取到Member的class对象,然后利用Class对象中的方法获取所有成员字段Field,最后利用field.getDeclaredAnnotations()遍历每个Field上的注解再通过注解的类型判断来构建建表的SQL语句。这便是利用注解结合反射来构建SQL语句的简单的处理器模型。
框架基础:深入理解Java注解类型(@Annotation)的更多相关文章
- (转)深入理解Java注解类型(@Annotation)
背景:在面试时候问过关于注解的问题,工作中也用到过该java的特性,但是也没有深入的了解. 秒懂,Java 注解 (Annotation)你可以这样学 ps:注解最通俗易懂的解释 注解是一系列元数据, ...
- 深入理解Java注解类型(@Annotation)
http://blog.csdn.net/javazejian/article/details/71860633 出自[zejian的博客] java注解是在JDK5时引入的新特性,鉴于目前大部分框 ...
- 理解Java注解类型
一. 理解Java注解 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类.而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy ...
- 框架基础——全面解析Java注解
为什么学习注解? 学习注解有什么好处? 学完能做什么? 答:1. 能够读懂别人写的代码,特别是框架相关的代码: 2. 让编程更加简洁,代码更加清晰: 3. 让别人高看一眼. spring.mybati ...
- 深入理解Java枚举类型(enum)
https://blog.csdn.net/javazejian/article/details/71333103 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(en ...
- (转)秒懂,Java 注解 (Annotation)你可以这样学
转自:秒懂,Java 注解 (Annotation)你可以这样学 注解如同标签 回到博文开始的地方,之前某新闻客户端的评论有盖楼的习惯,于是 “乔布斯重新定义了手机.罗永浩重新定义了傻X” 就经常极为 ...
- 你真的理解Java 注解吗?
你真的理解Java 注解吗? 1.什么是注解? 官方解释: Java 注解用于为 Java 代码提供元数据.作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的.Java ...
- Spring框架入门之基于Java注解配置bean
Spring框架入门之基于Java注解配置bean 一.Spring bean配置常用的注解 常用的有四个注解 Controller: 用于控制器的注解 Service : 用于service的注解 ...
- 深入理解 Java 注解
深入理解 Java 注解 本文内容基于 JDK8.注解是 JDK5 引入的,后续 JDK 版本扩展了一些内容,本文中没有明确指明版本的注解都是 JDK5 就已经支持的注解.
随机推荐
- MongoDB学习笔记(七、MongoDB总结)
1.为什么要NoSQL:nosql能解决sql中那些解决不了的问题 NoSQL是什么:Not Only SQL,本质上还是数据库,但它不会遵循传统数据库的规则(如:SQL标准.ACID属性[事务].表 ...
- C语言程序设计100例之(16):巧解算式
例16 巧解算式 问题描述 在1.2.3.4.5.6.7.8.9.10个数中间加上加号或减号,使得到的表达式的值为自然数N,如果中间没有符号,则认为前后为一个数,如1 2 3认为是一百二十三(123 ...
- 如何完成述职报告或年终总结PPT
对于我们 打工仔 职场人士来说,年有年度总结,月有月度报告,指不定有些小伙伴还会有周报和日报,不仅枯燥,而且浪费时间,头都要炸了 ,简直太有趣了呢. 所以,如何准确快速的写完述职报告呢? 这是个好问题 ...
- jQuery-实现简单的Ajax请求封装
封装的意义在于复用,在于减少重复的代码. 我在项目中做了简单的Ajax请求封装,实现方式如下: //封装Ajax请求 $.extend({ ajaxDirect:function(url,type,d ...
- 第一个月.day1
1. 编辑器下载 推荐的是hbulider 开发环境 2. 浏览器 推荐chrome 谷歌浏览器学习 3. 建立技术笔记 推荐博客园 Web 本月任务 搭建静态网页. 静态页面:不需要网络请求 ...
- 请求时发送OPTIONS请求
最近在用uni-app开发项目时,发现一个之前没注意到的点,当我发送POST请求的时候,在NetWork可以看到在发送正式的POST请求时,会先发送一个OPTIONS请求,OPTIONS请求后才会发送 ...
- 读后感:数据结构与算法JavaScript描述
本书看完,对常见的数据结构与算法从概念上有了更深入的理解. 书中关于数组.栈和队列.链表.字典.散列.集合.二叉树.图.排序.检索.动态规划.贪心算法都有详细的介绍.算是一本不错的学习书籍. 栈和队列 ...
- 学习强国docker文件用法
学习强国docker用法 docker文件地址 https://github.com/fuck-xuexiqiangguo/docker 构建 docker docker build -t D ...
- Scrapy框架-中间件
一.中间件中主要有3个函数方法 process_request:处理请求,默认返回值是None process_response:处理响应,默认返回值是response对象 process_exce ...
- .WrongArgumentException: Malformed database URL, failed to parse the connection string near ';characterEncoding=UTF-8&;serverTimezone=Asia/Shanghai'.)
连接mysql库报的异常信息: org.springframework.transaction.CannotCreateTransactionException: Could not open JDB ...