模拟spring IOC,DI,深入理解spring IOC,DI的好处
spring,java,ioc2016-11-13
网上随手可以查到各种IOC,DI的概念和讨论。这种概念对新手而言一向难以理解,在慕课网,极客学院这一讲spring入门评论区一片嘘声。
但如果能在学习前了解一下模拟实现的spring,想必很多概念性的东西也就能逐步理解。
以下代码参考于马士兵老师的教学视频。
首先,我们开始一个简单的java project。需求是实现一个用户账号密码插入数据库。
package com.fangkehang.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;
}
}
package com.fangkehang.service;
import com.fangkehang.dao.UserDAO;
import com.fangkehang.dao.impl.UserDAOImpl;
import com.fangkehang.model.User;
public class UserService {
public void add(User u){
//这里再调用数据库来实现一下用户 insert.
}
}
package com.fangkehang.dao;
import com.fangkehang.model.User;
public class UserDAO {
public void save(User u){
//这里再调用数据库来实现一下用户 insert.
System.out.println("a user saved!");//方便起见,我们用输出来代替数据库操作。
}
}
在UserSevice中
package com.fangkehang.service;
import com.fangkehang.dao.UserDAO;
import com.fangkehang.dao.impl.UserDAOImpl;
import com.fangkehang.model.User;
public class UserService {
private UserDAO userDAO = new UserDAO();
public UserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
public void add(User u){
this.userDAO.save(u);
}
}
package com.fangkehang.dao;
import com.fangkehang.model.User;
public interface UserDAO {
public void save(User u);
}
然后来一个实现:新建UserDAOImpl类
package com.fangkehang.dao.impl;
import com.fangkehang.dao.UserDAO;
import com.fangkehang.model.User;
public class UserDAOImpl implements UserDAO{
@Override
public void save(User u) {
// TODO Auto-generated method stub
System.out.println("a user saved!");
}
}
package com.fangkehang.service;
import static org.junit.Assert.*;
import org.hamcrest.Factory;
import org.junit.AfterClass;
import org.junit.Test;
import org.omg.CORBA.PRIVATE_MEMBER;
import org.omg.CORBA.portable.ApplicationException;
import com.fangkehang.dao.UserDAO;
import com.fangkehang.model.User;
import com.fangkehang.spring.ClassPathXmlApplicationContext;
public class UserServiceTest {
@Test
public void testAdd(){
UserService us = new UserService();
User u = new User();
us.add(u);
}
}
OK , 到此这个小项目就做完了。当然这个小项目还没有涉及到spring的东西,先继续往下思考。
上面这种形式的编程运用了面向对象和面向接口的技术,对各个功能的解耦合也做得相当好,但是我们在有三个地方用到了new对象,
UserService us = new UserService();
User u = new User();
private UserDAO userDAO = new UserDAOImpl();
于是每次我们要换一个数据库的时候,我们要把所有类型UserDAO的new实现改一次。我们只是想单纯做个插入操作,我们要把每一个类似这样的User都做这么多次new的操作,倘若有100个类似User的东西,我们要new一下每个的Service,DAOImpl…..这就让整个项目很臃肿。于是,我们用xml配置的方式来优化一下。
<beans>
<bean id="u" class="com.fangkehang.dao.impl.UserDAOImpl" />
<bean id="userService" class="com.fangkehang.service.UserService">
<property name="userDAO" bean="u"/>
</bean>
</beans>
package com.fangkehang.spring;
public interface BeanFactory {
public Object getBean(String name);
}
package com.fangkehang.spring;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jdom.Element;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
public class ClassPathXmlApplicationContext implements BeanFactory{
private Map<String, Object> beans = new HashMap<String, Object>();
public ClassPathXmlApplicationContext() throws Exception{
// TODO Auto-generated constructor stub
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 o = Class.forName(clazz).newInstance();
beans.put(id, o);
//这里是property里面的通过反射取出,然后配置这个userDAO的具体实现,模拟的是DI。
for(Element propertyElement: (List<Element>)element.getChildren("property") ) {
String name = propertyElement.getAttributeValue("name");
String bean = propertyElement.getAttributeValue("bean");
Object beanObject = beans.get(bean);
String methodName = "set" + name.substring(0,1).toUpperCase() + name.substring(1);
System.out.println("method name = " + methodName);
Method m = o.getClass().getMethod(methodName, beanObject.getClass().getInterfaces()[0]);
m.invoke(o, beanObject);
}
}
}
@Override
public Object getBean(String name) {
return beans.get(name);
}
}
看到这个类先不要害怕,这个长长的类名不过是sprign的jar包里面也采用这个名字我们才采用的。构造方法想做的就是采用jdom(这里因为采用了jdom,所以请你自行百度然后导入jdom的jar包)来取出beans.xml这个xml文件根元素beans下每一个bean标签下对应的id和name,把它放在map里面。整个类要实现的也就是把配置文件下的id,name放到map中去,然后返回。
其中带这个注释(//这里是property里面的通过反射取出,然后配置这个userDAO的具体实现,模拟的是DI。)模拟的是DI,所以之后你想修改数据库的实现方式,只需要改property配置。
package com.fangkehang.service;
import static org.junit.Assert.*;
import org.hamcrest.Factory;
import org.junit.AfterClass;
import org.junit.Test;
import org.omg.CORBA.portable.ApplicationException;
import com.fangkehang.dao.UserDAO;
import com.fangkehang.model.User;
import com.fangkehang.spring.ClassPathXmlApplicationContext;
public class UserServiceTest {
@Test
public void testAdd() throws Exception {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
UserService service = (UserService) ctx.getBean("userService");
User u = new User();
service.add(u);
}
}
之后可以成功运行,有没有发现,我们只需要new一下这个ClassPathXmlApplicationContext()的类,就可以实现对个类似User的配置了。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="u" class="com.fangkehang.dao.impl.UserDAOImpl">
</bean>
<bean id="userService" class="com.fangkehang.service.UserService">
<property name="userDAO" ref="u"/>
</bean>
</beans>
Junit test修改成:
package com.fangkehang.service;
import static org.junit.Assert.*;
import org.hamcrest.Factory;
import org.junit.AfterClass;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.fangkehang.model.User;
public class UserServiceTest {
@Test
public void testAdd() throws Exception {
BeanFactory factory = new ClassPathXmlApplicationContext("beans.xml");
UserService service = (UserService) factory.getBean("userService");
User u = new User();
service.add(u);
}
}
spring中同样有个接口叫Beanfactory,一个类叫做ClassPathXmlApplicationContext。他们的实现也是用反射的形式来实现的。
至此,你应该对spring容器有更深入的理解,如果没看明白,多看几遍,或者动手实验一下,有什么问题也可以给我留言。代码包链接在下面,第一个是模拟的,第二个是用spring实现的。
链接: https://pan.baidu.com/s/1c2cGWBu 密码: gqvf