Spring开发学习
Drunkbaby Lv6

Spring开发学习

Spring开发学习

代码已同步至 GitHub,有兴趣的师傅们可以自取 JavaSecurityLearning

0x01 前言

因为最近要开始看内存马了,按照 johnford 大师傅的说法,内存马里面会用到很多一些其他的知识;比如 Spring,Tomcat 以及 EL 表达式等等 ……

  • 所以最近先开始最简单与熟悉的 Spring 开发学习吧 ~

生成 getter,setter 方法:alt + insert

0x02 Spring 简介

关于 Spring

Spring 理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术

  • SSH:Struct2 + Spring + Hibernate
  • SSM: SpringMVC + Spring + Mybatis

Spring 的一些特点:

轻量级框架,特点 IoC:控制反转;AOP:切面编程。

组成

Spring 毕竟也是一个框架,所以框架肯定是支持很多功能的,我们要实现功能,或者说要通过框架来完成业务,只需要打配置即可。Spring 框架支持的一些功能如下图所示。

这里可以简单解释一下几点东西:

  • 最底下一定是我们的 Core,也就是所谓的 “核”
  • 往上走支持一些编程思想:IOC,AOP
  • 还支持 ORM(也就是处理数据库等语句,可以类比为 Mybatis)
  • 还支持很多 Web,比如 Web Application,Servlet 等

之前学习有个误区,觉得应该先学 SpringBoot 再去看其他的,其实正常的学习路线应该是:Spring —-> SpringBoot —-> SpringCloud 这个样子;

在 Spring 的官网上,Spring.io 有一条这样的学习路线:

为什么 Spring 之后是 SpringBoot,然后是 SpringCloud 呢?因为 Spring 的配置非常繁琐,有时候我们不得不配置一些与我们本身的 Web 应用关系不大的东西。所以 SpringBoot 顺势而生。

0x03 Spring 的核心之一 IOC

IOC

先新建一个项目,并创建 Module,导入 Maven 的 jar 包。

jar 包如下

1
2
3
4
5
6
7
8
9
10
11
<dependency>  
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.16</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.16</version>
</dependency>

后续整合的时候会用到 jdbc 的,所以这里可以先导进来。

正式开始我们的 IOC

传统的业务实现

在看 IOC 的编程思想之前,我们可以先看一看传统的编程思想:

传统的编程思想:Controller 层写接口,去调用 Service 层,Service 层里面有一个 Service 接口,还有一个 ServiceImpl 的实现类,具体的业务是写在 ServiceImpl 里面的。

Service 层去调用 Dao 层,也就是我们的实体类,我们的 Dao 层有一个 Dao 的抽象接口,还有一个 DaoImpl 的实现类。

  • 大致的流程就是 ControllerServiceDao

我们用代码来实现一下,看一看为什么要用到 IOC 的这种编程思维。

UserDAO.java

1
2
3
4
5
package DAO;  

public interface UserDAO {
public void getUser();
}

UserDAO 的实现类 ———— UserDAOImpl.java

1
2
3
4
5
6
7
8
9
10
package DAO;  

import DAO.UserDAO;

public class UserDAOImpl implements UserDAO {
@Override
public void getUser() {
System.out.println("输出获取用户数据");
}
}

UserService.java Service 业务层

1
2
3
4
5
package Service;  

public interface UserService {
public void getUser();
}

UserServiceImpl.java Service 业务实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package Service;  

import DAO.UserDAO;
import DAO.UserDAOImpl;

public class UserServiceImpl implements UserService{

private UserDAO userDAO = new UserDAOImpl();

@Override
public void getUser() {
userDAO.getUser();
}
}

这个地方需要提一嘴,我们的 UserService 全程都是在调用 DAO 层的,很有趣。

最后编写一个测试的启动类。

TestApplication.java

1
2
3
4
5
6
7
8
9
import Service.UserService;  
import Service.UserServiceImpl;

public class TestApplication {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.getUser();
}
}

跑一下

IOC 的处理情景

这样子做我们正常的业务是没有问题的,但是现在来了一个客户,他要求我们用 MySQL 获取到 User,这里我们应该把 UserServiceImpl 中的这一句语句加以修改

1
2
3
4
5
private UserDAO userDAO = new UserDAOImpl();

// 修改如下

private UserDAO userDAO = new 对应的 DAOImpl 类

看着也还好对吧,只修改了一个地方,因为我们本质还是去调用 DAO 层的东西了,这是因为我们只有一个 getUser() 方法,后续我们会有更多的方法,比如 getUserId()getUserName()getPasswd() 等等,这时候我们要是一个个换名字,就太麻烦了。所以这里我们采用 IOC 的编程思想。

