Spring入门IoC、DI

吃水不忘挖井人


Spring的诞生,源于其著作《Expert one on one J2EE design and development》中阐述的部分 理念和原型衍生而来。

Rod 在悉尼大学不仅获得了计算机学位,同时还获得了音乐学位。更 令人吃惊的是在回到软件开发领域之前,他还获得了音乐学的博士学位。 有着相当丰富的 C/C++技术背景的 Rod 早在 1996 年就开始了对 Java 服务器端技术的研究。他是一个在保险、电子商务和金融行业有着丰富经 验的技术顾问, 同时也是 JSR-154(Servlet2.4)和 JDO2.0 的规范专 家、 JCP 的积极成员,是 Java development community 中的杰出人物。

Spring 是容器框架


在 Spring 中,每个模块都有一个框架专门处理该模块。

Spring 是一个轻量级的 DI/IoC 和 AOP 容器的开源框架,致力于构建轻量级的 JavaEE 应用。Spring 能简化应用开发,本身涵盖了传统应用开发,还拓展到移动端,大数据等领域。

什么是容器(Container):从程序设计角度看就是装对象的的对象,因为存在放入、拿出等操作,所以容

器还要管理对象的生命周期,如 Tomcat 就是 Servlet 和 JSP 的容器。

Web 开发的最佳实践就是根据功能职责的不同,划分为控制层、业务层、持久层。
Java EE 的最佳实践就是在整个应用中按照功能之策不同,纵向划分为三层架构,不同的框架的目的就在于解决不同领域的问题

  • Spring 能帮我们低侵入/低耦合地创建并组装对象之间的依赖关系 – IoC/DI
  • Spring 面向切面编程能帮主我们无耦合的实现日志记录,性能统计,安全等 – AOP
  • Spring 能非常简单的且强大的声明式事务管理 – Aop+Tx
  • Spring 提供了与第三方持久层框架 (Hibernate、JPA) 的无缝集成,且自己也提供了一套 JDBC 模板来方便数据库访问 – ORM
  • Spring 提供与第三方 Web 框架 (如 Stuts1/2) 无缝集成,且自己也提供了一套 Spring MVC 框架,来方便 Web 层搭建 – Web
  • Spring 能方便的与 Java Mail、任务调度、缓存框架等技术整合,降低开发难度。
  • SpringBoot + SpringCloud 是主流的微服务架构开发

Spring 产品介绍


  • Spring FrameWork 帝国核心,其他 Spring 其他产品都基于 Spring框架而来
  • Spring Boot 设计目的用来简化新 Spring 应用的初始搭建以及开发过程,该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
  • Spring Cloud VS Dubbo 微服务架构下的一站式解决方案,包括注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,相对于 Dubbo 框架,SpringCloud 功能更强大,涵盖面更广
  • Spring Cloud Data Flow 专注于数据流处理的应用程序开发和部署。通过 SpringBoot 启动应用,采用 Spring Cloud Stream、Spring Cloud Task 完成微服务构建。
  • Spring Data 简化数据库访问,支持云服务的开源框架。统一和简化对各类型持久化存储,不拘泥是关系型数据库还是 NoSQL 数据存储。
  • Spring Batch 专门针对企业级系统中的日常批处理任务,帮助开发者方便地开发出强壮、高效的批处理应用程序
  • Spring Integration 为 Spring 编程模型提供了一个支持企业集成模式 (Enterprise Integration Patterns) 的扩展,在应用程序中提供轻量级的消息机制,可以通过声明式的适配器与外部系统进行集成。
  • Spring Security VS Shiro 基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架 类似于 Shiro框架, Shiro 更容易使用, SpringSecurity 名声大。

Spring 框架架构:

  • Core Container 包含 Beans、Core、Context和 SpEL 模块
模块名 功能
Core 封装了框架依赖的最底层部分,包括资源访问、类型转换及一些常用工具类。
Beans 供了框架的基础部分,包括反转控制和依赖注入。其中 Bean Factory 是容器核心,本质是“工厂设计模式”的实现,所有应用中对象间关系由框架管理,这些依赖关系都由 BeanFactory 来维护。
Context 以 Core 和 Beans 为基础,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE支持、容器生命周期、事件传播等;核心接口是 ApplicationContext。
SpEL 提供强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。
  • Data Access/Integration JDBC、ORM、OXM、JMS 和 Transaction 模块
