模拟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,修改起来方便,不用硬性的修改代码。