Spring 框架模拟实现
1. 前言
通过几个章节的学习,大家对于 Spring 已经有了初步的认知,我们通过案例练习,或者源码追踪,可以粗略的看到 Spring 框架初始化 bean 对象的过程,那么这个章节,我们模拟 Spring 框架的思路,来写一个类似 Spring 加载对象的案例,加深大家的印象。
2. 案例实现思路
2.1 步骤介绍
思路分析:
我们通过写过的案例可以知道:
- Spring 框架的容器 是一个接口 
ApplicationContext和接口的实现类ClassPathXmlApplicationContext来初始化的; - 在初始化容器对象的时候需要传递 xml 配置文件的位置;
 - xml 的配置文件中主要是通过 bean 标签可以对 Java 的类进行描述:类的路径 类的标识 类的构造参数等等;
 - 容器初始化以后需要解析 xml 配置文件的各个 bean 标签;
 - 实例化的对象如果有参数或者构造方法,那么也需要给参数赋值;
 
开发准备 :
为了方便理解测试 ,我们来自定义容器的接口和实现类。
名称改为 SpringContext 和 XmlSpringContext 区别于框架的接口和实现类。
接口定义方法 getBean 用于获取容器内的示例,实现类定义有参构造用于接受初始化时候的配置文件路径。
接口代码如下 :
public interface SpringContext {
    public Object getBean(String beanName);
}
实现类代码如下 :
public class XmlSpringContext  implements SpringContext  {
    Map<String,Object> map = new HashMap<String,Object>();
    public XmlSpringContext (String filename){
    }
    public Object getBean(String beanName){     
        return map.get(beanName);
    }
}
代码解释 :
- map 用于存储实例化的 bean 对象 ;
 - 有参构造方法逻辑暂时为空,下面会做实现,加载文件实例化对象在方法内部;
 - getBean 的方法用于通过 key 获取 map 中存储的实例。
 
为了测试对象的实例化,我们自定义 UserService 和 UserServiceImpl 作为测试的接口对象和实现类。
接口代码如下 :
public interface UserService {
    public void deleteById(Integer id);
}
接口的实现类代码如下 :
public class UserServiceImpl implements UserService {
    //持久层的dao属性
    private UserDao userDao;
    public UserDao getUserDao() {
        return userDao;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    //实现接口的方法
    public void deleteById(Integer id) {
        System.out.println("删除的方法执行");
    }
}
代码解释:dao 的属性其实是为了模拟属性赋值,后面依赖注入章节会详细讲解。
自定义一个 xml 文件 作为模拟框架的配置文件 :
<?xml version="1.0" encoding="UTF-8"?>
<beans>
     <bean name="userDao" class="com.wyan.dao.UserDaoImpl"></bean>
     <bean name="userService" class="com.wyan.service.UserServiceImpl">
         <property name="userDao" ref="userDao"></property>
     </bean>                    
</beans>
代码解释:userDao 的 bean 需要实例化 是因为 service 用到了它的引用,所以这里多个属性 property。
编写测试类加载文件测试 :
public class TestSpring {
    @Test
    public void test() {
        //初始化容器(读取配置文件 构建工厂)
        SpringContext context =
                new XmlSpringContext("applicationContext.xml");
        UserServiceImpl userService = (UserServiceImpl) context.getBean("userService");
        userService.deleteById(1);
        System.out.println(userService.getUserDao());
    }
}
代码解释:这里的目的只是测试能否获取对象调用方法,如果控制台打印证明案例成功
2.2 容器对象的实现类构造函数具体代码
思路分析 :
1. 读取初始化时候传递的文件路径;
2. 通过 SAXReader 解析 xml 文件的节点得到 beans 节点下对应多个 bean 节点集合;
3. 每一个 bean 表示一个对象,都需要被初始化,所以需要循环遍历集合;
4. 在循环遍历的过程中获取 id 属性和 class 属性,id 属性作为存入 map 的 key,class 属性用于反射实例化对象,并存储 map 的 value;
5. 继续解析子节点,如果有参数,反射获取 method 执行参数赋值。
完整代码 :
public XmlSpringContext(String filename){
        // xml文件的解析器
        SAXReader  sr = new SAXReader();
        try {
            //构建一个直接通向我们配置文件路径 的输入流
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(filename);
            //文档模型对象
            Document doc = sr.read(inputStream);
            //获取根标签
            Element root = doc.getRootElement();
            //获取当前根标签的子标签
            List<Element>  beans = root.elements("bean");
            for(Element bean:beans){            
                String key = bean.attributeValue("name");
                String value = bean.attributeValue("class");
                Class<?> myclass = Class.forName(value);
                //当前对象
                Object obj = myclass.newInstance();
                map.put(key, obj);              
                List<Element> elements = bean.elements("property");
                if(elements.size()>0){
                    for(Element pro: elements){
                         String av = pro.attributeValue("name");//dao--->setDao
                         //方法名
                         String methodName="set"+(av.charAt(0)+"").toUpperCase()+av.substring(1,av.length());                        
                         //方法参数
                         String refvalue = pro.attributeValue("ref");
                         Object refobj = map.get(refvalue);                      
                         //根据方法名称获取方法对象Method
                         Method method = myclass.getMethod(methodName,refobj.getClass().getInterfaces()[0]);                     
                         method.invoke(obj, refobj);
                    }
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }   
    }
测试结果:

3. 小结
本章节带着大家模拟一下 Spirng 加载文件的过程和实例化对象的过程,当然这个过程只是模拟 Spring 的框架的思路,而并不是真正的 Spring 框架源码,实际源码远比这个要复杂的多,
那么通过本章节我们收获哪些知识呢?
- Spring 容器类的使用
 - xml 配置文件的作用
 - 反射技术的应用
 
我不相信不劳而获,如果有谁告诉你,他可以做到 XX 速成,请远离他,他连基本的诚信都没有…
访问者可将本网站提供的内容或服务用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯本网站及相关权利人的合法权利。
本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站,邮箱:80764001@qq.com,予以删除。