数据访问/集成模块 功能
事务 用于 Spring 管理事务,支持编程和声明性的事务管理。
JDBC 提供了一个 JBDC 的模板,简化 JDBC 开发。
ORM 提供与流行的“对象-关系”映射框架的无缝集成,包括 Hibernate、JPA、iBatiss 等。而且可以使用 Spring 事务管理,无需额外控制事务。
OXM 提供了一个对 Object/XML 映射实现,将 java 对象映射成 XML 数据,或者将 XML 数据映射成 java对象,Object/XML 映射实现包括 JAXB、Castor、XMLBeans 和 XStream。
JMS 用于 JMS(Java Messaging Service),提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
  • Web 层包含了 Web、Servlet、WebSocket、Porlet 模块
Web模块 功能
Web 提供与 Struts1 和 Struts2 集成技术
Servlet 提供了一个 Spring MVC Web 框架实现
WebSocket 提供浏览器与服务端建立通信方式,解决 http 请求-响应带来过多的资源消耗,同时对特殊场景应用提供了全新的实现方式,比如聊天、股票交易、游戏等对对实时性要求较高的行业领域。
Portlet 提供 Portlet 组件和容器的支持和实现功能,用于构建 Portlet 应用。
  • AOP 模块提供了遵循 AOP 联盟标准的面向切面编程的实现。
切面编程 功能
AOP Spring AOP 模块提供了符合 AOP Alliance 规范的面向方面的编程现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,降低业务逻辑和通用功能的耦合。
Aspects 模块:提供了对 AspectJ 的集成,AspectJ 提供了比 Spring AOP 更强大的功能。
Instrumentation 提供了在特定服务器上的类加载器操作
Messaging 提供消息 API 和协议规范。
  • Test 模块支持使用 JUnit 和 TestNG 对 Spring 组件进行测试
切面编程 功能
Test Spring 支持 JUnit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,如在 Web环境中,模拟 HTTP 请求。

Spring 新特性


4.x

  1. 泛型限定式依赖注入
  2. 核心容器的改进
  3. Web 开发的增强
  4. 集成 Bean Validation 1.1(JSR-349) 到 SpringMVC
  5. Groovy Bean 定义 DSL
  6. 更好的 Java 泛型操作 API
  7. JSR 310 日期 API 支持
  8. 注解、脚本、任务、MVC 等其他特性改进

5.0

  • JDK 8+ 和 Java EE 7+ 以上版本
  • 整个框架的代码基于 java8, 运行时兼容 JDK9
  • 许多不建议使用的类和方法在代码库中被删除

核心特性:

  1. JDK8 的增强:Spring 5.0 框架自带了通用的日志封装
  2. 核心容器
    • 支持候选组件索引(也可以支持环境变量扫描)
    • 支持 @Nullable 注解
    • 函数式风格 GenericApplicationContext/AnnotationConfigApplicationContext
    • 基本支持 bean API 注册
    • 在接口层面使用 CGLIB 动态代理的时候,提供事务、缓存、异步注解检测
  3. Spring WebMVC
    • 全部的 Servlet3.1 签名支持在 Spring-provied Filter 实现
    • 在 Spring MVC Controller 方法里支持 Servlet4.0 PushBuilder 参数
    • SpringWebFlux 新的 spring-webflux 模块,一个基于 reactive 的 spring-webmvc,完全的异步非阻塞,旨在使用 enent-loop 执行模型和传统的线程池模型
    • 在 spring-web 包里包含 HttpMessageReade 和 HttpMessageWrite
  4. 测试方面的改进
    • 支持 Junit5
    • SpringExtension 是 Junit 多个可拓展 API 的一个实现,提供了对现存 Spring TestContext Framework 的支持,使用 @ExtendWith(SpringExtension.class) 注解引用。
    • SpringJunitConfig 一个复合注解
    • ExtendWith(SpringExtension.class) 来源于 Junit Jupit
    • ContextConfiguration 来源于 Spring TestContext 框架
    • DisableIf 如果提供的该属性值为 true 的表达或占位符,信号:注解的测试类或测试方法被禁用
    • 在 Spring TestContext 框架中支持并行测试

Hello World


