注解与注释
注解(Annotation)是给计算机看的,可以被计算机读取;注释(Comet)是给人看的,注解和注释有一定的相似性,但没什么太大的关系。
注解可以在package, class, method, field上使用,**注解的本质则是通过反射机制实现对这些元数据的访问。注解和反射是所有Java框架的底层。**在Java的诸多框架如SSM、SpringBoot中注解被广泛使用,所以务必深入理解。
内置注解
即Java语言自带的注解
@Override | 方法重写 |
@Deprecated | 表明该元素已弃用,不推荐使用 |
@SuppresWarnings | 抑制(关闭)编译时的警告信息 |
元注解
元注解的作用是负责注解说明其他注解,Java定义了4个标准的元注解(meta-annotation)类型:
- ⭐️
@Target
:描述注解的使用范围(类、方法、构造器…) - ⭐️
@Retention
:指明该注解在哪个级别有用(RUNTIME > CLASS > SOURCE),用于描述注解的生命周期。
⚠️默认都用RUNTIME,即在运行时注解有效 @Documented
:是否生成javadoc文档注释@Inherited
:说明子类可以继承父类中的注解
⭐️@AliasFor注解
@AliasFor
用于为注解属性声明别名。
@AliasFor的使用形式
1. 注解内部的显性别名
一个注解中,把@AliasFor声明在一对属性上,标明二者互为别名。
public @interface ContextConfiguration {
//value和locations互为
@AliasFor("locations")
String[] value() default {};
@AliasFor("value")
String[] locations() default {};
}
⚠️使用要求:
- 互为别名两属性前都要加@AliasFor,且AliasFor中的值必须为另一别名的属性名。
- 返回类型相同。
- 必须声明默认值且默认值必须相同。
❗️2. 用于其他注解属性的显性别名
如果@AliasFor
属性指向的是它所在注解之外的其他注解(其他注解类),那么这个属性被解释成其他注解属性的别名。(称之为显性的元注解属性重写)
通过重写来继承一个或多个其他注解的功能,可以更精准地控制重写继承哪些注解哪些属性,而不像Java中类的继承那样必须继承父类的所有功能。
使用@AliasFor
还可以为其他注解的value属性声明别名。如下所示:
@ContextConfiguration
public @interface MyTestConfig {
//第一个属性指明指向哪个注解,第二个属性指明指向哪个属性
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] xmlFiles();
}
@MyTestConfig
下的xmlFiles
指向的一个其他注解@ContextConfiguration
的属性locations
。
⚠️使用要求:
- 别名化的属性必须声明相同的返回结果
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
都重写了@ContextConfiguration
中locations
属性的,三个属性是彼此的隐性别名。
⭐️@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
注解中的value
和path
互为别名。如下所示:
@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 {};
...
}
❗️这么做的目的在于:
- 更便捷,只定义了一个属性时可以省略属性名,如:
@RequestMapping(“/user”)
- 顾名思义,使变量命名更加贴合特定场景。如:
@RequestMapping(path = “/user”,method = RequestMethod.GET)
显然强于@RequestMapping(value = “/user”,method = RequestMethod.GET)
更加好理解。
自定义注解
使用@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(); //数据长度
}