简体   繁体   English

关于MethodHandle API的一些基本问题

[英]Some basic questions about MethodHandle API

How can I obtain all declared method through MethodHandles.lookup() ? 如何通过MethodHandles.lookup()获取所有声明的方法? How can I obtain all declared fields? 如何获取所有声明的字段?

What is difference betweeen MethodHandle.invoke() , MethodHandle.invokeExact() and MethodHandle.invokeWithArguments() MethodHandle.invoke()MethodHandle.invokeExact()MethodHandle.invokeWithArguments()什么区别

Also, I will be appreciate for tutorial about using MethodHandle API for Java devloper . 此外,我将非常感谢有关使用Methodhandle API for Java devloper的教程 I emphasize, I am programming on statically typed language plain old Java and I am not JVM developer, particularly I am not interesting with whole byte-code crap (invokedynamic). 我强调,我正在编写静态类型语言普通旧Java,我不是JVM开发人员,特别是我对整个字节码废话(invokedynamic)并不感兴趣。 I want to figure out how can I use this new API instead of Java Core API. 我想弄清楚如何使用这个新API而不是Java Core API。

EDITED-2: EDITED-2:

@Glen Best below provided some refferences I want to provide only one http://www.oraclejavamagazine-digital.com/javamagazine/20130102?pg=52&search_term=methodhandle&doc_id=-1#pg50 This is exactly what I was looking for. @Glen Best下面提供了一些我只想提供的参考资料http://www.oraclejavamagazine-digital.com/javamagazine/20130102?pg=52&search_term=methodhandle&doc_id=-1#pg50这正是我想要的。 I figured out that actually there some new vocabalry.. For example, by target is actually meant MethodHandle (and not object to make dispatch upon) and call site is actually code that "invokes" "function pointer" aka MethodHandle. 我发现实际上有一些新的词汇表。例如, 目标实际上是指MethodHandle(而不是发送的对象),而调用站点实际上是“调用”“函数指针”又称MethodHandle的代码。 Also it is essential to understand that MethodHandle API is not replacement for Core Reflection API rather than suplement it. 此外,它是一定要了解MethodHandle API 不更换核心映像API,而不是suplement它。 For example, you can't "discover" all methods with MethodHandle you need Core Reflection API. 例如,您无法使用MethodHandle“发现”所有方法,而您需要Core Reflection API。 But when you "find" you desired method you can switch to MethodHandle and for example, bound some its parameters or "change" (adapt) it's signature to varargs for example. 但是当你“找到”你想要的方法时,你可以切换到MethodHandle,例如,绑定一些参数或者将它的签名“改变”(改编)为varargs。

EDITED: 编辑:

I am still trying to figure out answer. 我仍在努力找出答案。 I wrote some tests that I want to share with all. 我写了一些我想与大家分享的测试。

package alexander.berkovich;

import static org.junit.Assert.assertSame;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.junit.BeforeClass;
import org.junit.Test;

public class MethodHandlerCleanTest {

    public static MethodHandles.Lookup lookup;

    @BeforeClass
    public static void asetUp(){
        lookup = MethodHandles.lookup();
    }

    public static class Check {
        public void primitive(final int i){
        }
        public void wrapper(final Integer i){
        }
    }

    @Test
    public void testPrimitive() throws Throwable {
        Check check = new Check();

        MethodType type = MethodType.methodType(void.class, int.class);

        MethodHandle mh = lookup.findVirtual(Check.class, "primitive", type);
        mh.invokeWithArguments(check, 1);
        mh.invoke(check, (short)2);
        mh.invoke(check, Integer.valueOf(3));

        Method method = Check.class.getMethod("primitive", int.class);
        method.invoke(check, (short)20);
        method.invoke(check, Integer.valueOf(21));

    }

    @Test
    public void testWrapper() throws Throwable {
        Check check = new Check();

        MethodType type = MethodType.methodType(void.class, Integer.class);

        MethodHandle mh = lookup.findVirtual(Check.class, "wrapper", type);
        mh.invoke(check, 2);

        Method method = Check.class.getMethod("wrapper", Integer.class);
        method.invoke(check, 20);

    }

