Spring(1)实例浅析spring的产生原因及工作原理的探索

我spring的博客分为分析篇和使用篇,这里属于分析篇。注重分析,以便日后回顾。

本文前言:要想搞清楚Spring的工作原理,首先需要还原一个场景需求,即我们在使用spring前,到底遇到了什么样的问题,才让我们转而使用spring框架。

场景模拟还原

1. 需要实现用户账户的保存功能。
这里采用普通的开发模拟,主要体现一种思想和结构。

2. 在没有使用spring之前,按照一般的开发思路,我采用的是这样的结构:

  • Dao -> UserDao 数据持久层接口
  • DaoImpl -> UserDaoImpl 数据持久层实现类
  • service -> UserService 用户业务层接口类
  • serviceImpl -> UserServiceImpl 用户业务层实现类
  • ui -> Client 模拟客户端表现层

详细结构如图:
在这里插入图片描述
3. 类中具体代码内容如下(用简单的输出来表示模拟保存):

当然如果想使用一下原生的JDBC连接数据库,可以参考这篇文章,加以回顾:
JDBC连接数据库(原生方式)

  • UserDao 数据持久层接口
/**
 * 账户的持久层接口
 * @author QingBang.Wang
 */
public interface UserDao {
    /**
     * 模拟保存
     */
    void saveUser();
}
  • DaoImpl -> UserDaoImpl 数据持久层实现类
import day02.User.Dao.UserDao;

/**
 * @author QingBang.Wang
 */
public class UserDaoImpl implements UserDao {
    public void saveUser(){
        System.out.println("保存了账户");
    }
}
  • service -> UserService 用户业务层接口类
/**
 * 业务层接口
 * @author QingBang.Wang
 */
public interface UserService {
    /**
     * 模拟保存
     */
    void saveUser();
}
  • serviceImpl -> UserServiceImpl 用户业务层实现类
import day02.User.Dao.UserDao;
import day02.User.service.UserService;

/**
 * 账户的业务层实现类
 * @author QingBang.Wang
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImpl();
    public void saveUser(){
        userDao.saveUser();
    }
}
  • ui -> Client 模拟客户端表现层
import day02.User.service.UserService;

/**
 1. 控制层+模拟表现层
 2. @author QingBang.Wang
 */
public class Client {
    public static void main(String[] args) {
        UserService us = new UserServiceImpl();
        us.saveUser();
    }
}

4. 运行测试
在这里插入图片描述

5. 在写好这些类后,问题便浮出水面:模块之间耦合度太高!请看以下截图:
在这里插入图片描述
在这里插入图片描述
正是这种 new 的方式,让各个模块间产生了很强的耦合度。依赖关系难以松解。

如何解决这个耦合度过高的问题呢?
在写JDBC代码的时候,我们曾使用了Class.forName()来加载,而不是使用new的方式来加载驱动,这为我们解决问题提供了一种解决思路。

因而,这里我们从这个角度进行入手:

初步思路的探索过程

1. 单纯的使用Class.forName(“类名”),好处和弊端是什么?

好处:能够在一定程度上实现解耦
弊端:括号里面的类名是写死的,失去了代码的灵活性

2.为了解决上述的弊端,我们利用配置文件的方式来解决:
配置文件常采用 xml 和properties 两种,由于properties的较为简洁,因而我使用properties来描述,后来的xml配置会在使用篇中介绍。

  • 在resource文件中,创建bean.properties文件
UserService=day02.User.serviceImpl.UserServiceImpl
UserDao=day02.User.DaoImpl.UserDaoImpl

在这里插入图片描述
3.有了配置文件,我们就可以使用工厂模式来加载配置文件中的类

Bean 有着可重复使用组件的含义

  • 创建Factory包,新建BeanFactory类
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * @author QingBang.Wang
 */
public class BeanFactory {
    //定义一个Properties对象
    private static Properties props;