创建一个 Maven 的普通项目,如何创建见Maven构建web项目

设置 pom.xml 添加依赖

1
2
3
4
5
6
<!--Spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.0.RELEASE</version>
</dependency>

在 src/main/java下,创建cn.lizhaoloveit.helloword 包,创建 HelloWorld.java 文件

1
2
3
4
5
6
7
8
public class HelloWorld {
private String name;
private int age;

public void sayHello() {
System.out.println(name + "你好, 年龄" + age);
}
}

在 src/main/resources 下创建应用上下文配置文件 applicationContext.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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="helloWorld" class="cn.lizhaoloveit.helloword.HelloWorld">
<property name="name" value="lizhao"></property> <!-- 对应 HelloWorld 中的 setName 方法 -->
<property name="age" value="17"></property> <!-- 对应 HelloWorld 中的 setAge 方法 -->
</bean>
</beans>

<!-- 意识问题:看到类的全限定名,就应该想到需要使用反射去创建对象。 -->

通过解析 xml 拿到 class 全限定名,属性名和属性值,用 classForName 加载对应的类,然后反射获取类中的属性和方法并且调用对应的方法赋值。

意识问题:看到类的全限定名,就应该想到需要使用反射去创建对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class TestHelloWorld {

// 传统方式
@Test
public void testHelloWrold() throws Exception {
HelloWorld hello = new HelloWorld();
hello.setAge(19);
hello.setName("李朝");
hello.sayHello();
}

// spring 框架
@Test
public void testSetValueFromXML() throws Exception {
HelloWorld world = null;

// 加载 Spring 配置文件 applicationContet.xml
Resource resource = new ClassPathResource("applicationContext.xml");

// 创建 Spring 容器对象
BeanFactory factory = new XmlBeanFactory(resource);

// 从 Spring 容器中获取指定名为 helloWorld 的 bean
world = (HelloWorld) factory.getBean("helloWorld");
world.sayHello();
}

// 原理
@Test
public void testSpringPrinciple() throws Exception {
String helloWorld = "cn.lizhaoloveit.helloword.HelloWorld";
String dname = "name";
String dage = "age";
Class<?> helloClass = Class.forName(helloWorld);
Object world = helloClass.newInstance();
BeanInfo bean = Introspector.getBeanInfo(helloClass, Object.class);
PropertyDescriptor[] pds = bean.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (dname.equals(pd.getName()))
pd.getWriteMethod().invoke(world, "will");
if (dage.equals(pd.getName()))
pd.getWriteMethod().invoke(world, 17);
}

HelloWorld worlds = (HelloWorld) world;
worlds.sayHello();
}
}

传统方式创建对象,赋值和调用方法,使用 spring 框架的 core 模块做同样的事,最后是其工作原理原理。

BeanFactory

IoC 控制翻转,原本是我们管理对象,现在是框架来管理对象。我们只负责提供数据源 xml

BeanFactory 是 Spring 最古老的接口,是 Spring IoC 容器。生产 bean 对象,负责配置,创建和管理 bean。

bean: 被 Spring IoC 容器管理的对象称之为 bean。

IoC 容器管理 bean

获取bean的方式

配置方式 原理
XML-based configuration 基于 XML 文件
Annotation-based configuration 基于 Annotation
Java-based configuration 基于 Java 代码配置

管理 bean 的原理:

  1. 通过 Resource 对象加载配置文件
  2. 解析配置文件,得到指定名称的 bean
  3. 解析 bean 元素,id作为 bean 的名字,class 用于反射得到 bean 的实例
  4. 调用 getBean 方法的时候,从容器中返回对象实例

此时,bean 类必须存在一个无参数构造器(和访问权限无关),会调用 Class 对象的 newInstance()

结论:此方法将 Java 文件中的代码转移到了 XML 中。

getBean 方法

  1. 按照 bean 的名字,worl = (HelloWorld) factory.getBean("helloWorld"); 不太安全
  2. 按照类型,要求在 Spring 中只配置一个这种类型的实例
    • world = factory.getBean(HelloWorld.class);
  3. 按照名字+类型(推荐)
    • world = factory.getBean("helloWorld", HelloWorld.class);

Spring基本配置

id 和 name 属性,都可以定义 bean 元素的名称, id 遵循 XML 语法 ID约束。

