Spring 基础及 IOC 控制反转

初识 Spring

Spring 优势

  1. 方便解耦,简化开发
    Spring 就是一个容器,可以将所有对象创建和关系维护交给 Spring 管理
    什么是耦合度?对象之间的关系,通常说当一个模块 (对象) 更改时也需要更改其他模块 (对象),这就昰耦合,耦合度过高会使代码的维护成本增加。要尽量解耦。
  2. AOP 编程的支持
    Spring 提供面向切面编程,方便实现程序进行权限拦截,运行监控等功能。
  3. 声明式事务的支持
    通过配置完成事务的管理,无需手动编程。
  4. 方便测试,降低 JavaEE API 的使用
    Spring 对 Junit4 支持,可以使用注解测试。
  5. 方便集成各种优秀框架
    不排除各种优秀的开源框架,内提供了对各种优秀框架的直接支持。

    Spring 体系结构

    image

IOC 控制反转

初识 oc

** 控制反转 (Inverse Of Control)** 不是什么技术,而是一种设计思想。它的目的是指导我们设计出更加松耦合的程序。

控制:在 Java 中指的是对象的控制权限 (创建、销毁)
反转:指的是对象控制权由原来由开发者在类中手动控制反转到由 Spring 容器控制

image

举个栗子

  • 传统方式
    之前我们需要一个 userDao 实例,需要开发者自己手动创建 new UserDao ();
  • IOC 方式
    现在我们需要一个 userDao 实例,直接从 Spring 的 IOC 容器获得,对象的创建权交给了 Spring 控制。

Spring 快速入门

介绍

需求:借助 Spring 的 IOC 实现 service 层与 dao 层代码解耦合。

步骤分析

  1. 创建 java 项目,导入 spring 开发基本坐标
  2. 编写 Dao 接口和实现类
  3. 创建 spring 核心配置文件
  4. 在 spring 配置文件中配置 UserDaoImpl
  5. 使用 spring 相关 API 获得 Bean 实例

实现

  1. 创建 java 项目,导入 Spring 开发依赖坐标
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.10</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
    </dependency>
  2. 编写 Dao 接口及实现类
  • 接口
    1
    2
    3
    4
    5
    package com.orginly.dao;

    public interface IUserDao {
    public void save();
    }
  • 实现类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.orginly.dao.impl;

    import com.orginly.dao.IUserDao;

    public class UserDaoImpl implements IUserDao {

    @Override
    public void save() {
    System.out.println("dao 被调用...");
    }
    }
  1. 配置 Spring 核心配置文件

一般命名为 applicationContext.xml

beans 约束文件地址:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#xsd-schemas-beans

1
2
3
4
5
6
7
8
9
10
11
12
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 配置 UserDaoImpl
id:唯一标识
class:类全路径
-->
<bean id="userDao" class="com.orginly.dao.impl.UserDaoImpl" />

</beans>
  1. 使用 Spring 相关 api 获取 Bean 实例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void test() {
    // 获取到 spring 上下文对象,借助上下文对象可以获取到 IOC 容器中的 bean 对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 使用上下文对象从容器中获取到 bean 对象
    IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");

    userDao.save();
    }

Spring 常用 Api

API 继承体系介绍

Spring 的 API 体系异常庞大,我们现在只关注两个 BeanFactory 和 ApplicationContext

image

ApplicationContext

1
2
3
4
5
6
1. ClassPathXmlApplicationContext
它是从类的根路径下加载配置文件推荐使用这种。
2. FileSystemXmlApplicationContext
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
3. AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建 spring容器。它用来读取注解。
1
2
3
 // 获取到 spring 上下文对象,借助上下文对象可以获取到 IOC 容器中的 bean 对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

常用方法
| 方法声明 | 说明 |
| —————————————————– | ———————————————————— |
| Object getBean (String name) | 根据 Bean 的 id 从容器中获得 Bean 实例,返回是 Object,需要强转。 |
| <T> T getBean (Class< T > requiredType) | 根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。 |
| <T>T getBean (String name, Class< T > requiredType) | 根据 Bean 的 id 和类型获得 Bean 实例,解决容器中相同类型 Bean 有多个情况。 |

Spring Bean 配置文件

Bean 标签基本配置

1
2
3
4
5
6
7
8
<bean id="" class="" />
用于配置对象交由 Spring 来创建。

基本属性:
id:Bean实例在 Spring容器中的唯一标识
c1ass:Bean的全限定

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

Bean 标签范围配置

1
<bean id="" class="" scope="" />

