Java设计思想——Java反射机制

参考《Java编程思想》反射:运行时的类信息 一章 以及学习阅读其他技术大牛的文章


文章目录

  • 了解什么是反射机制
  • Class对象
      • 1、获取Class对象
      • 2、获取Field成员属性
      • 3、通过反射给属性赋值
      • 反射获取方法Method
      • invoke方法的应用
      • 反射还可以破环泛型的限制
  • 反射的优缺点
  • 动态代理
  • 反射的应用场景

了解什么是反射机制

在运行状态时访问和使用类的信息。

首先我们要了解两个概念:

静态编译 就是在编译时,把所有模块都编译进可执行文件(exe)里,当启动这个可执行文件时,所有模块都被加载进来;

动态编译 是将应用程序需要的模块都编译成动态链接库,启动程序(初始化)时,这些模块不会被加载,运行时用到哪个模块就调用哪个

Java反射机制使用的就是动态编译,在程序运行时,获取类的Class对象,把Class对象加载到JVM中,JVM通过反编译获取到类的信息,然后实例化对象。

动态编译最大限度发挥了Java的灵活性,体现了多态的应用,灵活并松耦合。

Class对象

Class对象包含了一个类的有关信息,类是程序的一部分,每个类都对应一个Class对象,每当我们编写或是编译一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中),为了生成这个类的对象,运行这个程序的JVM将使用被称为“类加载器”的子系统。
所有的类都是在第一次使用时,动态加载到JVM中的。
当程序创建第一个对类的静态成员引用时,就会加载这个类。
构造器也是类的静态方法,即使在构造器之前并没有使用static关键字,使用new操作符创建类的新对象也会被当作类的静态成员的引用。
——摘自《Java编程思想》

1、获取Class对象

有三种方法,代码如下

public static void main(String[] args) {
     

        //方法一
        Class c1 = null;
        try {
     
            c1 = Class.forName("com.reflectTest.Student");
        } catch (ClassNotFoundException e) {
     
            e.printStackTrace();
        }
        System.out.println(c1);
		
        //方法二:使用类字面常量

        Class c2 = Student.class;
        System.out.println(c2);
	
        //方法三
        Student student =  new Student();
        Class c3 = student.getClass();
        System.out.println(c3);
        
        //以上输出的都是:class com.reflectTest.Student
    }

Class Class.forName() 方法是Class类的一个static成员,我们可以获取并操作这个Class对象的引用(这也是类加载器的工作)
Class.forName()中必须使用全限定名(包含包名)。
你想在程序运行时使用类型信息,就必须获得Class对象的引用。

2、获取Field成员属性

//Student类
public class Student {
     
    public String name; //这一行是一个Field对象(成员变量)
    private int age;
    private int tel;
    private int sex;
	public String getName() {
      
        return name;
    }

	private int getAge() {
      //定义私有的get方法
        return age;
    }
}
//测试类
public class FieldTest01 {
     
 		Class c2 = Student.class;
 		
		//获取field[]
        Field[] field = c2.getFields();
        System.out.println("成员属性个数="+field.length);
        //Output:成员属性个数=1
        
		Field[] field1 = c2.getDeclaredFields();
        System.out.println("成员属性个数="+field1.length);
        //Output:成员属性个数=4
		
		//获取每个属性的访问修饰符和类型
        for(Field f:field1){
     
            //属性修饰符
            int i = f.getModifiers(); // 获取访问修饰符的数字代号
            String modifiersName = Modifier.toString(i);

            //属性类型
            Class type = f.getType(); //java.lang.String  注意getType()方法返回类型为Class
            String simpleType = type.getSimpleName(); //String

            //属性名
            String name = f.getName();

            System.out.println(modifiersName+" "+simpleType+" "+name);
        }
			/* Output:
			public String name
			private int age
			private int tel
			private int sex
			*/
}

getFields()方法:只能获取到public修饰的属性
getDelaredFields()方法:可以获取到全部属性

