Spring框架2:程序解耦和控制反转(IOC)

Posted 别再闹了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架2:程序解耦和控制反转(IOC)相关的知识,希望对你有一定的参考价值。

本系列笔记均是对b站教程https://www.bilibili.com/video/av47952931 的学习笔记,非本人原创

技术图片

技术图片

SpringMVC是表现层的框架,MyBatis是持久层框架

Spring是EE开发的一站式框架:有EE开发每一层的解决方案。以IOC(反转控制)和AOP(面向切面编程)为内核,同时提供了展现层Spring MVC赫尔持久层Spring JDBC等企业级应用技术,是使用最多的JAVA EE开源框架

  • WEB:SpringMVC

  • Service:Bean管理,Spring声明式管理

  • DAO层:Jdbc模板、ORM模块

Spring的优点:

  • 轻量,方便解耦,简化开发
  • 控制反转(Ioc)
  • 面向切面 (AOP)
  • 声明式事务的支持
  • 方便程序的测试
  • 可以集成其他框架
  • 降低Java EE API使用难度(封装了)

Spring:

  • docs:开发规范与API
  • libs:SPring的开放.jar包和源码
  • Schema:配置文件的约束

技术图片

core container:是spring的IOC部分,所有的spring应用都要基于这个核心容器

程序间的耦合和解耦

用一个例子来说明程序的耦合

package com.jiading.jdbc;

import java.sql.*;

public class JDBCDemo1 {
    /*
    用JDBC来讲解程序的耦合
    这里举的是程序间的依赖关系的例子(类之间的依赖、方法间的依赖)
     解耦:降低程序间的依赖关系
     */
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //1.注册驱动
        //在编译期就要依赖其他的程序,它的依赖性是很差的
        //实际开发时,应该做到:编译期不依赖,运行期才依赖
        //解决思路:
        /*
        使用反射来创建对象,避免使用new关键词
        读取配置文件来获取要创建的对象全限定类名
         */
        //DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接
        Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/jd_learning","root","<密码>");
        //3. 获取操作数据库的预处理对象
        PreparedStatement pstm=conn.prepareStatement("select * from account");
        //4. 执行SQL,得到结果集合
        ResultSet rest=pstm.executeQuery();
        //5. 遍历结果集合
        while(rest.next()){
            System.out.println(rest.getString("name"));
        }
        //6. 释放资源
        rest.close();
        pstm.close();
        conn.close();
    }
}

使用工厂设计模式进行解耦

技术图片

这是标准的工厂设计模式模板,接口和实现类分开,可以参考下:
IAccountDAO:

package com.jiading.dao;
/*
账户的持久层接口
 */
public interface IAccountDAO {
    /*
    模拟保存账户
     */
    void saveAccount();
}

AccountDAOImpl:

package com.jiading.dao.impl;

import com.jiading.dao.IAccountDAO;

public class AccountDAOImpl implements IAccountDAO {
    /*
    模拟保存
     */
    public void saveAccount() {
        System.out.println("保存了账户");
    }
}

IAccountService:

package com.jiading.service;

public interface IAccountService {
    /*
    模拟保存账户
     */
    void saveAccount();
}

AccountServiceImpl:
package com.jiading.service.impl;

import com.jiading.dao.IAccountDAO;
import com.jiading.dao.impl.AccountDAOImpl;
import com.jiading.factory.BeanFactory;
import com.jiading.service.IAccountService;

/
账户的业务层实现类
/
public class AccountServiceImpl implements IAccountService {
//new使得代码有依赖性,独立性差,需要随着依赖的对象的修改而修改
//private IAccountDAO accountDAO = new AccountDAOImpl();

//可以修改为:
private IAccountDAO accountDAO = (IAccountDAO)BeanFactory.getBean("accountDao");
public void saveAccount() {
    accountDAO.saveAccount();
}

}
Client:java
package com.jiading.ui;

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

/
模拟一个表现层,用于调用业务层
/
public class Client {
public static void main(String[] args) {
//new产生了依赖关系,使得代码的独立性差
//IAccountService as=new AccountServiceImpl();

    //可以改造为:
    IAccountService as= (IAccountService)BeanFactory.getBean("accountService");
    as.saveAccount();
}

}
BeanFactory:java
package com.jiading.factory;

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

