【spring】spring_IOC和DI

Spring概述

Spring是分层的 Java SE/EE应用full-stack轻量级开源框架以IOC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核

提供了表现层SpringMVC和持久层Spring JDBCTemplate以及业务层事务管理等众多的企业级应用技术,简单的说就是简化java开发。还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。

这里需要理解几个基本概念

  • 控制反转 :不是什么技术,它是一种降低对象耦合关系的一种设计思想。在Java开发中,Ioc意味着将对象交给容器控制

    其中最常见的方式叫做 依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)

    依赖注入 :指容器负责创建和维护对象之间的依赖关系,而不是通过对象本身负责自己的创建和解决自己的依赖。在当前类需要用到其他类的对象,由Spring为我们提供(就是省略new),我们只需要在配置中说明。

  • 面向切面编程 : 自己的理解:提取相同的代码,例如后台所有内容都需要登录为前提,呢么这个判断是否登录的方法就是面向切面编程

    • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。

Spring快速入门使用

ioc和依赖注入的由来

我们知道在面向对象设计的软件系统中,它的底层都是由N个对象构成的,各个对象之间通过相互合作,最终实现系统地业务逻辑,例如一个怀表,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。这就是耦合关系非常紧密的关系,所以为了解决,软件专家Michael Mattson 1996年提出了IOC理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中。

img

IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。如下图:

img

大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。

我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统:

img

我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现IOC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!

我们再来看看,控制反转(IOC)到底为什么要起这么个名字?我们来对比一下:

  • 软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

  • 软件系统在引入IOC容器之后,这种情形就完全改变了,如图3所示,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。

通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。

Spring的优势

1) 方便解耦,简化开发

通过Spring提供的IOC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度耦合。

用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

2) AOP 编程的支持

通过Spring的AOP功能,方便进行面向切面编程,许多不容易用传统OOP实现的功能可以通过AOP轻松实现。

3) 声明式事务的支持

可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量

4) 方便程序的测试

可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

Spring的体系结构

image-20211008085827787

Spring配置文件 => IoC容器

Bean标签基本配置

作用:通过配置将对象的创建交给Spring容器进行管理。

默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。

相关属性

  • id:Bean实例在Spring容器中的唯一标识;
  • class:Bean的全限定名称。

Bean标签范围配置

scope,指对象的作用范围,取值如下:

取值范围说明
singleton默认值,单例的
prototype多例的
requestWEB项目中,Spring创建一个Bean的对象,将对象存入到request域中
sessionWEB项目中,Spring创建一个Bean的对象,将对象存入到session域中
global sessionWEB项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession相当于session

当scope的取值为singleton时

  • Bean的实例化个数:1个
  • Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
  • Bean的生命周期:
    • 对象创建:当应用加载,创建容器时,对象就被创建了;
    • 对象运行:只要容器在,对象一直活着;
    • 对象销毁:当应用卸载,销毁容器时,对象就被销毁了。

当scope的取值为prototype时

  • Bean的实例化个数:多个
  • Bean的实例化时机:当调用getBean()方法时实例化Bean
  • Bean的生命周期:
    • 对象创建:当使用对象时,创建新的对象实例;
    • 对象运行:只要对象在使用中,就一直活着;
    • 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了。

bean生命周期配置

init-method:指定类中的初始化方法名称

destroy-method:指定类中销毁方法名称

bean实例化三种方式

Spring快速入门使用

使用无参构造方法实例化

根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败。

<bean id="userDao" class="com.qfedu.dao.impl.UserDaoImpl">

工厂静态方法实例化

创建静态工厂

public class StaticBeanFactory {
    public static UserDao getUserDaoImpl() {
        return new UserDaoImpl();
    }
}

在Spring配置文件中配置

<!-- 静态工厂初始化 -->
<bean id="userDao" class="com.qfedu.factory.StaticBeanFactory" factory-method="getUserDaoImpl"></bean>

测试

//演示通过静态工厂创建Bean
@Test
public void test1() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao = (UserDao) context.getBean("userDao");
    userDao.save();
}

工厂实例方法实例化

创建动态工厂

public class DynamicBeanFactory {
    public UserDao getUserDao() {
        return new UserDaoImpl();
    }
}

在Spring配置文件中配置

