Spring 前传~耦合 && 手动实现 Bean 容器。

Spring。



Why Spring。

Spring makes programming Java quicker, easier, and safer for everybody. Spring’s focus on speed, simplicity, and productivity has made it the world’s most popular Java framework.

“We use a lot of the tools that come with the Spring framework and reap the benefits of having a lot of the out of the box solutions, and not having to worry about writing a ton of additional code—so that really saves us some time and energy.”

Spring 框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。

  • Spring 是一个开源框架,为了解决企业应用开发的复杂性而创建的,但现在已经不止应用于企业应用。
  • Spring 是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
    - 从大小与开销两方面而言 Spring 都是轻量的。
    - 通过控制反转(IoC)的技术达到松耦合的目的。
    - 提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发。
    - 包含并管理应用对象的配置和生命周期,这个意义上是一种容器。
    - 将简单的组件配置、组合成为复杂的应用,这个意义上是框架。

选择 Spring。

在 Spring 上开发应用简单、方便、快捷。
Spring 带来了复杂 JavaEE 开发的春天。

Spring 的作用。

  • 容器。
  • 提供了对多种技术的支持。
    JMS
    MQ
    UnitTest
  • AOP(事务管理、日志等)。
  • 提供了众多方便应用的辅助类(JDBC Template 等)。
  • 对主流应用框架(Hibernate 等)提供了良好的支持。

适用范围。

  • 构建企业应用(SpringMVC+ Spring + Hibernate / ibatis)。
  • 单独使用 Bean 容器(Bean 管理)。
  • 单独使用 AOP 进行切面处理。
  • 其他的 Spring 功能,如:对消息的支持等。
  • 在互联网中的应用。

框架。

软件架框(software framework),通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求的基础功能的软件产品。
—— wikipedia。

使用别人的舞台,你来做表演。

特点。

半成品。
封装了特定的处理流程的控制逻辑。
成熟的、不断升级改进的软件。

框架与类库的区别。

框架一般是封装了逻辑、高内聚的。类库则是松散的工具组合。
框架专注于某一类领域,类库则是更通用的。

why 框架?

软件系统日趋复杂。
重用度高,开发效率和质量提高。
软件设计人员要专注于对业务逻辑领域的了解,使需求分析更充分。
易于上手、快速解决问题。

因为软件系统发展到今天已经很复杂了,特别是服务器端软件,涉及到的知识,内容,问题太多。在某些方面使用别人成熟的框架,就相当于让别人帮你完成一些基础工作,你只需要集中精力完成系统的业务逻辑设计。而且框架一般是成熟,稳健的,他可以处理系统很多细节问题,比如,事务处理,安全性,数据流控制等问题。还有框架一般都经过很多人使用,所以结构很好,所以扩展性也很好,而且它是不断升级的,你可以直接享受别人升级代码带来的好处。
框架一般处在低层应用平台(如 J2EE)和高层业务逻辑之间的中间层。
软件为什么要分层? 为了实现“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于延展,易于分配资源…总之好处很多啦。


耦合问题引入 Spring。

package com.geek.jdbc;

import java.sql.*;

/*
 * 程序的耦合。
 *
 *      耦合:
 *          类之间的依赖。
 *          方法间的依赖。
 *
 */
public class JDBCDemo01 {

    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        // 注册驱动。
//        DriverManager.registerDriver(new com.mysql.jdbc.Driver());

        Class.forName("com.mysql.jdbc.Driver");// 这里只是字符串,不再是一个类。
        // 如果没有这个类,上一句代码直接报红。
        // 而此句在运行后才抛出异常。
        // Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

