天天看点

关于Java注解的那些事

学习Java的注解有几个好处:首先是能够读懂别人的代码,特别是框架相关的代码;其次是能够让编程更加简洁、代码更清晰;最后让代码看起来高大上(高逼格^^);

1.常见注解与分类

即使你对Java注解有多么不熟悉,下面Java自带的注解几个你也一定遇到过:

  • @Override:覆盖父类方法
  • @Deprecated:表示该方法已过时,不建议使用,只是为了前向兼容
  • @SuppressWarning:忽略警告

如果是经常用框架类的同学,那么对第三方注解应该也是很熟悉的:

  • 比如Spring: @Autowired、@Service、@Repository
  • 比如Mybatis: @InsertProvider、@UpdateProvider、@Options

Java注解通常可以有不同维度分类,比如基于注解生效的时间进行分类,或者基于注解来源进行分类(Java自带注解、第三方注解等)。如果基于注解生效的时间对注解进行分类,可以分为下面三类:

  • 源码注解:注解只在源码中存在,编译成.class文件后就不存在,比如@Override、@Deprecated等。
  • 编译时注解:注解在源码和.class文件中都存在,
  • 运行时注解:通常采用反射机制来实现注解处理器,在程序运行阶段起作用,能够影响代码运行逻辑的注解,比如:@Autowired、@Service等。

2.自定义注解

自定义Java注解时需要遵循一定的语法要求:

  • 使用@interface关键字定义注解
  • 成员以无参无异常的方式说明

注意:

  • 可以用default为成员制定一个默认值
  • 成员类型是受限的,合法的类型包括原始类型、String、Class等
  • 如果注解只有一个成员,则成员名必须取名为value(),在使用时可以忽略成员名和赋值号(=)
  • 注解类可以没有成员,没有成员的注解称为标识注解

自定义注解时涉及到的元注解(解释注解的注解)

  • @Target:声明该注解的作用域,包括method、field、package、type(类或者接口)。
  • @Retention:声明注解的运行生命周期,包括runtime(运行时注解)、source(源码注解)、class(编译注解)
  • @Inherited:声明子类是否能继承使用了该注解的父类的注解,该元注解只对普通类有效,对于接口继承无效。
  • @Documented:生成javadoc时会包含注解的信息

使用注解的语法:@<注解名>(<成员名1>=<成员值1>,<成员名2>=<成员值2>)

3.自定义运行时注解

运行时注解时通过反射获取类、方法或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。

自定义注解类:

package com.yongfxu.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
//@Retention(RetentionPolicy.SOURCE)
//@Retention(RetentionPolicy.CLASS)
@Inherited // 是否允许子类继承使用了该注解的父类的注解,对接口继承无效,对普通类继承有效
@Documented // 是否生成javadoc
public @interface Description {
    
    String desc();
    
    String author();
    
    int age() default 18;
}
           

定义一个接口,声明要实现的方法:

package com.yongfxu.annotation;

public interface Person {
    
    String name();
    
    String sing();
    
    int age();
}
           

声明一个实现类,实现上述Person接口的方法,并增加注解:

package com.yongfxu.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

@Description(desc = "descClass", author = "authorClass")
public class Child implements Person {
    @Override
    @Description(desc = "descMethodName", author = "authorMethodName")
    public String name() {
        return "hahaha";
    }
    
    @Override
    @Description(desc = "descMethodSing", author = "authorMethodSing")
    public String sing() {
        return null;
    }
    
    @Override
    @Description(desc = "descMethodAge", author = "authorMethodAge")
    public int age() {
        return 0;
    }
    