    @SuppressWarnings("unused")
    public static class StaticInnerClass {

        public static String staticName;
        public String name;


        public void foo(){}

        public static void staticFoo(){}

    }

    @Test
    public void testStaticInnerClassStaticField() throws Throwable {
        MethodHandle mhSet = lookup.findStaticSetter(StaticInnerClass.class, "staticName", String.class);
        String expected = "mama";
        mhSet.invoke(expected);

        MethodHandle mhGet = lookup.findStaticGetter(StaticInnerClass.class, "staticName", String.class);
        Object obj = mhGet.invoke();
        String value = (String)obj;
        assertSame(expected, value);

    }

    @Test
    public void testStaticInnerClassField() throws Throwable {
        StaticInnerClass sut = new StaticInnerClass();
        Field f = StaticInnerClass.class.getDeclaredField("name");
        MethodHandle mhSetUnreflect = lookup.unreflectSetter(f); 
        String expectedUnreflect = "unreflect";
        mhSetUnreflect.invoke(sut, expectedUnreflect);


        MethodHandle mhSet = lookup.findSetter(StaticInnerClass.class, "name", String.class);
        String expected = "mama";
        mhSet.invoke(sut, expected);

        MethodHandle mhGet = lookup.findGetter(StaticInnerClass.class, "name", String.class);
        Object obj = mhGet.invoke(sut);
        String value = (String)obj;
        assertSame(expected, value);

    }

    @Test
    public void testStaticInnerClassConstructor() throws Throwable {
        StaticInnerClass sut = new StaticInnerClass();
        MethodType type = MethodType.methodType(void.class);
        MethodHandle mh = lookup.findConstructor(StaticInnerClass.class, type);
        mh.invoke();
    }

    @Test
    public void testStaticInnerClassMethod() throws Throwable {
        StaticInnerClass sut = new StaticInnerClass();
        MethodType type = MethodType.methodType(void.class);
        MethodHandle mh = lookup.findVirtual(StaticInnerClass.class, "foo", type);
        mh.invoke(sut);
    }

    @Test
    public void testStaticInnerClassStaticMethod() throws Throwable {
        MethodType type = MethodType.methodType(void.class);
        MethodHandle mh = lookup.findStatic(StaticInnerClass.class, "staticFoo", type);
        mh.invoke();
    }

    @SuppressWarnings("unused")
    private class InnerClass {
        public String name;

        public void foo(){}

    }
    @Test
    public void testInnerClassField() throws Throwable {
        InnerClass sut = new InnerClass();
        MethodHandle mhSet = lookup.findSetter(InnerClass.class, "name", String.class);
        String expected = "mama";
        mhSet.invoke(sut, expected);

        MethodHandle mhGet = lookup.findGetter(InnerClass.class, "name", String.class);
        Object obj = mhGet.invoke(sut);
        String value = (String)obj;
        assertSame(expected, value);

    }


    @Test
    public void testInnerClassConstructor() throws Throwable {
        MethodType type = MethodType.methodType(void.class, MethodHandlerCleanTest.class);

        //default constructor is private
        Field field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
        field.setAccessible(true);
        MethodHandles.Lookup trustedLookup = (MethodHandles.Lookup) 
                field
                .get(null);

        MethodHandle mh = trustedLookup.findConstructor(InnerClass.class, type);
        mh.invoke(this);
    }


    @Test
    public void testInnerClassMethod() throws Throwable {
        InnerClass sut = new InnerClass();
        MethodType type = MethodType.methodType(void.class);
        MethodHandle mh = lookup.findVirtual(InnerClass.class, "foo", type);
        mh.invoke(sut);
    }

} }

How can I obtain all declared method through MethodHandles.lookup()? 如何通过MethodHandles.lookup()获取所有声明的方法? How can I obtain all declared fields? 如何获取所有声明的字段?