IOC 这一编程思想光看理论有点累,所以我们可以直接用通俗易懂的代码方式来理解。

我们在 UserServiceImpl.java 的地方加一个 setter 方法,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Service;  

import DAO.UserDAO;
import DAO.UserDAOImpl;

public class UserServiceImpl implements UserService{

private UserDAO userDAO;

public void getUserDAO(UserDAO userDAO){
this.userDAO = userDAO;
}

@Override
public void getUser() {
userDAO.getUser();
}
}

这一个 setter 方法就非常灵性,它灵性于何处呢?我们可以看到在 TestApplication 里面,我们可以这样操作。

1
2
3
4
5
6
7
8
9
10
11
import DAO.MysqlUserDAOImpl;  
import DAO.UserDAO;
import Service.UserServiceImpl;

public class TestApplication {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDAO(new MysqlUserDAOImpl());
userService.getUser();
}
}

对应的,应该先新建一个 UserServiceImpl 类,去实现 UserDAO 的接口。

MysqlUserDAOImpl.java

1
2
3
4
5
6
7
8
package DAO;  

public class MysqlUserDAOImpl implements UserDAO{
@Override
public void getUser() {
System.out.println("通过 MySQL 的方式获取 User");
}
}

我个人感觉这里和构造函数的思想其实很像,但是确实牛逼。(后续又了解到这就是依赖注入当中的构造注入)不过我个人感觉,IOC 的思想就是依赖注入 + Set 注入

  • 之前,程序是主动创建对象!控制权在程序猿手上
  • 使用了 setter 注入后,程序不再具有主动性,而是变成了被动的接受对象!(主动权在客户手上

本质上解决了问题,程序员不用再去管理对象的创建;系统的耦合性大大降低,可以更专注在业务的实现上

这是 IOC(控制反转)的原型,反转(理解):主动权交给了用户;

这就是 Spring 当中的 IOC!

IOC 本质

控制反转IOC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

Spring 通过 XML 进行装配

这其实是一个蛮玄乎的地方,通过读取 xml 文件来读取类,确实玄乎。

这里我们先编写一个 Hello 实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package dao;  

public class Hello {
private String name;

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

public void show(){
System.out.println("Hello,"+ name );
}
}

再写一个 beans.xml,这用于我们 Spring 的装配

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就是java对象 , 由Spring创建和管理-->
<bean id="hello" class="dao.Hello">
<property name="name" value="Spring"/>
</bean>
</beans>

这里的 id 和 class 其实和 html 里面蛮像的,id 必然是只能有一个,class 就是对应我们要去装配的类,就和 Servlet 项目里面的 class 调用是一样的,简单。

property 就是给对象中的属性设置一个值。

接着编写测试代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import dao.Hello;  
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class test {
@Test
public void test() {
//解析beans.xml文件 , 生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//getBean : 参数即为spring配置文件中bean的id
Hello hello = (Hello) context.getBean("hello");
hello.show();
}
}

测试成功!

这种装配模式的思考

  • Hello 对象是谁创建的 ? hello 对象是由Spring创建的
  • Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的

这个过程就叫控制反转 :

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

IOC是一种编程思想,由主动的编程变成被动的接收

这是我们通过 .xml 文件获取类,并装配;如果我们根据不同的业务进行装配呢?就和之前的场景一致,有的业务需要我们从 MySQL 读取数据,有的业务需要我们从 Oracle 读取数据,我们该如何实现呢?

利用 IOC 的思想进行装配

到我们之前讲 IOC 的案例里面,我们新建一个 beans.xml,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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="mysqlImpl" class="DAO.MysqlUserDaoImpl"/>
<bean id="oracleImpl" class="DAO.OracleUserDaoImpl"/>

<bean id="UserServiceImpl" class="Service.UserServiceImpl">
<!--ref引用spring中已经创建很好的对象-->
<!--value是一个具体的值,基本数据类型-->
<property name="UserDao" ref="mysqlImpl"/>
</bean>
</beans>

对应的启动器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import DAO.MysqlUserDaoImpl;  
import Service.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class TestApplication {
@Test
public void TestApplication() {
// 获取ApplicationContext;拿到Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// 需要什么就直接get什么!
UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("UserServiceImpl");
userServiceImpl.getUser();
}
}

假若这时候需要 OracleImpl 对象,则只需在 xml 配置文件中设置 ref="oracleImpl" 即可,无需去改动代码。

总结:

  • 所有的类都要装配的 beans.xml 里面;
  • 所有的 bean 都要通过容器去取;
  • 容器里面取得的 bean,拿出来就是一个对象,用对象调用方法即可;

所谓的 IOC,一句话搞定 : 对象由 Spring 来创建 , 管理 , 装配 !

IOC 创建对象的方式

这里就和当初我们探究代码块运行顺序一样,IOC 主要有两种创建的方式,其他都不行的,师傅们可以自行尝试,我这里就挂两种可行的。

方法一、通过无参构造方法来创建

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class User {
private String name;

public User() {
System.out.println("user无参构造方法");
}

public String getName() {
return name;
}

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

public void show(){
System.out.println("name="+ name );
}
}

beans.xml

1
2
3
4
5
6
7
8
9
<?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="user" class="com.example.pojo.User">
<property name="name" value="Drunkbaby"/>
</bean>
</beans>

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import dao.User;  
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class TestApplication {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user = (User) context.getBean("user");
//调用对象的方法 . user.show();
}
}