属性 规则
id 必须以字母开始,可以使用字母、数字、连字符、下划线、句号、冒号,不能以”/“开头。
name 可以使用很多特殊字符,可以用 name 属性为 bean 元素起多个别名,别名之间使用逗号或者空格隔开,代码中依然使用 BeanFactory对象.getBean()获取
1
2
3
4
<bean name="/login" class="..."/>

<bean name="hello,hi" class="cn.lizhaoloveit.HelloWorld"/>
<bean name="hello hi" class="cn.lizhaoloveit.HelloWorld"/>

注意:从 Spring3.1开始,id 属性不再是 ID 类型了,而是 String 类型,也就是说 id 属性也可以使用 “/“ 开头,而 bean 元素的 id 的唯一性由容器负责检查。
bean起名尽量规范,尽量使用 id

在开发中,<bean> 的配置数量会很大,为了提高可读性,可以将 applicationContext.xml 文件分解成多个配置文件,然后再 applicationContext.xml 文件中包含其他配置文件。

hello.xml,文件存放目录与 HelloWorld.java 文件相同

1
2
3
4
5
6
7
8
9
10
11
<?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="helloWorld" class="cn.lizhaoloveit.helloworld.HelloWorld">
<property name="name" value="lizhao"></property> <!-- 对应 HelloWorld 中的 setName 方法 -->
<property name="age" value="17"></property> <!-- 对应 HelloWorld 中的 setAge 方法 -->
</bean>
</beans>
1
2
3
<import resource="classpath:路径1/路径2/hello.xml"/>

<import resource="classpath:cn/lizhaoloveit/helloworld/hello.xml"/>

默认情况下,从 classpath 的根路径寻找,classpath根目录指的是是在target的 classes 文件夹下
可以使用前缀来定位文件的基础位置:[classpath:] 后面的文件从 classpath 路径开始找(推荐)
[file:] 后面的文件使用文件系统的路径开始找。

只有当框架中实现了 Resource 接口才能识别上述前缀标识符,Spring框架中,加载任何某一配置文件的路径,都必须使用 classpath 前缀

Spring 测试框架

在 Spring 中每个模块都有对应的框架,测试也不例外
Spring 的创建容器的开销很大,而每次 Test 都会创建容器,这样做不合理,Test 应该由 Spring框架来管理。

1
2
3
4
5
6
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.0.RELEASE</version>
<scope>test</scope>
</dependency>

scope 规定作用域,我们需要在 java 项目中去写测试代码,则作用域就不要限制。

2019-07-29 at 5.24 P

@RunWith() 注解表示 Junit 直接启动 Spring 容器,测试类运行在 Spring容器中

如果是 JUnit5

测试类写法如下:

1
2
3
4
5
6
7
8
9
10
@springJUnitConfig
public class HelloTest {
@Autowired
private HelloWorld world;

@Test
void test1() throws Exception {
world.sayHello();
}
}

Spring4.x 以上 需要依赖的单元测试必须是最新的 junit4.12。

IoC容器


BeanFactory : Spring 最底层的接口,只提供了的 IoC 功能,负责创建、组装、管理 bean,在应用中, 一般不使用 BeanFactory,而推荐使用 ApplicationContext(应用上下文)。

ApplicationContext 接口继承了 BeanFactory,除此之外还提供 AOP 集成、国际化处理、事件传播、统 一资源价值等功能。

1
2
3
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

}
  • BeanFactory 需要等到获取某一个 bean 的时候才会创建 bean – 延迟初始化
  • ApplicationContext 在启动 Spring 容器的时候就会创建所有 bean(Web 应用)

2019-07-29 at 7.10 P

