Spring框架学习二:通过简单的HelloWorld程序理解IoC
实现第一个 Spring HelloWorld 程序
接着上一次笔记,上次我们讲了使用 Spring 框架需要做的一些准备工作。现在,我们就来实现我们的第一个 Spring HelloWorld 程序,先不管为什么,我们先看效果。通过程序的效果,我们再进一步探知 Spring IoC 容器的工作原理。
首先我们需要新建一个 Spring 配置文件:在src下右键 new —> other —> 找到 Spring文件夹,点击 Spring Bean Configurations File,起名字叫 ApplicationContext.xml,编写如下内容
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 5 <!-- class属性值需要写完整的类全名 --!> 6 <bean id="helloworld" class="com.bupt.springtest.beanXml.HelloWorld"/> 7 8 </beans>
1 package com.bupt.springtest.beanXml; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 //新建一个测试类 7 public class HelloWorld 8 { 9 public void greet() 10 { 11 System.out.println("Hello World!"); 12 } 13 14 public static void main(String[] args) 15 { 16 ApplicationContext ctx = new ClassPathXmlApplicationContext("HelloWorld.xml"); 17 18 HelloWorld hw = (HelloWorld) ctx.getBean("helloworld"); 19 20 hw.greet(); 21 } 22 }
以上程序我们可以得到结果:Hello World!
最后的结果 Hello World! 是怎么来的,很显然是调用了hw.greet(),而 hw 明显是 HelloWorld这个类的一个实例。但hw这个实例怎么来的?程序并没有为我们 new 出来一个HelloWorld实例。我们再来看上面两行代码,new ClassPathXmlApplicationContext("HelloWorld.xml") 这段代码,学过XML解析的同学应该会眼熟,这很像XML解析时的代码,同时结合到我们在xml文件中定义的一个 HelloWorld 类型的 bean 元素,以及ctx.getBean("helloworld")获取的对象,与 xml 中定义的 bean 的 id 值一样,我们应该可以大概得出结论:
解析xml文件后,将得到的各种 bean 放置到 ApplicationContext 容器中,容器对象再通过getBean()方法根据 bean id 获取对应的bean,接着就可以操作 bean 对象了。在这整个过程中,作为程序员的我们并没有任何创建对象的动作,创建 bean 实例的整个过程完全交由 Spring 框架来为我们实现,它何时创建,怎么创建我们一概不知。这时,我们再来看看IoC/DI的概念。
IoC(Inversion of Contorl)一般翻译成 "控制反转",其思想是反转资源获取的方向。传统的资源查找方式要求组件项容器发起请求查找资源。作为回应,容器适时的返回资源。而应用了 IoC 之后,则是容器主动地将资源推送给它所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源。
DI(Dependncy Injection)依赖注入:它是IoC的另一种表述方式,即组件以一些预先定义好的方式(如:setter方法)接受来自如容器的资源注入,相较于IoC而言,这种表述更直接。
通过概念的解释我们应该更好理解 IoC/DI 了吧,IoC就是一个容器,它为我们获取 bean 资源提供了一种方式。让我们不再手动的去 new 一个对象,而是容器自己会为我们自动生成好对象,只要把配置文件配置好。如此一来,大大降低了模块与模块之间的耦合性。因为当一个类需要持有另一类的对象时,不再需要去手动地实例化这个类,只要通过 Spring 框架为我们注入即可。当一个模块需求改变时,也不会涉及到对其他类的修改。
现在我们通过分析代码来实现自己的IoC容器,模拟整个 IoC/DI 过程。由前面我们知道,IoC容器会解析 xml 文件生成 bean 实例放置在容器中,xml 文件中配置有值为全类名的 class 属性。我们应该可以想到,这些 bean 实例是通过 反射生成的。所以,我们的 IoC 容器应该是:解析 xml 文件,将里面配置的 bean id 和通过反射生成的 bean 实例作为键值对放入一个Map对象中,这样我们才能通过 id 属性来获取 bean 实例。同时,我们还需要通过反射来为类中的属性赋值。下面来看代码
1 package com.bupt.test; 2 3 import java.lang.reflect.Field; 4 import java.lang.reflect.Method; 5 import java.util.HashMap; 6 import java.util.List; 7 import java.util.Map; 8 import org.dom4j.Document; 9 import org.dom4j.Element; 10 import org.dom4j.io.SAXReader; 11 12 /** 13 * 模拟Spring的ClassPathXmlApplicationContext对象 14 */ 15 public class ClassPathXmlApplicationContext 16 { 17 //创建Map对象用来存放beans.xml中配置的对象名(id)和对象(class.newInstance())的键值对 18 private Map<String, Object> beans = new HashMap<>(); 19 20 public ClassPathXmlApplicationContext(String filePath) throws Exception 21 { 22 //获取jom4j解析器 23 SAXReader sax = new SAXReader(); 24 //获取根节点 25 Document doc = sax.read(filePath); 26 //获取根元素节点beans 27 Element rootElement = doc.getRootElement(); 28 //获取根节点元素下所有的子元素bean 29 List<Element> elementList = rootElement.elements("bean"); 30 31 for(Element e : elementList) 32 { 33 //获取bean的id和class属性 34 String id = e.attributeValue("id"); 35 String className = e.attributeValue("class"); 36 37 //反射获取class属性对应类的实例 38 Object clazz = Class.forName(className).newInstance(); 39 40 beans.put(id, clazz); 41 42 //获取类中定义的所有属性 43 Field[] field = clazz.getClass().getDeclaredFields(); 44 //Map存放的是属性名和属性的class对象的键值对 45 Map<String, Class<?>> map = new HashMap<>(); 46 //遍历属性,将属性名和属性的Class对象放入map 47 for(Field f : field) 48 { 49 map.put(f.getName(), f.getType()); 50 } 51 52 //获取所有叫property的子元素 53 List<Element> propertyElement = e.elements("property"); 54 55 for(Element ele : propertyElement) 56 { 57 //获取property的name属性 58 String name = ele.attributeValue("name"); 59 //拼接方法名如setName() 60 String methodName = "set" + name.substring(0, 1).toUpperCase()+ name.substring(1); 61 Method m = null; 62 63 //获取property的bean和value属性 64 String beanProperty = ele.attributeValue("bean"); 65 String valueProperty = ele.attributeValue("value"); 66 67 //若第二个属性为另一个bean的引用 68 if(null != beanProperty) 69 { 70 //反射获取setXxx方法对象 71 m = clazz.getClass().getMethod(methodName, new Class[]{beans.get(beanProperty).getClass().getInterfaces()[0]}); 72 //执行setXxx方法 73 m.invoke(clazz, beans.get(beanProperty)); 74 75 } 76 //若第二个属性为value 77 if(null != valueProperty) 78 { 79 //根据value属性值,从map中获取setXxx方法的参数的Class对象 80 m = clazz.getClass().getMethod(methodName, new Class[]{map.get(name)}); 81 82 //判断参数类型,进行相应的类型变换,再将转换完后的参数传入invoke方法 83 if((String.class).equals(map.get(name))) 84 { 85 m.invoke(clazz, valueProperty); 86 } 87 else if((int.class).equals(map.get(name))) 88 { 89 m.invoke(clazz, Integer.parseInt(valueProperty)); 90 } 91 else if((double.class).equals(map.get(name))) 92 { 93 m.invoke(clazz, Double.parseDouble(valueProperty)); 94 } 95 } 96 } 97 } 98 } 99 100 //通过id值返回自动生成的对象 101 public Object getBean(String id) 102 { 103 return beans.get(id); 104 } 105 }
下面编写测试类
1 package com.bupt.test; 2 3 public interface UserDAO 4 { 5 public void save(); 6 }
1 package com.bupt.test; 2 3 public class UserDAOImpl implements UserDAO 4 { 5 @Override 6 public void save() 7 { 8 System.out.println("a user saved!"); 9 } 10 }
1 package com.bupt.test; 2 3 public class UserService 4 { 5 //持有对UserDAo的一个引用 6 private UserDAO userDAO; 7 private String name; 8 private int age; 9 private double height; 10 11 public double getHeight() 12 { 13 return height; 14 } 15 16 public void setHeight(double height) 17 { 18 this.height = height; 19 } 20 21 public String getName() 22 { 23 return name; 24 } 25 26 public void setName(String name) 27 { 28 this.name = name; 29 } 30 31 public int getAge() 32 { 33 return age; 34 } 35 36 public void setAge(int age) 37 { 38 this.age = age; 39 } 40 41 public void add() 42 { 43 userDAO.save(); 44 } 45 46 public UserDAO getUserDAO() 47 { 48 return userDAO; 49 } 50 51 public void setUserDAO(UserDAO userDAO) 52 { 53 this.userDAO = userDAO; 54 } 55 }
src下创建一个 bean.xml 文件
<?xml version="1.0" encoding="UTF-8"?> <beans> <!-- 把被引用的bean写在前面 --> <bean id="u" class="com.bupt.test.UserDAOImpl"> </bean> <bean id="userService" class="com.bupt.test.UserService"> <property name="userDAO" bean="u"/> <property name="name" value="tom"/> <property name="age" value="19"/> <property name="height" value="178.8"/> </bean> </beans>
编写执行测试类
package com.bupt.test; public class ApplicationContextTest { public static void main(String[] args) throws Exception { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("src/beans.xml"); UserService service =(UserService)ctx.getBean("userService"); service.add(); System.out.println(service.getName() + ", " + service.getAge() + ", " + service.getHeight() + ", " + service.getUserDAO()); } } /** * 结果为 * a user saved! * tom, 19, 178.8, com.bupt.test.UserDAOImpl@128cdfa */
这就是模拟 IoC 容器的代码,希望通过这段代码,能加深大家对 IoC 容器的理解
关于 Spring 面试时常问的关于 IoC/DI 的问题
spring 的优点?
1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦
2.可以使用容易提供的众多服务,如事务管理,消息服务等
3.容器提供单例模式支持
4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能
5.容器提供了众多的辅助类,能加快应用的开发
6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等
7.spring属于低侵入式设计,代码的污染极低
8.独立于各种应用服务器
9.spring的DI机制降低了业务对象替换的复杂性
10.Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可以自由选择spring的部分或全部
什么是DI机制?
依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色
需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中
创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者
因此也称为依赖注入。
spring以动态灵活的方式来管理对象 , 注入的两种方式,设置注入和构造注入。
设置注入的优点:直观,自然
构造注入的优点:可以在构造器中决定依赖关系的顺序。
什么是AOP?
面向切面编程(AOP)完善spring的依赖注入(DI),面向切面编程在spring中主要表现为两个方面
1.面向切面编程提供声明式事务管理
2.spring支持用户自定义的切面
面向切面编程(aop)是对面向对象编程(oop)的补充,
面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面。
AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象,
是对应用执行过程中的步骤进行抽象,,从而获得步骤之间的逻辑划分。
aop框架具有的两个特征:
1.各个步骤之间的良好隔离性
2.源代码无关性
posted on 2016-06-10 17:07 Traveling_Light_CC 阅读(390) 评论(0) 编辑 收藏 举报