成功

方法二、通过有参构造方法来创建

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class User {
private String name;

public User(String name){
this.name = name;
}
public String getName() {
return name;
}

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

public void show(){
System.out.println("name="+ name );
}
}

beans.xml 有三种方式编写

  • 下标赋值
1
2
3
4
<bean id="user" class="com.example.pojo.User"> 
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="Drunkbaby"/>
</bean>
  • 类型赋值(不建议使用)
1
2
3
4
<bean id="user" class="com.example.pojo.User">
<constructor-arg type="java.lang.String" value="Drunkbaby"/>
</bean>

  • 直接通过参数名(掌握)
1
2
3
4
<bean id="user" class="com.example.pojo.User">
<!-- name指参数名 -->
<constructor-arg name="name" value="Drunkbaby"/>
</bean>

结论:在配置文件加载的时候。其中管理的对象都已经初始化了(并且是单例模式,取到的对象是全局唯一)!

1
2
3
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
system.out.println(user == user2) //结果为true

0x04 Spring 配置

  • 在讲这个知识点之前,我们最好先再次理解一下封装的思维,以及为什么要用 Spring 来配置。

Spring 也被称为写配置写到吐,这不是没有原因的。

确实是要写配置写到吐。

我们前文说到 Spring 当中 有一个 bean.xml 叫做 applicationContext.xml,它是用来统领所有配置的,我们举个情景来说明一下。

有三位程序员,他们分别是张三、李四、王五。

张三写了代码,把程序打包到了 zhangsan.xml

同理有 lisi.xmlwangwu.xml

这时候,我们的 applicationContext.xml 来统领所有的配置,这样就非常符合我们的封装思想,很舒服。

  • 现在我们再回来讲一讲 Spring 的一些基础配置参数与其他的东西。

别名

alias 设置别名 , 为bean设置别名 , 可以设置多个别名

1
2
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="user" alias="userNew"/>

别名的理解很简单,我们要调用的时候可以调用 name,也可以调用别名。

Bean 的配置

  • id:bean 的唯一标识符,也就是相当于我们学的对象名
  • class:bean 对象所对应的会限定名:包名+类型
  • name:也是别名,而且 name 可以同时取多个别名
1
2
3
4
5
<bean id="user" class="com.example.pojo.User" name="u1 u2,u3;u4">

<property name="name" value="chen"/>

</bean>

import

import 一般用于团队开发使用,它可以将多个配置文件,导入合并为一个

这就是我之前提到的那个思想

  • 张三(beans.xm1)
  • 李四(beans2.xm1)
  • 王五(beans3.xm1)
  • applicationContext.xml
1
<import resource="beans.xm1"/>  <import resource="beans2.xml"/>  <import resource="beans3.xm1"/>

使用的时候,直接使用总的配置就可以了

有重名的情况出现时,按照在总的xml中的导入顺序来进行创建,后导入的会重写先导入的,最终实例化的对象会是后导入xml中的那个

0x05 DI 依赖注入

  • 这是我们学习 Spring 的另外一个大点,第一个大点是 IOC

构造注入

前文已经说过,其实就是对应的 IOC 业务情景。

Set 方法注入

  • 依赖注入:本质上是 Set 注入

    • 依赖:bean 对象的创建依赖于容器
    • 注入:bean 对象中的所有属性由容器来注入

pojo 实体类

Address.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package pojo;  

public class Address {
private String address;

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

// toString
}

Student

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package pojo;  

import java.util.*;

public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;

public String getName() {
return name;
}

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

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}

public String[] getBooks() {
return books;
}

public void setBooks(String[] books) {
this.books = books;
}

public List<String> getHobbies() {
return hobbies;
}

public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}

public Map<String, String> getCard() {
return card;
}

public void setCard(Map<String, String> card) {
this.card = card;
}

public Set<String> getGames() {
return games;
}

