java 8中的注解_Java 中的注解

注解的基础知识

元注解:@Retention @Target @Document @Inherited

Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口。

参数成员只能用public或默认(default)这两个访问权修饰

参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组。

要获取类、方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,除此之外没有别的获取注解对象的方法

注解也可以没有定义成员, 不过这样注解就没啥用了,只起到标识作用

JDK的元注解

JDK提供了4种元注解,分别是@Retention、@Target、@Document和@Inherited四种。这4个注解是用来修饰我们自定义的其他注解的,因此称为元注解。

1. @Retention

定义注解的保留策略。首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如

@Override 和@SuppressWarnings,则可选用 SOURCE 注解。

@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含

@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,

@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

2. @Target

定义注解的作用目标。也就是这个注解能加在类的哪些元素上。

@Target(ElementType.TYPE) //接口、类、枚举、注解

@Target(ElementType.FIELD) //字段、枚举的常量

@Target(ElementType.METHOD) //方法

@Target(ElementType.PARAMETER) //方法参数

@Target(ElementType.CONSTRUCTOR) //构造函数

@Target(ElementType.LOCAL_VARIABLE)//局部变量

@Target(ElementType.ANNOTATION_TYPE)//注解

@Target(ElementType.PACKAGE) //包

// Java8 中新加了下面两种

`TYPE_PARAMETER`

`TYPE_USE`

3.@Document

说明该注解将被包含在javadoc中

4.@Inherited

说明子类可以继承父类中的该注解。如果一个注解@XX被元注解@Inherited修饰,然后使用@XX修饰了一个类A,那么类A的子类B也可以继承@XX注解。

关于 @Inherited 注解,我举个列子多说两句。

public static void main(String[] args) {

B b = new B();

SpringBootApplication annotation = b.getClass().getAnnotation(SpringBootApplication.class);

String[] excludeNames = annotation.excludeName();

System.out.println("excludeNames" + Arrays.toString(excludeNames));

String[] basePackages = annotation.scanBasePackages();

System.out.println("basePackages" + Arrays.toString(basePackages));

}

@SpringBootApplication(excludeName = {"class1"}, scanBasePackages = {"com"})

public static class A {

}

public static class B extends A {

}

上面的 @SpringBootApplication 注解是用 @Inherited 标注的,所以子类 B 可以继承这个注解,上面的 main 方法会输出:

excludeNames[class1]

basePackages[com]

但是一旦,子类B也标注 @SpringBootApplication 注解的话,就不再继承父类的 @SpringBootApplication。

@SpringBootApplication(excludeName = {"class2"})

public static class B extends A {}

输出:

excludeNames[class2]

basePackages[]

自定义注解

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

public @interface Description {

String[] name();

String desc();

String author() default "JTZeng";

int age() default 21;

}

想要处理这些注解信息,我们必须使用反射技术获取这些注解然后再做相应的处理。

下面举个列子:使用反射获取当前包下面所有标注了Description的类信息。

@Description(desc = "Java lover", author = "csx")

public class CSX {

}

@Description(desc = "PHP lover", author = "zr")

public class ZR {

}

上面定义了两个类,分别用Description标注。

下面的代码首先获取了当前类所在的包名,然后将这个包下面的Class遍历了一遍。通过反射将标注有Description注解的类信息打印了出来。

