类加载机制与反射

Posted

清心雅筑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类加载机制与反射相关的知识,希望对你有一定的参考价值。

1、使用反射生成并操作对象
  通过反射来生成对象有如下两种方式:
  使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求改Class对象的对应类有默认构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例;
  先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ObjectPoolFactory {
        //定义一个对象池,前面是对象名,后面是实际对象
        private Map<String, Object> objectPool = new HashMap<>();
        
        public static void main(String[] args) {
                ObjectPoolFactory pf = new ObjectPoolFactory();
                pf.initPool("obj.txt");
                System.out.println(pf.getObject("a"));
                System.out.println(pf.getObject("b"));
        }
        
        //定义一个创建对象的方法
        //该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
        private Object createObject(String clazzName) throws Exception {
                //根据字符串来获取对应的class对象
                Class<?> clazz = Class.forName(clazzName);
                //使用clazz对应类的默认构造器创建实例
                return clazz.newInstance();
        }
        
        //根据指定文件来初始化对象池
        //它会根据配置文件来创建对象
        public void initPool(String fileName) {
                try {
                        FileInputStream fis = new FileInputStream(fileName);
                        Properties props = new Properties();
                        props.load(fis);
                        
                        for(String name : props.stringPropertyNames()) {
                                //每取出一对key-value对,就根据value创建一个对象
                                //调用createOjbect()创建对象,并将对象添加到对象池中
                                objectPool.put(name, createObject(props.getProperty(name)));
                        }
                        
                } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
        
        public Object getObject(String name) {
                //从objectPool中取出指定name对应的对象
                return objectPool.get(name);
        }
}
技术分享
obj.txt:
a=java.util.Date
b=javax.swing.JFrame
 
调用方法
  当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或指定方法--这两个方法的返回值是Method数组,或者Method对象。
  每个Method对象对应一个方法,获得Method对象后,程序就可通过该Method来调用它对应的方法。在Method里包含一个invoke()方法,该方法的签名如下:
  Object invoke(Object obj, Object... args):该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ExtendedObjectPoolFactory {
        //定义一个对象池,前面是对象名,后面是实际对象
        private Map<String, Object> objectPool = new HashMap<>();
        private Properties config = new Properties();
        
        public static void main(String[] args) throws Exception {
                ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
                epf.init("extObj.txt");
                epf.initPool();
                epf.initProperty();
                System.out.println(epf.getObject("a"));
        }
        
        //根据指定文件来初始化对象池
        //根据配置文件来创建对象
        public void initPool() throws Exception {
                for(String name : config.stringPropertyNames()) {
                        System.out.println("name: " + name);
                        //每取出一个key-value对,如果key中不包含百分号(%)
                        //表明是根据value来创建一个对象
                        //调用createObject创建对象,并将对象添加到对象池中
                        if (!name.contains("%")) {
                                objectPool.put(name, createObject(config.getProperty(name)));
                        }
                }
        }
        
        //根据属性文件来调用指定对象的setter方法
        public void initProperty() throws Exception {
                for (String name : config.stringPropertyNames()) {
                        //每取出一个key-value对,如果key中包含百分号(%)
                        //即可认为该key用于控制调用对象的setter方法设置值
                        //%前半为对象名字,后半控制setter方法名
                        if (name.contains("%")) {
                                //将配置文件中的key按%分割
                                String[] objAndProp = name.split("%");
                                //取出调用setter方法的参数值
                                Object target = getObject(objAndProp[0]);
                                //获取setter方法名:set+"首字母大写" + 剩下部分
                                String mtdName = "set" + objAndProp[1].substring(0, 1).toUpperCase()
                                                        + objAndProp[1].substring(1);
                                //通过target的getClass()获取他的实现类所对应的Class对象
                                Class<?> targetClass = target.getClass();
                                //获取希望调用的setter方法
                                Method mtd = targetClass.getMethod(mtdName, String.class);
                                System.out.println(mtd);
                                //通过Method的invoke方法执行setter方法
                                //将config.getProperty(name)的值作为调用setter方法的参数
                                mtd.invoke(target, config.getProperty(name));
                        }
                        
                }
        }
        
        public Object getObject(String name) {
                return objectPool.get(name);
        }
        
        //定义一个创建对象的方法
        //该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
        private Object createObject(String clazzName) throws Exception {
                //根据字符串来获取对应的Class对象
                Class<?> clazz = Class.forName(clazzName);
                //使用clazz对应类的默认构造器创建实例
                return clazz.newInstance();
        }
        
        //从指定属性文件中初始化Properties对象
        public void init(String fileName) {
                try (FileInputStream fis = new FileInputStream(fileName))
                {
                        config.load(fis);
                } catch (FileNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                } catch (IOException e) {
                        System.out.println("读取" + fileName + "异常");
                }
        }   
}

访问成员变量值

import java.lang.reflect.Field;

public class FieldTest {
        public static void main(String[] args) throws Exception {
                //创建一个Person对象
                Person p = new Person();
                //获取Person类对应的Class对象
                Class<Person> personClazz = Person.class;
                //获取Person的名为name的成员变量
                //使用getDeclaredField()方法声明可获取各种访问控制符的成员变量
                Field nameField = personClazz.getDeclaredField("name");
                //设置通过反射访问该成员变量时取消访问权限检查
                nameField.setAccessible(true);
                //调用set()方法为p对象的name成员变量设置值
                nameField.set(p, "Jason");
                //获取Person类名为age的成员变量
                Field ageField = personClazz.getDeclaredField("age");
                //设置通过反射访问该成员变量时取消访问权限检查
                ageField.setAccessible(true);
                //调用setInt()方法为p对象的age成员变量设置值
                ageField.setInt(p, 30);
                System.out.println(p);
        }
}

class Person{
        private String name;
        private int age;
        public String toString() {
                return "Person[name:" + name +
                                " ,age: " + age + " ]";
        }
}
  getDeclaredField()方法获取了名为name的成员变量,注意此处不是使用getField()方法,因为getField()方法只能获取public访问控制的成员变量,而getDeclaredField()方法则可以获取所有的成员变ageField.setAccessible(true);通过反射访问该成员变量时不受访问权限的控制。
2、使用反射生成JDK动态代理
  在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。
使用Proxy和InvocationHandler创建动态代理
  Proxy提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果在程序中为一个或多个接口动态的生成实现类,就可以使用Proxy来创建动态代理类;如果需要为一个或多个接口动态的创建实例,也可以使用Proxy来创建动态代理实例。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest {
        public static void main(String[] args) {
                //创建一个InvocationHandler对象
                InvocationHandler handler = new MyInvocationHandler();
                //使用指定的InvocationHandler来生成一个动态代理对象
                Person p = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[] {Person.class}, handler);
                //调用动态代理对象的walk()和sayHello()方法
                p.walk();
                p.sayHello("zhangsan");
        }
}