public void setGames(Set<String> games) {
this.games = games;
}

public String getWife() {
return wife;
}

public void setWife(String wife) {
this.wife = wife;
}

public Properties getInfo() {
return info;
}

public void setInfo(Properties info) {
this.info = info;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", address=" + address +
", books=" + Arrays.toString(books) +
", hobbies=" + hobbies +
", card=" + card +
", games=" + games +
", wife='" + wife + '\'' +
", info=" + info +
'}';
}
}

xml 注入文件

beans.xml

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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="student" class="pojo.Student">
<property name="name" value="Drunkbaby"/>
</bean>

</beans>

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.junit.Test;  
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;


public class TestApplication {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
Student student = (Student) context.getBean("student");
//调用对象的方法 . System.out.println(student.getName());
}
}

官方文档上支持非常多的注入方式,如图

我们下面就把这些的注入方式实现一下

Bean 注入

注意点:这里的值是一个引用,ref

1
2
3
4
5
6
7
<<bean id="addr" class="pojo.Address">  
<property name="address" value="China"/>
</bean>

<bean id="student" class="pojo.Student">
<property name="address" ref="addr"/>
</bean>

数组注入

1
2
3
4
5
6
7
<property name="books">  
<array>
<value>西游记</value>
<value>三国演义</value>
<value>红楼梦</value>
</array>
</property>

List 注入

1
2
3
4
5
6
7
8
<property name="hobbies">  
<list>
<value></value>
<value></value>
<value>rap</value>
<value>篮球</value>
</list>
</property>

Map 注入

1
2
3
4
5
6
7
<!--map键值对注入 -->
<property name="card">
<map>
<entry key="username" value="root" />
<entry key="password" value="root" />
</map>
</property>

set 注入

1
2
3
4
5
6
7
8
<!--set(可去重)注入 -->
<property name="games">
<set>
<value>wangzhe</value>
<value>lol</value>
<value>galname</value>
</set>
</property>

空指针 null 注入

1
2
3
4
<!--空指针null注入 -->
<property name="wife">
<null></null>
</property>

properties 常量注入

1
2
3
4
5
6
7
<!--properties常量注入 -->
<property name="info">
<props>
<prop key="id">20200802</prop>
<prop key="name">cbh</prop>
</props>
</property>

修改 Test 类如下

TestApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.junit.Test;  
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;


public class TestApplication {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
Student student = (Student) context.getBean("student");
System.out.println(student);
}
}

拓展方式注入

在官方文档中,它们是这两个注入方式,也被称之为 c 命令与 p 命名空间注入

其实这个地方,P 就代表着 Properties,c 就代表着 Constructor

所以这两个注入方式也不是什么很稀奇的事情,只是减轻了我们写配置文件的繁琐程度。

环境准备

新建一个实体类:

User.java【注意:这里没有有参构造器!】后续我们就可以看到为什么需要加上有参构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package pojo;  

public class User {
private String name;
private int age;

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

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

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

p 命名空间

P 命名空间注入 : 需要在头文件中加入约束文件。也不是说完全的约束文件吧,应该算是一个命名空间,就和 mybatis 那些一样的。

p 注入代码如下

1
2
3
4
 导入约束 : xmlns:p="http://www.springframework.org/schema/p"

<!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="pojo.User" p:name="Drunkbaby" p:age="20" />

编写一下 Test 类

Test2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;
import pojo.User;


public class Test2 {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user = (User) context.getBean("user");
System.out.println(user);
}
}

c 命名空间注入

也需要在头文件中加入约束文件

1
2
3
4
 导入约束 : 
xmlns:c="http://www.springframework.org/schema/c"
<!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="com.example.pojo.User" c:name="Drunkbaby" c:age="20"/>

发现问题:爆红了,刚才我们没有写有参构造!

我们是如何知道是因为有参构造的问题的呢?当时是官方文档!官方文档对于 C 命名空间的描述如图

解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!

把有参构造加上之后的 C 命名空间如图:

输出如图

Bean 的作用域

在 Spring 中,那些组成应用程序的主体及由 Spring IoC 容器所管理的对象,被称之为 bean。

简单地讲,**bean 就是由 IoC 容器初始化、装配及管理的对象 **

几种作用域中,request、session 作用域仅在基于 Web 的应用中使用(不必关心你所采用的是什么 Web 应用框架),只能用在基于 Web 的 Spring ApplicationContext 环境。

单例模式(Spring 默认机制)

默认的模式

1
2

<bean id="user" class="com.example.pojo.User"" c:name="cxk" c:age="19" scope="singleton"></bean>
  • 原型模式(Prototype)

每次从容器中 get 的时候,都产生一个新对象!