Bean实例化方式


  1. 构造器实例化(无参构造器),最标准,使用最多

    1
    <bean id="XxxBean" class="cn.lizhaoloveit.XxxBean">

    xxx 必须有无参数构造器

  2. 静态工程方法实例化可以解决系统遗留问题,已经不再使用

    1
    2
    3
    4
    5
    6
    7
    8
    public class SomeBean2 {
    }
    public class SomeBean2Factory {
    public static SomeBean2 createInstance() {
    // TODO
    return new SomeBean2();
    }
    }
    1
    <bean id="someBean2" class="cn.lizhaoloveit.SomeBean2Factory" factory-method="createInstance"/>
  3. 实例工厂方法实例化(已经不再使用)

    1
    2
    3
    4
    5
    6
    7
    8
    public class SomeBean3 {
    }
    public class SomeBean3Factory {
    public SomeBean3 createInstance() {
    // TODO
    return new SomeBean3();
    }
    }
    1
    2
    <bean id="factory" class="cn.lizhaoloveit.SomeBean3Factory"/>
    <bean id="=someBean3" factory-bean="factory" factory-method="createInstance"/>
  4. 实现 FactoryBean 接口实例化:实例工厂变种,多用于框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SomeBean4 {
}
public class SomeBean4FactoryBean implements FactoryBean<SomeBean4> {
public boolean isSingleton() {
return true;
}
public SomeBean4 getObject() throws Exception {
// TODO
return new SomeBean4();
}
public Class<?> getObjectType() {
return SomeBean4.class;
}
}
1
<bean id="someBean4" class="cn.lizhaoloveit.SomeBean4FactoryBean"/>

如果全限定名所指定的类实现了接口 FactoryBean,会被 Spring 框架认为是一个工厂对象,然后会通过调用其 getObject() 方法来返回一个 泛型类的实例。

Bean作用域


scope,在 Spring 容器中,创建的 Bean 对象相对于其他 Bean 对象的请求可见范围:

1
<bean id="" class="" scope="作用域"/>

scope 的值:

特点
singleton 单例,缺省值,在 Spring IoC 容器中仅仅存在一个 Bean 实例。
prototype 多例,每次从容器中调用 Bean 时,都返回一个新的实例,每次调用 getBean()时,相当于执行了 new XxxBean(),不会在容器启动时创建对象
request 用于 web 开发,将 Bean 放入 request 范围 ,request.setAttribute(“xxx”) , 在同一个 request 获得同一个 Bean。
session 用于 web 开发,将 Bean 放入 Session 范围,在同一个 Session 获得同一个 Bean 。
globalSession 一般用于 Porlet 应用环境 , 分布式系统存在全局 session 概念(单点登录),如果不是 porlet环境,globalSession 等同于 Session。
application Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext。
websocket Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext。

开发中主要使用 缺省值 singleton,DAO、Service、Controller等,Struts1 中的 Action 对象使用 request,Structs2 中的 Action 使用 prototype 类型。

1
2
3
4
5
6
7
8
9
10
@Test
public void testInitDestroy() throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:cn/lizhaoloveit/spring_test_deomo/HelloTest-context2.xml");
HelloWorld world = ctx.getBean("hello", HelloWorld.class);
System.out.println(ctx.getBean("hello", HelloWorld.class));
System.out.println(ctx.getBean("hello", HelloWorld.class));
System.out.println(ctx.getBean("hello", HelloWorld.class));
System.out.println(ctx.getBean("hello", HelloWorld.class));
world.sayHello();
}

singleton 会返回统一个对象,

prototype 的话结果如下,每次都会创建对象

1
2
3
4
5
6
7
8
9
10
11
12
初始化方法
cn.lizhaoloveit.spring_test_deomo.HelloWorld@53ca01a2
构造器...
初始化方法
cn.lizhaoloveit.spring_test_deomo.HelloWorld@358c99f5
构造器...
初始化方法
cn.lizhaoloveit.spring_test_deomo.HelloWorld@3ee0fea4
构造器...
初始化方法
cn.lizhaoloveit.spring_test_deomo.HelloWorld@48524010
lizhao你好, 年龄17

Bean 初始化和销毁


1
2
3
<bean id="someBean" class="..." init-method="该类中初始化方法" 
destroy-method="该类中销毁方法">
</bean>

Spring IoC 容器要管理 Bean 对象所有的状态,当然也包括创建,初始化和销毁。

init-method: bean 生命周期初始化方法,bean 对象创建后就进行调用
destroy-method: 容器被正常销毁时,如果 bean 被容器管理,则会调用该方法。

Bean 使用 prototype 修饰 scope ,需要自己管生命周期。并不会被 spring 容器管理 init-method 和 destroy-method 设置不会起作用。

Lombok 的 @Cleanup 注解

实例化过程


DI(注入)


两种方式:

  1. setter 方法注入
  2. Constructor