同样类似的还有getConstructors()和getDeclaredConstructors()、getMethods()和getDeclaredMethods(),这两者分别表示获取某个类的方法、构造函数。

3、通过反射给属性赋值

public static void main(String[] args) throws Exception {
     

        Class c = null;
        c = c.forName("com.reflectTest.Student");
        Object obj = c.newInstance();//底层调用无参构造器实例化对象

        Field fName = c.getDeclaredField("name");
        fName.set(obj,"张三");
        System.out.println(fName.get(obj));
		// Output: 张三
        Field fAge = c.getDeclaredField("age");
        //setAccessible(true)使私有属性外部也可以访问,这是反射机制的缺点,不安全破坏封装
        fAge.setAccessible(true); 
        fAge.set(obj,20);
        System.out.println(fAge.get(obj));
		// Output: 20
		
		/*没有setAccessible(true)这个设置,应该报错 Class com.reflectTest.FieldTest02 can
		not access a member of class com.reflectTest.Student with modifiers "private"*/
    }

反射获取方法Method

		Class<Student> studentClass = Student.class;
        //获取Student中getName的方法
        Method getName = studentClass.getDeclaredMethod("getName");
        //创建一个student对象,invoke方法需要一个student对象
        Student stu = new Student("张三",18);
        Object name= getName.invoke(stu); //相当于调用stu.getName()方法
        System.out.println(name);
        
        //获取私有的getAge方法
		Method getAge = studentClass.getDeclaredMethod("getAge"); 
		//暴力反射,无视访问权限,否则报错
        getAge.setAccessible(true);
        Object age = getAge.invoke(stu);
        System.out.println(age);
		/* Output:
		张三 
		18 */
		
		//有参数的方法:setName方法 需要方法名和参数类型
		Method setName = studentClass.getDeclaredMethod("setName", String.class);
        setName.invoke(stu,"你好"); //...args就是一个可变长度的参数列表,传入的参数跟在后面即可

getDeclaredMethod(“方法名”) 是获取一个方法,invoke是调用。

invoke方法的应用

invoke方法的应用是Spring AOP面向切面编程,在切面方法之前的前后使用invoke调用业务方法。

public class InvokeTest {
     
    public static void main(String[] args) {
     
        Student student = new Student("zhangsan",20);
//        invokeMethod(student,"getName",null);
        invokeMethod(student,"setName","李四");
    }

    public static Object invokeMethod(Object obj, String methodName, Object... params) {
     

        long startTime = System.currentTimeMillis();
        Object result = null;
        Class<?> objClass = obj.getClass();
        System.out.println("类:"+obj.getClass().getSimpleName());
        System.out.println("方法:"+methodName);
        if (params == null || params.length == 0) {
     
            //无参方法
            try {
     
                Method method = objClass.getDeclaredMethod(methodName); //获取方法
                result = method.invoke(obj); //调用方法
            } catch (NoSuchMethodException e) {
     
                e.printStackTrace();
            } catch (IllegalAccessException e) {
     
                e.printStackTrace();
            } catch (InvocationTargetException e) {
     
                e.printStackTrace();
            }
        } else {
     
            //有参方法
            System.out.println("参数列表:"+ Arrays.toString(params));
            int size = params.length;
            Class<?>[] classes = new Class[size];
            Object[] objArrays = new Object[size];
            for (int i = 0; i < size; i++) {
     
                objArrays[i] = params[i];
                classes[i] = params[i].getClass();
            }
            try {
     
                Method method = objClass.getDeclaredMethod(methodName,classes);//获取方法
                //获取有参数的方法时,getDeclaredMethod有两个参数:方法名和参数类型params[i].getClass()
                result = method.invoke(obj,objArrays);//调用方法

                long endTime = System.currentTimeMillis();
                System.out.println("方法执行时间:"+ (endTime-startTime));
            } catch (NoSuchMethodException e) {
     
                e.printStackTrace();
            } catch (IllegalAccessException e) {
     
                e.printStackTrace();
            } catch (InvocationTargetException e) {
     
                e.printStackTrace();
            }
        }
        return result;
    }
}