1
<bean id="user" class="com.example.pojo.User"" c:name="cxk" c:age="19" scope="prototype"></bean>
  • 其余的 request、session、application 这些只能在 Web 开放中使用!
  • 这些的部分我们会在内存马里面稍微提到,但是在那里面是 SpringBoot 的配置,所以会简单很多。

0x06 Bean 的自动装配

自动装配说明

由于在手动配置 xml 过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。

  • 自动装配是使用 spring 满足 bean 依赖的一种方法
  • spring 会在应用上下文中为某个 bean 寻找其依赖的 bean。

Spring 中 bean 有三种装配机制,分别是:

  1. 在 xml 中显式配置;
  2. 在 Java 中显式配置;
  3. 隐式的 bean 发现机制和自动装配 【重要】

环境

就是写一个手动装配的案例,一个人,拥有猫和兔子,师傅们是不是脑子里面已经有了对于的代码了呢?pojo,beans.xml…………

我们这里光速过一遍。

Cat.java

1
2
3
4
5
6
7
8

package pojo;

public class Cat {
public void shout(){
System.out.println("miao");
}
}

Rabbit.java

1
2
3
4
5
6
7
8

package pojo;

public class Rabbit {
public void shout(){
System.out.println("Zhizhi");
}
}

People.java

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

package pojo;
public class People {

private Cat cat;
private Rabbit rabbit;
private String name;

@Override
public String toString() {
return "People{" +
"cat=" + cat +
", rabbit=" + rabbit +
", name='" + name + '\'' +
'}';
}

public Rabbit getRabbit() {
return rabbit;
}

public void setRabbit(Rabbit rabbit) {
this.rabbit = rabbit;
}

public Cat getCat() {
return cat;
}

public void setCat(Cat cat) {
this.cat = cat;
}


public String getName() {
return name;
}

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

}

编写 Spring 配置文件

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<?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="cat" class="pojo.Cat"/>
<bean id="rabbit" class="pojo.Rabbit"/>

<bean id="people" class="pojo.People">
<property name="name" value="Drunkbaby"/>
<property name="cat" ref="cat"/>
<property name="rabbit" ref="rabbit"/>
</bean>
</beans>

测试

TestAutoWired.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.People;


public class TestAutoWired {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
People people = context.getBean("people",People.class);
people.getCat().shout();
people.getRabbit().shout();
}
}

byName 和 byType 自动装配

比较简单,修改 beans.xml 这一配置文件即可。

  • byName 的自动装配:

  • 去找 beanId 的,这个 Id 必须要是 setter 方法里有的,比如上文代码中,我们只可以 byNamebeanId 是 cat 和 rabbit

  • byType 的自动装配:

  • 去找的是 class 属性,是否是类当中的,也就是寻找 Bean

byName 自动装配

错误的实例:此时我们的 beanId 并不存在 rabbit123,但是存在 cat,所以 cat 的 shout 方法能够被正常调用。

正确的

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="cat" class="pojo.Cat"/>
<bean id="rabbit" class="pojo.Rabbit"/>

<bean id="people" class="pojo.People" autowire="byName">
<property name="name" value="Drunkbaby"/>
</bean>
</beans>

输出如图

byType 自动装配

比较简单,如图

要求 class 相同即可

使用注解自动装配

jdk1.5 开始支持注解,Spring2.5 开始全面支持注解。

准备工作:利用注解的方式注入属性。

  • 在 spring 配置文件中引入 context 文件头,这里可以通过添加这一行,代表支持注解的语句,会自动引入文件头。
1
2

<context:annotation-config/>

对于注解进行自动装配,对于我们最耳熟能详的,当属 @AutoWired 这个注解。

使用 @AutoWired 注解进行自动装配

@AutoWired 注解可以替代 setter 方法,这里我们可以在 People.java 这个文件夹下面动刀

People.java 修改如下

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

package pojo;

import org.springframework.beans.factory.annotation.Autowired;

public class People {

@Autowired
private Cat cat;
@Autowired
private Rabbit rabbit;
private String name;

@Override
public String toString() {
return "People{" +
"cat=" + cat +
", rabbit=" + rabbit +
", name='" + name + '\'' +
'}';
}

public Rabbit getRabbit() {
return rabbit;
}

public Cat getCat() {
return cat;
}

public String getName() {
return name;
}

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

}

并且修改 beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<context:annotation-config/>

<bean class="pojo.Cat"/>
<bean class="pojo.Rabbit"/>
<bean id="people" class="pojo.People">
<property name="name" value="Drunkbaby"/>
</bean>
</beans>

输出:

Tips1:

@Nullable 字段标记了这个注解,说明该字段可以为空

