注解和反射应该说是后期JAVA开发里面比较重要的技术了,通过注解和反射可以简化许许多多的操作和步骤,最重要的是为JAVA的运行提供了更多的灵活性。据我个人了解的是Hibernate就是基于反射和注解的一个框架,而且现在用到Hibernate的项目也是比较可观的。
由于上学期高级JAVA学的时候实在没怎么注意这个。。当时以为这玩意儿是个鸡肋,万万没想到今天在写SimpNk的时候配置文件部分代码实在太杂了,每加一个配置项目都要改数组、加项目、检查对不对,保存和加载配置文件还写得这么扯淡,搞得我满头都是金坷垃。。。。。然后昨天晚上自己突发奇想,写一个基于JAVA的脚本解释器的时候,由于变量确定用一般的代码写起来判断实在太过复杂,无意之中想起了用反射来做这件事情。于是就有了今天晚上把反射用到SimpNk加载和保存配置文件这件事。
那么,就容我说点完全不专业的废话,来简单谈谈我理解的注解和反射吧。
注解是从JDK5所加入的一项新的特性。注解,顾名思义,也就是对变量/方法/类的解释信息。它可以在运行时或者编译时提供一些额外的附加信息。这些信息并不会出现在你的主体代码之中。在前期初学JAVA基础的时候,比如你写一个子类重写了父类的方法,编译器会自动在方法上面加上一个“@Override”来表示这是重写了父类的方法,并且这个标记也让编译器能够对你是否是正确重写了父类的方法做出检查。(比如父类中有一个 write(Object x)方法,你在子类中写了一个 write()方法,如果你在这个方法上加上“@Override”注解,编译器会告诉你你并没有覆盖父类的方法,因为参数签名不同),除此以外,注解所提供的信息也可以在运行时提供。
在生产开发过程中,一般我们会使用自定义注解。自定义一个注解的方式非常简单,比如下面定义的 Example注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Example{ public static String STRING_DATA = "String"; public String name() default "New Example"; public String description(); public int id() default 0; }
如上所示的Example注解定义,其定义方式与JAVA接口的定义非常相似。不同的是,interface关键字前面加上了 “@”来作出区分这是注解而不是接口。并且注解定义中你可以看到在 name()和id()后面都有一个” default“,这表示当你使用该注解时,如果你没有指定这项的值,它将会使用default后面的作为默认值。
而注解上方的另外两个注解则指定了注解的有效策略和注解对应的对象类型。在这里,RUNTIME表示这个注解在运行时有效,而FIELD表示这个注解是为变量提出的(似乎是这个意思吧。如有不对请拍砖)
如何使用注解?举个栗子
@Example(name = "ExampleData" , description = "Simple Description") public static int dataexample = 1;
如你所见,注解的使用非常简单,在你需要做注解的变量上标注即可,并且在括号中表明注解项目的值。如果注解项目有default,你可以不写出来,否则你需要手动指定该项的值。在上述栗子中,id()没有标注,表示使用了默认值0,而name标注后则使用了新的值ExampleData,description也被标明。
那么你可能要问了,我这里定义了注解好像也就只能看看啊,又不能获取到当中的信息,这么写了顶个鸟用啊?
于是,反射就登场了。
你说你注解写了没用?怎么可能~~~老夫反射就是专门做这件事情的。
在程序运行的时候存在一些动态的信息你可能无法在编码的时候获取,而你正好又要在代码中使用,这时候反射就可以帮上忙。由于程序运行的时候你并不知道一个类里面的数据到底会是什么样子,而你也不可能为了每种情况都加上代码处理,显然这么做会增加代码量和维护成本,于是这个时候反射就像是一面“镜子”,用来帮助你获取这个类里面的信息。可以说反射几乎是无所不能的。而实现反射则用到了Java中的Class类。这个类中包含了许多方法,包括类中的方法,方法签名,构造函数,变量,私有域,等等。比如getMethod()获取全部方法,getField获取公开的变量。总之这玩意儿还是蛮好玩的~~
说了这么多,反射真的有这么强大么?可以来试试。还是举个栗子
定义Status类和IConfig注解,代码分别如下:
Status :
public class Status { @IConfig(name = "ontime.starttime", type = IConfig.DInteger) public static int data = 1; public static final int d = 2; }
IConfig:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface IConfig { public String name(); public String type(); public static String DInteger = "Integer"; public static String DDouble = "Double"; public static String DString = "String"; public static String DLong = "Long"; public static String DChar = "Character"; }
附上下列示例代码。这个代码的意思是:反射Status类中的全部变量,如果有IConfig注解,输出注解内容。并且给出一个通过反射来修改变量值的实例
public static void main(String[] args) throws ClassNotFoundException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, SecurityException, InvocationTargetException{ //加载这个类 Class<?> cls = Class.forName("cn.sunflyer.test.Status"); //这里是获取类中的全部公开的变量 即变量修饰符需要为public Field fs[] = cls.getFields(); if(fs != null){ //遍历变量数组 for(Field x:fs){ //打印出变量名称和修饰符的值,可以通过Modifier类中的常量对比获取修饰符类型 System.out.println("名称 " + x.getName() + " 修饰符 " + x.getModifiers()); Annotation rms = x.getAnnotation(IConfig.class); //如果获取到IConfig注解,则输出内容并根据注解动态修改数据 if(rms != null){ IConfig ic = (IConfig)rms; System.out.println("注解 name : " + ic.name()); System.out.println("注解 type : " + ic.type()); String da = "1234"; Object ras = null; if(ic.type().equals(IConfig.DString)){ ras = da; }else{ //加载JAVA基本数据类型的封装对象类 Class<?> fCls = Class.forName("java.lang." + ic.type()); //获取指定方法 Method ms = fCls.getMethod("valueOf", String.class); //调用方法 ras = ms.invoke(null , da); } x.set(null, ras); } } } }
运行后的结果为:
名称 data 修饰符 9
注解 name : ontime.starttime
注解 type : Integer
名称 d 修饰符 25
嗯,好像还不错。
于是经过一段折腾,总算是把这“高大上”的玩意儿用到SimpNk动态读取和加载配置文件那部分去了。有兴趣的同学可以在后面我上传后看下代码,虽然写的很烂~~如果有更好的方式或者有什么不对的,也欢迎拍砖~~
推荐你写博客的时候把代码缩进考虑一下。。。
之前一直懒得弄 = =