注解与注释

注解(Annotation)是给计算机看的,可以被计算机读取;注释(Comet)是给人看的,注解和注释有一定的相似性,但没什么太大的关系。

注解可以在package, class, method, field上使用,**注解的本质则是通过反射机制实现对这些元数据的访问。注解和反射是所有Java框架的底层。**在Java的诸多框架如SSM、SpringBoot中注解被广泛使用,所以务必深入理解。

内置注解

即Java语言自带的注解

@Override

方法重写

@Deprecated

表明该元素已弃用,不推荐使用

@SuppresWarnings

抑制(关闭)编译时的警告信息

元注解

元注解的作用是负责注解说明其他注解,Java定义了4个标准的元注解(meta-annotation)类型:

  1. ⭐️@Target:描述注解的使用范围(类、方法、构造器…)
  2. ⭐️@Retention:指明该注解在哪个级别有用(RUNTIME > CLASS > SOURCE),用于描述注解的生命周期。
    ⚠️默认都用RUNTIME,即在运行时注解有效
  3. @Documented:是否生成javadoc文档注释
  4. @Inherited:说明子类可以继承父类中的注解

⭐️@AliasFor注解

@AliasFor用于为注解属性声明别名。

@AliasFor的使用形式

1. 注解内部的显性别名

一个注解中,把@AliasFor声明在一对属性上,标明二者互为别名。

public @interface ContextConfiguration {
    //value和locations互为
    @AliasFor("locations")
    String[] value() default {};

    @AliasFor("value")
    String[] locations() default {};

}

⚠️使用要求:

  1. 互为别名两属性前都要加@AliasFor,且AliasFor中的值必须为另一别名的属性名。
  2. 返回类型相同。
  3. 必须声明默认值且默认值必须相同。

❗️2. 用于其他注解属性的显性别名

如果@AliasFor属性指向的是它所在注解之外的其他注解(其他注解类),那么这个属性被解释成其他注解属性的别名。(称之为显性的元注解属性重写)
通过重写来继承一个或多个其他注解的功能,可以更精准地控制重写继承哪些注解哪些属性,而不像Java中类的继承那样必须继承父类的所有功能。

使用@AliasFor还可以为其他注解的value属性声明别名。如下所示:

@ContextConfiguration
public @interface MyTestConfig {
    //第一个属性指明指向哪个注解,第二个属性指明指向哪个属性
    @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
    String[] xmlFiles();
}

@MyTestConfig下的xmlFiles指向的一个其他注解@ContextConfiguration的属性locations

⚠️使用要求:

  1. 别名化的属性必须声明相同的返回结果

3. 注解中的隐形别名

如果注解中的多个属性声明为同一个其他注解属性的属性重写,那么这些注解会被当作彼此的隐性别名集来对待,效果类似于注解中的显性别名

@ContextConfiguration
public @interface MyTestConfig {
    @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
    String[] value() default {};

    @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
    String[] groovyScripts() default {};

    @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
    String[] xmlFiles() default {};
}

上述注解中,value, groovyScripts, xmlFiles都重写了@ContextConfigurationlocations属性的,三个属性是彼此的隐性别名。

⭐️@AliaFor的使用场景

1. 复合注解

复合注解即将多个元注解属性组合成新注解,在当前注解中使用@AliaFor继承重写被组合注解中的属性。例如SpringBoot中主程序前的@SpringBootApplication。这样做显然更加便捷,只需要@SpringBootApplication一个注解就能开启自动配置,自动扫描来替代@Configuration、@ComponentSan、@EnnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM,
                classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
    
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

2. 继承注解的功能

@Controller,@Service,@Repository都继承了@Component的功能,而@Component又只有一个属性,这样就可以再@AliaFor属性中只写出注解省去属性名,完全继承了该注解。
@Service代码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

    @AliasFor(annotation = Component.class)
    String value() default "";

}

3. 定义别名

在一个注解中为同一个功能定义两个名称不一样的属性,那么这两个属性彼此互为别名,值相同。如@RequestMapping注解中的valuepath互为别名。如下所示:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";
    //path和value互为别名
    @AliasFor("path")
    String[] value() default {};
    
    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};
	...
}

❗️这么做的目的在于:

  1. 更便捷,只定义了一个属性时可以省略属性名,如:@RequestMapping(“/user”)
  2. 顾名思义,使变量命名更加贴合特定场景。如:@RequestMapping(path = “/user”,method = RequestMethod.GET)显然强于@RequestMapping(value = “/user”,method = RequestMethod.GET)更加好理解。

参考资料:@AliasFor注解

自定义注解

使用@Interface自定义注解时,自动继承了java.lang.annotation.Annotation接口。

注解中的每一个方法实际上是声明了一个配置参数,方法的名称即参数名称。

返回值类型就是参数的类型,可以通过default来声明参数的默认值。

如果只有一个参数成员,一般参数名为value(可以省去)。

注解元素必须要有值。

//测试元注解
public class Test02 {
    @MyAnnotation(name = "zhangsan", schools = {"THU","PKU"} )
    public void test1(){ }
 
    @OneParamAnnotation("test") //只有一个名为value的参数可以省略
    public void test2(){ }
}
 
//定义一个注解
@Target(value = {ElementType.METHOD, ElementType.TYPE} ) //可以在方法、类上用
@Retention(value = RetentionPolicy.RUNTIME) //表示在运行时有效
@Documented //生成Javadoc注释
@Inherited //子类可以继承父类的注解
@interface MyAnnotation{
    //注解的参数:参数类型+参数名()
    String name() default ""; //默认值空
    int age() default 0;
    int id() default -1; //默认值为-1代表不存在
 
    String[] schools();
}
//定义一个只有一个参数的注解
@interface OneParamAnnotation{
    String value();
}

反射操作注解实例

联系实现ORM(对象关系映射),利用注解和反射完成类和表结构的映射关系。可以看到我们注解时配置的属性通过反射可以非常方便的拿到,框架本身也正是这样才能大量使用注解,方便我们开发。

public class Test12 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("annotation.Student");
        //通过反射获得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //获得注解的value值
        Table table = (Table) c1.getAnnotation(Table.class);
        String value = table.value();
        System.out.println(value);
        //指定类中变量的名称
        java.lang.reflect.Field field = c1.getDeclaredField("name");
        Field annotation = field.getAnnotation(Field.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}
 
@Table("db_student")
class Student {
    @Field(columnName = "db_id", type = "int", length = 10)
    private int id;
    @Field(columnName = "db_age", type = "int", length = 10)
    private int age;
    @Field(columnName = "db_name", type = "varchar", length = 256)
    private String name;
}
 
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
    String value();
}
 
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field{
    String columnName(); //列名
    String type();       //数据类型
    int length();        //数据长度
}