模拟Spring阐述依赖注入的思想以及spring的内部的实现原理(读取配置文件,通过反射进行装配和依赖注入)

模拟Spring阐述依赖注入的思想以及spring的内部的实现原理(读取配置文件,通过反射进行装配和依赖注入)

/*

 * 想分层,把不同的层次作用以及之间的关系给别人说一遍。

 * 例如要想在数据库添加一个用户,一开始最土的方法是直接在main方法里面写数据库的连接,写直接写add一个用户,后来人们想到至少分一个层次出来即model层,但是添加的方法add写到

 * model层的话需要new一个对象才能使用这个方法,所以人们又想到对于用户的管理层,通过UserService调用User,之后添加用户会把User添加到DB中,也就是所UserService

 * 会访问User以及数据库DB。最初是这样的,但是这样不合理。如果我们想跨越数据库平台,我写的这个Userservice这个add方法呢,既能往mysql里面存,也能往Oracle中存,也

 * 可以往其他的数据库中存,那么这一个方法肯定是涵盖不了的,除非在里面写一大堆的if,所以更好的方法是抽象出来一个对数据库的管理层,这个我们可以称为DAO层,这里为UserDAO层

 * 由UserDAO负责和User以及数据库DB打交道(把User存到数据库中)对于UserService只需要访问UserDAO层,对外只提供UserService接口,由UserService来调用UserDAO

 * DAO层提供一个对数据库的屏蔽作用,例如底层数据库变了,只要变UserDAO就可以了。

 * 那么怎样实现跨数据库平台呢?我们在DAO层中的写UserDAO类,写成public class UserDAO{里面写 public void save(User user){}方法},但是这样的话把UserDAO写死了,因为save

 * 方法里面写了连接MySQL的代码,要想连接Oracle则需要改save方法里面的代码,把它换成连接Oracle的代码,这样比较麻烦。那怎么才能实现呢?才能变的灵活了?办法是面向抽象的编程,也就是面向接口的编程呢个,即把public class UserDAO

 * 该成public interface UserDAO{},接下来再分一个DAO的实现,实现了UserDAO 接口,然后实现里面的save方法。然后当我们使用UserService是对接口进行编程,userDAO = new UserDAOImpl();现在灵活性就出来了,要想实现跨数据库平台

 * 就可以多写几个UserDAO的实现,想连接mysql写UserDAOMysqlImpl的UserDAO的实现,相连接Oracle我们就写UserDAOOracleImpl的UserDAO的实现。然后Service层中的UserService需要用mysql存数据的时候就new UserDAOMysqlImpl()

 * 需要用Oracle的时候就new UserDAOOracleImpl()这个就叫做面向抽象的编程,原来的话就是写死的,现在就灵活了,相连接那个数据库就new哪个。

 * 假如说我有好多好多的DAO以及Service

 * 添加不同对象的管理,例如我想添加对老师的管理,就需要添加TeacherService,如果添加对课程的管理,就需要添加CourceService

 * 对应的子道中需要添加TeacherDAO,还需要添加TeacherDAOImpl,

 * 添加对学生的管理,就需要添加对学生的管理,需要添加StudentService,还需要添加StudentDAO,还需要

 * 添加其实现。这样就会产生很多的DAO。怎样管理不同对象的DAO呢,可以通过一个工厂来进行管理,就需要用到工厂模式

 * 这样其实也比较麻烦因为每一个不同的DAO都要产生不同的工厂,既然这样的话不如来一个大的工厂,把我们所有的具体的

 * DAO东西都用这个大的工厂来产生。这个工厂呢要求比较灵活的产生这些DAO,那么怎样实现呢?需要用配置文件来实现,用XML文件,那么就要学习怎样读取XML文件?

1.首先讲解dom4j 解析XML的过程:

     1.引入jar包,jdom-1.1.jar。

  

     2.把要解析的XML文件放到项目工程的src下面,代码如下

test.xml:

<?xml version="1.0" encoding="UTF-8"?>
<HD>
  <disk name="C">
    <capacity>8G</capacity>
    <directories>200</directories>
    <files>1580</files>
  </disk>

  <disk name="D">
    <capacity>10G</capacity>
    <directories>500</directories>
    <files>3000</files>
  </disk>
</HD> 


写解析jdom的方法:

import java.util.List;

import org.jdom.*;
import org.jdom.input.SAXBuilder;


public class sample1 {
     public static void main(String[] args) throws Exception{ 
            SAXBuilder sb=new SAXBuilder();
            //构造文档对象
            Document doc=sb.build(sample1.class.getClassLoader().getResourceAsStream("test.xml")); 
            //获取根元素HD
            Element root=doc.getRootElement(); 
            //取名字为disk的所有元素
            List list=root.getChildren("disk");
            //通过集合遍历所有元素,一个disk其实就是一个元素
            for(int i=0;i<list.size();i++){
               Element element = (Element) list.get(i);
               String name = element.getAttributeValue("name");//获取属性为name的值
               String capacity=element.getChildText("capacity");//取disk子元素capacity的内容
               String directories=element.getChildText("directories");
               String files=element.getChildText("files");
               System.out.println("磁盘信息:");
               System.out.println("分区盘符:"+name);
               System.out.println("分区容量:"+capacity);
               System.out.println("目录数:"+directories);
               System.out.println("文件数:"+files);
               System.out.println("-----------------------------------");
            }  
          }
}
运行结果:
 
磁盘信息:
分区盘符:C
分区容量:8G
目录数:200
文件数:1580
-----------------------------------
磁盘信息:
分区盘符:D
分区容量:10G
目录数:500
文件数:3000
-----------------------------------


2.使用反射的思想和jdom解析XML文件模拟Spring容器初始解析配置文件的过程以及搞明白什么是IOC/DI

在不用Spring的时候,我们在Service调用DAO,需要在UserService里面把new Dao层的对象。例如private UserDao userDAO = new UserDAOImpl();有了容器后容器帮我们把DAO层注入到Service层。怎么注入的?我们看下面的代码:

下面是我的整个项目工程:

这里给出部分代码:

DAO层:

package com.lp.dao;

import com.lp.model.User;
//UserDao负责把对象存到不同的数据库中去,考虑为什么写成接口,而不写成一个类?
//答案是:为了实现跨数据库,面向抽象的编程
public interface UserDao {
    public void save(User user);
}
package com.lp.dao.impl;

import com.lp.dao.UserDao;
import com.lp.model.User;
//UserDAOImpl负责和不同的数据库打交道,为了实现跨数据库,我们可以多写几个UserDao的实现
//例如mysql的连接就写一个UsermysqlDAO的实现,其它数据库的实现就写其他数据库的实现
public class UserDAOImpl implements UserDao {

    public void save(User user) {
        //这里写不同的数据库连接的实现
        //Hibernate
        //JDBC
        //XML
        //NetWork
        System.out.println("user saved!");
    }

}

Service层:

package com.lp.service;

import com.lp.dao.UserDao;
import com.lp.model.User;
import com.lp.dao.impl.UserDAOImpl;

public class UserService {
    //private UserDao userDAO = new UserDAOImpl();
    private UserDao userDAO;//有了配置文件就不用向上面的那样new了,
    
    public UserDao getUserDAO() {
        return userDAO;
    }

    public void setUserDAO(UserDao userDAO) {
        this.userDAO = userDAO;
    }
    //向数据库中添加一个用户,只要通过调用UserDAO,然后通过调用save()方法就能实现
    public void add(User user){
        userDAO.save(user);
    }
    
}

 模拟Spring里面的ApplicationContext.xml在src下面放置要解析的配置文件bean.xml,代码:

 

 

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <!-- 把 UserDAOImpl读出来放到DAO里面去,也就是new一个Class的对象为u的-->
    <bean id="u" class="com.lp.dao.impl.UserDAOImpl"/>
    <bean id="userService" class="com.lp.service.UserService">
    <!-- 在userService有一个属性,这个属性就是userDAO,更准确点就是setUserDAO方法,
    也就是说spring可以把userService下面的userDAO注入进来,装配到一块。
     -->
        <property name = "userDAO" bean="u"></property>
    </bean>
</beans>

 

跟上面读取配置文件一样。这个类实现了一个接口叫做BeanFactory。叫做bean工厂。

 

package com.lp.spring;
/*
 * 存放各种各样的DAO的工厂
 */