/* Output:
类:Student
方法:setName
参数列表:[李四]
方法执行时间:1
*/

调用invoke方法之前或之后进行一些操作,这样在setName()方法中就可以只实现核心业务代码,使关注点分离出来。详细的可以看这个SpringAOP的理解

反射还可以破环泛型的限制

        List<Student> stuList = new ArrayList<>();
        stuList.add(new Student("张三",18));
        stuList.add(new Student("李四",18));
        stuList.add(new Student("王五",18));

        Class<? extends List> stuListClass = stuList.getClass();
        Method method = stuListClass.getDeclaredMethod("add",Object.class);
        //数字和字符串都可以加进去
        method.invoke(stuList,123); 
        method.invoke(stuList,"123");
        for (Student student : stuList) {
     
            System.out.println(student);
        }

反射的优缺点

缺点:
反射能够破坏掉JAVA类本身的封装性,进而获取其私有的或公开的信息,也就能突破封装进而调用私有的或公开的方法,例如这个设置fAge.setAccessible(true);
Java反射机制使用不当,会给JVM性能增加很大的负担
优点:
松耦合,易扩展,使代码灵活,后期方便测试。

Class对象中的一些方法

public static void main(String[] args) throws ClassNotFoundException {
     
        Class c  = Class.forName("com.reflectTest.Student");

        System.out.println("获取全限定类名"+c.getName());
        System.out.println("获取当前类类名"+c.getSimpleName());
        System.out.println("获取继承父类名称"+c.getSuperclass());
        System.out.println("实现哪些接口"+ Arrays.toString(c.getInterfaces()));
    }
    /* Output:
    	获取全限定类名com.reflectTest.Student
		获取当前类类名Student
		获取继承父类名称class com.reflectTest.People
		实现哪些接口[interface java.io.Serializable]
    */

动态代理

动态代理可以将所有带哦用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个“实例”对象的引用,从而使得调用处理器在执行其中介任务时,可以转发请求。

1、动态代理属于AOP(面向切面编程)的一种思想。
2、代理对象和被代理对象都实现了相同接口。
3、动态代理是指系统根据要代理的接口,自动帮我们生成并编译和加载代理类(实现了要代理接口),并将代理类中的代理逻辑抽象到 InvocationHandler 中,由开发者实现。静态代理中,我们需要自己实现代理类,将代理逻辑也写在代理类里面。而动态代理省去了我们自己写代理类的工作,系统只需要知道要代理的接口,然后利用反射获取接口的方法信息,生成代理类,代理类其实只是些模板代码,而又为了能定制代理逻辑,系统便定义了一个 InvocationHandler,代理类中的方法都会调用 InvocationHandler 中的invoke方法,并将代理类对象,接口的方法对象,方法参数传入。所以真正的代理逻辑在 InvocationHandler 类中;

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

Proxy.newProxyInstance()可以创建动态,并返回代理接口实例,对该实例调用的方法,都会转移到代理类中。这个方法你需要得到一个类加载器(你通常可以从已被加载的对象中获取其类加载器,然后传递给他),一个你希望代理实现的接口列表,以及InvocationHandler接口的一个实现。

loader 参数用来加载自动生成的代理类,interfaces 参数为要代理的接口数组,h参数为我们要自己实现的代理逻辑类。

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

InvocationHandler的invoke方法中, proxy参数是系统帮我们生成的代理类对象,method参数为当前调用的方法对象,args为方法的参数。

反射的应用场景

反射是框架设计的灵魂。

在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制。
Spring中的IOC(控制反转)底层实现就是反射机制。
Spring容器能够帮我们创建实例,这个容器就是应用了反射机制的思想,通过解析XML文件,获取到id属性和class属性里的内容,利用反射原理创建配置文件中的实例对象,存入Spring的bean容器中。

举例:

1、我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;

2、Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:

1)将程序内所有 XML 或 Properties 配置文件加载入内存中;

2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息

3)使用反射机制,获得某个类的Class实例;

4)动态代理实例的属性

你可能感兴趣的:(Java)