        // 获取连接。
        Connection connection = DriverManager.getConnection("jdbc:mysql://192.168.223.128:3307/spring_demo", "root", "root");
        // 获取操作数据库的预处理对象。
        PreparedStatement preparedStatement = connection.prepareStatement("select * from account");
        // 执行 sql。得到结果集。
        ResultSet resultSet = preparedStatement.executeQuery();
        // 输出结果集。
        while (resultSet.next()) {
            System.out.println(resultSet.getString("name"));
        }
        // 释放资源。
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

// Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.


/*

CREATE DATABASE `spring_demo` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

 */

MySQL 注册驱动问题中,
如果使用 DriverManager.registerDriver(new com.mysql.jdbc.Driver());
com.mysql.jdbc 包不存在时,此行编译器代码会直接报红。
在这里插入图片描述

运行后会出现

在这里插入图片描述

而如果使用 Class.forName("com.mysql.jdbc.Driver");
代码编译器不会报错,而当程序运行时则会抛出异常。
Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

实现了把编译期异常转为运行时异常。

再来看看耦合问题。

// 注册驱动。
//        DriverManager.registerDriver(new com.mysql.jdbc.Driver());

        Class.forName("com.mysql.jdbc.Driver");// 这里只是字符串,不再是一个类。
        // 如果没有这个类,上一句代码直接报红。
        // 而此句在运行后才会出现异常。
        // Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

如果没有 com.mysql 这个包,上一句代码直接报红。

在这里插入图片描述

耦合:一个功能的实现对另外一个类的依赖。

本案例中,DriverManager.registerDriver(new com.mysql.jdbc.Driver()); 就需要依赖 com.mysql.jdbc.Driver() 这个类。也即 DriverManager.registerDriver()com.mysql.jdbc.Driver() 耦合。

  • 解耦:降低程序间的依赖关系。

编译期不依赖,运行时才依赖。

使用反射创建对象,避免使用 new。
通过读取配置文件,获取要创建的对象的全限定类名。


引例 2。

在这里插入图片描述

package com.geek.dao;

/**
 * 账户持久层接口。
 */
public interface IAccountDao {

    /**
     * 模拟保存账户。
     */
    void save();
}

package com.geek.dao.impl;

import com.geek.dao.IAccountDao;

/**
 * 账户持久层实现类。
 */
public class AccountDaoImpl implements IAccountDao {

    public void save() {
        System.out.println("account saved...");
    }
}

package com.geek.service;

/**
 * 账户业务层接口。操作账户。
 */
public interface IAccountService {

    /**
     * 模拟保存账户。
     */
    void saveAccount();
}

package com.geek.service.impl;

import com.geek.dao.IAccountDao;
import com.geek.dao.impl.AccountDaoImpl;
import com.geek.service.IAccountService;

/**
 * 账户的业务层实现类。
 */
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao = new AccountDaoImpl();

    public void saveAccount() {
        accountDao.save();
    }
}

package com.geek.ui;

import com.geek.dao.IAccountDao;
import com.geek.service.IAccountService;
import com.geek.service.impl.AccountServiceImpl;

/**
 * 模拟一个表现层,用于调用业务层。
 */
public class Client {

    public static void main(String[] args) {
        IAccountService accountService = new AccountServiceImpl();

        accountService.saveAccount();
    }
}

如果没有 AccountDaoImpl.java,编译器则会报错。

在这里插入图片描述


  • 解决:使用工厂。(第一版)。

在这里插入图片描述

package com.geek.factory;


import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/*
 *
 * bean —> 计算机英语:可重用组件。
 * JavaBean != 实体类(可重用组件的一部分)。
 *
 * JavaBean —> 用 Java 语言编写的可重用组件。
 * 实体类(可重用组件的一部分)。
 *
 * 可重用:(相机的以疏快门键:第一次拍照都需要按下)。
 *
 * 创建 Service 和 Dao 对象的工厂。
 *
 * 需求 how:
 *      1. 一个配置文件来配置 service 和 dao。
 *          内容:—> 唯一标识=全限定类名。(key=value)。
 *      2. 通过读取配置文件中的内容,反射创建对象。(把编译期错误转为运行时异常)。
 *
 *      配置文件: xml 或 properties。
 */

/**
 * 一个创建 Bean 对象的工厂。
 */
