反射

 反射


import java.lang.reflect.Field;

import java.lang.reflect.Method;

public class ReflectTest {
    public static void main(String[] args) throws ClassNotFoundException {
         reflectAll("java.lang.Class");
    }
      
    public static void reflectAll(String className) throws ClassNotFoundException{
        Class clazz = Class.forName(className);  
        System.out.println("类名:"+clazz.getSimpleName());  
        Method[] methods = clazz.getMethods();  
        for(int i =0; i<methods.length; i++){    
            String x = methods[i].getReturnType().getSimpleName();
            Class<?>[] c= methods[i].getParameterTypes();
            String paramaterTypes[] = new String[c.length];
            for(int j=0; j<c.length; j++){    
                paramaterTypes[j] = c[j].getSimpleName();
                System.out.println("方法名:"+methods[i].getName()+"|返回类型:"+x+"|参数类型:"+paramaterTypes[j]);
            }            
        }  
        Field[] fields = clazz.getFields();  
        if(fields.length==0){
            System.out.println("该类没有属性");    
        }else{
            for(int i=0;i<fields.length; i++){  
                System.out.println("Field:"+fields[i].getName()+"|Type:"+fields[i].getType().getSimpleName());
            }
        }
    }


 


 

 



不知道大家有没有发现没有参数名称,调查了大量资料,选优给大家看看:


 

 

关于题目

首先解释一下题目. 我们知道, Java通过反射,可以从一个类得知它有哪些方法,有哪些变量,也可以知道每个方法中有哪几个什么类型的传入参数。但有一个东西反射取不到,那就是我们对方法传入参数的命名

 

取得传入参数的名字有什么意义?

对这个问题的探究,源于在写一个测试类时候的需求。假设我们有一个类需要测试,这个类中有数十个方法。为每个方法编写测试类,将耗费大量的时间和精力。因此我有一种想法,就是通过java的反射,获得这个类所有的方法,再通过传入参数的名字和参数类型,来生成一些符合要求的数据进行传入。(能这样生成数据的前提是:这个类的编码需要遵循严格的规范,对参数的命名有统一的标准,同时,这个类应该和某种业务紧密相关,这样,才能通过业务和参数名字,判断应生成什么合适的数据)。如果能做到上面说的,那么对具有数十或数百个方法的类,要测试的话只需要传入这个类就可以了。

 

存在的问题

根据上面的设想,问题就出现了。获得类的方法,获得类的参数类型,反射都可以做到。但参数名称呢?上网求证,多数人给了直接否定的答案。因为API中根本没有提供相关的方法。但有一些人的观点启发了我。他们提到,IDE(如eclipse,myeclipse)中在编码过程中,调用一个类的方法,在代码提示的时候,ide是可以显示出方法中的参数名字的,如下图:


IDE是怎样做到的呢,如果IDE可以做到,我们是否可以尝试去分析它们的做法,来获得参数名称。

 

 

可能的做法

网上找到了一个很直观的方法——通过直接读取.java文件,把类作为一个普通文本,用正则表达式匹配方法,来直接获取参数的名字。

Java代码   收藏代码
  1. /**    
  2.  *   @author   zhangc    
  3.  *    
  4.  *一个测试程序,用来扫描文件(java文件),找出所有方法的参数列表    
  5.  */  
  6. import java.io.*;  
  7. import java.util.regex.*;  
  8.   
  9. public class ScanSource {  
  10.     static void findArgsList(Object targetSrc) {  
  11.         /* 
  12.          * 正则匹配串基本上是这样子分组情况(A(B(c(d)))) 
  13.          * 串是:(\\w+\\s+\\w+\\s*\\(((\\s*\\w+\\s*(\\[ 
  14.          * \\])*\\s*\\s+(\\[\\])*\\s*\\w+\\s*(\\[\\])*,?)+)\\)\\s*\\{) 比如public 
  15.          * static void findArgsList(Object targetSrc,int []a){ 
  16.          * A是匹配整个方法定义行:这里是:static void findArgsList(Object targetSrc,int []a){ 
  17.          * B是匹配匹配参数列表:这里是Object targetSrc,int []a 
  18.          * C是匹配一个参数,包括类型和类型名称和逗号:这里是Object targetSrc, D是匹配数组标识符:这里是[] 
  19.          * 这个串有点bt,水平有限,只能这样 
  20.          */  
  21.         Pattern p = Pattern  
  22.                 .compile("(\\w+\\s+\\w+\\s*\\(((\\s*\\w+\\s*(\\[\\])*\\s*\\s+(\\[\\])*\\s*\\w+\\s*(\\[\\])*,?)+)\\)\\s*\\{)");  
  23.         Matcher m = p.matcher((CharSequence) targetSrc);  
  24.   
  25.         // locate the all methord defination  
  26.         while (m.find()) {  
  27.             String methodName = m.group(0);  
  28.             String methodArgName = m.group(1);  
  29.             String strArgs = m.group(2);  
  30.             String fourArgs = m.group(3);  
  31.             System.out.println(methodName + "\n" + methodArgName + "\n" + strArgs + "\n" + fourArgs + "\n");  
  32.         }  
  33.   
  34.     }  
  35.   
  36.     public static String LoadTargetFile(String targetFileName) {  
  37.         String result = null;  
  38.         try {  
  39.             FileInputStream fis = new FileInputStream(targetFileName);  
  40.   
  41.             // 临时分配10000size给byte数组。  
  42.             byte[] bufReceived = new byte[10000];  
  43.   
  44.             int counts = fis.read(bufReceived);  
  45.             byte[] bufActual = new byte[counts];  
  46.             System.arraycopy(bufReceived, 0, bufActual, 0, counts);  
  47.             result = new String(bufActual);  
  48.         } catch (FileNotFoundException e) {  
  49.             e.printStackTrace();  
  50.         } catch (IOException e) {  
  51.             e.printStackTrace();  
  52.         }  
  53.         return result;  
  54.     }  
  55.   
  56.     public static void main(String[] args) {  
  57.         String target = LoadTargetFile("src/com/spring/aop/TestAspect.java");  
  58.         System.out.println(target);  
  59.         findArgsList(target);  
  60.     }  
  61. }  
 

 

 