public interface BeanFactory {
    public Object getBean(String name);//每个bean都有一个名字通过id指定
}

 

 

    为了模拟spring怎样读取配置文件还需要读取配置文件的类,这里我们自己创建一个包叫Com.lp.spring这里存放了spring里面的类:这里ClassPathXmlApplicationContext类:ClassPathXmlApplicationContext:使用这个类来读取bean.xml的配置文件,实现BeanFactory,实现里面获取bean对象的方法:

 

    package com.lp.spring;
    import java.lang.reflect.Method;
    import java.util.*;
    
    import org.jdom.*;
    import org.jdom.input.SAXBuilder;
    
    /*
     * ClassPathXmlApplicationContext是模拟spring的类
     */
    public class ClassPathXmlApplicationContext implements BeanFactory {
        //用Map容器存储bean的名字和值
    private Map<String , Object> beans = new HashMap<String, Object>();
        //IOC Inverse of Control DI Dependency Injection
        public ClassPathXmlApplicationContext() throws Exception {
            SAXBuilder sb=new SAXBuilder();
            //解析beans.xml文件
            Document doc=sb.build(this.getClass().getClassLoader().getResourceAsStream("beans.xml")); //构造文档对象
           //获取根元素beans
            Element root=doc.getRootElement(); 
            List list=root.getChildren("bean");
            //取名字为bean的所有的小孩存放到List集合当中
            for(int i=0;i<list.size();i++){
               Element element=(Element)list.get(i);
               String id=element.getAttributeValue("id");//取出属性值为id的值
               String clazz=element.getAttributeValue("class");//取出属性值为class的值
             //把class="com.lp.dao.impl.UserDAOImpl"生成一个对象 o 
               Object o = Class.forName(clazz).newInstance();
               //System.out.println(id);
               //System.out.println(clazz);
               beans.put(id, o);//然后把id和对应的对象放到容器里面
               
              //用了反射思想
             for(Element propertyElement : (List<Element>)element.getChildren("property")) {
                   String name = propertyElement.getAttributeValue("name"); //userDAO
                   String bean = propertyElement.getAttributeValue("bean"); //u
                   Object beanObject = beans.get(bean);//UserDAOImpl instance
                   
                   String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);//通过这句话构造setUserDAO()方法
                   System.out.println("method name = " + methodName);
                   Method m = o.getClass().getMethod(methodName, beanObject.getClass().getInterfaces()[0]);//setUserDAO(UserDao.class)
                   m.invoke(o, beanObject);
               }
            }  
        }
    /**
     * 从容器中把这个对象取出来
     */
        public Object getBean(String id) {
            return beans.get(id);
        }
    
    }

 