    public static void main(String[] args) {
        /*
         * 使用类加载器的反射解析注解的方式,该注解必须是@Retention(RetentionPolicy.RUNTIME),
         * 否则执行不出效果,因为类加载就是运行时执行的
         */
        try {
            // 1.使用类加载器加载类
            Class aClass = Class.forName("com.yongfxu.annotation.Child");
            // 2.找到类上的注解
            boolean isExist = aClass.isAnnotationPresent(Description.class);
            if (isExist) {
                // 3.拿到注解实例
                Description description = (Description) aClass.getAnnotation(Description.class);
                System.out.println(description.author());
            }
            
            // 4.找到方法上的注解
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                boolean isMExist = method.isAnnotationPresent(Description.class);
                if (isMExist) {
                    Description description = (Description) method.getAnnotation(Description.class);
                    System.out.println(description.author());
                }
            }
            
            // 5.另外一种解析方法
            for (Method method : methods) {
                Annotation[] as = method.getAnnotations();
                for (Annotation annotation : as) {
                    if (annotation instanceof Description) {
                        Description description = (Description) annotation;
                        System.out.println(description.author());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}           

执行上述类的main方法进行测试,得到下面的结果:

authorClass
authorMethodName
authorMethodSing
authorMethodAge
authorMethodName
authorMethodSing
authorMethodAge           

4.Java注解的应用

下面根据所学的注解,实现一个持久层架构的注解,用于代替Hibernate,核心代码就是通过注解来实现。下面直接上代码。

声明一个Table的注解类,用于标注数据库的表名:

package com.yongfxu.mock;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

声明一个Column的注解类,用于标注待查询的字段名:

package com.yongfxu.mock;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

声明一个基础Bean对象,用于承载数据库获取到的数据。为该类加上上述两注解:

package com.yongfxu.mock;

@Table("user")
public class User {
    
    @Column("user_name")
    private String userName;
    
    @Column("city")
    private  String city;
    
    @Column("profession")
    private String profession;
    
    @Column("age")
    private int age;
    
    public String getUserName() {
        return userName;
    }
    
    public void setUserName(String userName) {
        this.userName = userName;
    }
    
    public String getCity() {
        return city;
    }
    
    public void setCity(String city) {
        this.city = city;
    }
    
    public String getProfession() {
        return profession;
    }
    
    public void setProfession(String profession) {
        this.profession = profession;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
}
           

最后,声明一个Filter类,用于实现注解的解析流程:

package com.yongfxu.mock;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Filter {
    
    public static void main(String[] args) {
        // 根据用户名和年龄生成sql查询语句
        User user1 = new User();
        user1.setUserName("yongfxu");
        user1.setAge(36);
        
        // 根据用户名和其中一个岗位生成sql查询语句
        User user2 = new User();
        user2.setUserName("yongfxu");
        user2.setProfession("programmer, manager, engineer");
        
        System.out.println("sql1: " + query(user1));
        System.out.println("sql2: " + query(user2));
    }
    
    private static String query(User user) {
        // 1.获取对象的class
        Class userClass = user.getClass();
        // 2.验证注解是否存在
        boolean isExist = userClass.isAnnotationPresent(Table.class);
        if (!isExist) {
            return null;
        }
        
        StringBuffer stringBuffer = new StringBuffer("select * from ");
        // 3.获取注解Table中的内容
        Table table = (Table) userClass.getAnnotation(Table.class);
        String tableName = table.value();
        stringBuffer.append(tableName).append(" where 1 = 1");
        
        Field[] fields = userClass.getDeclaredFields();
        // 4.处理每个字段对应的SQL
        for (Field field : fields) {
            // 4.1 拿到字段的名字
            boolean isFieldExist = field.isAnnotationPresent(Column.class);
            if (!isFieldExist) {
                continue;
            }
            Column column = field.getAnnotation(Column.class);
            String columnName = column.value();
            // 4.2 拿到字段的值
            String fieldName = field.getName();
            String methodName = new StringBuffer("get").append(fieldName.substring(0, 1).toUpperCase()).append(fieldName.substring(1)).toString();
            Object fieldValue = null;
            try {
                Method method = userClass.getMethod(methodName);
                fieldValue = method.invoke(user);
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            if (fieldValue == null || (fieldValue instanceof Integer && (Integer) fieldValue == 0)) {
                // 字段值为null或者int为0
                continue;
            }
            
            // 4.3 拼接SQl
            stringBuffer.append(" and ");
            if (fieldValue instanceof Integer) {
                stringBuffer.append(columnName).append(" = ").append(fieldValue);
            } else if (fieldValue instanceof String) {
                if (((String) fieldValue).contains(",")) {
                    String[] strArray = ((String) fieldValue).split(",");
                    stringBuffer.append(columnName).append(" in (");
                    for (String string : strArray) {
                        stringBuffer.append("'").append(string.trim()).append("',");
                    }
                    stringBuffer.deleteCharAt(stringBuffer.length() - 1);
                    stringBuffer.append(")");
                } else {
                    stringBuffer.append(columnName).append(" = ").append("'").append(fieldValue).append("'");
                }
            }
        }
        return stringBuffer.toString();
    }
}
           

至此,已经完成注解的实现。执行Filter类的main函数进行测试,得到下述结论:

sql1: select * from user where 1 = 1 and user_name = 'yongfxu' and age = 36
sql2: select * from user where 1 = 1 and user_name = 'yongfxu' and profession in ('programmer','manager','engineer')

进程已结束,退出代码0