JAVA基础 (三)反射 深入解析反射机制
上一篇中已经介绍了一些关于反射的基本概念,这篇主要通过一个实例说一说反射的过程,以及实际中应用的例子。
这个例子是这样的设计思路:从一个属性文件中读取一段字符串,然后,根据该字符串生成对应的类实例对象;这之后还有一个增强版的例子,可以根据类里面的setter()方法将类的成员变量(引用类型)也进行初始化,Spring框架是这么实现的。
项目结构如下:
本例子包括三个类
1.reflect.properties属性文件,里面为key-value键值对,如下
name=javax.swing.JFrame
useraction=com.tgb.reflect.UserAction
2.UserAction.java,Action类
<span style="font-size:14px;">public class UserAction { public void addUser() { System.out.println("添加用户"); } }</span>
3.ObjectPoolFactory.java,该类负责读取文件,实例化对象
<span style="font-size:14px;">public class ObjectPoolFactory { //定义一个对象池,采用key-value key为对象名、value为对象 private Map<String, Object> objectPool=new HashMap<>(); //定义一个创建对象的方法,参数为字符串 类名 private Object createObject(String clazzName) throws InstantiationException,IllegalAccessException,ClassNotFoundException { //根据字符串来获取对应的class对象 Class<?> clazz=Class.forName(clazzName); return clazz.newInstance(); } //从属性文件中读取key-value初始化类的实例,也可以利用dom4j从配置文件中读取 public void initPool(String fileName) throws InstantiationException,IllegalAccessException,ClassNotFoundException { try(FileInputStream fis=new FileInputStream(fileName)) { Properties pros=new Properties(); //从输入流加载属性文件 pros.load(fis); //循环属性文件中的key for(String name:pros.stringPropertyNames()) { //取出key-value,根据value创建对象,并放入对象池中 objectPool.put(name, createObject(pros.getProperty(name))); } } catch (Exception e) { e.printStackTrace(); } } public Object getObject(String name) { return objectPool.get(name); } public void test() throws InstantiationException, IllegalAccessException, ClassNotFoundException { String path=this.getClass().getResource("/com/tgb/reflect/reflect.properties").toString(); path=path.substring(path.indexOf("/")+1); System.out.println(path); ObjectPoolFactory opf=new ObjectPoolFactory(); opf.initPool(path); UserAction userAction=(UserAction)opf.getObject("useraction"); userAction.addUser(); } public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException { ObjectPoolFactory opf=new ObjectPoolFactory(); opf.test(); }</span>此类中,主要是createObject()这个方法创建实例对象,然后又调用Class类的forName()方法,该方法返回的是一个类的Class对象,再利用Class对象的newInstance()返回它所代表的类的实例。
我们用一个map对象来存储一个已经创建好的对象,作为对象池使用。Class<?>这里使用了类型通配符,代表的意思是Class对象的类型,这次Class对象未知因此使用了类型通配符,这里其实使用类型参数也是可以的,如Class<T>,至于类型参数与类型通配符区别以后会介绍。
那么跟类加载器有什么关系呢?
我们可以看一下JDK源码,forName()这个方法重载了两个,有一个是需要提供类加载器这个参数的,如
<span style="font-size:14px;"> @CallerSensitive public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { if (loader == null) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader ccl = ClassLoader.getClassLoader(Reflection.getCallerClass()); if (ccl != null) { sm.checkPermission( SecurityConstants.GET_CLASSLOADER_PERMISSION); } } } return forName0(name, initialize, loader); }</span>这个方法是forName()参数最多,重载之前的方法,它为我们提供了默认的类加载器,里面还有关于一些安全方面的处理判断。
运行结果如下:
我们可以看到上面程序UserAction的addUser()已经执行,表名创建实例成功。
接下来我们完善该方法,实现对UserAction里面的一个引用属性赋值,修改UserAction如下增加了一个setter()方法,这里你也就知道setter()方法的作用,Spring进行诸如时就是根据setter()来给依赖属性注入的。
UserAction.java
<span style="font-size:14px;">public class UserAction { //依赖属性 private UserManager userManager; public void addUser() { System.out.println("执行UserAction的addUser()方法"); } //属性的set方法 public void setUserManager(UserManager userManager) { this.userManager = userManager; } }</span>在工厂类里面主要多了一个初始化属性的方法,其余有稍微改动但基本类似,如下
<span style="font-size:14px;">//初始化类的属性,也可以利用dom4j从配置文件中读取 public void initProperty() throws InstantiationException,IllegalAccessException,ClassNotFoundException { try { //循环属性文件中的key for(String name:pros.stringPropertyNames()) { if (name.contains("%")) { String[] namesArray=name.split("%"); Object target=getObject(namesArray[0]); Class<?> targetClass=target.getClass(); String mName="set"+namesArray[1].substring(0,1).toUpperCase()+namesArray[1].substring(1); //得到目标对象userManager属性的set方法 //第一个参数为方法名、第二个为set方法中传入的参数类型,即UserManager.class=userManager.getClass() Method m=targetClass.getMethod(mName,getObject(name).getClass()); //调用set方法给属性赋值 m.invoke(target,getObject(name)); } } } catch (Exception e) { e.printStackTrace(); } }</span>
通过看注释大家也可以理解,和上面类似这里只不过是找到属性对应的实例,然后,通过set方法把这个实例给属性赋值的。
属性文件改为了如下变量值:
name=javax.swing.JFrame
useraction=com.tgb.reflect.UserAction
useraction%userManager=com.tgb.reflect.UserManager
第三句用一个%分割,前面代表类后面代表该类的属性,该属性主要用于拼接set方法名,因为得到set方法时需要用到这个作为参数。
至此,反射的基本内容就介绍完了,相信大家通过上面的一些概念和简单的实例已经理解了反射的原理是怎么实现的,很多框架也是利用这一个过程通过xml配置文件来实例化各种类,所不同的是框架对于xml里面的标签已经作为限制,道理和读取属性文件是一样的,xml文件包含的信息会更丰富一些,这样在解析xml和实例化对象时也会更复杂一些,通过配置文件来处理类之间的各种关系。
我们通常接触到的AOP、IOC、容器、还有一些注解原理都依赖反射实现,多了解一些反射的机制是很有好处的。很多内容在内部都是有联系的,把它们都相互联系起来比较着学习和应用才会掌握的更好。