Java反射和注解
Java反射和注解
Reflection
今天来挑战一下如何在2000字以内把Reflection作用说明白?
Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.
https://docs.oracle.com/javase/tutorial/reflect/index.html
Java Reflection反射机制:Java可以获取/调用任意已加载类的所有信息(字段/方法/构造函数)。甚至改变类中成员的各种属性(又如private改成public)。官方API说明都在java.lang.reflect。
假设目前我们只有奥迪车需要测试运行速度。
package com.car.test;
// Car.java
class Car {int velocity;}
// Runnable.java
interface Runnable {public void run();}
// Audi.java
public class Audi extends Car implements Runnable {
Audi(int velocity){this.velocity = velocity;} public void run() { String className = this.getClass().getSimpleName(); System.out.println(className + " run " + velocity + "km/h"); }
}
// CarFactory.java
public class CarFactory {
public static void main(String[] args) { Audi audi = new Audi(120); audi.run(); }
}
上面因为我们知道需要测试的只有Audi这一种车型,所以在main里面可以直接用调用对应的构造函数进行测试,但是当我们的车型增加时(特斯拉也来啦),我们就不得不再次修改main函数。
public class CarFactory {
public static void main(String[] args) {
Tesla tesla = new tesla(150); tesla.run();
}
}
为了更好的测试不断新加车型,同时不修改我们的工厂测试主函数。我们可以:
package com.car.test;
// Tesla.java
// 省略以前已有不变的Audi
public class Tesla extends Car implements Runnable {
Tesla(int velocity){this.velocity = velocity;}
public void run() {
String name = this.getClass().getSimpleName(); System.out.println(name + " run " + velocity + "km/h");
}
}
package com.car.test;
// CarFactory.java
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class CarFactory {
public static void main(String[] args) {
try { //通过命令行把动态所需要测试的类名传入测试主函数。 //args[0] = "com.car.test.Tesla". //args[1] = "140". //得到类名.此函数需catch异常 ClassNotFoundException. Class<?> c = Class.forName(args[0]); //找到对应的构造函数并构造出实例 int velocity = Integer.parseInt(args[1]); Object car = c.getDeclaredConstructor(int.class).newInstance(velocity); //找到需要测试的函数定义 Method method = c.getDeclaredMethod("run"); //执行对应函数 method.invoke(car); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }
}
}
这样只要我们在命令行传入对应的类名,就可以执行对应的测试函数啦。
完美做到新加车型,不需要修改主测试函数。这就是java反射机制在运行时的一个基本示例。但是对于一个静态语言来说,这种动态调用太过灵活,所以需要每一步都要小心(上面的每个函数都需要catch异常)
With great power comes great responsibility.
上面就是事先不知道我们需要测试的是什么类,只有到了运行时才能得到对应的类,这就是应用反射机制的常用场景。它在运用在真实场景中一个典型例子就是Junit,它过去枚举了类中所有的方法getDeclaredMethods(),并把以testXXX开头的方法假设为测试函数并执行它们。但在JUit4后使用了注解(annotations)来替换了它。
不过注解的本质也是通过反射来实现的。
Annotations
在文章的开始处写反射是对类的属性/方法/构造函数的操作,并没有提到注解。但是通过Reflection API列表我们可以看到他有getAnnotation之类的函数,所以注解也是可以读写操作的。不过想对于上面说的,稍微复杂一点。
解析注解的方式有两种,编译期检查和运行期反射。
1.编译期检查
常见到的就是@Override,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。比如我们在上面的Car类中增加一个方法得到名字:
package com.car.test;
//car.java
class Car {
int velocity;
public String getName() {return "Car";}
}
// Tesla.java
public class Tesla extends Car implements Runnable {
Tesla(int velocity){this.velocity = velocity;}
// @Override
public get_name() { return "Tesla";}
// 省略以前有的
}
}
Tesla继承了Car,并想重写它的getName函数。但不小心手误写成了get_name,这时Tesla就同时有了这两个函数。为了避免这种低级错误,就使用@Overide,这告诉编译器,此方法是重写父类方法的,如果方法的定义(名字/返回值/参数)与父类不一致,则编译不通过。PS: 打开上面的注释,你就会得到一个编译报错。
可见@Override作用于方法,只在编译期解析,编译结束后,使命就完成了。不会把信息存到字节码中。
其它内置的注解还有
@Deprecated标记当前类/方法/字段不再被推荐使用,下次版本可能会不在支持它。
@SuppressWarnings明确告诉编译器这个警告我已发现了,你不用再来烦我。
2.元注解
为了在注解定义时规定生命周期(编译期/永久保存etc),作用范畴(字段/方法etc),又引入了注解的注解,也就是元注解,它是主要用于修饰注解的注解。比如在@override的定义中
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Target表示作用目标,METHOD作用于方法,还有其它的FIELD,PARAMETER之类的。
@Retention表示生命周期SOURCE编译器可见,不写入class文件;CLASS类加载时丢弃,会写入class文件,RUNTIME永久保存,可以通过反射读取。
@Documented是否在JavaDoc文档中出现。
Inherited是否允许子类继承该注解。
3.运行期注解
下面我们稍微改造一个上面car的例子来说明一下运行期的注解操作。
通过新建一个注解(@DriveAccess来表示控制可以允许运行run函数进行函数(当然,你可以有更好的方法来做这件事,这里只是为了用来演示注解如何工作)。
package com.car.test;
// DriveAccess.java
import java.lang.annotation.Documented;
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.METHOD) //作用于方法
public @interface DriveAccess {
public boolean canDrive() default false; //默认返回false
}
// Tesla.java
public class Tesla extends Car implements Runnable {
Tesla(int velocity) { this.velocity = velocity; }
@Override
public String getName() { return "Tesla"; }
@DriveAccess(canDrive = true)
public void run() {
String name = this.getClass().getSimpleName(); System.out.println(name + " run " + velocity + "km/h"); }
}
// CarFactory.java
import java.lang.reflect.Method;
public class CarFactory {
public static void main(String[] args) throws Exception {
Class<?> car = Class.forName(args[0]); for (Method method : car.getDeclaredMethods()) { if (method.isAnnotationPresent(DriveAccess.class)) { DriveAccess access = method.getAnnotation(DriveAccess.class); String methodName = method.toGenericString(); if (access.canDrive()) { System.out.println(methodName + " method can be accessed... "); Object c = car.getDeclaredConstructor(int.class).newInstance(100); method.invoke(c); } else { System.out.println(methodName + " method can not be accessed... "); } }else { System.out.println(methodName + " don't have DriveAccess Annotation..."); }
}
}
}
}
运行 java CarFactory com.car.test.Tesla得到
public void com.car.test.Tesla.run() method can be accessed...
Tesla run 100km/h
public java.lang.String com.car.test.Tesla.getName() don't have DriveAccess Annotation...
如果Tesla中的canDirve改成false则:
public void com.car.test.Tesla.run() method can not be accessed...
public java.lang.String com.car.test.Tesla.getName() don't have DriveAccess Annotation...
Summary
运用Java Reflection API可以读取/操作类中所有的元素。非常灵活强大,因为灵活,也会带来很多不确定的危险。所以如果可以用其它方法实现的,最好不要用反射。
原文地址https://www.cnblogs.com/zhongwencool/p/reflection_annotations.html
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
日志收集-Elk6
一:前言ELK是三个开源软件的缩写,分别表示:Elasticsearch , Logstash, Kibana , 它们都是开源软件。新增了一个FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat占用资源少,适合于在各个服务器上搜集日志后传输给Logstash,官方也推荐此工具。 Elasticsearch是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。 Logstash 主要是用来日志的搜集、分析、过滤日志的工具,支持大量的数据获取方式。一般工作方式为c/s架构,client端安装在需要收集日志的主机上,server端负责将收到的各节点日志进行过滤、修改等操作在一并发往elasticsearch上去。 Kibana 也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。 Filebeat隶属于Beats。目前Beat...
- 下一篇
python多线程同步实例分析
python多线程同步实例分析进程之间通信与线程同步是一个历久弥新的话题,对编程稍有了解应该都知道,但是细说又说不清。一方面除了工作中可能用的比较少,另一方面就是这些概念牵涉到的东西比较多,而且相对较深。网络编程,服务端编程,并发应用等都会涉及到。其开发和调试过程都不直观。由于同步通信机制的原理都是相通的,本文希通过望借助python实例来将抽象概念具体化。 阅读之前可以参考之前的一篇文章:python多线程与多进程及其区别,了解一下线程和进程的创建。 python多线程同步python中提供两个标准库thread和threading用于对线程的支持,python3中已放弃对前者的支持,后者是一种更高层次封装的线程库,接下来均以后者为例。 同步与互斥 相信多数学过操作系统的人,都被这两个概念弄混过,什么互斥是特殊的同步,同步是多线程或多进程协同完成某一任务过程中在一些关键节点上进行的协同的关系等等。 其实这两个概念都是围绕着一个协同关系来进行的,可以通过一个具体的例子来清晰的表达这两个概念: 有两个线程,分别叫做线程A和线程B,其中线程A用来写一个变量,线程B读取线程A写的变量,而且线...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Mario游戏-低调大师作品
- 2048小游戏-低调大师作品
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Thymeleaf,官方推荐html解决方案