/*

 * 测试类专门写在另外一个source包里面,然后引入junit.jar文件,然后右键

 * 要测试的类,选择jUnitCase创建测试类

 * 使用bean容器

 */

 

package com.lp.service;
/*
 * 测试类专门写在另外一个source包里面,然后引入junit.jar文件,然后右键
 * 要测试的类,选择jUnitCase创建测试类
 */
import static org.junit.Assert.*;

import org.junit.Test;

import com.lp.dao.UserDao;
import com.lp.model.User;
import com.lp.spring.BeanFactory;
import com.lp.spring.ClassPathXmlApplicationContext;

public class UserServiceTest {

    @Test
    public void testAdd() throws Exception {
        //把容器给new出来
        BeanFactory factory = new ClassPathXmlApplicationContext();
        //思考怎样使UserService也不用new?答案就是交给工厂BeanFactory,从配置文件里面读出来
        //UserService service = new UserService();
        UserService service = (UserService) factory.getBean("userService");
        /*UserDao userdao = (UserDao) factory.getBean("u");
        service.setUserDAO(userdao);//把这个对象注入到service中
*/        User u = new User();
        service.add(u);
        
    }
}

运行结果:

method name = setUserDAO
user saved!

以前要写的代码与在容器中配置的文件对应关系:

<bean id="userService" class="com.lp.service.UserService">

<property name = "userDAO" bean="u"></property>

</bean>

这段代码的意识:userService中调用userDao:

  相当于

    UserService service = new UserService();

 

    service.setUserDAO(userdao);

   这两句代码。

   意思:通过配置文件把userdao和service 这两个对象的关系设定好了,不用硬性的编码,也就是把userDao装配到service里面了。

 

IOC/DI原理:IOC(inverse of controll)控制反转:通过上面的例子可以看出,以前Service层中想调用Dao层中的方法,需要在Service层中new出来,是由自己控制的,而现在由Spring容器控制创建初始化bean(对象),维护bean与bean之间的关系,权利从程序中转移到了Spring容器,而程序本身不需要再维护。

DI(dependency injection)依赖注入:实际上和IOC是一个概念,为了更准确的描述Spring的核心技术,对象之间的关系是相互依赖的,一些通用的bean组件可以依赖容器注入进来。原理:也可以说成反射注入,通过使用解析XML技术,以及反射的机制,构造真实实例对象,构造set方法把userDAO这个bean注入到userService中。

好处:耦合性大大的降低,用配置文件注入bean,修改起来方便,不用硬性的修改代码。

 

 

 
posted @ 2012-10-09 15:43  积淀  阅读(1166)  评论(0编辑  收藏  举报