    //使用静态方法代码块为Properties对象赋值
    static {
        try {
            //实例化对象
            props = new Properties();
            //获取properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据Bean的名称获取bean对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        Object bean = null;
        try {
            String beanPath = props.getProperty(beanName);
            //用反射的方式来创建对象
            bean = Class.forName(beanPath).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bean;
    }
}

4.相应的,我们修改我们最初的工程代码

  • 修改serviceImpl -> UserServiceImpl 用户业务层实现类
    在这里插入图片描述

  • 修改ui -> Client 模拟客户端表现层
    在这里插入图片描述
    5.此时,我们进行测试运行
    在这里插入图片描述
    表明初步解耦成功

6.然鹅,出现了一个新的问题,我们进行一个简单的测试:
修改客户端:
在这里插入图片描述
运行:
在这里插入图片描述
7.不知道大家眼不眼熟,这就是java中的多例!

这里不详细讲述多例和单例
当前实例就是多例的一种,servlet 则通常是一种单例
多例:简述将就是每次使用产生新的对象,拥有独立的成员变量
单例:每次都使用同一个对象

在我们的service和dao中全是多例,没有单例中可以改变的类成员,想要实现改变类成员,只能在方法中定义。
同时,由于每次使用都产生新的对象,然后销毁,因而多例的执行效率远不如单例!

我们知道,由于java中的垃圾回收机制,虚拟机中创建的对象,一段时间不使用就会被销毁,如果我们想要保存产生的对象,我们需要马上把它存起来,存起来的这个东西,我们就称为容器。

8. 到这里,我们自然而然的就引入了容器这一思想

那么,如何做到产生容器的效果呢?
没错,就是利用我们Java中非常重要的反射机制!
Map就要登场了

再次改良我们的解决方案

1.首先到BeanFactory中,值得注意的是,我们找到了多例产生的原因,如图
在这里插入图片描述
正是因为 newInstance()的存在。才会每次都产生新的对象出现。

bean = Class.forName(beanPath).newInstance();
每次都会调用默认构造函数创建新的对象

2. 我们对BeanFactory进行修改:

import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @author QingBang.Wang
 */
public class BeanFactory {
    //定义一个Properties对象
    private static Properties props;

    //定义一个Map,用于存放我们要创建的对象,称之为容器
    private static Map<String,Object> beans;

    //使用静态方法代码块为Properties对象赋值
    static {
        try {
            //实例化对象
            props = new Properties();
            //获取properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            //实例化容器
            beans = new HashMap<String,Object>();
            //取出配置文件中所有的key
            Enumeration keys =  props.keys();   //Enumeration是枚举类型
            //遍历枚举
            while (keys.hasMoreElements()){
                //取出每个key
                String key = keys.nextElement().toString();
                //根据key获取value
                String beanPath = props.getProperty(key);
                //反射创建对象
                Object value = Class.forName(beanPath).newInstance();
                //把key和value存入容器中
                beans.put(key,value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //进行改造!

    /**
     * 单例模式,根据Bean的名称获取bean对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }

    /**
     * 多例模式,根据Bean的名称获取bean对象
     * @param beanName
     * @return
     */
//    public static Object getBean(String beanName){
//        Object bean = null;
//        try {
//            String beanPath = props.getProperty(beanName);
//            //用反射的方式来创建对象
//            bean = Class.forName(beanPath).newInstance();   //每次都会调用默认构造函数创建对象
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        return bean;
//    }


}

需要注意的是:
在这里插入图片描述
以及
在这里插入图片描述

静态代码块只在类执行的时候加载一次,此时已经将对象创建完毕放入容器中,
再次创建对象时就可以实现从容器中取出来!

3. 此时,再次运行客户端进行测试
在这里插入图片描述
也就是说,此时,多例模式已经成功的转为单例模式,同时实现了容器的存储,对象完全没必要反复创建
类成员也可以重复使用了,当然如果不想重复使用,完全可以放到方法中。

谜底揭晓

我们再次改良的方案,其实就是我们常说的IOC,控制反转

那么在我们探索的阶段,经过了怎样的模式变化呢?
下图是我们最初的模型,也就是说,工程中不仅拥有着很强耦合度,同时也很难保持工程资源的独立性

在这里插入图片描述

下图则是我们采用容器后产生的效果:

在这里插入图片描述

结束

扩展思考:这种模式为什么叫控制反转呢?
在我们的类中,类拥有着决定是否采用new这种方式或者采用容器工厂种方式的权利,如果选择了new这种方式,那么这个类便决定了将创建对象的控制权交给自己,拥有着独立控制权。而采用了容器工厂这种方式,则决定了是将控制权交由工厂处理,类本身无法独立拥有控制权,其中的好处便是降低了程序间的依赖关系,实现了资源的有效管控,削减计算机程序的耦合

至此,整体的探索也就完成了,从中也体现出了Spring当中最最重要的概念–IOC

不得不佩服创造这一思想的巨人们,让我们得以站在了巨人的肩膀上。

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值