定义
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。
它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明、注释、标注。
作用
- 编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
- 反射解析:通过代码里标识的元数据对代码进行分析,跟踪代码中的依赖性【使用反射】
- 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
分类
基本内置注解
@Override
它的作用是对覆盖超类中方法的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。
package annotation;
public class Hero {
String name;
@Override
public String toString(){
return name;
}
@Override
public String fromString(){
return name;
}
}
在fromString()方法上加上@Override 注解,就会提示错误警告。因为Hero类的父类Object,并没有fromString方法。
@Deprecated
它的作用是对不应该再使用的方法添加注解,当编程人员使用这些方法时,将会在编译时提示已经过时。
package annotation;
public class Hero {
String name;
@Deprecated
public void hackMap(){
}
public static void main(String[] args) {
new Hero().hackMap();
}
}
方法hackMap被注解为过期,在调用的时候,就会提示已经过期警告。
@SuppressWarnings
Suppress英文的意思是抑制的意思,这个注解的用处是忽略警告信息。
参数有:
- deprecation:使用了不赞成使用的类或方法时的警告(使用@Deprecated使得编译器产生的警告);
- unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 关闭编译器警告
- fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
- path:在类路径、源文件路径等中有不存在的路径时的警告;
- serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
- finally:任何 finally 子句不能正常完成时的警告;
- rawtypes 泛型类型未指明
- unused 引用定义了,但是没有被使用
- all:关于以上所有情况的警告。
package annotation;
import java.util.ArrayList;
import java.util.List;
public class Hero {
String name;
@SuppressWarnings({ "rawtypes", "unused" })
public static void main(String[] args) {
List heros = new ArrayList();
}
}
加上 @SuppressWarnings({ “rawtypes”, “unused” })后,就对这些警告进行了抑制,即忽略掉这些警告信息。编译器将不会出现警告。
@FunctionalInterface
@FunctionalInterface这是Java1.8 新增的注解,用于约定函数式接口。
函数式接口概念: 如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口称为函数式接口。函数式接口其存在的意义,主要是配合Lambda 表达式 来使用。
package annotation;
@FunctionalInterface
public interface Inter1 {
public void adAttack();
}
Inter1 只有一个抽象方法,可以被注解为@FunctionalInterface
package annotation;
@FunctionalInterface
public interface Inter2 {
public void apAttack();
public void apAttack2();
}
Inter1 有两个方法apAttack()和apAttack2(),那么就不能被注解为函数式接口。
元注解
讲解元注解概念之前,我们先建立元数据的概念。
元数据:为其他数据提供信息的数据。就是上面所说的注解。
元注解:用于注解 自定义注解 的注解。
元注解有这么几种:
- @Target
- @Retention
- @Inherited
- @Documented
- @Repeatable (java1.8 新增)
@Target
用于声明注解修饰范围。
例如:@Target({METHOD,TYPE}),表示他可以用在 方法和类型(类和接口)等上,但是不能放在属性等其他位置。
枚举值:
- ElementType.TYPE:能修饰类、接口或枚举类型
- ElementType.FIELD:能修饰成员变量
- ElementType.METHOD:能修饰方法
- ElementType.PARAMETER:能修饰参数
- ElementType.CONSTRUCTOR:能修饰构造器
- ElementType.LOCAL_VARIABLE:能修饰局部变量
- ElementType.ANNOTATION_TYPE:能修饰注解
- ElementType.PACKAGE:能修饰包
@Target({METHOD,TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface JDBCConfig {
String ip();
int port() default 3306;
String database();
String encoding();
String loginName();
String password();
}
可以在类、方法使用
@JDBCConfig(ip = "192.168.1.103", database = "mysql", encoding = "utf-8", loginName = "dbuser", password = "111111")
public class AnnotationDemo {
@JDBCConfig(ip = "192.168.1.103", database = "mysql", encoding = "utf-8", loginName = "dbuser", password = "111111")
public void test(){
}
}
但是不能在成员属性前使用
@JDBCConfig(ip = "192.168.1.103", database = "mysql", encoding = "utf-8", loginName = "dbuser", password = "111111")
public class AnnotationDemo {
@JDBCConfig(ip = "192.168.1.103", database = "mysql", encoding = "utf-8", loginName = "dbuser", password = "111111")
public void test(){}
/* 编译报错 */
@JDBCConfig(ip = "192.168.1.103", database = "mysql", encoding = "utf-8", loginName = "dbuser", password = "111111")
public String ip;
}
@Retention
@Retention 表示生命周期。 @Retention可选的值有3个:
- RetentionPolicy.SOURCE: 注解只在源代码中存在,编译成class之后,就没了。@Override 就是这种注解。
- RetentionPolicy.CLASS: 注解在java文件编程成.class文件后,依然存在,但是运行起来后就没了。@Retention的默认值,即当没有显式指定@Retention的时候,就会是这种类型。
- RetentionPolicy.RUNTIME: 注解在运行起来之后依然存在,程序可以通过反射获取这些信息,自定义注解@JDBCConfig 就是这样。
@Target({METHOD,TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface JDBCConfig {
String ip();
int port() default 3306;
String database();
String encoding();
String loginName();
String password();
}
@Inherited
@Inherited 表示该注解具有继承性。使得子类可以反射获取父类注解信息。
注解JDBCConfig 含有元注解@Inherited
@Target({METHOD,TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@interface JDBCConfig {
String ip();
int port() default 3306;
String database();
String encoding();
String loginName();
String password();
}
类DBUtil使用注解
@JDBCConfig(ip = "192.168.1.103", database = "mysql", encoding = "utf-8", loginName = "dbuser", password = "111111")
class DBUtil{
}
类ChildDBUtil继承DBUtil,并获取父类注解信息
class ChildDBUtil extends DBUtil{
public static void test(){
JDBCConfig annotation = ChildDBUtil.class.getAnnotation(JDBCConfig.class);
String ip = annotation.ip();
int port = annotation.port();
String loginName = annotation.loginName();
String password = annotation.password();
System.out.println("ip="+ip+",port="+port+",loginName="+loginName+",password="+password);
}
}
运行输出
public class AnnotationDemo {
public static void main(String[] argv){
ChildDBUtil.test();
}
}
输出:
ip=192.168.1.103,port=3306,loginName=dbuser,password=111111
结果看出,子类可以通过反射获取父类的注解信息。
如果注解删除元注解@Inherited
@Target({METHOD,TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface JDBCConfig {
String ip();
int port() default 3306;
String database();
String encoding();
String loginName();
String password();
}
运行报错:
Exception in thread "main" java.lang.NullPointerException
at 注解.ChildDBUtil.test(AnnotationDemo.java:17)
at 注解.AnnotationDemo.main(AnnotationDemo.java:10)
子类不能通过反射获取父类的注解信息。
@Documented
在用javadoc命令生成API文档后,DBUtil的文档里会出现该注解说明。
@Repeatable
表示这个声明的注解是可重复的。@Repeatable的值是另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解。
很拗口,看例子
文件夹中有很多文件类型,比如:java, html, css, js等待。
FileType 注解
@Target( METHOD )
@Retention( RetentionPolicy.RUNTIME )
@Repeatable( FileTypes.class )
public @interface FileType {
String value();
};
FileTypes 注解
@Target( METHOD)
@Retention( RetentionPolicy.RUNTIME )
public @interface FileTypes {
FileType[] value();
}
其中,@FileType注解上的元注解@Repeatable中的值使用了@FileTypes注解,@FileTypes 注解中包含的值类型是一个@FileType注解的数组。
这就解释了官方文档中@Repeatable中值的使用。
调用:
public class FindFiles {
public static void main(String[] argv) throws NoSuchMethodException {
FileType[] annotations = FindFiles.class.getMethod("test").getAnnotationsByType(FileType.class);
for(FileType fileType : annotations){
System.out.println(fileType.value());
}
}
@FileType("java")
@FileType("txt")
@FileType("jsp")
public static void test(){
}
}
输出:
java
txt
jsp
public T[] getAnnotationsByType(Class annotationClass);可以获取注解集合。
自定义注解
它类似于新创建一个接口文件,但为了区分,注解类型声明为 @interface。
上述元注解讲解中JDBCConfig、FileType等就是自定义注解。
例子:获取一个连接数据库test的连接Connection实例
public class DBUtil {
static String ip = "127.0.0.1";
static int port = 3306;
static String database = "test";
static String encoding = "UTF-8";
static String loginName = "root";
static String password = "admin";
static{
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
String url = String.format("jdbc:mysql://%s:%d/%s?characterEncoding=%s", ip, port, database, encoding);
return DriverManager.getConnection(url, loginName, password);
}
public static void main(String[] args) throws SQLException {
Connection c = getConnection();
System.out.println(c);
}
}
但是数据库相关信息写死在代码里,下面通过注解方式配置数据库信息。
1、自定义注解JDBCConfig
@Target({METHOD,TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface JDBCConfig {
String ip();
int port() default 3306;
String database();
String encoding();
String loginName();
String password();
}
2、注解方式DBUtil
数据库相关配置信息本来是以属性的方式存放的,现在改为了以注解的方式,提供这些信息了。
@JDBCConfig(ip = "127.0.0.1", database = "test", encoding = "UTF-8", loginName = "root", password = "admin")
public class DBUtil {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
3、解析注解
通过反射,获取这个DBUtil这个类上的注解对象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
@JDBCConfig(ip = "127.0.0.1", database = "test", encoding = "UTF-8", loginName = "root", password = "admin")
public class DBUtil {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException, NoSuchMethodException, SecurityException {
/* 通过反射,获取这个DBUtil这个类上的注解对象 */
JDBCConfig config = DBUtil.class.getAnnotation(JDBCConfig.class);
/* 通过注解对象方法,获取各个注解元素的值 */
String ip = config.ip();
int port = config.port();
String database = config.database();
String encoding = config.encoding();
String loginName = config.loginName();
String password = config.password();
String url = String.format("jdbc:mysql://%s:%d/%s?characterEncoding=%s", ip, port, database, encoding);
return DriverManager.getConnection(url, loginName, password);
}
public static void main(String[] args) throws NoSuchMethodException, SecurityException, SQLException {
Connection c = getConnection();
System.out.println(c);
}
}
仿hibernate
解决如下问题:
- 当前类是否实体类?
- 表名称?
- 主键对应哪个属性, 自增长策略是什么,对应字段名称是什么?
- 非主键属性对应字段名称是什么?
1、自定义hibernate注解
实体类注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyEntity {
}
表注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTable {
String name();
}
主键注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyId {
}
自增注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyGeneratedValue {
String strategy();
}
字段注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyColumn {
String value();
}
2、运用在Hero对象
@MyEntity
@MyTable(name="hero_")
public class Hero {
private int id;
private String name;
private int damage;
private int armor;
@MyId
@MyGeneratedValue(strategy = "identity")
@MyColumn("id_")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@MyColumn("name_")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@MyColumn("damage_")
public int getDamage() {
return damage;
}
public void setDamage(int damage) {
this.damage = damage;
}
@MyColumn("armor_")
public int getArmor() {
return armor;
}
public void setArmor(int armor) {
this.armor = armor;
}
}
3、创建一个解析类ParseHibernateAnnotation ,获取Hero类上配置的注解信息
思路如下:
- 首先获取Hero.class类对象。Hero.class
- 判断本类是否进行了MyEntity注解。getAnnotation(MyEntity.class)
- 获取注解 MyTable。getAnnotation(MyTable.class)
- 遍历所有的方法,如果某个方法有MyId注解,那么就记录为主键方法primaryKeyMethod。getAnnotation(MyId.class)
- 把主键方法的自增长策略注解MyGeneratedValue和对应的字段注解MyColumn 取出来。getAnnotation(MyGeneratedValue.class)、getAnnotation(MyColumn.class)
- 遍历所有非主键方法,并且有MyColumn注解的方法,打印属性名称和字段名称的对应关系。getAnnotation(MyColumn.class)
public class ParseHibernateAnnotation {
public static void main(String[] args) {
/* 获取Hero.class类对象 */
Class<Hero> clazz = Hero.class;
/* 判断本类是否进行了MyEntity注解 */
MyEntity myEntity = (MyEntity) clazz.getAnnotation(MyEntity.class);
if (null == myEntity) {
System.out.println("Hero类不是实体类");
} else {
System.out.println("Hero类是实体类");
/* 获取注解 MyTable */
MyTable myTable= (MyTable) clazz.getAnnotation(MyTable.class);
String tableName = myTable.name();
System.out.println("其对应的表名是:" + tableName);
/* 遍历所有的方法 */
Method[] methods =clazz.getMethods();
Method primaryKeyMethod = null;
for (Method m: methods) {
/* 判断是否有MyId注解 */
MyId myId = m.getAnnotation(MyId.class);
if(null!=myId){
/* 获取主键方法 */
primaryKeyMethod = m;
break;
}
}
if(null!=primaryKeyMethod){
System.out.println("找到主键:" + method2attribute( primaryKeyMethod.getName() ));
/* 获取主键自增属性 */
MyGeneratedValue myGeneratedValue = primaryKeyMethod.getAnnotation(MyGeneratedValue.class);
System.out.println("其自增长策略是:" +myGeneratedValue.strategy());
/* 获取主键字段名 */
MyColumn myColumn = primaryKeyMethod.getAnnotation(MyColumn.class);
System.out.println("对应数据库中的字段是:" +myColumn.value());
}
System.out.println("其他非主键属性分别对应的数据库字段如下:");
/* 遍历所有的方法,获取非主键方法*/
for (Method m: methods) {
if(m==primaryKeyMethod){
continue;
}
MyColumn myColumn = m.getAnnotation(MyColumn.class);
if(null==myColumn)
continue;
System.out.format("属性: %s\t对应的数据库字段是:%s%n",method2attribute(m.getName()),myColumn.value());
}
}
}
private static String method2attribute(String methodName) {
String result = methodName; ;
result = result.replaceFirst("get", "");
result = result.replaceFirst("is", "");
if(result.length()<=1){
return result.toLowerCase();
}
else{
return result.substring(0,1).toLowerCase() + result.substring(1,result.length());
}
}
}
目前,各种 Java 框架,如 Spring,Hibernate,Junit等均大量使用自定义的注解,用于登陆、权限拦截、日志处理等,内部都是通过运行时靠反射获取注解。