<bean id="factory" class="com.qfedu.factory.DynamicBeanFactory"></bean>
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>

测试同上

DI(依赖注入)

依赖注入:Dependency Injection ,指容器负责创建和维护对象之间的依赖关系,而不是通过对象本身负责自己的创建和解决自己的依赖。在当前类需要用到其他类的对象,由Spring为我们提供,我们只需要在配置中说明。

业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。

简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

即service中的private UserDao userDao = new 对象(),变成private UserDao userDao;,就是省略new

构造方法注入

  1. 创建接口UserService和实现类UserServiceImpl
public interface UserService {
    void save();
}

public class UserServiceImpl implements UserService {
    //这里一定要有该属性,我们最终的目的是让该属性关联一个UserDaoImpl的对象
    private UserDao userDao;

    public UserServiceImpl() {
    }
    
    //一定要有该有参的构造方法,通过该方法完成依赖注入
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
}
  1. 在Spring配置文件中配置
<bean id="userDao" class="com.qfedu.dao.impl.UserDaoImpl"></bean>

<bean id="userService" class="com.qfedu.service.impl.UserServiceImpl">
    <!-- 构造方法注入,通过ref将id为“userDao”的bean传递给了UserServiceImpl构造方法的userDao形参 -->
    <constructor-arg name="userDao" ref="userDao" />
</bean>
  1. 测试
@Test
public void test3() {
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    UserService userService = (UserService)context.getBean("userService");
    userService.save();
}

set方法注入(重点)

在UserServiceImpl中添加set方法

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

在Spring配置文件中配置

<bean id="userService" class="UserServiceImpl">
    <!-- set方法注入 -->
    <property name="userDao" ref="userDao"></property>
</bean>

测试方法同上

p名称空间注入

p命名空间注入本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件中

引入P命名空间

xmlns:p="http://www.springframework.org/schema/p"

在Spring配置文件中配置

<!-- p名称空间注入 -->
<bean id="userService" class="com.qfedu.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>

依赖注入其他类型

上面的案例,我们学习了如何注入引用类型的数据,除了引用数据类型,普通数据类型,集合数据类型也可以注入。

普通数据类型注入

创建Department实体类

//表示部门的实体类
public class Department {
    private Integer id;//部门编号
    private String name;//部门名称
    private String desc;//部门描述
    
    //set、get方法
    //toString方法
}
  1. 在Spring配置文件中配置
<!--
    通过Spring的IOC容器创建Department类的对象,并为其属性注入值
    无参构造方法实例化
-->
<bean id="department" class="com.qfedu.entity.Department">
    <!-- set方法注入
        value:简单类型
    -->
    <property name="id" value="1" />
    <property name="name" value="研发部" />
    <property name="desc" value="项目研发" />
</bean>

测试

@Test
public void test6() {
    //解析配置文件 -- 创建对象 -- 对象交给Spring的IOC容器进行管理
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    //获取Department的对象
    Department department = (Department)context.getBean("department");
    //打印对象
    System.out.println(department);
}

引用类型注入

  1. 创建实体类Department
//表示部门的实体类
public class Department {
    private Integer id;//部门编号
    private String name;//部门名称
    private String desc;//部门描述
    private Address address;//部门地址
    
    //set、get
    //toString
}

在Spring配置文件中配置

<bean id="address" class="com.qfedu.entity.Address">
    <property name="province" value="山东省" />
    <property name="city" value="青岛市" />
    <property name="county" value="市北区" />
    <property name="street" value="龙城路" />
    <property name="no" value="31号" />
</bean>

<!--
    通过Spring的IOC容器创建Department类的对象,并为其属性注入值
    无参构造方法实例化
-->
<bean id="department" class="com.qfedu.entity.Department">
    <!-- set方法注入
        value:简单类型
    -->
    <property name="id" value="1" />
    <property name="name" value="研发部" />
    <property name="desc" value="项目研发" />
    <!-- set方法注入
        ref:引用类型
    -->
    <property name="address" ref="address" />
</bean>

测试同上

集合数据类型(List<String>)的注入

创建Employee实体类

//表示员工的实体类
public class Employee {
    private Integer id;//员工编号
    private String name;//姓名
    private Integer age;//年龄
    private String gender;//性别
    private List<String> hobby;//爱好
        
