01_SpringIOC基础与DI依赖注入

文章目录

1. 概述

注: 在第一节概述中只讲解Spring的作用与由来,什么是IOC等知识,具体代码思想可以直接从第二节Spring开发步骤看起

1.1 spring是什么?

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

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

但是随着时代发展,我们现在在开发中使用的持久层框架一般为Mybatis而不是JDBCTemplate,所谓的SSM便是指Spring+SpringMVC+Mybatis

1.2 使用Spring的优点:

1. 方便解耦,简化开发

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

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

2. AOP 编程的支持

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

3. 声明式事务的支持

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

4. 方便程序的测试

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

5. 方便集成各种优秀框架

Spring对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的支持。

6. 降低 JavaEE API 的使用难度

Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低

7. Java 源码是经典学习范例

Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java 设计模式灵活运用以及对 Java技术的高深造诣。它的源代码无疑是 Java 技术的最佳实践的范例

1.3 spring体系结构构图:

01_SpringIOC基础与DI依赖注入

  • 最底层test表明上层所有东西都可以进行测试
  • 在上一层Core Container为核心容器层,由ioc思想实现
  • 再往上一层就是aop等内核
  • 最上层为web层与数据操作层

1.4 为什么要有Spring(IoC思想方面)?

试想一下,在我们以为的开发中,先是创建一个dao层接口,接着写一个这个接口的实现类,然后再写service层接口,再创建一个service层接口实现类,然后在service层中创建dao层实现类对象,使用这个对象对数据进行操作,很正常规范一波操作,对吧?

但如果此时我们需要增加一个dao层的实现类,我们需要做的事情就一下子多了起来:创建dao层实现类,在service实现类中修改对应的dao层实现

假设我们此时对这种增加dao层实现类的需求很大(例如修改数据库操作对象,修改对数据的操作等),那我们得做多少service层的修改呀?想想就头疼呢

也正因此,早期没有spring框架的时候,web开发人员在开发过程中是很痛苦的

但其实也有方法:

​ 我们在service层创建dao层对象的时候,不去创建具体的实现类对象,而是使用多态的思想,创建一个dao层接口对象,等具体需要使用的时候,再根据需求将这个对象指向具体的实现类

​ 这就是IOC思想的原型!

从一开始的直接创建到现在的创建方式,发生了什么区别呢:

​ 其实,在使用这种方式创建对象的时候,由以前的将所有对象都交给程序进行控制创建,变成了现在的由我们自行控制创建对象,也就是说,主动权交还给了调用者,程序已经不需要理会怎么创建怎么实现了,程序只需要提供一个接口给调用者选择创建什么样的对象就行了

​ 也就是说,程序员不需要再去管理对象的创建,可以更多的关注业务的实现,大大降低了程序开发中的耦合度,这也就是IOC的本质:控制反转

我们接着详细理解IOC控制反转:

1.5 IOC设计思想

IOC:

​ 是一种设计思想,其中DI(依赖注入)是这种思想的一种实现方法之一,在没有IOC的时候,我们使用的是面向对象编程,对象的创建与对象之间的依赖关系采用硬编码写在程序中

​ 但有了IOC思想之后,对象的创建转移给了第三方

​ 我们可以想象,在原始的创建对象中各个对象就像齿轮一样死死的耦合在一起,而有了IOC思想之后,出现了一个"第三方齿轮",他将各个对象解耦,只与这个第三方的齿轮产生耦合,而各个对象之间不再有耦合

​ 因此,我们理想的系统应该是连这个"第三方齿轮"都没有,各个对象之间完全独立,不再有耦合现象

​ spring提供了xml配置与注解开发的方式实现IOC,而在SpringBoot中我们甚至可以零配置实现IOC

​ 因此我们常戏说这种编程是面向配置文件编程

IOC在Spring底层实现步骤:

​ Spring容器在初始化的时候会先读取配置文件,根据配置文件中的数据创建与组织对象存入容器中,接着利用反射的方式创建对象,当需要使用对象时再从IOC容器中取出对应的对象

​ 在使用xml配置Bean的时候,Bean的定义信息是与实现分离的,而使用注解的方式则可以把俩者合为一体(将Bean的定义信息直接以注解的方式定义在实现类中)

控制反转:

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象

总结:

​ 控制反转(IOC)是一种通过xml或注解的方式并通过第三方去获得特定对象的方式,在Spring中的实现方式是Spring中封装的IOC容器,具体实现方式是DI(依赖注入)

