Spring学习笔记 —— 模拟一个简单的Spring项目工程

本文通过实战建立一个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管理模型原理图

Spring学习笔记 —— 模拟一个简单的Spring项目工程_第1张图片

对外提供一个接口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("-----------------------------------"); 
    }   
  } 
} 
运行结果:
C:\java>java   Sample1
磁盘信息:
分区盘符:C
分区容量:8G
目录数:200
文件数:1580
-----------------------------------
磁盘信息:
分区盘符:D
分区容量:10G
目录数:500
文件数:3000

-----------------------------------


使用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。




你可能感兴趣的:(Spring学习笔记 —— 模拟一个简单的Spring项目工程)