class MyInvocationHandler implements InvocationHandler
{

        /**
         * 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
         * 其中:
         *         proxy: 代表动态代理对象
         *  method: 代表正在执行的方法
         *  args: 代表调用目标方法时传入的实参
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("----正在执行的方法: " + method);
                if (args != null) {
                        System.out.println("下面是执行该方法时传入的实参为:");
                        for(Object val : args) {
                                System.out.println(val);
                        }
                } else {
                        System.out.println("调用该方法没有实参!");
                }
                return null;
        }
        
}

interface Person {
        void walk();
        void sayHello(String name);
}
  不管程序是执行代理对象的walk()方法,还是执行代理对象的sayHello()方法,实际上都是执行InvocationHandler对象的invoke()方法。
  实际上,在普通编程过程中,确实无须使用动态代理,但在编写框架或底层基础代码时,动态代理的作用就非常大。
动态代理和AOP
反射和泛型
  从JDK5以后,Java的Class类增加了泛型功能,从而允许使用泛型来限制Class类,例如,String.class的类型实际上是Class<String>。如果Class对应的类暂时未知,则使用Class<?>。通过在反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换
import java.util.Date;
import javax.swing.JFrame;

public class ObjectFactory2 {
        
        public static void main(String[] args) {
                try {
                        //获取实例后无需类型转换
                        Date d = ObjectFactory2.GetInstance(Date.class);
                        JFrame f = ObjectFactory2.GetInstance(JFrame.class);
                } catch (InstantiationException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                } catch (IllegalAccessException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
        public static <T> T GetInstance(Class<T> cls) throws InstantiationException, IllegalAccessException {
                return cls.newInstance();
        }
}
使用反射来获取泛型信息
 通过指定类对应的Class对象,可以获得该类里包含的所有成员变量,不管该成员变量是使用private修饰,还是使用public修饰。获得了成员变量对应的Field对象后,就可以很容易的获得该成员变量的数据类型,即使用如下代码即可获得指定成员变量的类型。
//获取成员变量f的类型
Class<?> a  = f.getType();
  但这种方式只对普通类型的成员变量有效。如果该成员变量的类型是有泛型类型的类型,如Map<String, Integer>类型,则不能准确的得到该成员变量的泛型参数。
  为了获得指定成员变量的泛型类型,应先使用如下方法来获取该成员变量的泛型类型。
//获取成员变量f的泛型类型
Class<?> a  = f.getGenericType();
  然后将Type对象强制类型转换为ParameterizedType对象,ParameterizedType代表被参数化的类型,也就是增加了泛型限制的类型。ParameterizedType类提供了如下两个方法:
  getRawType(): 返回没有泛型信息的原始类型
  getActualTypeArguments(): 返回泛型参数的类型。
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;

public class GenericTest {
        private Map<String, Integer> score;
        
        public static void main(String[] args) {
                Class<GenericTest> clazz = GenericTest.class;
                try {
                        Field f = clazz.getDeclaredField("score");
                        //直接使用getType()取出类型只对普通类型的成员变量有效
                        Class<?> a = f.getType();
                        System.out.println("score的类型是: " + a);
                        //获得成员变量f的泛型类型
                        Type gType = f.getGenericType();
                        //如果gType类型是ParameterizedType对象
                        if (gType instanceof ParameterizedType) {
                                //强制类型转换
                                ParameterizedType pType = (ParameterizedType) gType;
                                //获取原始类型
                                Type rType = pType.getRawType();
                                System.out.println("原始类型是: " + rType);
                                //取得泛型类型的泛型参数
                                Type[] tArgs = pType.getActualTypeArguments();
                                System.out.println("泛型信息是: ");
                                for (int i = 0; i < tArgs.length; i++) {
                                        System.out.println("第" + i + "个泛型类型是: " + tArgs[i]);
                                }
                        } else {
                                System.out.println("泛型类型出错!");
                        }
                } catch (NoSuchFieldException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                } catch (SecurityException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
}

Outputs:

score的类型是: interface java.util.Map
原始类型是: interface java.util.Map
泛型信息是: 
第0个泛型类型是: class java.lang.String
第1个泛型类型是: class java.lang.Integer

Type也是java.lang.reflect包下的一个接口,该接口代表所有类型的公共高级接口,Class是Type接口的实现类。Type包括原始类型、参数化类型、数组类型、类型变量和基本类型等

以上是关于类加载机制与反射的主要内容,如果未能解决你的问题,请参考以下文章

Java Review(三十九类加载机制与反射)

类加载机制与反射

Java注解与反射——获取反射对象 & 类加载机制

类加载机制与反射

Java反射与类加载过程会擦出什么样的火花

Java反射与类加载过程会擦出什么样的火花