1
2
3
4

public People(@Nullable String name) {
this.name = name;
}

Tips2:

1
2
3
4
5
6
7

@Autowired(required=false)
说明:false,对象可以为nulltrue,对象必须存对象,不能为null

//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;

这里的代码就不做演示了,有兴趣的师傅们可以自行尝试。

使用 @AutoWired + @Qualifier

是用来指定 ID 的,和 byName 很像

@Autowired不能唯一装配时,需要@Autowired+@Qualifier

如果 @Autowired 自动装配环境比较复杂。自动装配无法通过一个注解完成的时候,可以使用 @Qualifier(value = “xxx”) 去配合 @Autowired 使用,指定一个唯一的 id 对象

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

1
2

<bean id="dog2" class="com.example.pojo.Dog"/><bean id="cat2" class="com.example.pojo.Cat"/>

2、没有加Qualifier测试,直接报错

3、在属性上添加Qualifier注解

1
2
3
4
5
6
7

@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;

测试,成功输出!

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

1
2

<bean id="dog2" class="com.example.pojo.Dog"/><bean id="cat2" class="com.example.pojo.Cat"/>

2、没有加Qualifier测试,直接报错

3、在属性上添加Qualifier注解

1
2
3
4
5
6
7

@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;

测试,成功输出!

@Resource

非常灵活的一种自动注入,可以理解为 byName + byType

  • @Resource 如有指定的 name 属性,先按该属性进行 byName 方式查找装配;
  • 其次再进行默认的 byName 方式进行装配;
  • 如果以上都不成功,则按 byType 的方式自动装配。
  • 都不成功,则报异常。

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

1
<bean id="dog1" class="com.example.pojo.Dog"/><bean id="dog2" class="com.example.pojo.Dog"/><bean id="cat1" class="com.example.pojo.Cat"/><bean id="cat2" class="com.example.pojo.Cat"/>

修改 People.java,使用 @Resource 注解

1
2
3
4
5
6
7
public class People {
@Resource(name="cat2")
private Cat cat;
@Resource(name="dog2")
private Dog dog;
private String name;
}

测试,成功输出!

小结

@Autowired@Resource 异同:

  • @Autowired@Resource 都可以用来装配 bean。都可以写在字段上,或写在 set 方法上。
  • @Autowired 通过 byType 的方式实现,而且必须要求这个对象存在!【常用】
  • @Resource 默认通过 byname 的方式实现,如果找不到名字,则通过 byType 实现!如果两个都找不到的情况下,就报错!【常用】
  • 它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired 先 byType,@Resource先 byName。

0x07 使用注解开发

  • 也算是把我们在 Spring 中接触到的这些注解,做一个简单的总结。

关于注解的一些说明

在 Spring4 之后,想要使用注解形式,必须得要引入 aop 的包,这个在 Spring-webmvc 的大包里面基本也是自带的。

我们之前都是使用 bean 的标签进行 bean 注入,但是实际开发中,我们一般都会使用注解!

配置扫描哪些包下的注解

1
2
<!--指定注解扫描包-->
<context:component-scan base-package="com.example.pojo"/>

在指定包下编写类,增加注解

1
2
3
4
5
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name = "Drunkbaby";
}

AnnotationTest.java 测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.drunkbaby.pojo.User;  
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;



public class AnnotationTest {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user",User.class);
System.out.println(user.name);
}
}

成功

属性注入

  • 属性注入的说法比较学名,简单理解一下就是赋值

可以不用提供 setter 方法,直接在直接名上添加 @Value(“值”)

1
2
3
4
5
6
@Component
public class User {
// 相当于<property name="name" value="John"/>
@Value("Drunkbaby")
public String name;
}

如果提供了set方法,在set方法上添加@Value(“值”);

1
2
3
4
5
6
7
8
9
10
@Component
public class User {
public String name;

// 也可以放在set方法上面
@Value("Drunkbaby")
public void setName(String name) {
this.name = name;
}
}

一般没有人会写在 Setter 方法地方吧,这不是扰乱自己视线吗?

这个 @Value 的注解在 Spring-mvc 的项目会经常用到,而里面的这个值可以写在 application.properties 中,到时候就可以直接调用。

衍生的注解

我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!

@Component 三个衍生注解:

为了更好的进行分层,Spring 可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:web 层
  • @Service:service 层
  • @Repository:dao 层

写上这些注解,就相当于将这个类交给 Spring 管理装配了!学习 SpringBoot 的时候这种思想就更为明显。

自动装配注解

这个其实在之前已经讲过了,快速过一遍

@Autowired:默认是 byType 方式,如果匹配不上,就会 byName