​ IOC大大降低了程序中的耦合度,例如dao层代码改了,只需要修改xml文件,不需要修改服务层

2. Spring开发步骤(xml版)

在以前我们创建对象的方式:

UserDao userDao = new UserDaoImpl();

而我们在使用Spring创建对象时,只需要先在配置文件中定义id标识绑定全类名,使用这个id创建对象时spring会从配置文件中找到全类名通过反射创建对象再返回:

UserDao userDao = (UserDao) applicationContext.getBean("id");

下面是详细的spring开发步骤:

  1. 使用Maven创建项目后,在pom.xml中导入spring开发的基本包坐标(也可以直接创建spring项目):
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
</dependencies>
  1. 编写dao层接口与实现类

  2. 创建spring核心配置文件

    在resource目录下创建配置文件(选择xml配置文件下的spring config)习惯命名:applicationContext.xml

  3. 在Spring核心配置文件中配置UserDaoImpl

  4. 在service层中通过SpringAPI获取ApplicationContext对象,再通过ApplicationContext对象获得Bean实例对象

代码示例:

dao层接口:

package com.ahua.dao;

public interface UserDao {
    public void select();
}

dao层实现类:

package com.ahua.dao;

public class UserDaoImpl implements UserDao{
    @Override
    public void select() {
        System.out.println("查询成功");
    }
}

applicationContext.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">

    <bean id="UserDao" class="com.ahua.dao.UserDaoImpl"></bean>

</beans>

测试类:

package com.ahua.simple;

import com.ahua.dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SimpleTest {

    @Test
    public void testSpring(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("UserDao");
        userDao.select();
    }

}

3. 配置文件详解

3.1 bean标签

3.1.1 基本属性配置

bean标签用于将配置对象交由Spring 来创建。

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

其中基本属性有:

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

代码示例:

<bean id="UserDao" class="com.ahua.dao.UserDaoImpl"></bean>
<!-- 其中当bean标签体中不需要配置内容时可以直接/>自闭和标签 --> 

3.1.2 范围配置

通过scope属性可以配置对象的作用范围,取值如下:

取值范围 说明
singleton 默认值,单例的
相当于static,只会创建一个Bean对象
此时只要xml文件被读入内存即创建spring容器对象时,则这个文件里所有的单例bean对象就已经被创建了
prototype 多例的
此时要通过getBean()方法获取对象时才会创建对象
request WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
session WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
global session WEB 项目中,应用在 Portlet 环境
如果没有 Portlet 环境那么globalSession 相当

singleton的生命周期:

  • 对象创建:当应用加载,创建容器时,对象就被创建了

  • 对象运行:只要容器在,对象一直活着

  • 对象销毁:当应用卸载,销毁容器时,对象就被销毁了

prototype的生命周期:

  • 对象创建:当使用对象时,创建新的对象实例
  • 对象运行:只要对象在使用中,就一直活着
  • 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了

示例:

<!-- 单例示例: -->
<bean id="UserDao1" class="com.ahua.dao.UserDaoImpl" scope="singleton"></bean>

<!-- 多例示例: -->
<bean id="UserDao2" class="com.ahua.dao.UserDaoImpl" scope="prototype"></bean>

3.1.3 生命周期配置

init-method属性:

​ 可以指定类中的初始化方法名称,会使得创建对象时直接调用这个初始化方法

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

3.1.4 Bean实例化方式

共有三种示例化方式:

  • 无参构造方法实例化
  • 工厂静态方法实例化
  • 工厂示例方法实例化

无参构造方法实例化:

​ 当bean标签中没有配置与实例化相关的属性时默认使用无参构造方法实例化,此时若没有提供无参构造方法会报错,一般我们使用这个就可以了

工厂静态方法实例化:

​ 创建一个工厂类,在工厂类中提供静态方法获取实例化对象,在bean标签中设置factory-method属性后,spring容器会通过这个工厂类获取bean对象,**注意:**此时bean标签中设置的class属性值为工厂类的全类名

​ 示例:

工厂类StaticFactoryBean.java:

public class StaticFactoryBean {
	public static UserDao createUserDao(){
		return new UserDaoImpl();
	} 
}

配置文件applicationContext.xml:

<bean id="userDao" class="com.ahua.factory.StaticFactoryBean" factory-method="createUserDao" />

