IOC是怎么来的?实现一个简单的IOC

Servlet三层架构

Servlet三层架构

DemoDaoImpl

public class DemoDaoImpl implements DemoDao {
    @Override
    public List<String> findAll() {
        return Arrays.asList("aaa","bbb","ccc");
    }
}

DemoServiceImpl

public class DemoServiceImpl implements DemoService {

    private DemoDaoImpl demoDaoImpl = new DemoDaoImpl();
    @Override
    public List<String> findAll() {
        return demoDao.findAll();
    }
}

DemoServlet

@WebServlet(urlPatterns = "/demo2")
public class DemoServlet extends HttpServlet {

    DemoServiceImpl demoServiceImpl = new DemoServiceImpl();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println(demoServiceImpl.findAll().toString());
    }
}

这里简单模拟一下三层架构

【问题】需求变更

修改数据库为Oracal
对于 MySQL 跟 Oracle ,在有一些特定的 SQL 上是不一样的(比如分页),这样我还不能只把数据库连接池的相关配置改了就好使,每个 DaoImpl 也得改啊!于是乎,你开始修改起工程里所有的 DaoImpl

public class DemoDaoImpl implements DemoDao {
    @Override
    public List<String> findAll() {
        return Arrays.asList("oracal,oracal,oracal");
    }
}

再变更呢?让你再把oracal换成MySQL

【方案】引入静态工厂

事先把这些 Dao 都写好了,之后用一个静态工厂来创建特定类型的实现类,这样万一发生需求变更,是不是就可以做到只改一次代码就可以了!

/**
 * 当要变更dao时(使用MySql变成Oracal)
 * 我们可以引入一个静态工厂,来创建特定的类型的实现类,在Service中使用工厂来使用我们需要的实现类
 */
public class BeanFactory {
    public static DemoDao getDemoDao(){
        return new DemoOrcalDao();
//        return new DemoDaoImpl();
    }
}

修改Service

public class DemoServiceImpl implements DemoService {

//    private DemoDaoImpl demoDaoImpl = new DemoDaoImpl();
    private DemoDao demoDao = BeanFactory.getDemoDao();
    @Override
    public List<String> findAll() {
        return demoDao.findAll();
    }
}

【问题】紧耦合

如果这里源码文件丢失,项目连编译都无法通过因为这里类与类之间的依赖关系是紧耦合的关系

public class BeanFactory {
    public static DemoDao getDemoDao(){
        return new DemoOrcalDao(); //这里BeanFactory这个类强依赖于DemoOrcalDao类
//        return new DemoDaoImpl();
    }

【方案】解决紧耦合

为了解决上面紧耦合的关系,我们可以使用反射,将他们之间的依赖关系转变成弱依赖
反射可以声明一个类的全限定名,来获取它的字节码描述,这样也能构造对象!这样项目就不会在编译器出现错误

public static DemoDao getDemoDao1() {
    try {
        return (DemoDao) Class.forName("com.xmz.dao.impl.DemoDaoImpl").newInstance();
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException("DemoDao instantiation error, cause: " + e.getMessage());
    }
}

【问题】硬编码

(硬编码)但是上面的类路径名是写死的,这样在我们每次切换数据库后还得重新编译工程才可以正常运行,
这显得貌似很没必要,应该有更好的处理方案。我们可以使用 外部配置文件的形式解决硬编码的问题

【改良】引入外部化配置文件

在resources中建立一个factory.properties 文件

demoService=com.xmz.service.impl.DemoServiceImpl
demoDao=com.xmz.dao.impl.DemoDaoImpl

使用外部化配置+反射,对于这种可能会变化的配置、属性等,通常不会直接硬编码在源代码中,而是抽取为一些配置文件的形式( properties 、xml 、json 、yml 等),配合程序对配置文件的加载和解析,从而达到动态配置、降低配置耦合的目的。

public class BeanFactory {
   private static Properties properties;
   