@Nullable:字段标记了这个注解,说明该字段可以为空

@Resource:默认是 byName 方式,如果匹配不上,就会 byType

作用域

@scope

  • singleton:默认的,Spring 会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收

小结

xml 与注解:

  • xml 更加万能,维护简单,适用于任何场合
  • 注解不是自己的类使用不了,维护复杂,开发简单方便

最佳实践:

  • xml 用来管理 Bean
  • 注解只用来完成属性的注入
  • 可以不用扫描,扫描是为了类上的注解,但是要开启注解支持 <context:annotation-config/>

0x08 用 Java 的方式配置 Spring

这个到了 SpringBoot 里面可以说是主流的选择了,也就是我们平常写项目时的 config 文件夹,因为 Spring 本身写 XML 太累了,繁琐至极,所以用 Java 的方式写 config,可以使得我们代码的可读性更高。

完全不使用 XML

User.java

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
package pojo;  

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

// 这里这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中
@Component
public class User {
private String name;

public String getName() {
return name;
}

// 属性注入值
@Value("Drunkbaby")
public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

MyConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package config;  

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import pojo.User;

@Configuration
@ComponentScan("pojo")
public class MyConfig {

@Bean
public User getUser(){
return new User();
}
}
  • 测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  
import config.MyConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;


public class ConfigTestApplication {
@Test
public void TestApplication() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
User user = (User) applicationContext.getBean("getUser");
System.out.println(user.getName());
}
}

成功!

0x09 代理模式与 AOP

这里就不再赘述了,在这篇文章中有详细的学习
Java反序列化基础篇-04-JDK动态代理)

0x10 结合 mybatis 的实际开发

基础开发,也是炒冷饭环节

导包:

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
<dependencies>  
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>

实体类 User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.drunkbaby.pojo;  

public class User {

private int id;
private String name;
private String pwd;

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}

编写核心配置文件 mybatis-config.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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<typeAliases>
<package name="com.drunkbaby.pojo"/>
</typeAliases>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>

<mappers>
<package name="com.drunkbaby.mapper"/>
</mappers>
</configuration>

编写接口 UserMapper

1
2
3
4
5
6
7
8
9
package com.drunkbaby.mapper;  

import com.drunkbaby.pojo.User;

import java.util.List;

public interface UserMapper {
public List<User> selectUser();
}

编写对应的 xml 文件 UserMapper.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.drunkbaby.mapper.UserMapper">

<select id="selectUser" resultType="User">
select * from test.user </select>

</mapper>

测试 MybatisTest

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
import com.drunkbaby.mapper.UserMapper;  
import com.drunkbaby.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisTest {
@Test
public void selectUser() throws IOException {

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

List<User> userList = mapper.selectUser();
for (User user: userList){
System.out.println(user);
}

sqlSession.close();
}

}

Mybatis-Spring

  • 上文是一个正常的 Mybatis 程序的应用,接下来,我们要把它与 Spring 做一个整合,充分发挥出 Spring 的特点。

在整合之前,不免的,要唠点基础知识。

基础知识

在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要

MyBatis-Spring 需要以下版本:

MyBatis-SpringMyBatisSpring 框架Spring BatchJava
2.03.5+5.0+4.0+Java 8+
1.33.4+3.2.2+2.1+Java 6+

如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

  • 官方文档中是如下记述的:
关于 SqlSessionFactory

在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

1
2
3
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>

注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。

SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。

一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 <settings><typeAliases>元素。

关于 SqlSessionTemplate

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。

模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

1
2
3
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
public class UserDaoImpl implements UserDao {

private SqlSession sqlSession;

public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}

public User getUser(String userId) {
return sqlSession.getMapper...;
}
}

按下面这样,注入 SqlSessionTemplate:

1
2
3
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSession" ref="sqlSession" />
</bean>

上述都是官方文档的做法,接下来根据我们自己的业务需求,进行整合。

整合实现一

1、首先,创建一个 applicationContext.xml

1
2
3
4
5
6
<?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">
</beans>

2、配置数据源替换 mybaits 的数据源

1
2
3
4
5
6
7
<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</bean>

3、配置 SqlSessionFactory,关联 MyBatis