工厂示例方法实例化:

​ 创建一个工厂类,在工厂类中提供非静态方法获取实例化对象,同时此时不能直接在bean标签中设置factory-method,会报无法找到对应的工厂实例方法错误

​ 正确方法:在一个bean标签中通过id与class绑定工厂类,在另一个bean标签中设置id’属性,再通过 factory-bean属性绑定第一个bean标签的id,然后再在bean标签中设置factory-method属性对应的实例方法

​ 示例:

工厂类DynamicFactoryBean.java:

public class DynamicFactoryBean {
	public UserDao createUserDao(){
		return new UserDaoImpl();
	} 
}

配置文件applicationContext.xml:

<bean id="factoryBean" class="com.ahua.factory.DynamicFactoryBean"/>
<bean id="userDao" factory-bean="factoryBean" factory-method="createUserDao"/>

3.1.5 IOC实现方式DI再解

依赖注入(Dependency Injection):

​ 是 Spring 框架核心 IOC 的具体实现

​ 在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况

​ 但是IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。

​ 这样这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取

​ DI使得我们编程时就像搭积木一样的可以对那些对象进行抽拔式使用

​ 简单粗暴的理解:在服务层与控制层通过spring套娃

依赖注入方式:

​ 举个例子:我们现在需要做的是将UserDao注入到UserService内部,此时有俩种方式:

​ 一是通过UserServiceImpl的构造方法,二是通过UserServiceImpl的set方法

3.1.5.1 set方法

set方法注入分为property子标签与P命名空间俩种注入方式:

property子标签使用步骤:

  1. 首先在UserServiceImpl中添加setUserDao方法:
public class UserServiceImpl implements UserService {
    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
    	this.userDao = userDao; 
    } 
}
  1. 在UserService的bean标签中配置property子标签,设置子标签的name属性与ref或value属性,其中name为set方法名后部分(首字母小写),ref连接引用对象id,使这个对象作为参数传入set方法初始化成员变量,同时只有引用对象需要ref,普通数据类型用value,但此时也就把代码写死了
<bean id="userDao" class="com.ahua.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.ahua.service.impl.UserServiceImpl">
	<property name="userDao" ref="userDao"/>
</bean>

P命名空间注入方式:

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

  1. 第一步也是在UserServiceImpl中添加setUserDao方法,同上
  2. 第二步在beans根标签中添加P命名空间属性:
xmlns:p="http://www.springframework.org/schema/p"

<!-- 其实就是将第一行默认带的属性名xmlns改为xmlns:p,将属性值末尾的beans改为P -->
<!-- 第一行属性值如下: -->
xmlns="http://www.springframework.org/schema/beans"
  1. 此时只要往bean标签中增加一个属性即可,即p:name值-ref = “引用对象id”,value也是如此:
<bean id="userDao" class="com.ahua.dao.impl.UserDaoImpl"/>

<bean id="userService" class="com.ahua.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>

同时对于多个set方法的注入只需要多写几个子标签即可

3.1.5.2 构造方法:constructor-arg子标签

构造方法注入步骤:

  1. 在UserServiceImpl中创建有参构造:
public class UserDaoImpl implements UserDao{

    public UserDaoImpl() {
        System.out.println("创建成功");
    }

    @Override
    public void select() {
        System.out.println("查询成功");
    }

}
  1. 在UserServiceImpl的bean标签中增加constructor-arg子标签,同时配置name属性与ref属性或者value属性

    注意此时的name与ref代表含义与set注入方式的name不同:

    此时的name意思为变量名(形参)

    代码示例:

<bean id="UserDao" class="com.ahua.dao.UserDaoImpl"></bean>

<bean id="UserService" class="com.ahua.service.impl.UserServiceImpl">
    <constructor-arg name="userDao" ref="UserDao"/>
</bean>

对于构造方法中多个参数也一样只需多些几个子标签即可

3.1.5.3 集合数据类型注入

