跳至主要内容

Java中使用MethodHandle的API反射

java-reflection-method-handle.md

简介

Java的反射API从1.0就开始提供,虽然反射的性能在逐步提高,而且对现在大部分应用来说,反射的开销都不太会影响性能。但是如果反射使用特别频繁或需要更进一步提高性能,可以使用从Java1.7开始提供的MethodHandle相关的API。

Method Handle

method handle 的API有以下几个主要对象:

  • Lookup 用于实例化各类method handle对象,是使用method handle的入口对象

  • MethodType 用于表示反射方法的参数对象

  • MethodHandle用于实际操作被反射的字段、方法等

下面使用一个简单的Person类来作为例子:

public class Person {

    public String job;

    private String name;
    private String location;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public void print(){
        System.out.println("Person job is " + job);
    }
}

获得Class对象

获得class对象,之前常用的有Class.forName(),getClass(),这些方法也可以用。methodHandle也提供了一个新方法:

MethodHandles.lookup().findClass(Person.class.getName());

读写对象实例的字段

从上面可以看到Person的Job字段是public的,使用methodHandle反射读写示例如下:

MethodHandle jobSetter = lookup.findSetter(personClass, "job", String.class);
jobSetter.invoke(p, "fun job");
System.out.println("set job...");
System.out.println("p.job=" + p.job);

MethodHandle jobReader = lookup.findGetter(personClass, "job", String.class);
Object readResult = jobReader.invoke(p);
System.out.println("jobReader="+readResult);

可以看到methodHandle的使用相对来说会比较麻烦,对实例的字段的读写被分为getter和setter,都是MethodHandle对象,不是很好区分,并且只能读写public的字段。

实践中,绝大多数Java对象的字段都是private的,上面这段示例实践中并不太实用。

下面是读写private字段的办法,在Java9之前会显得比较繁琐:

System.out.println("p.getName=" + p.getName());
Field nameField8 = Person.class.getDeclaredField("name");
nameField8.setAccessible(true);


MethodHandle privateNameSetter = lookup.unreflectSetter(nameField8);
privateNameSetter.invoke(p, "MyName");
System.out.println("p.getName=" + p.getName());

MethodHandle privateNameGetter = lookup.unreflectGetter(nameField8);
Object readName = privateNameGetter.invoke(p);
System.out.println("readPrivateName="+readName);

可以看到,需要用methodHandle读写private字段,需要先用reflection api先获取到field,setAccessible为true之后才能读写。

Java9之后有对应的methodHandle方法可以用:

System.out.println("p.getLocation=" + p.getLocation());
MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(Person.class, lookup);

MethodHandle locationSetter = privateLookup.findSetter(Person.class, "location", String.class);
locationSetter.invoke(p, "MyLocation");

MethodHandle locationGetter = privateLookup.findGetter(Person.class, "location", String.class);
Object locationGetterResult = locationGetter.invoke(p);
System.out.println("locationGetterResult=" + locationGetterResult);

调用对象的方法

调用对象方法也是类似的:

MethodType printMethodType = MethodType.methodType(void.class);
MethodHandle print = lookup.findVirtual(Person.class, "print", printMethodType);
print.invoke(p);

MethodType构建的第一个参数是返回值class,第二个和之后的就是入参的class。

总结

可以看到MethodHandle的API相对来说比较难用,但是因为不会每次调用都进行安全检查,所以性能相对较高。

参考资料

[1]示例工程:java-api-demos/reflection at master · fengdongyi/java-api-demos (github.com)

评论

此博客中的热门博文

国密SM2签名封装成PKCS7格式

在国内做金融行业,难免会有被强制使用国密算法的情况,而且一般还会指定必须使用硬件加密机之类的设备,所以我也稍微的研究了一下国密算法,使用软算法签名并封装 PKCS7 格式(文档中的一个交互)。 以下是基于 Bouncy Castle 的示例,密钥对的生成可以参考 Bouncy Castle 中 test 包下 SM2 相关代码 public static String sign ( ) throws Exception { //加载公钥 byte [ ] plainText = "hello, world" . getBytes ( ) ; FileInputStream input = new FileInputStream ( "F:\\certificate\\public.cer" ) ; CertificateFactory certificateFactory = new CertificateFactory ( ) ; X509Certificate certificate = ( X509Certificate ) certificateFactory . engineGenerateCertificate ( input ) ; input . close ( ) ; //加载私钥,private为换成实际的私钥 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec ( "private" . getBytes ( ) ) ; //SM2算法实际上为ECC算法,并指定了一些参数值,所以这里的参数是EC KeyFactory factory = KeyFactory . getInstance ( "EC" , "BC" ) ; PrivateKey privateKey = factory . generatePrivate ( spec ) ; //以下为签名并封装成PKCS7格式 byte [ ] signedMessag

Spring Boot Actuator 2 示例

Welcome file 简介 Spring Boot Actuator为应用程序提供了各种开箱即用的运维特性,可以与应用方便的交互和监控。 使用环境:Java 11 和 Spring Boot 2.4.3.RELEASE 集成Spring Boot Actuator 在Spring Boot中集成Spring Boot Actuator与集成其他的框架类似,在 pom.xml 里引入相关的starter就可以: < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-actuator </ artifactId > </ dependency > < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-web </ artifactId > </ dependency > 由于大部分的使用场景还是web,所以这里也用Spring MVC做示例。 配置好 pom.xml 后,默认actuator仅暴露一些基本功能,实际使用中,根据需求暴露对应功能。为了简便测试,这里在 application.yml 中配置暴露全部功能: management : endpoints : web : exposure : include : "*" endpoint : health : enabled : true show-details : always probes : enabled : true shutdown : enabled : true metr

NextCloud数据目录迁移

最近服务器的环境坏了,所以迁移了NextCloud的数据目录。不过在迁移过程中有点小问题。 环境: Ubuntu 18.04 Docker 19.03.7 1.NextCloud页面不正常,Docker日志显示XX目录permission denied 参考了 这里 的做法,不过是把  /var/www/html/   整个目录的权限都修改为  chown -R www-data:www-data ,之后就不再报权限问题了。 2.数据库配置修改 因为NextCloud是在初始化时填的数据库连接信息,所以直接迁移数据目录的情况下,会导致应用连不到新的数据库环境。此时可以找到数据目录下的  config/config.php 文件,直接修改数据库连接配置。