scope 属性指对象的作用范围,取值如下:
| 取值范围 | 说明 |
| ————– | ———————————————————— |
| singleton | 默认值,单例的 |
| prototype | 多例的 |
| request | WEB 项目中, Spring 创建一个 Bean 的对象,将对象存入到 request 域中 |
| session | WEB 项目中, Spring 创建一个 Bean 的对象,将对象存入到 session 域中 |
| global session | WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么 globalSession 相当于 session |

  1. 当 scope 的取值为 singleton 时
    Bean 的实例化个数:1 个
    Bean 的实例化时机:当 Spring 核心文件被加载时,实例化配置的 Bean 实例。
    Bean 的生命周期:
     对象创建:当应用加载,创建容器时,对象就被创建
     对象运行:只要容器在,对象一直活看
     对象销毀:当应用卸戟,销毁容器时,对象就被销毀了
    
  2. 当 scope 的取值为 prototype
    Bean 的实例化个数:多个
    Bean 的实例化时机:当调用 getBean () 方法时实例化 Bean
    Bean 的生命周期:
     对象创建:当使用对象时,创建新的对象实例
     对象运行:只要对象在使用中,就一直活着
     对象销毀:当对象长时间不用时,被Java的垃圾回收器回收了
    

Bean 生命周期配置

1
2
3
4
<bean id="" class="" scope="" init-method="" destroy-method="" />

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

Bean 实例化三种方式

  1. 无参构造方法实例化
  2. 工厂静态方法实例化
  3. 工厂普通方法实例化

    无参构造方法实例化

    它会根据默认无参构造方法来创建类对象,如果 bean 中没有默认无参构造函数,将会创建失败。
    1
    <bean id="userDao" class="com.orginly.UserDaoImp1"/>

工厂静态方法实例化

应用场景
依赖的 jar 包中有个 A 类,A 类中有个静态方法 m1,m1 方法的返回值是一个 B 对象。如果我们频繁使用 B 对象,此时我们可以将 B 对象的创建交给 spring 的 IOC 容器,以后我们在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。

1
2
3
4
5
public class StaticFactoryBean {
public static IUserDao createUserDao() {
return new UserDaoImpl();
}
}
1
<bean id="userDao" class="com.orginly.factory.StaticFactoryBean" factory-method="createUserDao"/>

工厂普通方法实例化

应用场景
依赖的 jar 包中有个 A 类,A 类中有个普通方法 m1,m1 方法的返回值是一个 B 对象。如果我们频繁使用 B 对象,此时我们可以将 B 对象的创建权交给 spring 的 IOC 容器,以后我们在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。

1
2
3
4
5
public class DynamicFactoryBean {
public IUserDao createUserDao() {
return new UserDaoImpl();
}
}
1
2
3
<!-- 工厂普通方法实例化 -->
<bean id="dynamicFactoryBean" class="com.orginly.factory.DynamicFactoryBean" />
<bean id="userDao" factory-bean="dynamicFactoryBean" factory-method="createUserDao"/>

Bean 依赖注入概述

** 依赖注入 Dl (Dependency Injection)**:它是 Spring 框架核心 IOC 的具体实现。

在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如业务层仍会调用持久层的方法。

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

Bean 依赖注入方式

构造方法

