星空网 > 软件开发 > Java

Java:反射

初识Java反射机制:

Java:反射

  从上面的描述可以看出Java的反射机制使得Java语言可以在运行时去认识在编译时并不了解的类/对象的信息,并且能够调用相应的方法或修改属性的值。Java反射机制的核心就是允许在运行时通过Java Reflection APIs来取得已知名字的class类的内部信息(包括其modifiers(如public, static等)、superclass(如Object)、interfaces(如Serializable),也包括fields和methods的所有信息),动态地生成此类,并调用其方法或修改其域(甚至是本身声明为private的域或方法)。这种反射机制为Java本身带来了动态性,是一个非常强大的工具,能够让代码变得更加灵活。

 

各种反射用法简单示例:

Java:反射

Java:反射

Java:反射

Java:反射

 

细说Java反射机制:

 

此处参考了这篇文章:Java反射机制  http://blog.csdn.net/jackiehff/article/details/8509075

 

(1)Class对象

  Class对象是Java反射的基础,它包含了与类相关的信息,实际上,Class对象是用来创建类的所有对象的。Class对象是java.lang.Class<T>这个类生成的对象,其中参数T表示用此Class对象建模的类的类型。例如,String.class的类型是 Class<String>;如果被建模的类的类型未知,则使用Class<?>,下图有所体现:

Java:反射

以下是Java API的描述:

  Class类的实例表示正在运行的 Java应用程序中的类和接口基本的Java类型(booleanbytecharshortintlongfloatdouble)和关键字void也表示为Class对象。所有具有相同元素类型和维数的数组都共享同一个Class对象。

  实际上,每个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中)。如果我们想生成某个类的对象,运行这个程序的虚拟机(JVM)将使用类加载器检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件,并将其载入,一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

  Class没有公共构造方法,Class对象是在加载类时由Java虚拟机以及通过调用类加载器中的defineClass方法自动构造的,获取Class对象有三种方式:

A、通过实例变量的getClass()方法。

示例:Class c = new String("abc").getClass();

B、通过Class类的静态方法——forName()来实现。

示例:Class c =Class.forName("className");

注:当使用Class.forName()方法时,你必须提供完整类名(类名要包括包名)。例如,如果MyObject是位于包com.test下,那么类的完整类名是com.test.MyObject。如果运行时在类路径上找不到指定的类,Class.forName()方**抛出一个ClassNotFoundException。

C、使用类字面常量或TYPE字段。

示例:Class c = MyObject.class;

类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型,这种方式不仅更简单,而且更安全,因为它在编译时就会受到检查,并且根除了对forName方法的调用,所以也更高效,建议使用“.class”的形式。

Class c = Integer.TYPE;

TYPE是基本数据类型的包装类型的一个标准字段,它是一个引用,指向对应的基本数据类型的Class对象,附表如下,两边等价:

Java:反射

(2)获取类名:从Class对象中可以获取两个不同的类名。

A、完整类名(包括包名)可以使用getName()或getCanonicalName()方法获取

示例:

Class c = MyObject.class;String className = c.getName();

B、如果想要获取不含包名的类名可以使用getSimpleName()方法

示例:

Class c = MyObject.class;String className = c.getSimpleName();

(3)获取类的修饰符:使用Class对象可以获取一个类的修饰符,类的修饰符即为关键字"public"、"private"、"static"等,修饰符被包装进一个int

示例:

Class c= MyObject.class;int modifiers = c.getModifiers();

每一个修饰符都是一个标志位,可以使用java.lang.reflect.Modifier中的以下方法来检验修饰符:

Modifier.isAbstract(int modifiers)Modifier.isFinal(int modifiers)Modifier.isInterface(int modifiers)Modifier.isNative(int modifiers)Modifier.isPrivate(int modifiers)Modifier.isProtected(int modifiers)Modifier.isPublic(int modifiers)Modifier.isStatic(int modifiers)Modifier.isStrict(int modifiers)Modifier.isSynchronized(int modifiers)Modifier.isTransient(int modifiers)Modifier.isVolatile(int modifiers)

 

(4)获取类实现的接口:一个类可以实现多个接口,通过给定的类可以获取这个类所实现的接口列表

