JAVA反射机制分析-------spring的通过反射创建bean实例对象以及属性注入的原理解析

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41908550/article/details/86528794

                                                 JAVA反射机制

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

巧妙的利用java中的反射机制,能够帮助我们进行程序开发时达到意想不到的效果,本片博文将对java中的反射机制进行分析以及针对spring中通过配置文件完成bean实例对象的创建原理进行分析。

一:java中获取类对象以及通过类对象获取实例化对象,并获取/修改类属性,方法。

二:通过读取自定义配置文件,完成对象的创建以及调用

三:模仿spring创建beanFactory的实现过程。

一:类对象

1. 什么是类对象,java的反射机制是什么意思?如何获取类对象

java的反射机制,意思很简单,就是通过类对象获取类的信息。

那什么是类对象呢?每一个类型的类都有且仅有一个类对象Class,该类对象中记录了这个类包含了哪些属性、哪些方法、以及有哪些构造方法等信息。那通过类对象,就可以反向获取该类的构造方法,就可以完成实例化,也可以访问类中的方法和属性。完成赋值、调用方法等操作。那类对象如何获取呢?很简单,只需要知道他的类名即可。

获取类对象的三种方法:

①Class a = Class.forName(pojo.Hero);

②Class b = Hero.class;

③Class c = new Hero().getClass;

并且a=b=c,因为在同一个JVM中,或者说在一个ClassLoader中,Hero这个类,有且只有一个类对象。

注意,在获取类对象时,除了Hero.class这种方式,其他的会导致类的属性被初始化,即该类中的静态代码块执行或者静态属性被初始化,可以理解为加载类时完成的事情,并且该初始化过程只会被执行一次。例如在Hero这个类中,添加static静态代码块,控制台输出"初始化",则通过三种方式获取类对象,类中的代码块会被执行一次,并在控制台打印了"初始化"。

2. 通过类对象创建实例化对象、访问属性、访问方法。

㈠通过类对象创建实例化对象:直接看代码,一个类对象可以通过构造方法创建无数个实例化对象。

    try {
			//Class h = Hero.class;
			//Class h = Class.forName("test.pojo.Hero");
			Class h = new Hero().getClass(); //获取类对象
			Constructor c = h.getConstructor(); //根据类对象获取默认构造器
			Hero h1 = (Hero)c.newInstance();  //通过构造器调用构造方法完成实例化
			h1.showName();  //通过实例化好的对象即可访问该类中的方法
		} catch (Exception e) {
			e.printStackTrace();
		}

㈡通过类对象访问类属性,修改类属性值

getField和getDeclaredField的区别
这两个方法都是用于获取字段
getField 只能获取public的,包括从父类继承来的字段。
getDeclaredField 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))

        Hero h = new Hero();
		h.setPrice("qq");  
		System.out.println(h.getPrice());
		Field f = h.getClass().getDeclaredField("price"); //根据类对象获取该类的price属性
		f.set(h, "tt"); //通过获取的属性,给h对象修改这个属性的值
		System.out.println(h.getPrice());

㈢通过类对象访问类的方法,并执行该方法。

        Hero h = new Hero();
		Hero h1 = new Hero();
		
		Method m = Class.forName("test.pojo.Hero").getMethod("setPrice", String.class);//通过类对象获取该类中的方法以及该方法中需要传入的参数对象
//如果没有参数的方法则不需要写第二个参数
		System.out.println(h.getPrice());
		m.invoke(h, "访问方法"); //通过m对象,访问h对象的setPrice,并传入参数值。此时便对h对象执行了该方法。
		m.invoke(h1, "h1");
		System.out.println(h.getPrice());
		System.out.println(h1.getPrice());

至此,已经完成了通过类对象调用构造方法、调用方法、修改属性值操作了。

接下来,将演示java的反射机制如何使用如何利用反射,以及在哪些场景中使用。

二:读取配置文件

1. 为什么要使用反射?如何利用反射机制

假设有一个场景,有两个不同的类根据业务需要可能需要随时调换,如果通过new的方式,则每次调换时需要修改代码。如果通过反射来完成,我们可以将类全名写在配置文件中,通过IO读取配置文件来决定实例化哪个对象。我们就不需要修改代码了,直接修改配置文件中的类名即可。这只是个小例子,JAVA的反射机制远比这要强大。下面通过读取配置文件的方式完成调用不同的类中的不同方法。