注入值类型:

  • 简单类型:value 元素
  • 对象: ref 元素
  • 集合: 对应集合类型元素

setter方法注入


注入常量

注入简单类型,语法:<property name="对象属性名称" value="需要注入的值">

1
2
3
4
5
6
public class Employee {
private String name;
private Integer age;
private BigDecimal salary;
private URL url;
}
1
2
3
4
5
6
<bean id="employee" class="...Employee">
<property name="name" value="will"/>
<property name="age" value="17"/>
<property name="salary" value="800"/>
<property name="url" value="http://www.wolfcode.cn"/>
</bean>

一般用于给一个对象设置固定的值,比如给连接池对象设置数据库连接信息

注入对象

1
2
3
4
5
6
7
8
9
public class EmployeeDAO {

}
public class EmployeeService {
private EmployeeDAO dao;
public void setDao(EmployeeDAO dao) {
this.dao = dao;
}
}

语法:<property name="对象属性名称" ref="被注入对象的bean的id"/>

1
2
3
4
<bean id="employDAO" class="...EmployeeDAO">
<bean id="employeeService" class="...EmployeeService">
<property name="dao" ref="employeeDAO" />
</bean>

注入集合

1
2
3
4
5
6
7
public class CollectionBean {
private Set<String> set;
private List<String> list;
private String[] array;
private Map<String, String> map;
private Properties prop;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<bean id="collectionBean" class="...CollectionBean">
<property name="set">
<set>
<value>set1</value>
<value>set2</value>
</set>
</property>
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
</list>
</property>
<property name="array">
<list>
<value>array1</value>
<value>array2</value>
</list>
</property>
<property name="map">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
<property name="prop"> <!-- 属性 property -->
<props>
<prop key="pkey1">pVal1</prop>
<prop key="pkey2">pVal2</prop>
</props>
</property>
<property name="prop"> <!-- 属性 property 简单类型 -->
<value>
p1=v1
p2=v2
p3=v3
</value>
</property>
</bean>

构造器注入


  • setter 方式注入:<property/> 元素
  • 构造器注入 <constructor-arg/> 元素

三种赋值方式:

  1. 按index: 构造器中的参数位置,从0开始 (不建议)
  2. type : 构造器中的参数类型 (不建议)
  3. name : 构造器中按照构造器的参数名设置值。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SomeBean {
private String name;
private int age;
private OtherBean other;
private Properties prop;

public SomeBean(String name, int age, OtherBean other, Properties prop) {
this.name = name;
this.age = age;
this.other = other;
this.prop = prop;
}
}

下面两个方式得到的结果是一致的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<bean id="person1" class="cn.lizhaoloveit.spring_test_deomo.Person">	
<constructor-arg name="name" value="will"></constructor-arg>
<constructor-arg name="age" value="17"></constructor-arg>
<constructor-arg name="properties">
<value>
user=lizhaoloveit.com
</value>
</constructor-arg>
</bean>

<bean id="person" class="cn.lizhaoloveit.spring_test_deomo.Person">
<property name="name" value="will"></property>
<property name="age" value="17"></property>
<property name="properties">
<props>
<prop key="user">lizhaoloveit</prop>
</props>
</property>
</bean>

IoC、DI创建数据库连接池对象


使用 Spring 以 IoC 和 DI 的方式创建数据库连接池,获取数据库连接对象

1
2
3
4
5
6
7
8
// 我们需要数据库连接池,可以用接口去引用一个实现类对象
@Autowired
private DataSource dataSource;

@Test
public void testConnection() throws Exception {
System.out.println(dataSource);
}

注解要去获取 dataSource 会去找应用上下文配置文件 context.xml,是否有该类型,或者该实现类的 bean 对象

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 上下文加载器的属性占位符功能,加载配置文件,system-properties-mode 表示
忽略系统的环境变量的属性名,避免加载的时候加载到的值不是我们想要的
-->
<context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER"/>

<bean id="conn" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>

db.properties

1
2
3
4
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&useSSL=false
jdbc.username=root
jdbc.password=123456

jdbc. 前缀 与 system-properties-mode 解决的问题是一样的,表示该占位符加载的属性是用于
jdbc连接数据库的。

Spring集成Mybatis


在 ibatis3 问世之前,Spring3 的开发工作就完成了,所以 Spring3 还是没有对 Mybatis3 支持,因此由 Mybatis 社区自己开发了一个 Mybatis-Spring 用来满足 Mybatis 用户整合 Spring 的需求。

在使用 MyBatis 时,SqlSession 主导了 Mybatis 所有的操作,在 Mybatis-Spring 中给我们封装了一个 SqlSessionFactoryBean,来获取 SqlSession。

在以前,我们通过 new SqlSessionFactoryBuild 来构建一个 SqlSessionFactory ,通过解析配置文件会得到一个 SqlSession 对象,然后早 dao 中进行大量重复的代码来做增删改查。但是随着 Spring 框架的加入,IoC(管理反转)和DI(数据注入) ,这一情况会如何,看下面一个小 demo

一些基础类:

1
2
3
4
public class Account {
private String name;
private Integer balance;
}

目的如下:我们需要在测试方法中调用 service.gets() 查询数据库中的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AppTest {
// 自动注入 mapper 映射对象
@Autowired
private AccountMapper accountMapper;

// 自动注入 service 对象
@Autowired
private IAccountService service;

@org.junit.Test
public void testMapper() throws Exception {
accountMapper.gets().forEach(System.out::println);
}

@org.junit.Test
public void testName() throws Exception {
service.gets().forEach(System.out::println);
}
}

通过 Mybatis工作原理 可以知道,调用 service.gets() 实际上是调用 mapper 的gets方法,因为 service 中会引用 dao 然后间接引用 mapper 接口。而 IoC 则是用 xml 配置文件通过 Spring 获取对象,可以不需要自己手动创建 java 对象,只需要在 xml 中配置 bean 标签,然后就可以通过 Spring 的beanFactory.getBeans 获取对象即可。

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 1.加载db.properties 文件 -->
<context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER"/>
<!-- 2.配置Druid连接池对象,让Spring帮我们管理连接池对象 -->

<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<!--
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&useSSL=false
jdbc.username=root
jdbc.password=123456
-->

<!-- 3.配置SqlSessionFactory 需要注意的是,SqlSessionFactoryBean
实现了 Spring 的 FactoryBean 接口,说明了由 Spring 最终创建的 bean 不是
SqlSessionFactoryBean 本身,而是工厂类的 getObject() 返回的方法的结果
这种情况下,Spring 将会在应用启动时为你创建 SqlSessionFactory 对象,相同代码:
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
SqlSessionFactory sessionFactory = factoryBean.getObject(); -->
<bean id="mySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 1. 关联连接池,这里的 dataSource,可以是任意实现的 DataSource,
配置项(四要素)和Spring 数据库连接是一样的 -->
<property name="dataSource" ref="myDataSource"></property>
<!-- 2. 该属性可以给包中的类注册别名,注册后,可以直接使用类名,而不用
使用全限定的类名,该属性可以配置多个使用 ,;\t\n分割 -->
<property name="typeAliasesPackage" value="cn.lizhaoloveit.aop.domain"></property>
<!-- 3. 设置Mybatis 自身的属性配置,该属性最终是把值赋给了 configuraton 属性 -->
<property name="configurationProperties">
<value>
lazyLoadingEnabled=true
aggressiveLazyLoading=false
lazyLoadTriggerMethods=clone
</value>
</property>
<!--
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="lazyLoadingEnabled" value="true"></property>
</bean>
</property>
作用与上面相同
-->

<!-- 4. 关联Mapper映射文件 -->
<property name="mapperLocations" value="classpath:mappers/*Mapper.xml"></property>
</bean>
<!-- 4.配置MapperFactoryBean,Spring帮我们创建Mapper的代理对象 -->
<bean id="accountMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="mySqlSessionFactory"/>
<property name="mapperInterface" value="cn.lizhaoloveit.aop.mapper.AccountMapper"/>
</bean>
<!-- 创建Service对象 -->
<bean id="accountService" class="cn.lizhaoloveit.aop.service.impl.AccountServiceImpl">
<property name="accountMapper" ref="accountMapper"></property>
</bean>
</beans>

首先 applicationContext.xml 文件会随着 Spring 框架启动而被解析,通过
<context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER"/> 来加载 db.properties 文件中的属性。system-properties-mode 属性时为了防止误读系统的同名属性

我们知道 Mybatis 的SqlSession 的创建依赖于连接池对象的,所以接下来我们会创建一个连接池对象

1
2
3
4
5
6
7
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

将连接池对象作为属性,赋值给 SqlSession 并且创建 SqlSession 对象

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="mySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"></property>
<property name="typeAliasesPackage" value="cn.lizhaoloveit.aop.domain"></property>
<property name="configurationProperties">
<value>
lazyLoadingEnabled=true
aggressiveLazyLoading=false
lazyLoadTriggerMethods=clone
</value>
</property>
<property name="mapperLocations" value="classpath:mappers/*Mapper.xml"></property>
</bean>

需要注意的是,SqlSessionFactoryBean 实现了 Spring 的 FactoryBean 接口,说明 Spring 最终创建的对象不是 SqlSessionFactoryBean 类本身的对象,而是 FactoryBean 接口的 getObjct() 方法返回的对象。这种情况下,通过上述 bean 的配置,我们会得到一个 SqlSessionFactory 对象

相同代码:

1
2
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
SqlSessionFactory sessionFactory = factoryBean.getObject();
创建 SqlSessionFactory 需要的属性 属性所具有的功能
dataSource 最核心的属性,连接池对象
typeAliasesPackage 该属性可以给包中的类注册别名,注册后,可以直接使用类名,而不用使用全限定的类名,该属性可以配置多个使用 ,;\t\n分割
configurationProperties 设置Mybatis 自身的属性配置,该属性最终是把值赋给了 SqlSession的 configuraton 属性
mapperLocations 关联Mapper映射文件

给 configuration 赋值 的另一种写法:

1
2
3
4
5
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="lazyLoadingEnabled" value="true"></property>
</bean>
</property>

有了 SqlSession 之后,接下来该创建 Mapper 的实现类对象了,也就是之前的
StudentMapper mapper = session.getMapper(StudentMapper.class);

在以前,需要对各个 DAO 层接口编写实现类,而 Mybatis Spring 根据 DAO 层接口生成实现代理类,从而使我们可以从机械、繁琐的 DAO 层实现类编码中解脱出来。

1
2
3
4
<bean id="accountMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="mySqlSessionFactory"/>
<property name="mapperInterface" value="cn.lizhaoloveit.aop.mapper.AccountMapper"/>
</bean>

点击 MapperFactoryBean 查看 MapperFactoryBean 做了些什么事。实际上,MapperFactoryBean 根据 AccountMapper 接口和AccountMapper.xml 配置文件会创建一个实现 mapper 接口的动态代理类实例,该实例可以实现 mapper.xml 中配置的 sql 语句。内部会调用 SqlSession 去执行 mapper.xml 中的 sql。

1
2
3
4
<!-- 创建Service对象 -->
<bean id="accountService" class="cn.lizhaoloveit.aop.service.impl.AccountServiceImpl">
<property name="accountMapper" ref="accountMapper"></property>
</bean>

把 mapper 动态代理实例赋值给 service 的 accountMapper 属性,让 service 可以直接使用 mapper 的动态代理实例去实现业务功能。

纵观全局,我们发现,DAO 中繁琐的使用 mybatis 代码消失了。这一切都是因为 Spring-Mybatis 中使用了 IoC+DI 根据配置文件生成与之对应的动态代理类实例,去完成业务功能。本质是通过 xml 解析出元数据,并通过反射将元数据赋值到对应的动态代理类中使之拥有完成业务功能的能力。

最后,有必要说一下 pom.xml 中依赖的 jar 包。

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<dependencies>
<!--Spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.0.RELEASE</version>
</dependency>
<!-- 测试组件 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- spring-jdbc 事务管理,缺少这个 jar 包会报找不到事务管理异常 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.0.RELEASE</version>
</dependency>
<!-- druid的连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<!-- mysql 驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<!-- Mybatis-spring,案例中作用是 mapper 的注入和 SqlSession 的创建 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.0</version>
</dependency>
<!-- mybatis 执行 sql -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- 日志组件 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>

解决 bean 的 class 属性无法关联类的问题

cmd + shift + t 在全项目中查找类
然后右键类名复制全限定名。

文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2019/07/25/Spring%E5%85%A5%E9%97%A8IoC%E3%80%81DI/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论