示例:

Class c= MyObject.class;Class[] interfaces = c.getInterfaces();

在Java反射机制中,接口也由Class对象表示。

注意:只有给定类声明实现的接口才会返回。例如,如果类A的父类B实现了一个接口C,但类A并没有声明它也实现了C,那么C不会被返回到数组中。即使类A实际上实现了接口C,因为它的父类B实现了C。

所以如果想得到一个给定的类所实现接口的完整列表,需要递归访问类和其超类。

 

(5)获取类的构造函数:使用Class对象可以获取类的构造函数

示例:

Class c= MyObject.class;Constructor[] constructors = c.getConstructors();

Constructor数组为类中每一个声明为public构造函数保存一个Constructor实例

 

如果知道要访问的构造函数确切的参数类型,可以不获取构造函数数组,而是直接获取该构造函数

下述示例将返回给定类中接受一个字符串作为参数的公共构造函数:

Class c= MyObject.class;//MyObject有一个参数为字符串的公共构造函数Constructor constructor = c.getConstructor(new Class[]{String.class});

如果没有匹配给定的构造函数参数,在这个例子当中是String.class,会抛出 NoSuchMethodException 异常.

 

获取类的构造函数并实例化对象,这是通过Java类java.lang.reflect.Constructor来实现的。

示例:

//获取使用字符串作为参数的constructorConstructor constructor = MyObject.class.getConstructor(String.class);MyObject myObject = (MyObject)constructor.newInstance("constructor-arg1");

Constructor.newInstance() 方法使用可变长度的参数,但是在调用构造函数时必须为每一个参数提供一个准确的参量。在这个例子中,构造函数接受一个字符串作为参数,所以必须要提供一个字符串。

 

(6)获取类的字段(成员变量):使用Class对象可以获取类的字段(成员变量)

示例:

Class c= MyObject.class;Field[] methods = c.getFields();

Field数组为类中每一个声明为public字段保存一个Field实例。

此时某些读者可能会问类中声明为private字段该如何获取,其实某些细心的读者应该已经发现此处使用的一些方法与本文最开始的示例中使用的方法不太一样,后文会进一步揭开反射的神秘面纱

 

一旦获得一个 Field实例,可以通过Field.getName()方法获取它的字段名字

示例:

Field field = ... //获取 field 对象String fieldName = field.getName();

 

如果知道要访问的字段名称,可以这样获取该字段:

Class c= MyObject.classField field = c.getField("FieldName");

如果不存在getField()方法中所给参数名字对应的字段,会抛出NoSuchFieldException异常。

 

可以通过Field.getType()方法确定一个字段的类型(String,int等),如下:

Field field = aClass.getField("someField");Object fieldType = field.getType();

 

可以通过Field.get() 和 Field.set()方法获取和设置某个字段的值,如下:

Class c= MyObject.classField field = c.getField("FieldName");MyObject object = new MyObject();//构造一个MyObject类型的对象实例Object value = field.get(object);field.set(objet, value);

传递给get和set方法的对象实例应该是拥有该字段的类的一个实例。在上述的例子中使用了MyObject的一个实例,若获取的字段为静态字段(public static),则给get和set方法传递的对象实例应该为"null",而不是上述示例中传递的object参数。

 

(7)使用Class对象可以获取类的成员方法

Class c = MyObject.class;Method[] methods = c.getMethods();

Method数组将为类中每一个声明为public的方法保存一个Method实例。

 

如果知道要访问的方法的参数类型,可以不必获取方法数组,而是直接获取该方法

下述示例返回给定类中接受一个字符串作为参数的公共方法”doSomething”:

Class c= MyObject.class;Method method = c.getMethod("doSomething", new Class[]{String.class});

如果没有方法匹配所给的方法名和参数,在这个例子中是String.class,将抛出NoSuchMethodException异常。

 

如果你想访问的方法没有参数,传递 null作为参数类型数组,如下:

Class c= MyObject.class;Method method = c.getMethod("doSomething", null);

 

使用Method对象可以获取方法的所有参数,如下:

Method method = ... //获取 method – 如上Class[] parameterTypes = method.getParameterTypes();

 

使用Method对象可以获取方法的返回值类型,如下:

Method method = ... //获取 method – 如上Class returnType = method.getReturnType();

 

动态执行方法

Method method = MyObject.class.getMethod("doSomething", String.class);Object returnValue = method.invoke(null, "parameter-value1");

上述代码中invoke()方法的第一个参数是你想要调用方法的对象,如果该方法是静态的,使用"null",而不是一个对象实例。在这个例子中,如果doSomething(String.class)不是静态的,需要提供有效的MyObject的对象实例,而不是使用"null"作为参数;

Method.invoke(Object target, Object ...parameters)方法接受可变长度的参数,但是在调用时必须为每一个参数提供一个准确的参量。在这个例子中,方法以字符串作为参数的,所以必须提供一个字符串。

 

(8)检测一个给定的类有哪些get和set方法

可以通过扫描一个类的所有方法并检查每个方法是否是get或set方法,如下代码段可用来找到类的get和set方法:

public static void printGettersSetters(Class c) {    Method[] methods = c.getMethods();    for (Method method : methods) {      if (isGetter(method)) {        System.out.println("getter: " + method);      }      if (isSetter(method)) {        System.out.println("setter: " + method);      }    }  }  public static boolean isGetter(Method method) {    if (!method.getName().startsWith("get")) {      return false;    }    if (method.getParameterTypes().length != 0) {      return false;    }    if (void.class.equals(method.getReturnType())) {      return false;    }    return true;  }  public static boolean isSetter(Method method) {    if (!method.getName().startsWith("set")) {      return false;    }    if (method.getParameterTypes().length != 1) {      return false;    }    return true;  }

 

(9)访问私有字段

此处解答前面留下的疑问: Class.getFields()和Class.getField("FieldName")方法仅返回public字段,要想访问private字段需要调用 Class.getDeclaredFields()或Class.getDeclaredField("FieldName")方法

示例:

public class Test {    private String name = null;    public Test(String name) {      this.name = name;    }}

Test test= new Test ("cat");Field privateField = Test.class. getDeclaredField("name");privateField.setAccessible(true);String fieldValue = (String) privateField.get(test);System.out.println("fieldValue = " + fieldValue);

注意Test.class.getDeclaredField("name")的使用,该方法仅仅返回指定类声明的字段,而不包括其任何父类中声明的字段。

注意上述代码中标色语句,通过调用Field.setAcessible(true)方法关闭了特定Field实例的访问检查,现在通过反射可以访问它,即使它是私有的或被保护的。编译器不允许普通代码访问该字段,因为仅适用于反射。

 

(10)访问私有方法

Class.getMethods()Class.getMethod(String name, Class[]parameterTypes)方法仅返回public方法,想要访问私有方法需要调用Class.getDeclaredMethods()Class.getDeclaredMethod(String name,Class[] parameterTypes)方法

示例:

public class Test {  private void call(){    System.out.println("Hello");  }}

Test test = new Test();Method privateMethod = Test.class.getDeclaredMethod("call", null);privateMethod.setAccessible(true);privateMethod.invoke(test, null);

注意Test.class.getDeclaredMethod("call",null)的使用,该方法仅仅返回指定类声明的方法,而不包括其任何父类中声明的方法。

注意上述代码中标色语句,通过调用Method.setAcessible(true)方法关闭了特定Method实例的访问检查,现在通过反射可以访问它,即使它是私有的或被保护的。编译器不允许普通代码访问该方法,因为仅适用于反射。

 

(11)数组

创建数组是通过Java反射机制由java.lang.reflect.Array类来完成的,如下:

int[] array = (int[]) Array.newInstance(int.class, 3);

上述代码创建了一个int数组,Array.newInstance()方法的第一个参数告诉我们数组的元素类型,第二个参数声明了数组需要为多少个元素分配空间。

 

访问数组的元素可以通过Array.get(...)Array.set(...)方法实现,如下:

int[] array = (int[]) Array.newInstance(int.class, 3);Array.set(array, 0, 123);Array.set(array, 1, 456);Array.set(array, 2, 789);System.out.println("array[0] = " + Array.get(array, 0));

 

获得某种类型的数组的Class对象

Class c = String[].class;

除了上述方式外,还可以使用 Class.forName()方法

例如,可以通过如下方式获取int数组的Class对象:

Class c = Class.forName("[I");

解说:字母I在JVM中代表一个int类型,左边的 [ 表示它是一个int数组的class,这对其它的基本类型也有效。

对于对象,需要使用一个稍微不同的记号:

Class c = Class.forName("[Ljava.lang.String;");

解说:在左边的” [L”和右边的”  ; ”之间的类名代表对象数组的类型。

另外一个需要注意的是不能使用Class.forName()方法获取基本类型的Class对象,下面两个例子都会抛出ClassNotFoundException异常:

Class c1 = Class.forName("I");Class c2 = Class.forName("int");

可以像下面这样获取基本数据类型和对象的Class名:

public Class getClass(String className){    if("int" .equals(className)){      return int .class;    }    if("long".equals(className)){      return long.class;    }    ...    return Class.forName(className);}

 

一旦获得了某个类型的Class对象,有一个简单的方法可以用来获得该类型的数组的Class对象。解决方案是创建所需类型的空数组并从这个空数组获取Class对象,如下:

Class cl = getClass("ClassName");//此处调用上述getClass()方法Class theClass = Array.newInstance(cl, 0).getClass();

这提供了一个单一的、统一的方法来获取任何类型的数组的Class对象。

 

(12)注解

使用Java反射可以在运行时访问Java类中的注解,那么首先要知道什么是注解?注解是Java5的一个新功能,可以在Java代码中插入注释或元数据。这些注解可以在编译时由预编译工具进行处理,也可以在运行时通过Java反射机制来处理。

下面是一个类注解的示例:

注解的定义:

package com.test;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface MyAnnotation {    public String name();    public String value();}

注解的使用:

package com.test;@MyAnnotation(name="someName", value = "Hello World")public class TheClass {}

解说:注解像接口那样定义,TheClass类的上面有@MyAnnotation注解,在interface前面的”@”表明它是一个注解。一旦定义了某个注解就可以在代码中使用它,就像上面的例子那样。

在注解的定义中的两个指令@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)表明注解是如何使用的

@Retention(RetentionPolicy.RUNTIME)表明在运行时可以使用反射来访问这个注解,如果没有设置这个指令,在运行时这个注解将不会被保存,因此通过反射访问不到。

@Target(ElementType.TYPE) 表明注解仅能用于类、接口或枚举声明等类型上,你也可以指定METHODFIELD,或者是@Target什么都不指定,这样它可以用在任何程序元素上。

 

使用Class对象可以获取类的注解

例如:

package com.test;import java.lang.annotation.Annotation;public class WW {    public static void main(String[] args){    Class c = TheClass.class;    Annotation[] annotations = c.getAnnotations();//此处获得类的所有注解for(Annotation annotation : annotations){        if(annotation instanceof MyAnnotation){          MyAnnotation myAnnotation = (MyAnnotation) annotation;          System.out.println("name: " + myAnnotation.name());          System.out.println("value: " + myAnnotation.value());         }    }      }}

也可以直接指定需要获取的类注解,如下:

package com.test;import java.lang.annotation.Annotation;public class WW {    public static void main(String[] args){    Class aClass = TheClass.class;    Annotation annotation = aClass.getAnnotation(MyAnnotation.class);//此处获得类的某个指定的注解if(annotation instanceof MyAnnotation){         MyAnnotation myAnnotation = (MyAnnotation) annotation;         System.out.println("name: " + myAnnotation.name());         System.out.println("value: " + myAnnotation.value());    }  }}

 

使用Method对象可以获取方法的注解

示例:

package com.test;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyAnnotation {    public String name();    public String value();}

注:此处注解的定义与前述注解的定义有不一样的地方

package com.test;public class TheClass {  @MyAnnotation(name="someName",value = "Hello World")  public void doSomething(){}}

package com.test;import java.lang.annotation.Annotation;import java.lang.reflect.Method;public class WW {    public static void main(String[] args){    Class c= TheClass.class;    Method method = null;    try {      method = c.getMethod("doSomething", null);      Annotation[] annotations = method.getDeclaredAnnotations();//此处获取指定方法的所有注解for(Annotation annotation : annotations){           if(annotation instanceof MyAnnotation){             MyAnnotation myAnnotation = (MyAnnotation) annotation;             System.out.println("name: " + myAnnotation.name());             System.out.println("value: " + myAnnotation.value());           }      }    } catch (NoSuchMethodException e) {      e.printStackTrace();    } catch (SecurityException e) {      e.printStackTrace();    }          }}

获取指定方法的某个特定的注解(与上例相比仅测试代码不一样):

package com.test;import java.lang.annotation.Annotation;import java.lang.reflect.Method;public class WW {    public static void main(String[] args){    Class c= TheClass.class;    Method method = null;    try {      method = c.getMethod("doSomething", null);      Annotation annotation = method.getAnnotation(MyAnnotation.class);//此处获取指定方法的某个特定的注解if(annotation instanceof MyAnnotation){          MyAnnotation myAnnotation = (MyAnnotation) annotation;          System.out.println("name: " + myAnnotation.name());          System.out.println("value: " + myAnnotation.value());      }    } catch (NoSuchMethodException e) {      e.printStackTrace();    } catch (SecurityException e) {      e.printStackTrace();    }          }}

 

获取方法参数的注解

注解的定义(注意下述代码中着色部分):

package com.test;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.PARAMETER)public @interface MyAnnotation {    public String name();    public String value();}

注解的使用(位于方法参数前):

package com.test;public class TheClass {  public void doSomething(@MyAnnotation(name="aName", value="aValue")String parameter){}}

测试(Method.getParameterAnnotations()方法返回的是二维的Annotation数组,每个方法参数都有一个一维的Annotation数组):

package com.test;import java.lang.annotation.Annotation;import java.lang.reflect.Method;public class WW {    public static void main(String[] args){    Class c= TheClass.class;    Method method = null;    try {      method = c.getMethod("doSomething",new Class[]{String.class});      Annotation[][] parameterAnnotations = method.getParameterAnnotations();      Class[] parameterTypes = method.getParameterTypes();      int i=0;      for(Annotation[] annotations : parameterAnnotations){           Class parameterType = parameterTypes[i++];           for(Annotation annotation : annotations){            if(annotation instanceof MyAnnotation){              MyAnnotation myAnnotation = (MyAnnotation) annotation;              System.out.println("param: " + parameterType.getName());              System.out.println("name : " + myAnnotation.name());              System.out.println("value: " + myAnnotation.value());            }           }      }    } catch (NoSuchMethodException e) {      e.printStackTrace();    } catch (SecurityException e) {      e.printStackTrace();    }          }}

 

获取字段的注解

注解的定义:

package com.test;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface MyAnnotation {    public String name();    public String value();}

注解的使用:

package com.test;public class TheClass {  @MyAnnotation(name="aName", value="aValue")  public String myField = null;}

测试:

package com.test;import java.lang.annotation.Annotation;import java.lang.reflect.Field;import java.lang.reflect.Method;public class WW {    public static void main(String[] args){    Class c= TheClass.class;    Field field;    try {      field = c.getField("myField");      Annotation[] annotations = field.getDeclaredAnnotations();//此处获取字段的所有注解for(Annotation annotation : annotations){           if(annotation instanceof MyAnnotation){            MyAnnotation myAnnotation = (MyAnnotation) annotation;            System.out.println("name: " + myAnnotation.name());            System.out.println("value: " + myAnnotation.value());           }      }    } catch (NoSuchFieldException e) {      e.printStackTrace();    } catch (SecurityException e) {      e.printStackTrace();    }          }}

获取指定字段的某个特定的注解(与上例相比仅测试代码不一样):

package com.test;import java.lang.annotation.Annotation;import java.lang.reflect.Field;import java.lang.reflect.Method;public class WW {    public static void main(String[] args){    Class c= TheClass.class;    Field field;    try {      field = c.getField("myField");      Annotation annotation = field.getAnnotation(MyAnnotation.class);if(annotation instanceof MyAnnotation){           MyAnnotation myAnnotation = (MyAnnotation) annotation;           System.out.println("name: " + myAnnotation.name());           System.out.println("value: " + myAnnotation.value());      }    } catch (NoSuchFieldException e) {      e.printStackTrace();    } catch (SecurityException e) {      e.printStackTrace();    }          }}

 

(13)泛型

泛型方法的返回值类型

如果你获取到一个java.lang.reflect.Method 对象,可以获取它的返回值类型信息,下面是一个示例,一个类有一个有参数化返回值类型的方法:

public class MyClass {    protected List<String> stringList = ...;    public List<String> getStringList(){      return this.stringList;    }}

在这个类中可以获取 getStringList()方法的泛型返回值类型。换句话说,可以探测到getStringList()返回的是List<String> 而不仅是一个 List,如下:

Method method = MyClass.class.getMethod("getStringList", null);Type returnType = method.getGenericReturnType();if(returnType instanceof ParameterizedType){    ParameterizedType type = (ParameterizedType) returnType;    Type[] typeArguments = type.getActualTypeArguments();    for(Type typeArgument : typeArguments){      Class typeArgClass = (Class) typeArgument;      System.out.println("typeArgClass = " + typeArgClass);    }}

代码输出 "typeArgClass = class java.lang.String"。

Type[]数组 typeArguments包含一项 -一个代表类java.lang.String Class实例 。 Class实现了 Type接口。

 

方法的泛型参数类型

通过Java反射可以在运行时访问方法的泛型参数的类型,下面的示例中,类有一个使用参数化的List作为参数的方法:

public class MyClass {  protected List<String> stringList = ...;   public void setStringList(List<String> list){    this.stringList = list;  }}

可以访问方法参数的泛型类型,如下:

Method method = Myclass.class.getMethod("setStringList", List.class);Type[] genericParameterTypes = method.getGenericParameterTypes();for(Type genericParameterType : genericParameterTypes){    if(genericParameterType instanceof ParameterizedType){      ParameterizedType aType = (ParameterizedType) genericParameterType;      Type[] parameterArgTypes = aType.getActualTypeArguments();      for(Type parameterArgType : parameterArgTypes){        Class parameterArgClass = (Class) parameterArgType;        System.out.println("parameterArgClass = " + parameterArgClass);      }     }}

代码输出"parameterArgType= class java.lang.String"。

 Type[]数组 parameterArgTypes包含一项 -一个代表类java.lang.String Class实例 。 Class实现了 Type接口。

 

泛型字段类型

可以访问public字段的泛型类型。字段即类的成员变量-静态的或实例变量。下面是一个例子,类有一个实例变量stringList.

public class MyClass {  public List<String> stringList = ...;}

访问:

Field field = MyClass.class.getField("stringList");Type genericFieldType = field.getGenericType(); if(genericFieldType instanceof ParameterizedType){  ParameterizedType aType = (ParameterizedType) genericFieldType;  Type[] fieldArgTypes = aType.getActualTypeArguments();  for(Type fieldArgType : fieldArgTypes){    Class fieldArgClass = (Class) fieldArgType;    System.out.println("fieldArgClass = " + fieldArgClass);  }}

代码将输出"fieldArgClass = class java.lang.String"。

Type数组fieldArgTypes包含一项 – 一个代表类java.lang.String Class实例 。 Class实现了 Type接口。

 

 

 

结束语:

Java:反射

 




原标题:Java:反射

关键词:JAVA

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。

护肤品:https://www.goluckyvip.com/tag/8086.html
Gojek:https://www.goluckyvip.com/tag/8087.html
深圳年审:https://www.goluckyvip.com/tag/80874.html
东莞审计:https://www.goluckyvip.com/tag/80876.html
年销千万:https://www.goluckyvip.com/tag/8088.html
美国炒股开户:https://www.goluckyvip.com/tag/80887.html
如何从抖音上多挣钱?记住这12个字 :https://www.kjdsnews.com/a/1836445.html
连麦专家:2024年到底如何做小红书? :https://www.kjdsnews.com/a/1836446.html
相关文章
我的浏览记录
最新相关资讯
海外公司注册 | 跨境电商服务平台 | 深圳旅行社 | 东南亚物流