/*
一个Bean对象的工厂
Bean:在计算机英语中,有可重用组件的含义
JavaBean > 实体类,javabean是用java语言编写的可重用组件
它就是创建我们的service和dao对象的

  1. 需要一个配置文件来配置service和dao
    配置的内容:全限定类名和对应的唯一标志
    配置文件可以是xml或者是properties
  2. 通过读取配置文件中配置的内容,反射创建bean对象
    */
    public class BeanFactory {
    //定义一个Properties对象
    private static Properties props;

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

    //使用静态代码块为Properties对象赋值
    static {
    //还是用了new关键字,但是这次依赖的是Java中的模块而不是我们自己写的了,这就是进步
    //依赖只能降低而不能消除,不能在编程时候一个new关键字不用

     try {
         //1.实例化对象
         props = new Properties();
         //获取properties文件的流对象
         //在resources下的文件在部署时会被放置在根路径下,所以不需要写包名
         InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
         props.load(in);
    
         beans=new HashMap<String, Object>();
         Enumeration keys = props.keys();
         //遍历枚举
         while(keys.hasMoreElements()){
             String key=keys.nextElement().toString();
             //根据key获取value
             String beanPath=props.getProperty(key);
             //反射创建对象(也就是在初始化环节中在容器中分别创建一个对象以供之后调用,也就是单例模式
             Object value=Class.forName(beanPath).newInstance();
             //存入容器,以供调用
             beans.put(key,value);
         }
    
     } catch (IOException e) {
         throw new ExceptionInInitializerError("初始化properties失败");
     } catch (IllegalAccessException e) {
         e.printStackTrace();
     } catch (InstantiationException e) {
         e.printStackTrace();
     } catch (ClassNotFoundException e) {
         e.printStackTrace();
     }

    }

    /
    根据Bean的名称获取bean对象
    /
    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();
    }
    /
    //有了容器后,就有了更方便的getBean方法

     return beans.get(beanName);

    }
    }
    bean.properties:properties
    accountService=com.jiading.service.impl.AccountServiceImpl
    accountDao=com.jiading.dao.impl.AccountDAOImpl
    ```
    现在我们正式使用spring框架,由spring创建框架。这里的配置文件就选择xml

    IOC

    控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring_day01_xmlSpring</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
</project>

Client:

package com.jiading.ui;

import com.jiading.dao.IAccountDAO;
import com.jiading.service.IAccountService;
import com.jiading.service.impl.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/*
模拟一个表现层,用于调用业务层
 */
public class Client {
    /*
    获取spring的IOC核心容器,并根据ID获取对象
     */
    public static void main(String[] args) {
        //1. 获取核心容器对象
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        //2.根据id获取bean对象
        IAccountService as=(IAccountService)ac.getBean("accountService");
        //除了获取Object对象自己再强制转换,也可以直接在getBean的时候就传入类型
        IAccountDAO adao=ac.getBean("accountDAO",IAccountDAO.class);
        System.out.println(as);
        System.out.println(adao);
        //这样获取的对象是单例的
        //as.saveAccount();
    }
}

bean.xml:

<?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">
    <!--把对象的创建交给spring来管理,每一项由一个bean标签表示 -->
    <bean id="accountService" class="com.jiading.service.impl.AccountServiceImpl"></bean>
    <bean id="accountDAO" class="com.jiading.dao.impl.AccountDAOImpl"></bean>
</beans>

创建bean的方式以及bean对象的生命周期

<?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">
    <!--把对象的创建交给spring来管理,每一项由一个bean标签表示 -->
    <bean id="accountService1" class="com.jiading.service.impl.AccountServiceImpl"></bean>
    <!-- spring对bean的管理细节
     1. 创建bean的三种方式
        1. 使用默认构造函数创建:
            在配置文件中使用bean标签,配以id和class属性后,且没有其他属性和标签时,采用的就是默认构造函数创建
            此时如果类中没有默认的、无参的构造函数,则无法创建
        2. 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象并存入spring容器)
            有时候要引用一个jar包中的类,我们无法修改原有的默认构造函数,而我们想获取的不是这个类本身(往往这样的类只是个工厂类,我们需要的是工厂的商品)
        3. 使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)

     2. bean对象的作用范围
     3. bean对象的生命周期
     -->
    <!-- 2. 使用普通工厂中的方法创建对象-->
    <bean id="InstanceFactory" class="com.jiading.factory.InstanceFactory"></bean>
    <bean id="accountService" factory-bean="InstanceFactory" factory-method="getInstanceFactory"></bean>
    <!-- 一目了然,是吧-->
    <!-- 使用工厂中的静态方法创建对象-->
    <bean id="accountService2" class="com.jiading.factory.StaticFactory" factory-method="getInstanceFactory"></bean>
    <!-- Bean的作用范围调整
        bean标签的scope属性:用于指定bean的作用范围
        取值:
            singleton:单例,也是默认值,常用
            prototype:多例,常用
            request:作用于web应用的请求范围
            session:作用于web应用的会话范围
            global-session:作用于集群环境的会话范围(全局会话范围),如果不是集群的话和session等效
    -->
    <!-- Bean对象的生命周期
        在类中添加init()和destory()方法,然后在bean标签中加上init-method和destory-method,就实现了由框架调用对象的构造函数和析构函数
        单例对象:在配置文件解析完、容器创建后就创建,一直保存在容器存在时,随着容器销毁而销毁
            但是如果是在main函数直接调用、不手动使用close()关闭容器的话,对象会因为程序执行完成而被自动回收内存,不能调用销毁函数。所以要销毁的话,需要手动调用clsoe方法来关闭容器
            但是ApplicationContext类是没有close方法的,要销毁容器的话需要直接创建子类ClassPathXmlApplicationContext的对象,也即:
            ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        多例对象:
            对象只要在使用过程中就一直存活,而当对象长时间没有使用且没有别的对象引用该对象时,由java的垃圾回收器回收
    -->
</beans>

以上是关于Spring框架2:程序解耦和控制反转(IOC)的主要内容,如果未能解决你的问题,请参考以下文章

Spring01-控制反转(IOC)与依赖注入(DI)

Spring基础IOC(控制反转)AOP(面向切面编程)Log4j注解配置

Spring入门2

Spring框架知识复习之一

第245天学习打卡(知识点回顾 spring IOC)

04-Spring01-IOC