小技巧分享之3种方式获取Java方法参数名称

小技巧
placeholder image
admin 发布于:2019-08-10 19:05:30
阅读:loading

基本描述

rt. 分享Java获取方法参数定义的名称,什么意思?就是获取方法定义时的参数起名,如 sayHello(String name)的“name”,如果你不写底层,也许找不到这东西会有什么用途,实际上最初在接触到springmvc的时候就有此一问,为什么直接在方法中声明变量就可以获取到参数,不知道它是怎么做到的,当然使用@RequestParam注解就比较好理解了。记得去年在翻看spring-core.jar文件时,找到了LocalVariable的定义,然后瞪着眼睛试了一下,哟呵,竟然是获取方法参数名称定义的实现,于是借着这个理念改版了一下全局的日志记录实现,可转至《新的Spring Aop实现全局日志记录功能》。

回到主题,获取参数定义的实现这里一共提供三种实现,分别是Spring、JDK8、Javassist,通过定义一个类和方法(方法中提供一些参数)来实践期望得到的结果,该方法的类定义如下:

 package cn.chendd.tips.examples.varible;

import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.awt.*;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * @author chendd
 */
@RunWith(JUnit4.class)
public abstract class BaseTest {

   
protected Method method = super.getClass().getMethod("sayHello", String.class, Integer.TYPE,
            Character.
class, Date.class, Point.class , Boolean.TYPE);

   
public BaseTest() throws NoSuchMethodException {
    }

   
/**
     *
定义一个函数,包含一些基本类型与包装类型及对象类型
    
*/
   
public String sayHello(String name, int age, Character sex, Date birthday, Point point , boolean flag) {

       
return "hello: " + name;
    }

}

Spring

Spring的实现主要是基于ParameterNameDiscoverer接口的实现,更老的版本没去查看,3.X的版本中就有此实现,本次整理了三种实现,分别是:LocalVariableTableParameterNameDiscoverer、DefaultParameterNameDiscoverer及基于Aop实现的获取(见上面文章地址中的实现),其中前两种经过尝试均可以实现且他们都实现该接口,并且最后查看源码发现DefaultParameterNameDiscoverer的实现似乎更为严格,它有涵盖LocalVariable的实现,另外像Standard的标准实现并未正常获取,参考它们的继承实现结构为:

image.png

代码参考

package cn.chendd.tips.examples.varible;

import org.junit.Assert;
import org.junit.Test;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;

/**
 * 
使用Spring core自带的函数实现
 
@author chendd
 * @date 2019-08-08 20:39:20
 */
public final class GetDefinitionBySpring extends BaseTest {

    
public GetDefinitionBySpring() throws NoSuchMethodException {
        
super();
    }

    
@Test
    
public void testLocalVariable() {
        LocalVariableTableParameterNameDiscoverer localParameter = 
new LocalVariableTableParameterNameDiscoverer();
        String parameterNames[] = localParameter.getParameterNames(
method);
        Assert.assertEquals(parameterNames.
lengthmethod.getParameterCount());
        
for (int i = 0; i < parameterNames.length; i++) {
            System.
out.print(parameterNames[i] + ",  ");
        }
        System.
out.println();
    }

    
@Test
    
public void testDefaultVariable() {
        DefaultParameterNameDiscoverer defaultParameter = 
new DefaultParameterNameDiscoverer();
        String[] parameterNames = defaultParameter.getParameterNames(
method);
        Assert.assertEquals(parameterNames.
lengthmethod.getParameterCount());
        
for (int i = 0; i < parameterNames.length; i++) {
            System.
out.print(parameterNames[i] + ",  ");
        }
        System.
out.println();
    }

}

运行结果

image.png

JDK8

JDK8的新特性中有一项是编译字节码的时候按照源码定义进行编译,在此之前均被编译成arg0、arg1这种,现在可以在编译的时候通过增加编译参数使其编译的class文件支持按源码定义编译,再配合反射进行获取就可以了,当然它必须要求JDK8及其以上版本,如果你觉得无法接受还需去设置IDE的编译参数,而又实用maven之类的项目构建,也可以给maven编译时增加这项参数,这样就无须在检出项目的时候进行参数设置了,IDE以IDEA为例,修改设置如下:

image.png

maven编译

image.png

参考代码

package cn.chendd.tips.examples.varible;

import org.junit.Test;
import java.lang.reflect.Parameter;

/**
 *
使用JDK8新特性实现
 
* @author chendd
 * @date 2019-08-08 21:39:20
 */
public class GetDefinitionByJDK8 extends BaseTest {

   
public GetDefinitionByJDK8() throws NoSuchMethodException {
    }

   
@Test
   
public void testJDKVariable() {
        Parameter[] parameters =
method.getParameters();
       
for (Parameter parameter : parameters) {
            System.
out.println(parameter);
        }
    }

}

运行结果

image.png

Javassit

Javassit是一个很高大上的动态编程技术,比如可以帮你想实现类似JavaScript的eval函数的功能)我猜Apache Commons JCI应该也可以),这里重点至少拿它去打个蚊子,实现一个获取函数中的参数定义名称的功能,参考代码如下:

package cn.chendd.tips.examples.varible;

import javassist.*;
import javassist.bytecode.*;
import org.junit.Test;

/**
 *
使用Javassit实现
 
* @author chendd
 * @date 2019-08-09 22:39:20
 */
public class GetDefinitionByJavassist extends BaseTest {

   
public GetDefinitionByJavassist() throws NoSuchMethodException {
    }
   
   
@Test
   
public void testJavassistVariable() throws NotFoundException {

        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.getCtClass(
super.getClass().getName());
        CtMethod ctMethods[] = ctClass.getMethods();
        String methodName =
"sayHello";
       
for (CtMethod ctMethod : ctMethods) {
           
if(! methodName.equals(ctMethod.getName())){
               
continue;
            }
            MethodInfo methodInfo = ctMethod.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attribute = (LocalVariableAttribute) codeAttribute.getAttribute(
"LocalVariableTable");//LocalVariableTable
           
int lens = attribute.tableLength();
           
for (int i = 1; i < lens; i++) {
                System.
out.print(attribute.variableName(i) + ",\t");
            }

        }
    }
}

运行结果就不提供了,跟你预料的一样。至此提供了三种方式的获取方法的参数定义名称,简单总结一下:

(1)如果使用Spring则可以考虑第一种方法,第一种方法个人觉得Default的实现就好;

(2)如果使用JDK8及其以上版本,并不介意修改了IDE编译器设置或者直接使用maven构建项目使用这种方法也挺好,直接正常反射就行;

(3)如果有更加高大上的动态编程可以考虑使用Javassist;

源码地址

https://gitee.com/88911006/chendd-examples 见 tips 模块。


 点赞


 发表评论

当前回复:作者

 评论列表


留言区