Think of java.lang.invoke as a (fast performing) extension to reflection (java.lang.reflect) - ie "invoke" classes are dependent upon "reflection" classes. 将java.lang.invoke视为反射(快速执行)的反射扩展(java.lang.reflect) - 即“调用”类依赖于“反射”类。

  • You obtain reference to all methods/constructors/fields via reflection (java.lang.Class and java.lang.reflect): 您可以通过反射(java.lang.Class和java.lang.reflect)获取对所有方法/构造函数/字段的引用:

     java.lang.Class<?> someClass = ...; // obtain a Class somehow // Returns all constructors/methods/fields declared in class, // whether public/protected/package/private, // but does NOT include definitions from any ancestors: java.lang.reflect.Constructor<?>[] declaredConstructors = someClass.getDeclaredConstructors(); java.lang.reflect.Method[] declaredMethods = someClass.getDeclaredMethods(); java.lang.reflect.Field[] declaredFields = someClass.getDeclaredFields(); // Returns all constructors/methods/fields declared as public members // in the class AND all ancestors: java.lang.reflect.Constructor<?>[] publicInheritedConstructors = someClass.getConstructors(); java.lang.reflect.Method[] publicInheritedMethods = someClass.getMethods(); java.lang.reflect.Field[] publicInheritedFields = someClass.getFields(); 
  • You convert them to MethodHandles via java.lang.invoke.MethodHandles.Lookup: 您可以通过java.lang.invoke.MethodHandles.Lookup将它们转换为MethodHandles:

     java.lang.invoke.MethodType mt; java.lang.invoke.MethodHandle mh; java.lang.invoke.MethodHandles.Lookup lookup = MethodHandles.lookup(); // process methods for (java.lang.reflect.Method method: declaredMethods) { mh = lookup.unreflect(method); // can call mh.invokeExact (requiring first parameter to be the class' // object instance upon which the method will be invoked, followed by // the methodparameter types, with an exact match parameter and return // types) or // mh.invoke/invokeWithArguments (requiring first parameter to be the // class' object instance upon which the method will be invoked, // followed by the method parameter types, with compatible conversions // performed on input/output types) } // process constructors for (java.lang.reflect.Constructor<?> constructor: declaredConstructors) { mh = lookup.unreflectConstructor(constructor); // can call mh.invokeExact or // mh.invoke/invokeWithArguments } // process field setters for (java.lang.reflect.Field field: declaredFields) { mh = lookup.unreflectSetter(field); // can call mh.invokeExact or // mh.invoke/invokeWithArguments } // process field getters for (java.lang.reflect.Field field: declaredFields) { mh = lookup.unreflectGetter(field); // can call mh.invokeExact or // mh.invoke/invokeWithArguments } 
  • You can determine if the signature of methods/constructors/fields via java.lang.reflect: 您可以通过java.lang.reflect确定方法/构造函数/字段的签名:

     // If generics involved in method signature: Type[] paramTypes = method.getGenericParameterTypes(); Type returnType = method.getGenericReturnType(); // Note: if Class is non-static inner class, first parameter of // getGenericParameterTypes() is the enclosing class // If no generics involved in method signature: Class<?>[] paramTypes = declaredMethod.getParameterTypes(); Class<?> returnType = declaredMethod.getReturnType(); // Note: if Class is non-static inner class, first parameter of // getParameterTypes() is the enclosing class // Same method calls for declaredConstructor 
  • You can determine if the methods/constructors/fields are static via java.lang.reflect: 您可以通过java.lang.reflect确定方法/构造函数/字段是否是静态的:

     int modifiers = method.getModifiers(); // same method for constructor/field boolean isStatic = java.lang.Modifier.isStatic(modifiers); 

