Java基础知识之注解、元注解
注解
java注解也称java标注,是jdk1.5(5.0)后的新特征。java语言中的类、方法、变量、参数和包等都可以被标注。和javadoc不同,java注解可以通过反射获取标注内容,在编译器生成类文件时,标注可以被嵌入到字节码中,java虚拟机可以保留标注内容,在运行时可以获取到标注内容,当然它也支持自定义java标注
功能:用于说明程序
用途:一般用在框架中使用
格式:@annotationname
文档注释:
@param @return @exception从根本上是一个注释,不存在代码编译,不会生成对应的文档
注解:
@override并不是没有编译就有效果,是因为不管是eclipse还是idea都可以预编译java代码生成对应的.class文件的
注解作用
生成文档
代码中生成对应的javadoc api文档
@param @return
【idea javadoc工具使用参数】
other command line arguments:-encoding utf-8 -charset utf-8
解决中文乱码,因为idea默认编码集为utf-8 windows默认编码集为 gbk
代码检查
继承重写,或者说接口遵从之后的实现中,存在@override
代码数据获取:【小型框架】
通过反射获取指定注解中的一些内容,例如:配置,数据,操作,验证等
java预定义的注解
@override
重写/实现方法的情况下,检查方法声明是否和父类或者接口中的方法声明一致,强制格式检查
@deprecated
标记当前方法已过时
@suppresswarnings(“all”)
压制警告,可以用于一些代码中存在明确无异常的情况下,压制一些警告
annotation注解属性【难点】
属性
开发实际使用注解的方式中,数据使用方式更加偏向于属性概念
使用
- 书写代码中使用
@myannotation(id=1, name=“ocean”, age=16) - 使用反射时,会涉及到getxxx方法
通过属性名获取对应值的概念来完成的
实际上是利用abstract方法来完成属性概念的
属性使用的格式【实际按照方法格式操作】
- 属性的值数据类型和对应的具体数据 => 返回值类型和返回的数据属性类型支持:
- 基本数据类型
- string类型
- 其他数据类型
- enmu枚举类型,一个带有名字的常量,为了更好的阅读性和操作
- 以上类型对应的数组
- 属性值要求
- 定义属性时可以使用default关键字,加上默认值,该属性在使用的过程中是没有强制要求属性值,如果没有赋予属性值,采用对应的默认值操作,如果赋值,使用对应值
- 如果注解中有且只有一个value属性,或者说注解中除value属性之外,都有默认值,不管是类,方法,成员变量,包使用当前注解是可以直接在括号内加入
对应数据类型数值- 如果属性是数组类型, {}大括号保存,并且不同的内容,使用,隔开属性的键名字 ==> 方法的名字
自定义注解
格式: public @interface annotationname { 属性列表; } annotation注解是可以编译得到对应的.class字节码文件,验证了注解是可以参与编译过程的 通过反编译工具可以得到一下内容 【annotation本质】 public interface myannotation1 extends java.lang.annotation.annotation { } myannotation1 本质是一个interface,同时java.lang.annotation.annotation 子接口
package cn.ocean888.a_annotation.myannotation; /** * 自定义注解!!! * public interface myannotation1 extends java.lang.annotation.annotation { * } * * @author anonymous * @date 2020/3/10 11:01 */ public @interface myannotation1 { // 属性 ==> 方法形式 }
元注解
基于注解的解释,用来约束注解的的一些操作问题
@retention
表示这个注解的保存方式,是只在代码中,还是编入class文件中,或者是运行时可以通过反射访问
retentionpolicy.runtime:当前注解会编译生成对应的.class字节码文件,并且可以加载到jvm中,参与代码执行
retentionpolicy.class
retentionpolicy.source:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
@document
标记这些注解是否包含在用户文档中
是否可以通过javadoc工具,生成对应的api文档
@target
标记这个注解应该是那种java成员
属性:elementtype
type:当前注解可以用于类声明
method:当前注解可以用于方法声明位置
field:当前注解可以用于成员变量声明位置
@inherited
标记这个注解是继承于那个注解类(默认 注解不继承于任何子类)
获取类上的注解
java获取类上的注解有下面3个方法:
- class.getannotations() 获取所有的注解,包括自己声明的以及继承的
- class.getannotation(class< a > annotationclass) 获取指定的注解,该注解可以是自己声明的,也可以是继承的
- class.getdeclaredannotations() 获取自己声明的注解、
java.lang.class类的isannotation()方法用于检查此class是否为annotation类型
java.lang.class类的isannotationpresent()方法用于检查此类中是否存在指定注释类型的注释
实例:
实例1:两种属性文件的加载的方式
1.properties
classname = cn.ocean888.a_annotation.person id = 1 name = "ocean"
person类
package cn.ocean888.a_annotation; public class person { integer id; string name; public person() { } public person(integer id, string name) { this.id = id; this.name = name; } public integer getid() { return id; } public void setid(integer id) { this.id = id; } public string getname() { return name; } public void setname(string name) { this.name = name; } @override public string tostring() { return "person{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
第一种使用反射来完成
package cn.ocean888.a_annotation; import java.io.fileinputstream; import java.io.ioexception; import java.lang.reflect.field; import java.lang.reflect.invocationtargetexception; import java.util.properties; public class demo1 { public static void main(string[] args) throws classnotfoundexception, nosuchmethodexception, invocationtargetexception, instantiationexception, illegalaccessexception, ioexception, nosuchfieldexception { properties properties = new properties(); properties.load(new fileinputstream("./src/1.properties")); string classname = properties.getproperty("classname"); string id = properties.getproperty("id"); string name = properties.getproperty("name"); system.out.println(classname); /* 使用反射的方法 */ class<?> aclass = class.forname(classname); person person = (person) aclass.getconstructor().newinstance(); system.out.println(person); field declaredfield = aclass.getdeclaredfield("id"); declaredfield.setaccessible(true); declaredfield.set(person, integer.parseint(id)); field declaredfield2 = aclass.getdeclaredfield("name"); declaredfield2.setaccessible(true); declaredfield2.set(person, name); system.out.println(person); } }
第二种使用反射加注解的方式来完成
自定义注解
package cn.ocean888.a_annotation; import java.lang.annotation.elementtype; import java.lang.annotation.retention; import java.lang.annotation.retentionpolicy; import java.lang.annotation.target; // 声明当前注解有且只能用于类名之上 @target(elementtype.type) // 当前注解参与代码运行 @retention(retentionpolicy.runtime) @interface myannotaion1 { // 属性 string classname(); int id(); string name(); }
reflectannotation.java
package cn.ocean888.a_annotation; import java.lang.annotation.annotation; @myannotaion1(classname = "cn.ocean888.a_annotation.person", id = 2, name = "ocean") public class reflectannotation { public static void main(string[] args) { // 加载reflectannotation class<reflectannotation> cls = reflectannotation.class; // 因为注解再类名之上,通过class获取对应的annotation myannotaion1 annotation = cls.getannotation(myannotaion1.class); string s = annotation.classname(); int id = annotation.id(); string name = annotation.name(); system.out.println(s); system.out.println(id); system.out.println(name); } }
实例2使用注解测试代码运行
对tools方法中带有@check注解标记的方法进行检查
tools.java
package cn.ocean888.a_annotation_checkmethod; import java.util.arraylist; /** * 需要测试的方法 */ public class tools { @check public void test1() { string str = null; system.out.println(str.tostring()); } @check public void test2() { int[] arr = null; system.out.println(arr[5]); } @check public void test3() { int[] arr = {1,2,3,4,5}; system.out.println(arr[3]); } @check public void test4() { arraylist<integer> integers = new arraylist<>(); system.out.println(integers.get(20).tostring()); } @check public void test5() { throw new nullpointerexception("nullpointexception"); } }
utils.java
package cn.ocean888.a_annotation_checkmethod; public class utils { @check public void test() { throw new indexoutofboundsexception("下标越界"); } }
check.java
package cn.ocean888.a_annotation_checkmethod; import java.lang.annotation.elementtype; import java.lang.annotation.retention; import java.lang.annotation.retentionpolicy; import java.lang.annotation.target; /** * 该注解没有任何属性,只是作为是否需要测试的标记 */ @target(elementtype.method) @retention(retentionpolicy.runtime) public @interface check { }
classannotation.java
package cn.ocean888.a_annotation_checkmethod; 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 classannotation { string classname(); }
testproject.java
package cn.ocean888.a_annotation_checkmethod; import java.io.bufferedwriter; import java.io.filewriter; import java.io.ioexception; import java.lang.reflect.invocationtargetexception; import java.lang.reflect.method; /** * 测试tools类内的方法,如果方法带有check注解,执行测试并记录异常 */ @classannotation(classname = "cn.ocean888.a_annotation_checkmethod.tools") public class testproject { public static void main(string[] args) throws classnotfoundexception, nosuchmethodexception, invocationtargetexception, instantiationexception, illegalaccessexception, ioexception { // 从注解中获取对应的属性 class<testproject> testprojectclass = testproject.class; // 获取所有指定的注解 classannotation annotation = testprojectclass.getannotation(classannotation.class); string s = annotation.classname(); // s = cn.ocean888.a_annotation_checkmethod.utils class<?> aclass = class.forname(s); object tools = aclass.getconstructor().newinstance(); // 获取所有tools类内的方法,不包括父类方法 method[] declaredmethods = aclass.getdeclaredmethods(); // 记录错误出现次数 int count = 0; long l = system.currenttimemillis(); bufferedwriter bufferedwriter = new bufferedwriter(new filewriter("./src/log.txt")); // 遍历方法数组 for (method declaredmethod : declaredmethods) { declaredmethod.setaccessible(true); // 判断当前方法是否带有注解@check标记 if (declaredmethod.isannotationpresent(check.class)) { try { declaredmethod.invoke(tools); } catch (exception e) { count += 1; // 1.哪一个方法出现异常 bufferedwriter.write("方法:" + declaredmethod.getname()); bufferedwriter.newline(); // 2.发生异常原因,获取对应的类型 bufferedwriter.write("异常类型:" + e.getcause().getclass().getsimplename()); bufferedwriter.newline(); // 3.异常信息 bufferedwriter.write("异常信息:" + e.getcause().getmessage()); bufferedwriter.newline(); } } } long l1 = system.currenttimemillis(); bufferedwriter.write("出现错误的次数" + count); bufferedwriter.newline(); bufferedwriter.write("总耗时" + (l1 - l)); bufferedwriter.close(); } }
代码的灵活性在于可以对classname直接进行替换
注解使用总结
- 注解在大多数情况下,都是使用过程,而不是自定义,会使用到框架中预处理好的注解
- 注解使用对象
- 编译器
- 解析代码
- jvm运行代码使用
- 注解是一个标签,有时候是做标记,有时候标记有属性
注解和python装饰器的区别
先说java的注解(annotation),实际上是给语法元素打一个标记。比如你可以给一个函数打一个标记,给一个类打一个标记等等。java只保证记录这个标记,但是不会主动根据这给标记做任何事。
比如,你在spring里,给一个私有成员打 @autowired 这个标记。
public class xxxservice { @autowired private xxxxrepository xxxxrepository; // ... }
如果你不用spring框架的话,不会有任何事情发生,直接访问这个字段就是空。当如果你配置了合适的处理流程,而这个流程就会根据有没有这个标记干活。比如你要求spring “auto scan” 并且注入依赖,这个处理过程会用反射去读哪些元素被做了某个特定标记。没有标记就不理,有标记就注入。
python里的decorator是一个语法糖,是希望把“decorator”这个形式写得更漂亮。比如,你想记录一个函数开始执行之前和之后的log:
def foo(): print("hello") def logit(fn): def inner(): print("before execute") fn() printf("after execute") return inner
这时,你可以魔改以下foo的实现,用logit这个“装饰器”来部分修改foo的行为,然后执行:
foo = logit(foo) foo()
但python里的语法可以让这个东西写成:
@logit def foo(): print("hello") foo()
也就是说,python这里的装饰器是一个有逻辑的,可以执行的函数,只不过其写法有些特殊要求;而java里面的annotation只是个标记,需要其他代码来“根据标记执行“。
当然,装饰器模式是个很通用的东西,无论是python,java还是其他语言都可以写。只是python提供了特殊的语法糖而已。但java世界里做类似decorator的事情,希望动态魔改一个函数的行为,可以用动态代理或者aop。
java的annotation因为相当于多加了一层(标记 + 处理逻辑),是一把双刃剑。好处是,在不动代码的情况下你可以通过外部配置来修改程序的行为。比如给一个函数打上@test标。如果通过ut框架运行,这些打标的函数会被当作是测试用例;但如果外部直接用普通的main启动,这些@test就会没有一样,不会影响代码本身的逻辑。但反过来,也容易引来一些问题。比如有的时候,你很难知道那个根据标记执行的逻辑是不是真的跑了。也许你哪里配置拼错一个字,或者classpath少依赖一个包,就造成那个逻辑并没有真的执行。这时从表面上也许很难看出来出错了。
总结
到此这篇关于java基础知识之注解、元注解的文章就介绍到这了,更多相关java注解、元注解内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!