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编辑  收藏  举报

导航