一 注解
在学习和使用 Java 编程语言的过程中会遇到各种各样的注解,带来了很多的便利性,比如:
-
@Override
告诉编译器这个方法是覆盖父类的方法。
-
@WebServlet
表示某个类是一个 Servlet,Web 容器就会识别这个注解,在运行的时候调用它。
-
@Controller
告诉 Spring 框架该类是一个控制器类。
诸如很多此类的注解,可以说如果不能够熟练掌握和使用注解,就不能称之为一个合格的 Java 程序员。
那么为什么简简单单的一个符号加一个单词就可以实现这么多的功能呢?
1 注解的定义
注解(Annotation)从 JDK5 开始引入的新技术。
注解不是程序本身,但是可以对程序做出解释,可以看作是一个类或者方法的一个拓展的模板,可以被其他程序(比如:编译器)读取。
注解可以附加在 package,class,method,field 等上面,相当于给他们添加了额外的辅助信息,我们就可以通过反射机制编程实现对这些元数据的访问。
用通俗易懂的话说就是:注解就是给框架容器解释这个类或者方法的意义。
格式
@注解名
,还可以添加一些参数值。比如:@Controller("/user")
举例
比如正在开发一个 Web 容器(类似于Tomcat),其基本功能就是加载 Servlet,并且管理 Servlet 的生命周期,那么就必须要先知道程序中哪些类是 Servlet 类。
解决方案:程序启动的时候,扫描所有的类,找出哪些类添加了@WebServlet
注解,进行加载。
2 常见的内置注解
注解 | 解释 |
---|---|
@Override | 附加在方法上,表示一个方法声明重写父类中的另一个方法声明。 |
@Deprecated | 可以作用在方法、属性、类,表示不鼓励使用这样的元素,通过是因为它很危险,或者存在更好的选择。但是依旧可以使用。 |
@SuppressWarning | 用来抑制编译时的警告信息。该注释需要添加一个参数才能够正确使用。比如:“all”、“unchecked”…… |
…… | …… |
2 注解和注释的区别
很多人认为注解就是注释,但事实上,注解和注释是完全不同的两个东西,就像 Java 和 JavaScript 一样,仅仅只是看上去类似,其实没有一点关系。注释是对一段程序的描述,给开发者看的,用以帮助阅读,不会影响程序的编译和运行,机器并不会识别注释内容;而注解则是给框架容器看的,确确实实会影响程序的运行。
3 注解的作用范围
不同的注解具有不同的作用范围,就是说不同的注解都用不同的起作用的时间。
-
RUNTIME
在运行时起作用。
@WebServlet
就是在程序运行的时候起作用,帮助程序在运行的时候知道那些类是 Servlet。 -
SOURCE
在编译的时候起作用。
@Override
就是一个给编译器看的注解,编译器在工作的时候识别出了包含@Override
注解的方法,就自动去检查当前类父类的方法中是否有相应的方法,存在则通过,不存在就会报错。
4 注解的结构
注解的一般结构:
@Target(...)
@Retention(...)
public @interface AnnotationName{
// 注解参数1
// 注解参数2
// ...
}
通过@Override
注解的结构来说明:
4.1 元注解
上图中的 @Target
和 @Retention
就是元注解。元注解可以理解为描述注解的注解。Java定义了4个标准的 meta-annotation 类型,它们被用来提供对其他 annotation 类型做说明。
-
@Target
指定注解针对的地方,括号中的参数是一个枚举类型,分别代表不同的意思。
枚举 目标 Element.TYPE 类、接口 Element.FIELD 成员变量 Element.METHOD 成员方法 Element.PARAMETER 方法参数 Element.CONSTRUCTOR 构造方法 Element.ANNOTATION_TYPE 注解 Element.PACKAGE 包 -
@Retention
表示需要在什么级别保存该注释信息,用于描述注解的声明周期,可以理解为标注注解的作用范围。同样通过一个枚举类型作为参数。
枚举 作用域 RetentionPolicy.RUNTIME 由 JVM 读取,运行时使用 RetentionPolicy.SOURCE 源代码级别,由编译器进行处理,处理之后不再保留 RetentionPolicy.CLASS 注解信息保留到类对应的 class 文件中 SOURCE < CLASS < RUNTIME
-
@Document
说明该注解将被包含在 javadoc 中。
-
@Inherited
说明子类可以继承父类中的该注解。
4.2 注解参数
现在来看 @Target
的结构。
其中 ElementTYpe[] value()
并不和一般的类一样表示方法。而是声明了要配置参数。
方法的名称就是参数的名称。
方法的返回值就是参数的类型。注意:这里的返回值只能是基本类型,比如:Class,String,enum……
可以通过 default 来声明参数的默认值。
如果只有一个参数,一般参数名为 value,使用注解的使用就可以直接填值,不需要value="..."
。参考 @Override
注解元素必须要有值,定义注解元素时,经常使用空字符串,0 来作为默认值。
二 反射
反射是 Java 非常强大的一个机制,使得 Java 从静态语言拥有动态语言的特性。
动态语言
在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码都可以被引进,已有的函数可以被删除或者是其他结构上的变化。通俗易懂地说就是可以在运行的时候根据某些条件改变自身结构。
比如:Object-C、C#、JavaScript、PHP、Python 等。以 JavaScript 为例:
function f(){
var x = "var a = 3; var b=5; alert(a+b)";
eval(x);
}
// x 从字符串类型变成了可执行的代码
静态语言
那么静态语言显然就是和动态语言相对,运行时结构不可改变。
Java不是动态语言,但是因为Java有一定的动态性,所以可以把 Java 称作为准动态语言。我们可以利用反射机制获得类似动态语言的特性。使 Java 编程更加灵活。
1 反射定义
反射(Reflection)机制允许程序在执行期间借助于 Reflection API 获取任何类的内部信息,并且能够直接操作任意对象的内部属性及方法。
Java 加载完类之后,在堆内存的方法区中就产生了一个Class 类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,这种方式被形象地成为:反射!
获得类对象的方式:Class clazz = Class.forName(java.lang.String)
反射使用场景示例:
优点:可以实现动态创建对象和编译,非常灵活。
缺点:对性能有一定的影响。使用反射基本上是一中解释操作,告诉 JVM,我们需要做什么。这种操作一定慢于直接执行相同的操作。
2 Class 类
对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。
- Class 本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个 Class 实例
- 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过 Class 可以完整地得到一个类中的所有被加载的结构
- Class 类是 Reflection 的根源,针对动态加载、运行的类,必须要先获得对应的 Class 对象
获得 Class 类实例的几种方式
// 已知具体的类,通过类的class属性获取,该方法最为安全可靠,性能最高
Class clazz = User.class;
// 已知类的实例,调用该实例的 getClass() 方法获取 Class 对象
Class clazz = person.getClass();
// 已知一个类的全类名,可通过Class类的静态方法 forName() 获取,可能抛出 ClassNotFoundException
Class clazz = Class.for("com.code.User");
// 内置的基本数据类型可以直接用类名.TYPE
Class clazz = String.TYPE;
// 利用 ClassLoader ...
3 通过反射操作注解
了解注解的结构之后和反射机制之后,就可以自己动手分装一个注解。
3.1 编写自定义注解
@Target(ElementType.METHOD) // 表示该注解作用在成员方法上
@Retention(RetentionPolicy.RUNTIME) // 表示改注解在运行时生效
public @interface InitMethod {
}
3.2 编写使用该注解的类
public class InitDemo {
@InitMethod
public void init(){ // 使用自定义注解的方法
System.out.println("init....");
}
public void test(){ // 没有使用自定义注解的方法
}
}
3.3 通过反射的方式来实现注解
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.example.griffin.InitDemo"); // 获得类
Method[] methods = clazz.getMethods(); // 遍历获得当前类的全部方法
if (methods != null) {
for (Method method : methods) {
boolean isInitMethod = method.isAnnotationPresent(InitMethod.class); // 判断这个方法是否有自定义注解
System.out.println(method.getName()+":"+isInitMethod);
if (isInitMethod){
// 如果这个方法存在注解,就通过放射的方法运行这个方法
method.invoke(clazz.getConstructor(null).newInstance(null),null);
}
}
}
}
}
3.4 执行结果
可以看到成功执行了附加了我们自定义注解@InitMethod
的方法。
如果有任何问题私信留言!!