本文通过实战建立一个Spring工程,来介绍其整个架构以及原理
新建一Web项目工程,假设我们现在要写一个模块,比如用户管理,运用面向对象的思维,首先肯定至少得有个用户类。
(思考:什么是面向对象的思维,假如拿到一个问题要用面向对象的思维去解决,首先应该干什么? —答:首先找实体类,实实在在存在概念的类)
所以我们创建一个User类
package springtest.model; public class User { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
现在的目标是新增一个功能:添加用户 addUser
整个Spring管理模型原理图
对外提供一个接口UserService,对于UserService来说只需访问UserDAO,由UserDAO跟User和DB打交道。
现在我们创建一个用户管理类 UserService
package springtest.service; import springtest.dao.UserDAO; import springtest.model.User; public class UserService { private UserDAO userDAO; public UserDAO getUserDAO() { return userDAO; } public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } public void add(User u) { this.userDAO.save(u); } }创建一个UserDAO类
package springtest.dao; import springtest.model.User; public class UserDAO { public void save(User u) { System.out.println("user saved!"); } }
有人会问,这个UserService和UserDAO怎么重复了,直接在Userservice里存储不就行了吗,为啥还得去调用一个UserDAO呢?——Service这个层次不仅仅只是简单的做数据存储这么简单,还可以处理各种各样的业务逻辑,UserDAO只是单纯的和数据库打交道。
有人会问,怎么去跨数据库平台呢? 在UserDAO类 save方法里面如果换了数据库,得换掉该方法里面的代码,这样显然太麻烦,我们可以使用抽象编程,不把UserDAO写死,我们把它作为一个interface,然后用一个UserDAOImpl来实现这个UserDAO接口。
UserDAO代码如下
package springtest.dao; import springtest.model.User; public interface UserDAO { public void save(User u); }UserDAOImpl代码如下:
package springtest.impl; import springtest.dao.UserDAO; import springtest.model.User; public class UserDAOImpl implements UserDAO { @Override public void save(User u) { // TODO Auto-generated method stub System.out.println("user saved!"); } }
这样UserService的代码就改变成
package springtest.service; import springtest.dao.UserDAO; import springtest.impl.UserDAOImpl; import springtest.model.User; public class UserService { private UserDAO userDAO = new UserDAOImpl(); public UserDAO getUserDAO() { return userDAO; } public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } public void add(User u) { this.userDAO.save(u); } }这样就可以多写几个UserDAO的实现,分别实现各大数据库的存储方法,要用哪个就在Service方法里面去new哪个,这样就简单灵活多了。
但是这样也会有问题,如果一个项目模块多了,那么就会有各种DAO,并且在Service里面还要改程序,这样就显得麻烦,那么能不能把UserDAOImpl类写到一个配置文件里面,从配置文件里面读取呢?
以前对于这种不同的DAO的解决方案是用工厂方法,那样也很麻烦,因为每一个不同的DAO都要创建不同的工厂,既然这样不如我们创建一个大的总的工厂,把所有要产生的具体的DAO都用这个工厂来产生,而且这个工厂我们还可以做得更加灵活一些,不生成很多Create DAO,把这些写在配置文件里面就可以了。
Spring的配置文件是XML格式的
那么Java怎么读取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>上面的sample.xml文档,描述了某台电脑中硬盘的基本信息(根节点<HD>代表硬盘,<disk>标签代表硬盘分区,从它的name属性可以看出有两个盘符名称为"C"和"D"的分区;每个分区下都包含<capacity>,<directories><files>三个节点,分别代表了分区的空间大小、目录数量、所含文件个数)
import java.util.*; 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("sample.xml"); //构造文档对象 Element root=doc.getRootElement(); //获取根元素 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"); 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("-----------------------------------"); } } }运行结果:
-----------------------------------
使用JUnit对UserService类的addUser进行代码测试
package springtest.service; import org.junit.Test; import springtest.model.User; public class UserServiceTest { @Test public void testAdd() { UserService service = new UserService(); User u = new User(); service.add(u); } }现在我们把DAO文件各种各样的配置都写在配置文件里头去,这个配置文件用XML
现在模拟了一个配置文件beans.xml如下:
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="u" class="springtest.impl.UserDAOImpl"/> </beans>我们的想法是把这个配置文件中的bean读出来,然后放到UserService的变量userDAO里面去,这样我们就可以通过修改配置文件来改变userDAO的实现方式。那么怎么读呢?
为了模拟Spring,这里模拟Spring一个单独的类——ClassPathXmlApplicationContext,关于Context可以理解为一个运行环境,代码如下:
package springtest.spring; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jdom.Document; import org.jdom.Element; import org.jdom.input.SAXBuilder; public class ClassPathXmlApplicationContext implements BeanFactory { //使用Map作为容器最合适 private Map<String,Object> beans = new HashMap<String,Object>(); public ClassPathXmlApplicationContext() throws Exception { SAXBuilder sb = new SAXBuilder(); Document doc = sb.build(this.getClass().getClassLoader().getResourceAsStream("beans.xml")); Element root = doc.getRootElement();//获取根元素 List list = root.getChildren("bean"); for(int i=0;i<list.size();i++) { //将XML文件里的Bean属性依次拿出来,并放入容器内 Element element = (Element)list.get(i); String id = element.getAttributeValue("id"); String clazz = element.getAttributeValue("class"); System.out.println(id + ":" + clazz); Object obj = Class.forName(clazz).newInstance(); beans.put(id, obj); } } @Override public Object getBean(String name) { // TODO Auto-generated method stub return beans.get(name); } }ClassPathXmlApplicationContext实现了一个BeanFactory,简单的说,就是我们用到的各种DAO的工厂,我们需要的时候就在这个工厂里拿,通过工厂类的方法getBean("Bean名"),Bean名已经写在配置文件
BeanFactory代码如下:
package springtest.spring; public interface BeanFactory { public Object getBean(String name); }然后在用JUnit进行测试,UserServiceTest代码如下:
package springtest.service; import org.junit.Test; import springtest.dao.UserDAO; import springtest.model.User; import springtest.spring.BeanFactory; import springtest.spring.ClassPathXmlApplicationContext; public class UserServiceTest { @Test public void testAdd() throws Exception { BeanFactory factory = new ClassPathXmlApplicationContext(); UserService service = new UserService(); UserDAO userDAO = (UserDAO)factory.getBean("u"); service.setUserDAO(userDAO); User u = new User(); service.add(u); } }
UserService代码如下:
package springtest.service; import springtest.dao.UserDAO; import springtest.impl.UserDAOImpl; import springtest.model.User; public class UserService { private UserDAO userDAO; public UserDAO getUserDAO() { return userDAO; } public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } public void add(User u) { this.userDAO.save(u); } }
因为每次都需要把UserService实例化出来,然后把想要的实现通过BeanFactory拿出来,然后还需要set进去,所以很麻烦,能不能省掉这一步
我们可以用Spring的自动装配来实现这一点
配置文件Beans.xml文件修改为
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="u" class="springtest.impl.UserDAOImpl"/> <bean id="userService" class="springtest.service.UserService"> <property name="userDAO" bean="u"/> </bean> </beans>ClassPathXmlApplicationContext代码修改为
package springtest.spring; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jdom.Document; import org.jdom.Element; import org.jdom.input.SAXBuilder; public class ClassPathXmlApplicationContext implements BeanFactory { private Map<String,Object> beans = new HashMap<String,Object>(); public ClassPathXmlApplicationContext() throws Exception { SAXBuilder sb = new SAXBuilder(); Document doc = sb.build(this.getClass().getClassLoader().getResourceAsStream("beans.xml")); Element root = doc.getRootElement();//获取根元素 List list = root.getChildren("bean"); for(int i=0;i<list.size();i++) { Element element = (Element)list.get(i); String id = element.getAttributeValue("id"); String clazz = element.getAttributeValue("class"); System.out.println(id + ":" + clazz); Object obj = Class.forName(clazz).newInstance(); beans.put(id, obj); //子元素为property的全部拿出来 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);//beanObject.getClass()为UserDAOImpl String methodName = "set" + name.substring(0,1).toUpperCase() + name.substring(1); System.out.println("method name = " + methodName); //setUserDAO //beanObject.getClass().getInterfaces()[0]为UserDAOImpl实现的第一个接口 Method m = obj.getClass().getMethod(methodName, beanObject.getClass().getInterfaces()[0]); m.invoke(obj, beanObject); } } } @Override public Object getBean(String name) { // TODO Auto-generated method stub return beans.get(name); } }
好,现在我们来测试,UserServiceTest代码如下:
package springtest.service; import org.junit.Test; import springtest.dao.UserDAO; import springtest.model.User; import springtest.spring.BeanFactory; import springtest.spring.ClassPathXmlApplicationContext; public class UserServiceTest { @Test public void testAdd() throws Exception { BeanFactory factory = new ClassPathXmlApplicationContext(); UserService service = (UserService)factory.getBean("userService"); User u = new User(); service.add(u); } }运行得到结果
u:springtest.impl.UserDAOImpl
userService:springtest.service.UserService
method name = setUserDAO
user saved!
搞定!
最好我们理解一下Spring的一个概念 IOC(DI)
IOC——控制反转,DI——依赖注入(Dependency Injection),两个意思一样
后者貌似更容易理解,UserService中UserDAO的属性是依赖容器Spring来帮我注入进来,而不是我自己写死了;控制反转的意思大抵差不多,原来的UserDAO是由当前类自己控制的,现在改为Spring容器来帮助控制,好处就是耦合性就降低了,修改userDAO的实现只需要修改配置文件就OK。