What is difference betweeen MethodHandle.invoke(), MethodHandle.invokeExact() and MethodHandle.invokeWithArguments()? MethodHandle.invoke(),MethodHandle.invokeExact()和MethodHandle.invokeWithArguments()之间的区别是什么?

  • see http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandle.html#invoke%28java.lang.Object...%29 请参阅http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandle.html#invoke%28java.lang.Object...%29
  • If the MethodHandle is for a non-static method, the first parameter provided to these methods is an instance of the Class which declares the method. 如果MethodHandle用于非静态方法,则提供给这些方法的第一个参数是声明该方法的Class的实例。 The method is invoked on this instance of the class (or on the Class itself for static methods). 在该类的实例上调用该方法(或者在静态方法的类本身上调用)。 If the Class is a non-static inner class, the second parameter is an instance of the enclosing/declaring class. 如果Class是非静态内部类,则第二个参数是封闭/声明类的实例。 The subsequent parameters are the method signature parameters, in order. 后续参数是方法签名参数,按顺序。
  • invokeExact does not do automatic compatible type conversion on input parameters. invokeExact不对输入参数执行自动兼容类型转换。 It requires parameter values (or parameter expressions) to be an exact type match to the method signature, with each parameter provided as a separate argument OR all arguments provided together as an array (signature: Object invokeExact(Object... args) ). 它要求参数值(或参数表达式)与方法签名完全匹配,每个参数作为单独的参数提供,或者所有参数作为数组一起提供(签名: Object invokeExact(Object... args) )。
  • invoke requires the parameter values (or parameter expressions) to be type compatible match to the method signature - automatic type conversions are performed, with each parameter provided as a separate argument OR all arguments provided together as an array (signature: Object invoke(Object... args)) invoke要求参数值(或参数表达式)与方法签名类型兼容 - 执行自动类型转换,每个参数作为单独的参数提供或者所有参数作为数组一起提供(签名:Object invoke(Object。 .. args))
  • invokeWithArguments requires the parameter values (or parameter expressions) to be type compatible match to the method signature - automatic type conversions are performed, with each parameter provided within a List (signature: Object invokeWithArguments(List<?> arguments) ) invokeWithArguments要求参数值(或参数表达式)与方法签名类型兼容 - 执行自动类型转换,每个参数在List中提供(签名: Object invokeWithArguments(List<?> arguments)

I will be appreciate for tutorial about using MethodHandle API for Java devloper 关于为Java devloper使用MethodHandle API的教程,我将不胜感激

There's not much out there, unfortunately. 不幸的是,那里并不多。 You could try the following. 您可以尝试以下方法。 Hopefully, I've given enough info above :^) 希望我已经给出了足够的信息:^)

http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandle.html http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandle.html
http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandles.Lookup.html http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandles.Lookup.html
http://www.java7developer.com/blog/?p=191 http://www.java7developer.com/blog/?p=191
http://www.oraclejavamagazine-digital.com/javamagazine/20130102?pg=52&search_term=methodhandle&doc_id=-1#pg50 http://www.oraclejavamagazine-digital.com/javamagazine/20130102?pg=52&search_term=methodhandle&doc_id=-1#pg50
http://www.amazon.com/Well-Grounded-Java-Developer-techniques-programming/dp/1617290068 http://www.amazon.com/Well-Grounded-Java-Developer-techniques-programming/dp/1617290068

What is difference betweeen MethodHandle.invoke(), MethodHandle.invokeExact() and MethodHandle.invokeWithArguments() MethodHandle.invoke(),MethodHandle.invokeExact()和MethodHandle.invokeWithArguments()之间有什么区别

Since I also struggled with this, I decided to revisit this question and write an example that shows exactly what the semantic differences between these methods are. 由于我也在努力解决这个问题,所以我决定重新审视这个问题并编写一个示例,准确显示这些方法之间的语义差异。