 这个通过正则表达式的类,在我写的一个简单的测试类中,是可以取得参数的值的,但当把它用在我们那个有几十个方法的类的时候,表达式的匹配就失效了,没有得到任何的结果(具体原因可能是正则表达式的错误,没能匹配到一些方法)。同时,这种方法需要有.java这个源文件,而在IDE中引入的常常是.class组成的Jar包。为了进一步了解IDE对方法传入参数名的处理,下面我做了一个测试。

 

 

测试IDE对方法传入参数的处理

建立一个工程。在工程中新建如下的一个类:

 

 

Java代码   收藏代码
  1. package testplugin;  
  2.   
  3. public class TestJar {  
  4.     public void testJar(String jarName, String yourName){  
  5.         System.out.println("jarName:" + jarName + "|| yourName:" + yourName);  
  6.     }  
  7. }  
 

接着我们用2种方式对这个类打jar包:

 

1. 用javac编译类文件然后打到jar包中,命名为testPlugin_javac.jar.

2. 用MyEclipse直接对工程进行导出,导出为testPlugin_myeclipse.jar.

 

(打开2个jar中的TestJar.class文件,会发现2个class文件有差异)。

 

再建立一个工程,先后将2个jar包引入做实验,可以看到:

 

1. 引入testPlugin_javac.jar, 调用testJar方法,如下图



 可以看到,2个传入参数失去了原有的名称。

 

2. 移除上面的包,引入testPlugin_myEclipse.jar, 调用testJar方法,如下图



 可以看到,参数名称被识别出来了。

 

关键在于,2个jar包中的class文件不同。我们打开2个class文件(我们只是直观的看一下class文件中的变量,所以没有用专用的工具查看):

javac生成的.class:

 

 

myelipse直接打出来的.class(实际上就是调用了debug模式编译出来的.class):

 

2个class文件里下面的部分都有SourceFile块。应该是用来表示这个class文件是从哪个java文件编译来的。我们重点看上面的部分。

 

可以看到,用普通的javac编译出来的类,方法的传入参数名会被编译器修改,于是上面第一个图里SourceFile以上的部分就找不到jarName和yourName 2个名字。而只有通过-debug模式编译出来的类,它的参数名才能被保存下来。而也就是在.class文件中有保留下来参数名的jar包,在IDE中代码提示才能正确显示出参数名字。

 

那么说明IDE是否能识别类中的方法名取决于编译过后产生的不同的class文件。那么下一节我们会使用工具来解析这2个class文件来看其中到底有什么不同。

