Java-注解

定义

注解(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. 当前类是否实体类?
  2. 表名称?
  3. 主键对应哪个属性, 自增长策略是什么,对应字段名称是什么?
  4. 非主键属性对应字段名称是什么?

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类上配置的注解信息
思路如下:

  1. 首先获取Hero.class类对象。Hero.class
  2. 判断本类是否进行了MyEntity注解。getAnnotation(MyEntity.class)
  3. 获取注解 MyTable。getAnnotation(MyTable.class)
  4. 遍历所有的方法,如果某个方法有MyId注解,那么就记录为主键方法primaryKeyMethod。getAnnotation(MyId.class)
  5. 把主键方法的自增长策略注解MyGeneratedValue和对应的字段注解MyColumn 取出来。getAnnotation(MyGeneratedValue.class)、getAnnotation(MyColumn.class)
  6. 遍历所有非主键方法,并且有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等均大量使用自定义的注解,用于登陆、权限拦截、日志处理等,内部都是通过运行时靠反射获取注解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会叫的狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值