The major differences are: 主要区别是:

  1. invokeExact only accepts exact arguments and return types, and does not accept arguments as an array. invokeExact 接受精确的参数和返回值,并且接受参数数组。 Calling eg method signature (Integer,Integer)Integer with an int argument is not allowed, but also calling it with an Object argument is not allowed, even if the object is actually of type Integer - the compiletime type of the argument must be of class Integer, not the runtime instance: 调用例如方法签名(Integer,Integer)Integer不允许(Integer,Integer)Integerint参数,但也不允许使用Object参数调用它,即使该对象实际上是Integer类型 - 参数的编译时类型必须是类整数,而不是运行时实例:

     Object arg = 1; Object[] args = {1,1}; Integer i = (Integer)handle.invokeExact(1,1); // OK Object o = handle.invokeExact(arg,arg); // ERROR handle.invokeExact(args); // ERROR 

  1. invoke automatically converts argument types and return types and also converts between primitive types and the corresponding wrapper classes. invoke自动转换参数类型和返回类型,并在原始类型和相应的包装类之间进行转换。 But it does also not accept arguments as an array. 但它也不接受作为数组的参数。 Eg for method signature (Integer,Integer)Integer : 例如,方法签名(Integer,Integer)Integer

     Object arg = 1; Object[] args = {1,1}; Integer i = (Integer)handle.invoke(1,1); // OK Object o = handle.invoke(arg,arg); // OK! o = handle.invoke(args); // ERROR 

  1. invokeWithArguments removes all these restrictions and works very similar to Method#invoke - you can also supply an array (or a java.util.List ) as an argument (but this flexibility comes with a huge performance penalty). invokeWithArguments删除所有这些限制,并且与Method#invoke非常相似 - 您还可以提供一个数组(或java.util.List )作为参数(但这种灵活性会带来巨大的性能损失)。 Eg for method signature (Integer,Integer)Integer : 例如,方法签名(Integer,Integer)Integer

     Object arg = 1; Object[] args = {1,1}; Integer i = (Integer)handle.invokeWithArguments(1,1); // OK Object o = handle.invokeWithArguments(arg,arg); // OK o = handle.invokeWithArguments(args); // OK! 

Here a complete example: 这是一个完整的例子:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.WrongMethodTypeException;
import java.lang.reflect.Method;
import java.util.Arrays;

public class MethodHandleTest {

    public static class TestClass{
        public int test(int a, Integer b){
            return a+b;
        }
    }