public class Demo {

public static void main(String[] args) {

Class demoClass = Demo.class;

String name = demoClass.getPackage().getName();

List> classes = getClasses(name);

for (Class> aClass : classes) {

Description annotation = aClass.getAnnotation(Description.class);

if(annotation!=null){

System.out.println(annotation.author()+":"+annotation.desc());

}

}

System.out.println("end...");

}

public static List> getClasses(String packageName){

//第一个class类的集合

List> classes = new ArrayList>();

//是否循环迭代

boolean recursive = true;

//获取包的名字 并进行替换

String packageDirName = packageName.replace('.', '/');

//定义一个枚举的集合 并进行循环来处理这个目录下的things

Enumeration dirs;

try {

dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);

//循环迭代下去

while (dirs.hasMoreElements()){

//获取下一个元素

URL url = dirs.nextElement();

//得到协议的名称

String protocol = url.getProtocol();

//如果是以文件的形式保存在服务器上

if ("file".equals(protocol)) {

//获取包的物理路径

String filePath = URLDecoder.decode(url.getFile(), "UTF-8");

//以文件的方式扫描整个包下的文件 并添加到集合中

findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);

} else if ("jar".equals(protocol)){

//如果是jar包文件

//定义一个JarFile

JarFile jar;

try {

//获取jar

jar = ((JarURLConnection) url.openConnection()).getJarFile();

//从此jar包 得到一个枚举类

Enumeration entries = jar.entries();

//同样的进行循环迭代

while (entries.hasMoreElements()) {

//获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件

JarEntry entry = entries.nextElement();

String name = entry.getName();

//如果是以/开头的

if (name.charAt(0) == '/') {

//获取后面的字符串

name = name.substring(1);

}

//如果前半部分和定义的包名相同

if (name.startsWith(packageDirName)) {

int idx = name.lastIndexOf('/');

//如果以"/"结尾 是一个包

if (idx != -1) {

//获取包名 把"/"替换成"."

packageName = name.substring(0, idx).replace('/', '.');

}

//如果可以迭代下去 并且是一个包

if ((idx != -1) || recursive){

//如果是一个.class文件 而且不是目录

if (name.endsWith(".class") && !entry.isDirectory()) {

//去掉后面的".class" 获取真正的类名

String className = name.substring(packageName.length() + 1, name.length() - 6);

try {

//添加到classes

classes.add(Class.forName(packageName + '.' + className));

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}

}

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

} catch (IOException e) {

e.printStackTrace();

}

return classes;

}

public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List> classes){

//获取此包的目录 建立一个File

File dir = new File(packagePath);

//如果不存在或者 也不是目录就直接返回

if (!dir.exists() || !dir.isDirectory()) {

return;

}

//如果存在 就获取包下的所有文件 包括目录

File[] dirfiles = dir.listFiles(new FileFilter() {

//自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)

public boolean accept(File file) {

return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));

}

});

//循环所有文件

for (File file : dirfiles) {

//如果是目录 则继续扫描

if (file.isDirectory()) {

findAndAddClassesInPackageByFile(packageName + "." + file.getName(),

file.getAbsolutePath(),

recursive,

classes);

}

else {

//如果是java类文件 去掉后面的.class 只留下类名

String className = file.getName().substring(0, file.getName().length() - 6);

try {

//添加到集合中去

classes.add(Class.forName(packageName + '.' + className));

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}

}

}

输出如下:

csx:Java lover

zr:PHP lover

end...

JDK8可重复注解

重复注解:即允许在同一申明类型(类,属性,或方法)前多次使用同一个类型注解。

在java8 以前,同一个程序元素前最多只能有一个相同类型的注解;如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。

public @interface Authority {

String role();

}

public @interface Authorities { //@Authorities注解作为可以存储多个@Authority注解的容器

Authority[] value();

}

public class RepeatAnnotationUseOldVersion {

@Authorities({@Authority(role="Admin"), @Authority(role="Manager")})

public void doSomeThing(){

}

}

java8 新增了重复注解,其使用方式为:

//这边还是需要定义注解容器

@Repeatable(Authorities.class)

public @interface Authority {

String role();

}

public @interface Authorities {

Authority[] value();

}

public class RepeatAnnotationUseNewVersion {

@Authority(role="Admin")

@Authority(role="Manager")

public void doSomeThing(){ }

}

不同的地方是,创建重复注解 Authority 时,加上@Repeatable,指向存储注解 Authorities,在使用时候,直接可以重复使用 Authority 注解。从上面例子看出,java 8里面做法更适合常规的思维,可读性强一点。但是,仍然需要定义容器注解。

两种方法获得的效果相同。重复注解只是一种简化写法,这种简化写法是一种假象:多个重复注解其实会被作为“容器”注解的 value 成员的数组元素处理。(一种语法糖而已,Java中类似的语法还有很多。具体内容可以参考博客Java中的语法糖)

参考

公众号推荐

欢迎大家关注我的微信公众号「程序员自由之路」

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值