    //set、get方法
    //toString方法
}
  1. 在Spring配置文件中配置
<!--
    通过Spring的IOC容器创建Employee类的对象,并为其属性注入值
    无参构造方法实例化
-->
<bean id="e1" class="com.qfedu.entity.Employee">
    <property name="id" value="1" />
    <property name="name" value="zs" />
    <property name="age" value="30" />
    <property name="gender" value="" />
    <!--        集合类型注入    -->
    <property name="hobby">
        <list>
            <value>学习1</value>
            <value>学习2</value>
            <value>学习3</value>
        </list>
    </property>
</bean>

测试同上

集合数据类型(List<对象>)的注入

  1. 修改Department实体类
//表示部门的实体类
public class Department {
    private Integer id;//部门编号
    private String name;//部门名称
    private String desc;//部门描述
    private Address address;//部门地址
    private List<Employee> emps;//普通员工
    
    //set、get方法
    //toString方法
}
  1. 在Spring配置文件中配置
<bean id="e1" class="com.qfedu.entity.Employee">
    <property name="id" value="1" />   <property name="name" value="zs" />    <property name="age" value="30" />    <property name="gender" value="" />
    <!--  集合类型注入         -->
    <property name="hobby">        <list>            <value>学习1</value>            <value>学习2</value>            <value>学习3</value>        </list>    </property>
</bean>

<bean id="e2" class="com.qfedu.entity.Employee">
    <property name="id" value="1" />    <property name="name" value="ls" />    <property name="age" value="31" />    <property name="gender" value="" />
    <!--集合类型注入 -->
    <property name="hobby">        <list>            <value>爬山</value>            <value>游泳</value>            <value>网游</value>        </list>    </property>
</bean>

<bean id="department" class="com.qfedu.entity.Department">
    <!-- set方法注入
            value:简单类型
        -->
    <property name="id" value="1" />
    <property name="name" value="研发部" />
    <property name="desc" value="项目研发" />
    <!-- set方法注入
            ref:引用类型
         -->
    <property name="address" ref="address" />
    <property name="emps">
        <list>
            <ref bean="e1" />
            <ref bean="e2" />
        </list>
    </property>
</bean>

测试同上

集合数据类型(Map<String>)的注入

  1. 修改Department,添加属性
//表示部门的实体类
public class Department {
    private Integer id;//部门编号
    private Map<String, Employee> leader;//部门主管
    //set、get
    //toString
}
  1. 在Spring配置文件中配置
<bean id="e1" class="com.qfedu.entity.Employee">
    <property name="id" value="1" />
</bean>

<bean id="e2" class="com.qfedu.entity.Employee">
    <property name="id" value="2" />
</bean>


<bean id="department" class="com.qfedu.entity.Department">
    <property name="id" value="1" />
    <property name="leader">
        <map>
            <entry key="CEO" value-ref="e1" />
            <entry key="CTO" value-ref="e2" />
        </map>
    </property>
</bean>

测试同上

集合数据类型(properties)的注入

创建实体类JdbcConfig,添加Properties

package com.qfedu.entity;

import java.util.Properties;

public class JdbcConfig {
    private Properties config;

    public Properties getConfig() {
        return config;
    }

    public void setConfig(Properties config) {
        this.config = config;
    }

    @Override
    public String toString() {
        return "JdbcConfig{" +
                "config=" + config +
                '}';
    }
}

在Spring配置文件中配置

<bean id="jdbcConfig" class="com.qfedu.entity.JdbcConfig">
    <!-- Properties类型的注入 -->
    <property name="config">
        <props>
            <prop key="driverName">com.mysql.jdbc.Driver</prop>
            <prop key="url">jdbc:mysql://localhost:3306/test</prop>
            <prop key="username">root</prop>
            <prop key="password">root</prop>
        </props>
    </property>
</bean>

测试

@Test
public void test8() {
    //解析配置文件 -- 创建对象 -- 对象交给Spring的IOC容器进行管理
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    //获取Employee的对象
    JdbcConfig config = (JdbcConfig)context.getBean("jdbcConfig");
    //打印对象
    System.out.println(config);
}

引入其他配置文件

实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载。

<import resource="applicationContext-xxx.xml"/>
posted @ 2022-04-02 09:45  coderwcb  阅读(2)  评论(0编辑  收藏  举报