service 实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UserServiceImpl implements IUserService {

// 存储注入对象
private IUserDao userDao;

/**
* 有参构造用于有参构造依赖注入
* @param userDao
*/
public UserServiceImpl(IUserDao userDao) {
this.userDao = userDao;
}

@Override
public void save() {
userDao.save();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean id="userDao" class="com.orginly.dao.impl.UserDaoImpl"/>
<!-- 配置 userService -->
<bean id="userService" class="com.orginly.service.impl.UserServiceImpl">
<!-- 有参构造完成依赖注入
index:参数的位置
type:类型
ref:对应的bean标签
-->
<!-- <constructor-arg index="0" type="com.orginly.dao.IUserDao" ref="userDao"/>-->
<!-- 简化写法
name:构造方法的参数名称
ref:对应的bean标签
-->
<constructor-arg name="userDao" ref="userDao"/>
</bean>

Setter 方法完成注入

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserServiceImpl implements IUserService {

// 存储注入对象
private IUserDao userDao;

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

@Override
public void save() {
userDao.save();
}
}
1
2
3
4
5
6
<bean id="userDao" class="com.orginly.dao.impl.UserDaoImpl"/>
<!-- 配置 userService -->
<bean id="userService" class="com.orginly.service.impl.UserServiceImpl">
<!-- setter 完成方法注入-->
<property name="userDao" ref="userDao"/>
</bean>

Bean 依赖注入的数据类型

依赖注入普通数据类型

上面操作,都是注入 Bean 对象,除了对象的可以注入,普通数据类型和集合都可以在容器中进行注入。

注入数据的三种数据类型

  1. 普通数据类型
  2. 引用数据类型
  3. 集合数据类型 其中引用数据类型,此处就不再述了,之前的操作都是对 UserDao 对象的引用进行注入的。下面将以 Setter 方法注入为例,演示普通数据类型和集合数据类型的注入。

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UserServiceImpl implements IUserService {

private String name;
private int age;

@Override
public void save() {
userDao.save();
System.out.printf("name = %s,age = %d\n",name,age);
}

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

}
1
2
3
4
5
6
7
<!-- 配置 userService -->
<bean id="userService" class="com.orginly.service.impl.UserServiceImpl">
<!-- setter 完成方法注入-->
<!-- ref:用于注入引用类型,value:用于注入普通数据类型 -->
<property name="name" value="feng"/>
<property name="age" value="18"/>
</bean>

依赖注入集合数据类型

  1. List 集合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <bean id="userService" class="com.orginly.service.impl.UserServiceImpl">
    <!-- 进行 List 集合进行依赖注入 -->

    <property name="list">
    <list value-type="java.lang.String">
    <value>feng</value>
    <value>li</value>
    <!-- 可以用 ref 进行对象的引用 -->
    </list>
    </property>
    </bean>
  2. Set 集合

1
2
3
4
5
6
7
8
9
<bean id="userService" class="com.orginly.service.impl.UserServiceImpl">
<!-- 进行 Set 集合进行依赖注入 -->
<property name="map">
<set>
<value>feng</value>
<value>li</value>
</set>
</property>
</bean
  1. Array 集合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <bean id="userService" class="com.orginly.service.impl.UserServiceImpl">
    <!-- 进行 Set 集合进行依赖注入 -->
    <property name="arr">
    <array>
    <value>feng</value>
    <value>li</value>
    </array>
    </property>
    </bean>
  2. Map 集合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <bean id="userService" class="com.orginly.service.impl.UserServiceImpl">
    <!-- 进行 Set 集合进行依赖注入 -->
    <property name="map">
    <map>
    <entry key="feng" value="18" />
    <entry key="li" value="18" />
    </map>
    </property>
    </bean>
  3. Properties 配置注入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class UserServiceImpl implements IUserService {

    private Properties properties;

    public void setProperties(Properties properties) {
    this.properties = properties;
    }

    @Override
    public void save() {
    System.out.println(properties.toString());
    }
    }
1
2
3
4
5
6
7
8
<bean id="userService" class="com.orginly.service.impl.UserServiceImpl">
<property name="properties">
<props>
<prop key="k1">v1</prop>
<prop key="k2">v2</prop>
</props>
</property>
</bean>

spring 配置文件模块化

实际开发中, Spring 的配置内容非常多,这就导致 Spring 配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,也就是所调的配置文件模块化。

  1. 并列的多个配置文件
    1
    Applicationcontext act = new ClassPathXml Applicationcontext("beans1.xml","beans2.xml","...")
  2. 主从配置文件

PS:开发环境中如果配置后运行找不到文件,那么需要重新 IDEA 让编辑器重新编译

1
2
3
<import resource="classpath:applicationContext-dao.xml"/>
<import resource="classpath:applicationContext-service.xml"/>
<import resource="classpath:applicationContext-user.xml"/>

注意

  • 同一个 XML 中不能出现相同名称的 bean 如果出现会报错
  • 多个 XML 如果出现相同名称的 bean,不会报错,但是后加载的会盖前加载的 bean

Spring 注解开发

Spring 是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替 xm 配置文件可以简化配置,提高开发效率。

Spring 常用注解

Spring 常用注解主要是替代 <bean> 的配置
| 注解 | 说明 |
| ————— | ———————————————– |
| @Component | 使用在类上用于实例化 Bean |
| @Controller | 使用在 Web 层类上用于实例化 Bean |
| @Service | 使用在 service 层类上用于实例化 Bean |
| @Repository | 使用在 dao 层类上用于实例化 Bean |
| @Autowired | 使用在字段上用于根据类型依赖注入 |
| @Qualifier | 结合 @Autowired 一起使用根据名称进行依赖注入 |
| @Resource | 相当于 @Autowired + @Qualifier,按照名称进行注入 |
| @value | 注入普通属性 |
| @Scope | 标注 Bean 的作用范围 |
| @PostConstruct | 使用在方法上标注该方法是 Bean 的初始化方法 |
| @PreDestroy | 使用在方法上标注该方法是 Bean 的销毁方法 |

  1. @Component @Controller @Service @Repository
    相当于配置 <bean> 生成类实例对象到 IOC 容器中
  2. @Autowired @Qualifier @Resource @value
    相当与配置了 <property>, 进行依赖注入

PS:JDK11 以后完全移除了 javax 扩展导致不能使用 @resource 注解
如果要使用 @resource 需要引入 Mevan 依赖

1
2
3
4
5
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>

注意
使用注解进行开发时,需要在 applicationContext.xml 中配置组件扫描,作用是指定哪个包及其子包下的 Bean 需要进行扫描以便识别使用注解配置的类、字段和方法

1
2
3
4
5
6
7
8
9
10

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!--注解的组件扫描-->
<context:component-scan base-package="com.orginly"/>
</beans>

Spring 新注解

使用上面的注解还不能全部替代 xml 配置文件,还需要使用注解替代的配置如下

  1. 非自定义的 Bean 的配置(如外部引入的 Jar 包):<bean>
  2. 加载 properties 文件的配置:<context:property-placeholder>
  3. 组件扫描的配置:<context:component-scan>
  4. 引入其他文件:<import>
    注解说明
    @Configuration用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
    @Bean使用在方法上,标注将该方法的返回值存储到 Spring 容器中
    @PropertySource加载 properties 文件中的配置
    ComponentScan用于指定 Spring 在初始化容器时要扫描的包
    @Import用于导入其他配置类