public class BeanFactory {

    // 定义一个 properties 对象。
    private static Properties properties;

    // 使用静态代码块为 properties 对象赋值。
    static {
        // 实例化对象。
        properties = new Properties();// 耦合只能降低,不能消除。

        // 获取 properties 文件的流对象。

//        new FileInputStream("src/...")
        // web 项目不能用 src 路径。
        // 使用绝对路径不能保证每次需要的文件一样。还是存在耦合。
        // ==》
        InputStream inputStream = BeanFactory.class.getClassLoader()
                .getResourceAsStream("bean.properties");// 创建在 resources 文件夹下的文件会成为类根路径下的文件。
        try {
            properties.load(inputStream);
        } catch (IOException e) {
//            e.printStackTrace();
            throw new ExceptionInInitializerError("初始化 properties 失败。");
        }
    }

    /**
     * 根据类的名称获取 Bean 对象。(多例)。
     *
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        Object bean = null;
        String beanPath = properties.getProperty(beanName);// 从 properties 文件中获取对应全限定名。
        try {
            bean = Class.forName(beanPath).newInstance();
//            System.out.println("beanPath = " + beanPath);
//            System.out.println("bean = " + bean);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return bean;
    }

}

引入配置文件。
/src/main/resources/bean.properties

accountService=com.geek.service.impl.AccountServiceImpl
accountDao=com.geek.dao.impl.AccountDaoImpl

对应类的改造。

package com.geek.service.impl;

import com.geek.dao.IAccountDao;
import com.geek.factory.BeanFactory;
import com.geek.service.IAccountService;

/**
 * 账户的业务层实现类。
 */
public class AccountServiceImpl implements IAccountService {

//    private IAccountDao accountDao = new AccountDaoImpl();

    private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");


//    int i = 1;// 如果是单例模式,print(i) 会改变。
    // 线程共享问题。
    // 如果在方法体中,则不存在此问题。


    public void saveAccount() {
        accountDao.save();

        int i = 1;
        System.out.println("i = " + i);

        i++;
    }
}

package com.geek.ui;

import com.geek.factory.BeanFactory;
import com.geek.service.IAccountService;
import com.geek.service.impl.AccountServiceImpl;

/**
 * 模拟一个表现层,用于调用业务层。
 */
public class Client {

    public static void main(String[] args) {
//        IAccountService accountService = new AccountServiceImpl();

        IAccountService accountService = (IAccountService) BeanFactory.getBean("accountService");

//        System.out.println("accountService = " + accountService);

        accountService.saveAccount();
    }
}

使用此方法,代码中不再依赖某一具体的 Java 类,而是将传入的配置文件(字符串形式)利用反射机制动态获取 JavaBean。

但是如果 AccountDaoImpl.java 不存在了,同样也会报运行时异常。只是在编译期间不会报错。

在这里插入图片描述
这样一来也就解决了编译期间的依赖耦合问题。


单例模式和多例模式实现原理。

工厂模式创建对象的单例模式多例模式探究。

运用上面的工厂类,假设我们需要创建多个对象。

package com.geek.ui;

import com.geek.factory.BeanFactory;
import com.geek.service.IAccountService;

/**
 * 模拟一个表现层,用于调用业务层。
 */
public class Client {

    public static void main(String[] args) {
//        IAccountService accountService = new AccountServiceImpl();

        for (int i = 0; i < 5; i++) {
            IAccountService accountService = (IAccountService) BeanFactory.getBean("accountService");
            System.out.println("accountService = " + accountService);
            System.out.println("i = " + i);
        }

    }
}

~~~

accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
i = 1
accountService = com.geek.service.impl.AccountServiceImpl@677327b6
i = 1
accountService = com.geek.service.impl.AccountServiceImpl@14ae5a5
i = 1
accountService = com.geek.service.impl.AccountServiceImpl@7f31245a
i = 1
accountService = com.geek.service.impl.AccountServiceImpl@6d6f6e28
i = 1

——多例模式。

可以看到每个对象在内存中的地址都不同。
说明此处创建的对象有多个,是多例模式(每次 getBean() 都会创建一个对象,这个对象在内存中的不同位置,说明是不同的对象)。

而如果像以下这样,多个对象是在内存中的同一位置,就说明创建对象是单例模式。

// accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
//accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
//accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
//accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
//accountService = com.geek.service.impl.AccountServiceImpl@1540e19d

单例:对象只被创建一次,从而类中的成员也就只能初始化一次,存在线程安全问题。
多例:对象被创建多次,每个对象都有单独的成员变量,但执行效率低。

这里创建对象会使用多例模式是因为

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

那么如何使用单例模式呢?

在 Java 中,对象如果不被使用,一段时间后会被 java 垃圾回收机制回收。
所以想要使用单例模式(创建了一个对象,让 ta 一直可以被程序使用,而不被 gc 回收),就要想办法将创建的对象存储起来。

package com.geek.factory;

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

/**
 * 一个创建 Bean 对象的工厂。
 */
public class BeanFactory {

    // 定义一个 properties 对象。
    private static Properties properties;

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

    // 使用静态代码块为 properties 对象赋值。
    static {

        try {
            // 实例化对象。
            properties = new Properties();// 耦合只能降低,不能消除。

            // 获取 properties 文件的流对象。

//        new FileInputStream("src/...")
            // web 项目不能用 src 路径。
            // 使用绝对路径不能保证每次需要的文件一样。还是存在耦合。
            // ==》
            InputStream inputStream = BeanFactory.class.getClassLoader()
                    .getResourceAsStream("bean.properties");// 创建在 resources 文件夹下的文件会成为类根路径下的文件。

            properties.load(inputStream);

            // ~~~单例。

            // 实例化容器。
            beans = new HashMap<String, Object>();
            // 取出配置文件中所有的 key。
            Enumeration<Object> keys = properties.keys();
            // 遍历枚举。
            while (keys.hasMoreElements()) {
                // 取出每个 key。
                String key = keys.nextElement().toString();
                // 根据 key 获取 value。
                String beanPath = properties.getProperty(key);
                // 反射创建对象。
                Object instance = Class.forName(beanPath).newInstance();
                // 把 key 和 value 放入容器中。
                beans.put(key, instance);
            }

        } catch (Exception e) {
//            e.printStackTrace();
            throw new ExceptionInInitializerError("初始化 properties 失败。");
        }
    }
}

~~~

accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
~ ~ ~ ~ ~ ~ ~
account saved...
~ ~ ~ ~ ~ ~ ~
i = 1
accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
~ ~ ~ ~ ~ ~ ~
account saved...
~ ~ ~ ~ ~ ~ ~
i = 2
accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
~ ~ ~ ~ ~ ~ ~
account saved...
~ ~ ~ ~ ~ ~ ~
i = 3
accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
~ ~ ~ ~ ~ ~ ~
account saved...
~ ~ ~ ~ ~ ~ ~
i = 4
accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
~ ~ ~ ~ ~ ~ ~
account saved...
~ ~ ~ ~ ~ ~ ~
i = 5

Process finished with exit code 0

用此方式创建的对象,like this, 地址都相同,说明是同一对象。——> 单例模式。

// accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
//accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
//accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
//accountService = com.geek.service.impl.AccountServiceImpl@1540e19d
//accountService = com.geek.service.impl.AccountServiceImpl@1540e19d

定义一个 Map 用于存放我们要创建的对象,我们称之为容器。
把单例模式创建的对象放入 Map(容器),要使用的时候我们去容器中拿就可以了。


以上就是 Spring 容器基本原理,和 Spring 框架中 Bean 以及单例多例的实现原理。

发布了47 篇原创文章 · 获赞 1 · 访问量 1177

猜你喜欢

转载自blog.csdn.net/lyfGeek/article/details/104582819