    public static void main(String[] args) throws Throwable{
        Method method = TestClass.class.getMethod("test", int.class, Integer.class);
        MethodHandle handle = MethodHandles.lookup().unreflect(method).bindTo(new TestClass());

        int arg_int = 1;
        Integer argInteger = 1;

        Object[] argArray = {1,1};

        //----------------------------
        // MethodHandle#invokeExact() 
        //----------------------------

        // ONLY an exact invocation is allowed for invokeExact:
        int result = (int) handle.invokeExact(arg_int, argInteger); 

        // inexact first argument type -> throws WrongMethodTypeException - "expected (int,Integer)int but found (Integer,Integer)int"
        Exception e = null;
        try {
            result = (int) handle.invokeExact(argInteger,argInteger);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        // inexact return type -> throws WrongMethodTypeException - "expected (int,Integer)int but found (int,Integer)Integer"
        try {
            result = (Integer) handle.invokeExact(arg_int,argInteger);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        // inexact return type -> throws WrongMethodTypeException - "expected (int,Integer)int but found (int,Integer)Object"
        try {
            Object o = handle.invokeExact(arg_int,argInteger);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        // "argObject" is ALSO NOT OK! - the compile time type of the argument must be of class Integer, not the runtime instance!
        // -> throws WrongMethodTypeException - "expected (int,Integer)int but found (int,Object)int"
        Object argObject = argInteger;
        try {
            result = (int) handle.invokeExact(arg_int,argObject);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        // Array of the arguments NOT allowed -> throws WrongMethodTypeException - "expected (int,Integer)int but found (Object[])int"
        try {
            result = (int) handle.invokeExact(argArray);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        // But explicit cast of first or second argument is OK
        result = (int) handle.invokeExact((int)argInteger,argInteger);
        result = (int) handle.invokeExact(arg_int,(Integer)arg_int);

        //-----------------------
        // MethodHandle#invoke() 
        //-----------------------

        // invoke() with exact types - OK -> actually calls invokeExact() behind the scenes
        result = (int) handle.invoke(arg_int, argInteger);

        // implicit conversion of inexact arguments and return type -> OK!
        result = (Integer) handle.invoke(argInteger,argInteger); 

        // Object arguments or return type is OK!
        Object o = handle.invoke(argObject,argObject);

        // Array of the arguments NOT allowed -> throws WrongMethodTypeException - "cannot convert MethodHandle(int,Integer)int to (Object[])int"
        try {
            result = (int) handle.invoke(argArray);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        //------------------------------------
        // MethodHandle#invokeWithArguments() 
        //------------------------------------

        // invoke() with exact types - OK
        result = (int) handle.invokeWithArguments(arg_int,arg_int);

        // implicit conversion of inexact arguments and return type -> OK
        result = (Integer) handle.invokeWithArguments(argInteger,argInteger); 

        // Object arguments or return type is OK!
        o = handle.invoke(argObject,argObject);

        // Array of the arguments -> OK
        result = (int) handle.invokeWithArguments(argArray);

        // List of arguments possible -> same as calling invokeWithArguments(list.toArray())
        result = (int) handle.invokeWithArguments(Arrays.asList(argArray));


    }
}

As Balder said both calls invoke and invokeExact do not accept arguments passed in as an array. 正如Balder所说,两个调用都invoke并且invokeExact不接受作为数组传入的参数。 (However, they do accept array arguments.) (但是,它们接受数组参数。)

int[] args = {1,1};

// handle for Math.addExact(int, int)
Object o = handle.invokeExact(1,1); // OK
Object o = handle.invoke(1,1); // OK
Object o = handle.invokeExact(args); // ERROR
Object o = handle.invoke(args); // ERROR

The error is always a wrong type exception: 错误总是错误的类型异常:

java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(int, int)int to (Object[])int

So it does not unpack the array. 所以它没有解压缩数组。 But passing in an array where it is required works: 但是传入一个需要它的数组:

// handle for Arrays.sort(int[]) 
handle.invokeExact(args); // OK
handle.invoke(args); // OK

As Balder said, implementing the desired behavior with invokeWithArguments() may incur a quite substantial overhead. 正如Balder所说,使用invokeWithArguments()实现所需的行为可能会产生相当大的开销。

In order to get the desired behavior for unpacking argument lists as known from varargs, one has to turn the handle into a spreader: 为了从varargs中获得解压缩参数列表所需的行为,必须将句柄转换为spreader:

 // handle for Math.addExact(int, int)
 handle = handle.asSpreader(int[].class, 2);
 handle.invokeExact(args); // OK
 handle.invoke(args); // OK

Of course, the same functionality as for explicit argument passing accounts again when the array is defined to be generic: 当然,当数组被定义为泛型时,与显式参数传递帐户相同的功能:

 Object[] args = new Object[]{1,1};
 // handle for Math.addExact(int, int)
 handle = handle.asSpreader(int[].class, 2);
 handle.invokeExact(args); // ERROR
 handle.invoke(args); // OK

I have not conducted any performance comparison between the calls. 我没有在电话之间进行任何性能比较。 For those interested, it is quite straight forward to extend this benchmark: http://rick-hightower.blogspot.de/2013/10/java-invoke-dynamic-examples-java-7.html 对于那些感兴趣的人来说,扩展这个基准是非常简单的: http//rick-hightower.blogspot.de/2013/10/java-invoke-dynamic-examples-java-7.html

Details: 细节:

Essentially invokeWithArguments does similar things but does so for every call: 基本上invokeWithArguments做类似的事情,但每次调用都这样做:

 public Object invokeWithArguments(Object... arguments) throws Throwable {
    MethodType invocationType = MethodType.genericMethodType(arguments == null ? 0 : arguments.length);
    return invocationType.invokers().spreadInvoker(0).invokeExact(asType(invocationType), arguments);
}

So creating a spreader once and reusing it, will most likely yield similar performance as invoke and invokeExact . 因此,创建一个spreader invokeExact它,很可能会产生与invokeinvokeExact类似的性能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM