重拾Java基础知识:反射

前言

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制

什么是反射?

当我们编写完一个Java项目之后,每个Java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。

反射的工作原理就是借助Class.java、Constructor.java、Method.java、Field.java这四个类在程序运行时动态访问和修改任何类的行为和状态。简单来说就是:反射就是把Java类中的各种成分映射成一个个的Java对象。比如:一个类中的方法,构造函数、属性等等。通过反射把它们变成一个对象。很多框架中都用到了反射,反射是框架设计的灵魂

在正式讲解反射之前,我们先来讲解什么是正射,它们的区别是什么:

public class ReflectTest {
    public String method(){
        return "method";
    }

    public static void main(String[] args) {
        //正射
        ReflectTest reflectTest = new ReflectTest();
        String method = reflectTest.method();
        //反射
        Class<?> aClass = null;
        try {
            aClass = Class.forName("com.test.ReflectTest");
            Method declaredMethod = aClass.getDeclaredMethod("method");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

正射通过new来实例化对象,在程序运行之前确认对象类型,属于静态编译;反射属于动态编译,只有运行时他才会去获得该对象的实例。

获取Class对象

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是JVM中每个类都有该Class(包括基本数据类型)对象。Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。

要操作一个类的字节码,需要首先获取到这个类的字节码,有三种获取的方式:

public class ReflectTest {

    public static void main(String[] args) {
        //方式一:Object的方法
        ReflectTest reflectTest = new ReflectTest();
        reflectTest.getClass();
        //方式二:静态方法
        try {
            Class<?> aClass = Class.forName("com.test.ReflectTest");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //方式三:属性
        Class reflectTestClass = ReflectTest.class;
    }
}

在运行期间,一个类,只有一个Class对象产生。方式一的用法很有趣,对象都实例化了还需要反射,意义不大。

创建新实例

newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。

public class ReflectTest {

    public void method(){
        System.out.println("method");
    }

    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.test.ReflectTest");
            ReflectTest reflectTest = (ReflectTest) aClass.newInstance();
            reflectTest.method();
            /** Output:
             *  method
             */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

构造函数

我们可以通过反射来调用类的构造函数:

public class ReflectTest {

    private String p1;
    private int    p2;

    public ReflectTest() {
    }

    public ReflectTest(String p1, int p2) {
        this.p1 = p1;
        this.p2 = p2;
    }

    private ReflectTest(String p1) {
        this.p1 = p1;
    }

    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.test.ReflectTest");
            System.out.println("==========获取所有公有的构造函数==========");
            Constructor<?>[] constructors = aClass.getConstructors();
            for (Constructor con:constructors) {
                System.out.println(con);
            }
            System.out.println("==========获取指定公有的构造函数==========");
            Constructor<?> constructor = aClass.getConstructor();
            System.out.println(constructor);
            System.out.println("==========获取所有(包括:私有、受保护、默认、公有)的构造函数==========");
            constructors = aClass.getDeclaredConstructors();
            for (Constructor con:constructors) {
                System.out.println(con);
            }
            System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的构造函数==========");
            constructor = aClass.getDeclaredConstructor(String.class);
            System.out.println(constructor);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        /** Output:
         *  ==========获取所有公有的构造函数==========
         *  public com.study.test.ReflectTest(java.lang.String,int)
         *  public com.study.test.ReflectTest()
         *  ==========获取指定公有的构造函数==========
         *  public com.study.test.ReflectTest()
         *  ==========获取所有(包括:私有、受保护、默认、公有)的构造函数==========
         *  private com.study.test.ReflectTest(java.lang.String)
         *  public com.study.test.ReflectTest(java.lang.String,int)
         *  public com.study.test.ReflectTest()
         *  ==========获取指定(包括:私有、受保护、默认、公有)的构造函数==========
         *  private com.study.test.ReflectTest(java.lang.String)
         */
    }
}

getConstructors()方法可以获取反射中所有公有的构造函数getConstructor()方法可以获取反射中指定公有的构造函数getDeclaredConstructors()方法可以获取反射中所有构造函数不受访问权限限制getDeclaredConstructor()方法可以获取反射中指定构造函数不受访问权限限制

其中getConstructor()方法和getDeclaredConstructor()方法中可传入相对应的参数类型这样就能调用相对应的构造函数,如果是无参则可以不填,默认调用无参构造。

实例化

既然知道如何通过反射得到构造函数,就可以通过newInstance()方法去调用对应的构造函数。

public class ReflectTest {

    private String p1;
    private int    p2;

    public ReflectTest() {
        System.out.println("empty constructor");
    }

    public ReflectTest(String p1, int p2) {
        System.out.println("p1:"+p1+",p2:"+p2);
        this.p1 = p1;
        this.p2 = p2;
    }

    private ReflectTest(String p1) {
        System.out.println("p1:"+p1);
        this.p1 = p1;
    }

    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.test.ReflectTest");
            System.out.println("==========实例化指定公有的构造函数==========");
            Constructor<?> constructor = aClass.getConstructor(String.class,int.class);
            constructor.newInstance("test",12);
            System.out.println("==========实例化指定(包括:私有、受保护、默认、公有)的构造函数==========");
            constructor = aClass.getDeclaredConstructor(String.class);
            constructor.newInstance("test");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        /** Output:
         *  ==========实例化指定公有的构造函数==========
         *  p1:test,p2:12
         *  ==========实例化指定(包括:私有、受保护、默认、公有)的构造函数==========
         *  p1:test
         */
    }
}

在调用newInstance()方法时可以对指定构造函数的参数类型进行赋值操作,如果没有,可忽略。

方法

反射除了可以调用构造函数之外,也可以调用方法:

public class ReflectTest {

    public void method(){
    }

    public String method2(int param,String param2){
        return "method2";
    }

    private void method3(){
    }

    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.test.ReflectTest");
            System.out.println("==========获取所有公有的方法==========");
            Method[] method = aClass.getMethods();
            for (Method method1:method) {
                System.out.println(method1);
            }
            System.out.println("==========获取指定公有的方法==========");
            Method method2 = aClass.getMethod("method2", int.class, String.class);
            System.out.println(method2);
            System.out.println("==========获取所有(包括:私有、受保护、默认、公有)的方法==========");
            method = aClass.getDeclaredMethods();
            for (Method method1:method) {
                System.out.println(method1);
            }
            System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的方法==========");
            Method method3 = aClass.getDeclaredMethod("method3");
            System.out.println(method3);
            /** Output:
             * ==========获取所有公有的方法==========
             * 省略系统自带原始方法...
             * public void com.test.ReflectTest.method()
             * public java.lang.String com.test.ReflectTest.method2(int,java.lang.String)
             * ==========获取指定公有的方法==========
             * public java.lang.String com.test.ReflectTest.method2(int,java.lang.String)
             * ==========获取指定(包括:私有、受保护、默认、公有)的方法==========
             * 省略系统自带原始方法...
             * public void com.test.ReflectTest.method()
             * public java.lang.String com.test.ReflectTest.method2(int,java.lang.String)
             * private void com.test.ReflectTest.method3()
             * ==========获取指定(包括:私有、受保护、默认、公有)的方法==========
             * private void com.test.ReflectTest.method3()
             */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

getMethods()方法可以获取反射中所有公有方法getMethod()方法可以获取反射中指定公有方法getDeclaredMethods()方法可以获取反射中所有方法不受访问权限限制getDeclaredMethod()方法可以获取反射中指定方法不受访问权限限制

其中getMethod()方法和getDeclaredMethod()方法中有两个参数:第一个参数是对应的方法名;第二个参数对应的参数类型(可传多个),如果方法没有参数则可以不填。

调用

既然知道如何通过反射得到方法,就可以通过invoke()方法去调用对应的方法。

public class ReflectTest {

    public void method(){
        System.out.println("method");
    }

    public String method2(int param,String param2){
        System.out.println("param:"+param+",param2:"+param2);
        return "method2";
    }

    private void method3(){
        System.out.println("method3");
    }

    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.test.ReflectTest");
            Object reflectTest = aClass.newInstance();
            System.out.println("==========获取指定公有的方法==========");
            Method method2 = aClass.getMethod("method2", int.class, String.class);
            method2.invoke(reflectTest,100,"test");
            System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的方法==========");
            Method method3 = aClass.getDeclaredMethod("method3");
            method3.invoke(reflectTest);
            /** Output:
             * ==========获取指定公有的方法==========
             * param:100,param2:test
             * ==========获取指定(包括:私有、受保护、默认、公有)的方法==========
             * method3
             */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

invoke()方法有两个参数:第一个参数是调用方法的对象;第二个参数是方法的参数内容(可以有多个参数),如果没有参数,可忽略。

字段

当然可以通过反射来获取字段属性:

public class ReflectTest {

    private String param;
    public Long param1;
    protected Integer param2;


    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.test.ReflectTest");
            Object reflectTest = aClass.newInstance();
            System.out.println("==========获取所有公有的字段==========");
            Field[] fields = aClass.getFields();
            for (Field field:fields) {
                System.out.println(field);
            }
            System.out.println("==========获取指定公有的字段==========");
            Field param = aClass.getField("param1");
            System.out.println(param);
            System.out.println("==========获取所有(包括:私有、受保护、默认、公有)的字段==========");
            fields = aClass.getDeclaredFields();
            for (Field field:fields) {
                System.out.println(field);
            }
            System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的字段==========");
            param = aClass.getDeclaredField("param2");
            System.out.println(param);
            /** Output:
             * ==========获取所有公有的字段==========
             * public java.lang.Long com.test.ReflectTest.param1
             * ==========获取指定公有的字段==========
             * public java.lang.Long com.test.ReflectTest.param1
             * ==========获取所有(包括:私有、受保护、默认、公有)的字段==========
             * private java.lang.String com.test.ReflectTest.param
             * public java.lang.Long com.test.ReflectTest.param1
             * protected java.lang.Integer com.test.ReflectTest.param2
             * ==========获取指定(包括:私有、受保护、默认、公有)的字段==========
             * protected java.lang.Integer com.test.ReflectTest.param2
             */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

getFields()方法可以获取反射中所有公有字段getField()方法可以获取反射中指定公有字段getDeclaredFields()方法可以获取反射中所有字段不受访问权限限制getDeclaredField()方法可以获取反射中指定字段不受访问权限限制

其中getField()方法和getDeclaredField()方法通过传入指定字段名称,来获取字段信息。

赋值和获取

我们可以通过set()方法和get()方法,为字段赋值和获取值:

public class ReflectTest {

    private String param;
    public Long param1;
    protected Integer param2;


    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.test.ReflectTest");
            Object reflectTest = aClass.newInstance();
            System.out.println("==========获取指定公有的字段==========");
            Field param = aClass.getField("param1");
            param.set(reflectTest,1L);
            System.out.println(param.get(reflectTest));
            System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的字段==========");
            param = aClass.getDeclaredField("param2");
            param.set(reflectTest,100);
            System.out.println(param.get(reflectTest));
            /** Output:
             * ==========获取指定公有的字段==========
             * 1
             * ==========获取指定(包括:私有、受保护、默认、公有)的字段==========
             * 100
             */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

set()方法有两个参数:第一个参数是赋值字段的对象;第二个参数字段的值。get()方法通过传入对象参数,获取对象的字段值。

setAccessible()方法

上面的所有案例都是再本内中操作的,所以看不出效果,如果再其他类中使用,会看出不同的效果:

public class ReflectTest {
    private String param;
    public Long param1;
    protected Integer param2;
}
public class Test {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.study.test.ReflectTest");
            Object reflectTest = aClass.newInstance();
            System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的字段==========");
            Field param = aClass.getDeclaredField("param");
            param.set(reflectTest,"13");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

抛出错误:

java.lang.IllegalAccessException: Class com.test.Test can not access a member of class com.test.ReflectTest with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.set(Field.java:761)
	at com.study.test.Test.main(Test.java:28)

如果通过反射访问其他内的私有方法、属性等,需要通过setAccessible()方法取消访问权限检查。

public class ReflectTest {
    private String param;
    public Long param1;
    protected Integer param2;
}
public class Test {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.test.ReflectTest");
            Object reflectTest = aClass.newInstance();
            System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的字段==========");
            Field param = aClass.getDeclaredField("param");
            param.setAccessible(true);
            param.set(reflectTest,"13");
            System.out.println(param.get(reflectTest));
            /** Output
             * ==========获取指定(包括:私有、受保护、默认、公有)的方法==========
             * 13
             */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

setAccessible()方法表示:值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查;由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的 。

反射main()方法

似乎实际也不怎么会用到,但还是要讲解:

public class ReflectTest {
    public static void main(String[] args) {
        System.out.println("invoke main()");
    }
}
public class Test {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.test.ReflectTest");
            Object reflectTest = aClass.newInstance();
            Method main = aClass.getMethod("main", String[].class);
            main.invoke(reflectTest,new String[1]);
            /** Output
             * invoke main()
             */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

运行配置文件内容

我们可以通过反射来获取配置文件信息,这样无需对源码进行更新,即可获取最新的配置内容。

先创建一个文本文件:
在这里插入图片描述
代码示例如下:

public class ReflectTest {
    public static void main(String[] args) {
        //工具类
        Properties properties = new Properties();
        try {
            //加载文件
            properties.load(new FileReader("D:\\mnt\\this.txt"));
            //通过key获取value
            Object aPackage = properties.get("package");
            System.out.println(aPackage);
            //通过key获取value
            Object aClass = properties.get("class");
            System.out.println(aClass);
            /** Output:
             * com.test.ReflectTest
             * ReflectTest
             */
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            properties.clone();
        }
    }
}

越过泛型检查

泛型用在编译期,编译过后泛型擦除(消失掉)。所以可以通过反射越过泛型检查:

public class ReflectTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        Class aClass = list.getClass();
        try {
            Method add = aClass.getMethod("add", Object.class);
            add.invoke(list,100);
            System.out.println(list.toString());
            /** Output:
             *  [a, b, c, 100]
             */
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

本章小结

在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。当然,也不是所有的都适合反射。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CGI(Common Gateway Interface)是一种标准,用于在Web服务器上运行外部程序。 CGI程序可以与Web服务器进行通信,从而让Web服务器获取外部程序生成的数据,并将其返回给客户端浏览器。在这个过程,CGI程序可以读写文件、处理表单数据、查询数据库等。 文件上传是Web应用程序非常常见的一种功能。上传文件的过程涉及到客户端浏览器将文件数据发送到Web服务器,Web服务器将文件保存到指定的目录,并将文件相关的信息存储到数据库。CGI程序可以处理上传文件的请求,并实现文件的保存和数据库的更新等操作。 cgicc是一个C++库,用于处理CGI程序的表单数据。它提供了一组简单易用的API,可以方便地读取和处理表单数据,并且支持文件上传等功能。使用cgicc可以极大地简化CGI程序的开发。 下面是一个使用cgicc处理文件上传的示例: ```cpp #include <iostream> #include <fstream> #include <cgicc/Cgicc.h> #include <cgicc/HTTPHTMLHeader.h> #include <cgicc/HTMLClasses.h> using namespace std; using namespace cgicc; int main() { Cgicc cgi; const_file_iterator file = cgi.getFile("file"); if(file != cgi.getFiles().end()) { string filename = file->getName(); string filepath = "/var/www/upload/" + filename; ofstream ofs(filepath.c_str(), ios::out | ios::binary); file->writeToStream(ofs); ofs.close(); cout << HTTPHTMLHeader() << endl; cout << HTMLDoctype(HTMLDoctype::eStrict) << endl; cout << html().set("lang", "en").set("dir", "ltr") << endl; cout << head() << title("File Upload Result") << head() << endl; cout << body() << h1("File Upload Result") << endl; cout << p("File " + filename + " uploaded successfully!") << endl; cout << body() << html(); } else { cout << HTTPHTMLHeader() << endl; cout << HTMLDoctype(HTMLDoctype::eStrict) << endl; cout << html().set("lang", "en").set("dir", "ltr") << endl; cout << head() << title("File Upload Result") << head() << endl; cout << body() << h1("File Upload Result") << endl; cout << p("No file uploaded!") << endl; cout << body() << html(); } return 0; } ``` 在这个示例,我们使用cgicc库处理表单数据,并通过getFile函数获取上传的文件。如果getFile返回的迭代器不等于getFiles返回的迭代器末尾,说明有文件上传。我们可以通过getName获取上传文件的名称,并指定文件保存的路径。然后,我们使用writeToStream将文件写入到指定的文件路径。 最后,我们输出一个HTML响应,显示文件上传的结果。如果有文件上传成功,输出“File uploaded successfully!”,否则输出“No file uploaded!”。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值