2. 编写配置文件,如下,配置文件中声明需要创建的类名称、需要调用的方法名、参数名、以及该参数的值。

通过读取该配置文件,完全利用反射来完成对象的创建和属性的注入。

①创建两个类文件,service、service1,在这两个类中分别写两个打印的方法,代表两种不同的业务,如果需要创建的是service1的话,则程序需要自动把读取到的name值注入到该对象的name属性中。

public class Service {
	public void show(String s) {
		System.out.println("this is show1"+s);
	}
}
public class Service1 {
	public String name;	
	public void show(String s) {
		System.out.println("this is show2"+s);
	}	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}	
}

②在项目中创建配置文件context.txt文件:

class=example.pojo.Service1
method=show
property=name
name=zhangSan

③编写一个方法,用来读取配置文件,并创建对象。输出需要调用的方法。如果是service1的话,则返回一个对象,该对象中已经被注入好了name值。

public Object getObj() throws Exception {
		File f = new File("F:\\project\\fansheORzhujie\\fanshe\\src\\example\\context.txt"); //读取配置文件
		Properties springConfig = new Properties(); 
		springConfig.load(new FileInputStream(f)); //加载配置文件
		String className1 = (String) springConfig.get("class"); //获取配置文件中class的值
		String method1 = (String) springConfig.get("method"); //获取配置文件中method的值
		String pro = (String) springConfig.get("property");  //获取配置文件中的属性名
		String value = (String) springConfig.get("name");   //获取配置文件中name的属性值
		
		//根据读取的配置文件创建类对象
		Class c1 = Class.forName(className1);  
		
		//根据类对象创建类实例
		Constructor ct1 = c1.getConstructor();
		
		//实例化类对象
		Object o1 = ct1.newInstance();
		
		//根据反射读取类对象的方法名,然后具体某个实例化对象执行方法
		Method m1 = c1.getMethod(method1, String.class);
		
		m1.invoke(o1, "我是类对象中的方法"); //给这个方法注入参数
		
		
		//根据配置文件的创建,本例通过反射,读取配置文件中的信息,也完成创建对象(反转控制)和属性的依赖注入
		//如果类对象时service1,则判断,获取到该类中的name属性,并将实例化的对象中的name值设置为配置文件中name的值。
		if(c1 == Service1.class) {
			Field f1 = c1.getDeclaredField(pro);
			f1.set(o1, value);
		}
		return o1;
	}

④编写测试类:

	public static void main(String[] args) throws Exception {
		Test t = new Test();
		Object obj = t.getObj();
		if(obj.getClass() == Service1.class) {
			Service1 s = (Service1) obj;
			System.out.println(s.getName());
		}
	}

⑤输出结果:

⑥这样通过配置文件的方式,如果此时需要切换创建的对象,则只需要修改配置文件中的class值,修改类名,或者需要调用别的方法,则修改方法名。从而达到通过配置文件调控实例化哪个类的对象。或者,通过配置文件实例化一个对象,并给该对象注入参数值的实现。

spring中的bean的创建就是通过java的反射机制来完成的,基本实现过程与本例类似,spring实现时,是通过不断的读取配置文件,将class值,属性名,属性值,实例化成一个一个的object对象,然后将这些对象放到一个map中,key值就是配置文件中的id值或者name值,也就是形成了所谓的IOC容器beanFactory,使用时,直接通过key值获取,然后强转成需要的类型即可直接使用spring帮我们创建好的实例化对象了。接下来我将仿照spring中beanFactory的实现过程进行分析。

 

三:模拟spring创建beanFactory的实现过程

1. 简介

通过spring的配置文件,配置bean,使用spring类文件实例化bean中配置的对象,在测试类中直接返回使用。本demo中包含一个配置文件:spring.xml、一个spring类文件:applicationContext.java、一个实体类:product.java、一个测试类:text.java。

2.创建实体类与配置文件spring.xml,放在src路径下,设置好bean的属性。

package beans;
public class Product {
	public void show() {
		System.out.println("我是实例化对象product");
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans>
	<bean id="p" class="beans.Product">		
	</bean>	
	<!-- <bean id="h" class="beans.Hero">
	</bean> -->
</beans>

3. 接下来我们自己创建一个bean工厂,用来生产实例化对象配置文件中配置好的bean,从而达到spring完成创建对象的目的。

①先读取到配置文件spring.xml,并创建一个存放bean的私有静态全局变量map集合。

②然后对该文件进行解析,获取到文件中的跟标签。也就是最外面的那层标签beans,需要导入dom4j-1.6.jar、xml-apis-1.0.b2.jar资源文件

循环跟标签beans中的bean标签,将每一个bean中的属性值获取出来,根据class属性通过反射创建类对象并完成实例化,然后将这个对象放在map中,key值是读取到的id的值,value值是实例化好的object对象。配置文件中有多少bean标签就会循环创建多少个对象。

④重写该类的构造方法,并传入参数,参数值为配置文件的路径文件名。

⑤编写getBean方法,传入参数值,参数为bean中的id,从而达到通过调用该方法,传入配置的id值,获取到bean工厂中的实例化对象。此方法也是提供给外部访问map的唯一入口。

请看代码:
 

package spring;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
 * spring的配置工厂类文件
 * @author lemon
 *
 */
public class ApplicationContext {

	private static Map<String, Object> beans = new HashMap<String, Object>();
	
	public ApplicationContext(String file) {
		buildBeanFactory(file);
	}
	
	public void buildBeanFactory(String file) {
		//读取配置文件	
		InputStream in = ApplicationContext.class.getClassLoader().//类的加载容器,把类的实例加载到内存中
						getResourceAsStream(file);//这个方法直接从src下读取该文件
		//解析文件
		//创建解析器对象
		SAXReader saxReader = new SAXReader();
		Document dom = null;
		try {
			dom = saxReader.read(in);
		} catch (DocumentException e) {
			e.printStackTrace();
			throw new RuntimeException("解析文件时抛出异常");
		}
		
		//解析完了该文件,接下来需要拿到根元素(bean元素)
		Element root = dom.getRootElement();
		
		//获取根元素所有的bean元素,并存在放list中,并且将所有的bean标签循环读取,把每一个bean都创建成一个实例化对象
		//然后存放在map中,此时,即完成了spring创建bean的过程。
		List<Element> list = root.elements("bean");
		for (Element e : list) {
			String className = e.attributeValue("class");
			String idName = e.attributeValue("id");
			Object obj = null;
			try {
				obj = Class.forName(className).newInstance();
			} catch (Exception e1) {
				e1.printStackTrace();
				throw new RuntimeException("创建对象时抛出了异常");
			}
			beans.put(idName, obj);
		}
	}
	
	/*
	 * 提供方法,以供外部应用访问并获取map中的值。也就是getBean方法
	 */
	
	public Object getBean(String id) {
		return beans.get(id);
	}
	/*
    *提供方法,通过调用清初始化spring工厂中的所有值
    */
	public void cleanBeans() {
		beans.clear();
		beans = null;
	}
}






4. 编写测试类:

创建spring类,传入配置文件名,调用getBean方法,类型转换至具体类型对象,直接调用对象中的属性和方法即可。

public class Test {
	public static void main(String[] args) {
		ApplicationContext app = new ApplicationContext("spring.xml");
		Product pro = (Product) app.getBean("p");
		System.out.println(pro);
		pro.show();
	}
}

5. 输出结果:

6. 总结:

spring在为我们使用时,内部源码异常复杂,此过程只不过是简化了的实现过程,实则有很多情况需要处理。

在tomcat中使用spring时,tomcat是如何加载spring的?

其实在tomcat的启动过程中,会加载web.xml文件,我们在该文件中配置了监听上下文启动的监听器。在tomcat启动时,该监听器会被加载,然后执行该类中的初始化方法。在初始化时,默认读取默认路径下的默认spring配置文件名applicationContext.xml文件,并且开始加载解析,根据反射机制创建了配置文件中bean的实例化对象,并存在在map集合中,并完成加载其他资源文件等过程。从而spring就跟随者tomcat的启动完成了加载。

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

对于spring的世界,还有许许多多的未知等待我们去探索,在我以后的学习时间里,我会更专注于spring原理的解读。

 

 

 

 

 

展开阅读全文

没有更多推荐了,返回首页