   // 使用静态代码块初始化properties,加载factord.properties文件
   static {
       properties = new Properties();
       try {
           // 必须使用类加载器读取resource文件夹下的配置文件
           properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
       } catch (IOException e) {
           // BeanFactory类的静态初始化都失败了,那后续也没有必要继续执行了
           throw new ExceptionInInitializerError("BeanFactory initialize error, cause: " + e.getMessage());
       }
   }
   public static Object getBean(String beanName) {
       try {
           Class<?> beanClazz = Class.forName(properties.getProperty("demoDao"));
           return  beanClazz.newInstance();
       } catch (ClassNotFoundException e) {
           throw new RuntimeException("BeanFactory have not [" + beanName + "] bean!", e);
       } catch (IllegalAccessException | InstantiationException e) {
           throw new RuntimeException("[" + beanName + "] instantiation error!", e);
       }
   }
}

【问题】多重构建

public class DemoServiceImpl implements DemoService {
    public DemoServiceImpl(){
        for (int i = 0; i < 10; i++) {
            System.out.println(BeanFactory.getBean("demoDao"));
        }
    }
}

在这里插入图片描述
可以发现每次打印的内存地址都不相同,证明是创建了10个不同的 DemoDaoImpl !

【改良】引入缓存

由于上面的getBean 可能在每次获取一个bean的时候,都会创建一个新的实例
所以我们在这里引入缓存, 如果对于这些没必要创建多个对象的组件,如果能有一种机制保证整个工程运行过程中只存在一个对象,那就可以大大减少资源消耗。于是可以在 BeanFactory 中加入一个缓存区:

// 缓存区,保存已经创建好的对象
private static Map<String, Object> beanMap = new HashMap<>();
public static Object getBean(String beanName) {
    // 双检锁保证beanMap中确实没有beanName对应的对象
    if (!beanMap.containsKey(beanName)) {
        synchronized (BeanFactory.class) {
            if (!beanMap.containsKey(beanName)) {
                // 过了双检锁,证明确实没有,可以执行反射创建
                try {
                    Class<?> beanClazz = Class.forName(properties.getProperty(beanName));
                    Object bean = beanClazz.newInstance();
                    // 反射创建后放入缓存再返回
                    beanMap.put(beanName, bean);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException("BeanFactory have not [" + beanName + "] bean!", e);
                } catch (IllegalAccessException | InstantiationException e) {
                    throw new RuntimeException("[" + beanName + "] instantiation error!", e);
                }
            }
        }
    }
    return beanMap.get(beanName);
}

在这里插入图片描述

总结

  • 静态工厂可将多处依赖抽取分离
  • 外部化配置文件+反射可解决配置的硬编码问题
  • 缓存可控制对象实例数
private DemoDao dao = new DemoDaoImpl();
private DemoDao dao = (DemoDao) BeanFactory.getBean("demoDao");

上面的是强依赖 / 紧耦合,在编译期就必须保证 DemoDaoImpl 存在;下面的是弱依赖 / 松散耦合,只有到运行期反射创建时才知道 DemoDaoImpl 是否存在。
再对比看,上面的写法是主动声明了 DemoDao 的实现类,只要编译通过,运行一定没错;而下面的写法没有指定实现类,而是由 BeanFactory 去帮咱查找一个 name 为 demoDao 的对象,倘若 factory.properties 中声明的全限定类名出现错误,则会出现强转失败的异常 ClassCastException 。
仔细体会下面这种对象获取的方式,本来咱开发者可以使用上面的方式,主动声明实现类,但如果选择下面的方式,那就不再是咱自己去声明,而是将获取对象的方式交给了 BeanFactory 。这种将控制权交给别人的思想,就可以称作:控制反转( Inverse of Control , IOC )。而 BeanFactory 根据指定的 beanName 去获取和创建对象的过程,就可以称作:依赖查找( Dependency Lookup , DL )。

静态工厂:是为了beanFactory做伏笔,有一个统一的工厂类提供实现类,封装了实现类的生产过程,便于给实现类的创建过程做扩展。
反射: 解决扩展性问题,spring作为框架,不可能预先知道需要构建的类,只能通过反射的方式来加载用户定义的bean.
缓存 :生产bean的过程,可以生产不同生命周期的bean,单例或者原型的。单例就可以用上缓存。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值