在set和构造方法注入中,除了可以注入普通数据类型和引用数据类型,还可以注入集合数据类型:

  1. 首先不管是哪种注入方式,都需要先写对应的子标签,即constructor-arg或者property,同时配置对应的name属性

  2. 在constructor-arg或者property子标签下根据对于集合数据类型标签

    例如list集合就是<list>,再根据集合内数据类型,在list标签里增加value或者其他标签给集合赋值

    map集合就是<map>标签,同时map集合中的键值对写在<entry>标签中,在entry标签中配置key属性或者key-ref属性配置键值,通过value或者value-ref属性配置值

    对于Properties集合的注入,先使用<props>父标签,在再props下写prop标签,由于Properties集合通常都是字符串的键值对,所以一般直接在prop标签中通过key属性确定键值,再在标签体中注入值即可,不需要value属性

    对于在集合中注入引用数据类型,可以通过bean标签中配置class属性值,也可以通过ref标签的bean属性值注入,示例:

<bean id="u1" class="com.ahua.domain.User"/>
<bean id="u2" class="com.ahua.domain.User"/>
<bean id="userDao" class="com.ahua.dao.impl.UserDaoImpl">
    <property name="userList">
        <list>
            <bean class="com.ahua.domain.User"/>
			<bean class="com.ahua.domain.User"/>
			<ref bean="u1"/>
			<ref bean="u2"/>
		</list>
	</property>
</bean>

代码示例:

  1. list集合示例:
<bean id="userDao" class="com.ahua.dao.impl.UserDaoImpl">
	<property name="strList">
        <list>
    		<!-- 写在标签体里的不需要加双引号 -->
    		<!-- 写在属性值里的需要加双引号 -->
            <value>aaa</value>
            <value>bbb</value> 
            <value>ccc</value>
        </list>
	</property>
</bean>
  1. map集合示例:
<bean id="UserDao" class="com.ahua.dao.UserDaoImpl" scope="prototype"></bean>

<bean id="UserService" class="com.ahua.service.impl.UserServiceImpl">
    <property name="map">
        <map>
           <entry key="user1" value-ref="UserDao"/>
           <entry key="user2" value-ref="UserDao"/>
        </map>
    </property>
</bean>
  1. Properties集合示例:
<bean id="userDao" class="com.ahua.dao.impl.UserDaoImpl"> 
	<property name="properties"> 
		<props>
        	<prop key="p1">aaa</prop> 
        	<prop key="p2">bbb</prop> 
        	<prop key="p3">ccc</prop>
		</props>
	</property>
</bean>

3.2 import标签

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

即分模块开发

示例:

<import resource="applicationContext-2.xml"/>

3.3 总结

<bean>标签
id属性:在容器中Bean实例的唯一标识,不允许重复
class属性:要实例化的Bean的全限定名
scope属性:Bean的作用范围,常用是Singleton(默认)和prototype
<property>标签:属性注入
name属性:属性名称
value属性:注入的普通属性值
ref属性:注入的对象引用值
<list>标签
<map>标签
<properties>标签
<constructor-arg>标签

​ name属性:属性名称

​ value属性:注入的普通属性值…同上

<import>标签:导入其他的Spring的分文件

4. SpringAPI详解

4.1 ApplicationContext的继承体系

ApplicationContext:

​ 接口类型,代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象

继承体系图:

01_SpringIOC基础与DI依赖注入

相关实现类:

1. ClassPathXmlApplicationContext

​ 从类的根路径下加载配置文件,也就是说使用的是相对路径,当配置文件在resources下时传入参数直接写配置文件名即可,推荐使用这种,

2. FileSystemXmlApplicationContext

​ 从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置,相当于绝对路径

3. AnnotationConfigApplicationContext

​ 当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

相关API:

​ getBean()方法

4.2 getBean()方法

先看下getBean()方法的源码:

public Object getBean(String name) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(name);
}
public <T> T getBean(Class<T> requiredType) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(requiredType);
}

​ 其中,当参数的数据类型是字符串时,表示根据Bean的id从容器中获得Bean实例,此时返回值类型是Object,需要强转

​ 当参数的数据类型是Class类型(类名.class)时,表示根据类型从容器中匹配Bean实例(此时不需要强转),当容器中相同类型的Bean有多个时,则此方法会报错,不推荐使用,后续版本也不支持这种方法了

​ 但也可以把id和类名都传进去,此时就不需要强转也不怕存在多个相同类型Bean时的冲突

示例:

UserDao userDao = (UserDao) app.getBean("UserDao", UserDaoImpl.class);

5. 通过Spring的数据库连接池

数据源绿色环保
数据库连接池可以用spring创建,自己写一份druid的
在resource下的配置文件路径可以写classpath:

上一篇:模板静态化


下一篇:springboot:常用注解