 章节2:

javac和Eclipse编译出来的class文件在传入参数名称上的区别

在上文中可以看出,由javac编译的类,IDE在引用了改类的时候,无法获得方法传入参数原来的命名,只能重新赋予arg0,arg1之类的名字。而由eclipse(MyEclipse)编译出来的class文件,却具有传入参数原来的名字。

 

为了能进一步清楚这个区别,我们这里借助一个软件来查看class文件:JClassLib

JClassLib 写道
JClassLib不但是一个字节码阅读器而且还包含一个类库允许开发者读取,修改,写入Java Class文件与字节码。
 

我这里只使用了jclasslib的GUI那个版本。jclasslib作为一个java库还可以有更多更强大的功能。用jclasslib分别打开2个class文件,如下图:


上图可以看出,2个class文件相同的testJar方法下,下面那个testJar方法中还注入了"[1]LocalVariableTable"属性。这个属性通过右边的详细可以看出,存储的就是jarName和yourName2个传入参数的名字。而上面那个javac编译出来的class是没有这个属性的。

对于没有注入参数名称的class,按照我的推断,他应该是根据


上面这个方法中的Descriptor这个标志来判断传入参数是什么类型的,再根据类型给一个默认的名称。

 

注:上图中用红色框起来的descriptor的意思是  <(参数1数据类型,参数2数据类型) 方法返回类型>

 

因此,我们使用Eclipse等IDE在调用spring.jar等jar的方法时,代码提示可以正确读取到传入参数的原命名是因为 spring.jar等jar包在编译时就向class文件注入了参数名字。因此,eclipse等IDE也是通过读取.class的字节码来获得方法的参数名称的。

 

通过网上资料得知,eclipse\myEclipse并没有引用sun默认的javac编译器,而是有自己的实现,因此在编译java文件的时候,由eclipse编译出来的与由javac编译出来的就有所不同。

 

 

Eclipse/MyEclipse中 .class没有注入参数名称的jar在 attach source之后的区别

上一节的分析,我们了解到,倘若在生成.class文件的时候没有注入参数名称的信息,那么在引入这个class之后,使用它类的方法时,代码提示的参数就只会是arg0,arg1之类的名称。但当我们对这个引入的jar包进行了 attach source之后,代码提示的时候就可以按照source中的正确参数名称来显示。

是不是attach source之后,IDE向jar包中写入了参数名称的信息?

用jclasslib打开attach source以后的class.可以发现这个.class没有发生变化。那么还有一种可能性,就是进行了这个操作之后,eclipse再进行代码提示的时候,是直接从source中读取参数名称的。为了验证这点,我们修改source中方法的参数名。将jarName改成jarNameTest. 再次使用代码提示,可以看到,原来的参数名字没有发生变化。因此,IDE并没有每次都从sourc中读取参数的名称。而是在attach的时候读取了一次,之后做了缓存。

 

看到这里,我的第一个反应是eclipse在这个工程的目录下保存了类似“索引”的东西,将方法参数对应的名称保存了下来。针对这个猜测,我监测了attach前后的工程目录情况,如下图:



 发生变化的只有.classpath文件。source的路径被写到了这个文件中。因此,IDE并没有建立索引。那么可以猜测,eclipse可能在内存中做了缓存,也可能是在自己的公共目录下建立了索引。

 

对于在内存中缓存的可能,我们只要杀死eclipse的进程便可以验证(重启eclipse).结果发现,参数名称变成了修改后的。那么可以看出,这个参数名称的缓存,eclipse是放在自己开辟的一块内存空间内的,只是临时的。在失去了这个缓存之后就会重新读取。

 

 

小结

通过上面的分析,我们可以得出一个结论,eclipse等IDE在代码提示时对方法参数名称的获取,有2种途径。

A.当.class文件中持有 参数名称 信息的时候,会直接从class中读取。

B.当没有时,从.java源文件中,通过字节流读取参数名称,然后缓存起来。

 

因此,我们要模仿IDE的做法,获得一个方法的传入参数名称,我们有上面2种途径。具体的实现,在下一篇心得里会继续分析。

 

 

可惜了,上文作者并没有跟新出第三贴,可想而知,确实有难度.作为刚入门者,我也并不像花大量时间去琢磨这个, 如果有新的发现,或者你有写好的代码,请联系我:qq806129001 

还有什么annotation。。第三方插件(javaassist、ASM)曲线救国的方式。。在这里就不像大家介绍了,总之先实现了才是好的程序员.

 

第二次编辑:jdk1.8 经常测试可反射参数名称,但是此版本貌似有各种各样的问题,可以单独拿出parameter.CLASS


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值