1
2
3
4
5
6
7
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/example/dao/*.xml"/>
</bean>

4、注册 sqlSessionTemplate,关联 sqlSessionFactory;

1
2
3
4
5
<!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--利用构造器注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

5、增加 Dao 接口的实现类;私有化 sqlSessionTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserMapperImpl implements UserMapper {

//sqlSession不用我们自己创建了,Spring来管理
private SqlSessionTemplate sqlSession;

public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}

public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}

}

6、注册 bean 实现

1
2
3
<bean id="userMapper" class="com.example.dao.userMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>

7、测试

1
2
3
4
5
6
7
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper");
List<User> user = mapper.selectUser();
System.out.println(user);
}

整合成功!

整合实现二

dao 继承 Support 类 , 直接利用 getSqlSession() 获得 , 然后直接注入 SqlSessionFactory . 比起方式一 , 不需要管理 SqlSessionTemplate , 而且对事务的支持更加友好

SqlSessionDaoSupport

SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法,就像下面这样:

1
2
3
4
5
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
public User getUser(String userId) {
return getSqlSession().selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
}
}

在这个类里面,通常更倾向于使用 MapperFactoryBean,因为它不需要额外的代码。但是,如果你需要在 DAO 中做其它非 MyBatis 的工作或需要一个非抽象的实现类,那么这个类就很有用了。

SqlSessionDaoSupport 需要通过属性设置一个 sqlSessionFactorySqlSessionTemplate。如果两个属性都被设置了,那么 SqlSessionFactory 将被忽略。

假设类 UserMapperImplSqlSessionDaoSupport 的子类,可以编写如下的 Spring 配置来执行设置:

1
2
3
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

测试:

1、将我们上面写的 UserMapperImpl 修改一下

1
2
3
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

2、修改 bean 的配置,这时候要把第一种的 userMapper 配置注释一下

1
2
3
<bean id="userMapper" class="com.example.mapper.UserMapperImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

3、测试

1
2
3
4
5
6
7
8
@Test
public void test() throws IOException{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}

总结 : 整合到 Spring 以后可以完全不要 mybatis 的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现。

0x11 声明式事务

  • Spring 学习的终章篇

回顾事务

  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。

事务四个属性ACID

  • 原子性(atomicity)

事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用

  • 一致性(consistency)

一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中

  • 隔离性(isolation)

可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏

  • 持久性(durability)

事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

测试

在之前的案例中,我们给 userMapper 接口新增三个方法,删除和增加用户;

1
2
3
4
5
6
7
//添加一个用户
int addUser(User user);

//根据id删除用户
int deleteUser(int id);

public List<User> test();

编写接口的实现类,在实现类中,我们去操作一波

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
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {

//增加一些操作
public List<>User test() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
User user = new User(5,"小明","123456");
mapper.addUser(user);
mapper.deleteUser(5);
return mapper.selectUser();
}

public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.selectUser();
}

//新增
public int addUser(User user) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.addUser(user);
}
//删除
public int deleteUser(int id) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.deleteUser(id);
}

}

到UserMapper.xml配置文件中添加映射,并且故意把 deletes 写错

1
2
3
4
5
6
7
8
9
<insert id="addUser" resultType="User">
insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd})
</insert>

<delete id="deleteUser" resultType="User">
deletes from mybatis.user where id=#{id}
</delete>

<select id="test" resultType="User"/>

测试

1
2
3
4
5
6
7
8
9
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper");
List<User> userlist = mapper.test();
for (User user : userlist) {
System.out.println(user);
}
}

报错:sql异常,delete写错了

结果 :插入成功!

没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!

以前我们都需要自己手动管理事务,十分麻烦!

但是Spring给我们提供了事务管理,我们只需要配置即可;

Spring中的事务管理

Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。

编程式事务管理

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 一般情况下比编程式事务好用。
  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

使用Spring管理事务,注意头文件的约束导入 : tx

1
2
3
4
xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

JDBC事务

1
2
3
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

配置好事务管理器后我们需要去配置事务的通知

1
2
3
4
5
6
7
8
9
10
11
12
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

配置AOP

导入aop的头文件!

1
2
3
4
5
<!--配置aop织入事务-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.example.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

最终我们的spring-dao.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
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--绑定MyBatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/example/mapper/UserMapper.xml"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>

<!--结合AOP实现事务的织入-->
<!--配置事务的类:-->
<!--配置事务通知:-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给哪些方法配置事务-->
<!--配置事务的传播特性-->
<tx:attributes>
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.example.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>

进行测试

删掉刚才插入的数据,再次测试!

1
2
3
4
5
6
7
8
9
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper");
List<User> userlist = mapper.test();
for (User user : userlist) {
System.out.println(user);
}
}

这时候仍然报错但是插入不成功了!

思考问题?

为什么需要配置事务?

  • 如果不配置事务,可能存在数据提交不一致的情况下;
  • 如果不在spring中去配置声明式事务,我们就需要在代码中手动配置事务
  • 事务在项目的开发中非常重要,涉及到数据的一致性和完整性问题!

0x12 参考资料

Spring基础
https://www.bilibili.com/video/BV1WE411d7Dv

 评论