课程地址:https://www.bilibili.com/video/BV1Vf4y127N5

jar包等资料地址:链接:https://pan.baidu.com/s/1dsgpwfj1Jrn16PCsteo-lQ?pwd=bjqz 提取码:bjqz

Spring5 概述

  • Spring 是轻量级的开源的 JavaEE 框架
  • Spring 可以解决企业应用开发的复杂性
  • Spring 有两个核心的部分:IOC、AOP
    • IOC:控制反转,把创建对象的过程交给Spring进行管理
    • AOP:面向切面,不修改源代码进行功能增强
  • Spring 特点:
    • 方便解耦,简化开发
    • AOP编程支持
    • 方便程序测试
    • 方便进行事务操作
    • 降低API开发难度

下载Spring5相关jar包

下载地址:https://repo.spring.io/ui/native/libs-release-local/org/springframework/spring

这里我们选择下载 5.2.6 版本进行下载

Snipaste_2022-05-13_20-28-03.png

点击链接,选择下载

Snipaste_2022-05-13_20-29-20.png

下载解压后的目录如图所示,其中所有的 jar 包都在 libs 文件中

Snipaste_2022-05-13_20-30-00.png

入门案例

首先看一下 Spring 的框架结构

Snipaste_2022-05-13_20-35-59.png

Core Container 模块表示核心模块,我们演示入门案例,只需用到日下几个包即可

A20210630171147607.png

其中 commons-logging 是一个日志包,其他都依赖于这个日志包

commons-logging-1.1.1 下载地址:https://search.maven.org/artifact/commons-logging/commons-logging-api/1.1/jar

首先创建java项目,目录结构如下

Snipaste_2022-05-13_21-32-48.png

在 pro01 module 包的src 下新建 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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" >

<bean id="user" class="com.szx.spring5.demo.User"></bean>

</beans>
  • id:表示 User 类的一个别名
  • class:指向 User 类的地址

创建一个User类

1
2
3
4
5
public class User {
public void add(){
System.out.println("Hello Spring");
}
}

通过Spring的方式创建User类的实例,并调用add方法

1
2
3
4
5
6
7
8
9
10
11
public class test {
@Test
public void testuser(){
// 读取配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 创建user实例
User user = context.getBean("user", User.class);
// 调用add方法
user.add();
}
}

运行效果,可以正常打印add方法中的 Hello Spring

Snipaste_2022-05-13_21-36-08.png

IOC接口

  • IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
  • Spring 提供 IOC 容器实现两种方式:(两个接口)
    • BeanFactory:IOC 容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用,加载配置文件时不会创建对象,在获取对象时才会去创建对象
    • ApplicationContext:BeanFactory 接口的子接口,提供更多的更强大的功能,一般由开发人员进行使用。加载配置文件时就会把配置文件对象进行创建

Bean管理XML方式

通过xml配置文件注入属性值

首先新建 Book.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
public class Book {
String name;
String author;

public String getName() {
return name;
}

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

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

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

然后再 xml 文件中添加如下配置

1
2
3
4
<bean id="book" class="com.szx.spring5.demo.Book">
<property name="name" value="三体"></property>
<property name="author" value="刘慈欣"></property>
</bean>
  • name:类中定义的属性名
  • value:设置属性值

编写测试类来测试

1
2
3
4
5
6
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Book book = context.getBean("book", Book.class);
System.out.println("book = " + book);
}

通过Spring 获取 Book 实例,然后打印 book,可以看到我们在配置文件中设置的初始值已经被赋值了

Snipaste_2022-05-14_17-48-51.png

有参构造注入赋值

新建Order类,类中声明一个带参数的构造器

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
public class Order {
String name;
String address;

public Order(String name, String address) {
this.name = name;
this.address = address;
}

public String getName() {
return name;
}

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

public String getAddress() {
return address;
}

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

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

在 xml 中添加配置文件

1
2
3
4
5
<!-- 使用构造器方式赋值 -->
<bean id="order" class="com.szx.spring5.demo.Order">
<constructor-arg name="name" value="手机"></constructor-arg>
<constructor-arg name="address" value="中国"></constructor-arg>
</bean>
  • name:构造器参数中的属性名
  • value:构造器参数中的属性值

添加测试代码

1
2
3
4
5
6
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Object order = context.getBean("order");
System.out.println("order = " + order);
}

Snipaste_2022-05-14_20-39-31.png

p名称注入(了解)

首先在 xml 中添加一行约束

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

Snipaste_2022-05-14_20-51-11.png

然后注入属性的操作可以这样写

1
2
3
<!-- p名称注入 -->
<bean id="book" class="com.szx.spring5.demo.Book" p:name="平凡的世界" p:author="莫言">
</bean>

运行后也可以正常打印值

Snipaste_2022-05-14_20-52-47.png

注入null值和特殊符号

配置xml

1
2
3
4
5
6
7
8
9
<!-- 注入空值和特殊符号 -->
<bean id="student" class="com.szx.spring5.demo.Student">
<property name="name">
<null></null>
</property>
<property name="bookName">
<value><![CDATA[ <<特殊值>> ]]>></value>
</property>
</bean>
  • null 值直接使用 <null></null> 标签
  • 特殊值使用 CDATA

运行看效果

Snipaste_2022-05-14_21-26-10.png

注入外部bean

新建 UserDao 接口

1
2
3
public interface UserDao {
void update();
}

创建 UserDao 实现类 UserDaoImpl

1
2
3
4
5
6
public class UserDaoImpl implements UserDao {
@Override
public void update() {
System.out.println("update......");
}
}

创建 UserService

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserService {
// 声明userDao
private UserDao userDao;
// 创建userDao的set方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

public void update(){
System.out.println("userservice....");
userDao.update();
}
}

配置xml

1
2
3
4
5
6
7
<!-- 对象的创建 -->
<bean id="userservice" class="com.szx.spring5.service.UserService">
<!-- 使用ref注入外部bean -->
<property name="userDao" ref="userdao"></property>
</bean>

<bean id="userdao" class="com.szx.spring5.dao.impl.UserDaoImpl"></bean>

编写测试方法

Snipaste_2022-05-14_21-51-26.png

内部注入bean

这里使用 部门 和 员工 来举例

创建一个部门类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Dept {
String deptName;

public Dept() {
}

public String getDeptName() {
return deptName;
}

public void setDeptName(String deptName) {
this.deptName = deptName;
}

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

创建员工类,员工类中声明一个部门属性

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
public class Emp {
String name;
String gender;
Dept dept;

public Emp() {
}

public String getName() {
return name;
}

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

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public Dept getDept() {
return dept;
}

public void setDept(Dept dept) {
this.dept = dept;
}

@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", dept=" + dept +
'}';
}
}

配置xml

1
2
3
4
5
6
7
8
9
10
11
12
<!--内部注入bean-->
<bean name="emp" class="com.szx.spring5.bean.Emp">
<!--赋值普通属性值-->
<property name="name" value="张三"></property>
<property name="gender" value="男"></property>
<!--内部注入引用属性值-->
<property name="dept">
<bean id="dept" class="com.szx.spring5.bean.Dept">
<property name="deptName" value="技术部"></property>
</bean>
</property>
</bean>

编写测试方法

Snipaste_2022-05-14_22-22-00.png

级联赋值

方式一

xml配置代码

1
2
3
4
5
6
7
8
9
10
<!--级联赋值-->
<bean id="emp" class="com.szx.spring5.bean.Emp">
<property name="name" value="李四"></property>
<property name="gender" value="女"></property>
<property name="dept" ref="dept"></property>
</bean>

<bean id="dept" class="com.szx.spring5.bean.Dept">
<property name="deptName" value="财务部"></property>
</bean>

运行效果

Snipaste_2022-05-14_22-25-37.png

方式二

这种方式要在员工类中声明部门属性值的 getters 方法

xml配置代码

1
2
3
4
5
6
7
8
9
10
11
<bean id="emp" class="com.szx.spring5.bean.Emp">
<property name="name" value="李四"></property>
<property name="gender" value="女"></property>
<property name="dept" ref="dept"></property>
<!--这种方法要提前在Emp类中添加dept属性值的getters方法,返回Dept类-->
<property name="dept.deptName" value="人事部"></property>
</bean>

<bean id="dept" class="com.szx.spring5.bean.Dept">
<property name="deptName" value="财务部"></property>
</bean>

运行效果

Snipaste_2022-05-14_22-29-02.png

注入集合类型的属性

首先新建User类,里面包含各种类型的属性值

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
public class User {
String[] strings;
List<String> lists;
Map<String,String> maps;
Set<String> sets;

public String[] getStrings() {
return strings;
}

public void setStrings(String[] strings) {
this.strings = strings;
}

public List<String> getLists() {
return lists;
}

public void setLists(List<String> lists) {
this.lists = lists;
}

public Map<String, String> getMaps() {
return maps;
}

public void setMaps(Map<String, String> maps) {
this.maps = maps;
}

public Set<String> getSets() {
return sets;
}

public void setSets(Set<String> sets) {
this.sets = sets;
}

public void pring(){
System.out.println(Arrays.toString(strings));
System.out.println(lists.toString());
System.out.println(maps.toString());
System.out.println(sets.toString());
}
}

编写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
<bean id="user" class="com.szx.spring.pojo.User">
<!--数组类型赋值-->
<property name="strings">
<array>
<value>java课程</value>
<value>mysql课程</value>
</array>
</property>
<!--集合类型赋值-->
<property name="lists">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!--map类型赋值-->
<property name="maps">
<map>
<entry key="name" value="张三"></entry>
<entry key="sex" value="男"></entry>
</map>
</property>
<!--set类型赋值-->
<property name="sets">
<set>
<value>javaweb</value>
<value>jdbc</value>
</set>
</property>
</bean>

编写测试方法,调用 print 方法

Snipaste_2022-05-15_16-47-47.png

往集合中注入对象类型

首先新建 Course 类

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

public String getName() {
return name;
}

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

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

然后再User类中添加 Course 的集合属性

1
2
3
4
5
6
7
8
9
List<Course> courseList;

public List<Course> getCourseList() {
return courseList;
}

public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}

添加xml配置文件

1
2
3
4
5
6
7
<!--声明多个Course对象bean-->
<bean id="course1" class="com.szx.spring.pojo.Course">
<property name="name" value="Java"></property>
</bean>
<bean id="course2" class="com.szx.spring.pojo.Course">
<property name="name" value="Mysql"></property>
</bean>

然后再user对象bean中通过如下方式注入

1
2
3
4
5
6
7
<!--对象类型集合-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>

运行效果

Snipaste_2022-05-15_21-29-16.png

提取集合的注入部分

首先新建UtilList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UtilList {
List<String> nameList;

public List<String> getNameList() {
return nameList;
}

public void setNameList(List<String> nameList) {
this.nameList = nameList;
}

public void eachList(){
nameList.forEach(item -> {
System.out.println(item);
});
}
}

编写配置文件

第一步:修改xml配置文件的约束

在头部 beans 标签中添加如下两行代码

1
xmlns:util="http://www.springframework.org/schema/util"
1
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd"

完整的引入

1
2
3
4
5
6
7
8
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd" >

第二步:抽取list的注入

1
2
3
4
5
6
<!--提取出list的注入-->
<util:list id="utilList">
<value>Java</value>
<value>JavaWeb</value>
<value>MySql</value>
</util:list>

第三步:配置UtilList对象bean

1
2
3
<bean id="util" class="com.szx.spring.pojo.UtilList">
<property name="nameList" ref="utilList"></property>
</bean>

测试查看运行效果

Snipaste_2022-05-15_21-39-47.png

定义工厂Bean

上面我们定义的都是普通Bena,定义的是什么对象类型,返回的就是这个类型,而工厂Bean则可以返回不同的对象类型。要想实现工厂Bean,在类中要实现 FactoryBean 接口

定义 FactoryBean 接口的实现类 MyBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyBean implements FactoryBean<Course> {
@Override
public Course getObject() throws Exception {
// 定义Course类的实例并返回
Course course = new Course();
course.setName("Java");
return course;
}

@Override
public Class<?> getObjectType() {
return null;
}

@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}

配置xml文件

1
<bean id="myBean" class="com.szx.spring.pojo.MyBean"></bean>

编写测试方法

1
2
3
4
5
6
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Course myBean = context.getBean("myBean", Course.class);
System.out.println(myBean);
}

通过运行可以发现返回的是 Course 对象

Snipaste_2022-05-15_22-00-10.png

xml方式自动注入属性值

首先新建员工类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Emp {
Dept dept;

public void setDept(Dept dept) {
this.dept = dept;
}

@Override
public String toString() {
return "Emp{" +
"dept=" + dept +
'}';
}
}

然后创建部门类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Dept {
String name;

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

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

添加xml配置代码

在bean标签中添加 autowire 属性实现自动注入

autowire 的值有两个,分别为:

  • byName:根据属性名称自动注入,要求类中的属性名和bean中的Id一样

  • byType:根据属性的类型自动注入,但是要求一个Bean类型只能有一个

1
2
3
4
5
<bean id="emp" class="com.szx.spring.pojo.Emp" autowire="byName"></bean>

<bean id="dept" class="com.szx.spring.pojo.Dept">
<property name="name" value="开发部"></property>
</bean>

测试运行效果

Snipaste_2022-05-18_21-10-54.png

Bean 的作用域

我们在xml中配置bena时,默认生成的对象都是单例的,也就是我们调用多次getBean,返回的都是同一个对象实例,例如:

Snipaste_2022-05-16_15-45-04.png

要想实现多例模式,可以在bean 标签上添加 scope=”prototype” 属性

Snipaste_2022-05-17_08-53-44.png

此时再来调用两次 getBean 方法

Snipaste_2022-05-17_08-55-10.png

可以发现两次打印的地址不同,这就代表两次返回的不是同一个对象实例

scope 的值有多个,其中常用的有两个

  • singleton (默认值)单例模式
  • prototype 多例模式

singleton 和 prototype 的区别

  • singleton 是默认值,表示生成的对象是单例的
  • prototype 表示生成的对象时多例的
  • 创建的时机不同,singleton 是在读取 xml 配置文件时就已经创建好了。prototype 在调用 getBean 方法时才会创建对象实例

Bean 的生命周期

一般情况下,Bean 的声明周期有五步

  • 第一步 调用空参构造器
  • 第二步 调用set方法设置属性值
  • 第三步 调用初始化方法,需要额外配置
  • 第四步 获取到对象实例
  • 第五步 对象实例销毁方法,需要额外配置

通过代码演示

首先新建 Order 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Order {
public Order() {
System.out.println("第一步 调用空参构造器");
}

String oname;

public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步 调用set方法设置属性值");
}

public void initMethod(){
System.out.println("第三步 调用初始化方法");
}

public void destroyMethod(){
System.out.println("第五步 调用销毁方法");
}
}

配置xml

  • init-method 属性用来设置调用初始化方法
  • destroy-method 属性用来设置调用销毁方法
1
2
3
<bean id="order" class="com.szx.spring.pojo.Order" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="Spring5"></property>
</bean>

编写测试方法

1
2
3
4
5
6
7
8
9
10
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Order order = context.getBean("order", Order.class);
System.out.println("第四步 获取对象实例");
System.out.println("order = " + order);

// 手动执行销毁方法
context.close();
}

执行效果

Snipaste_2022-05-17_11-43-29.png

Bean 的后置处理器

添加了后置处理器后,Bean 的生命周期会变成七个

添加后置处理器要让类实现 BeanPostProcessor 接口,并重写如下两个方法

  • postProcessBeforeInitialization 在初始化之前执行
  • postProcessAfterInitialization 在初始化之后执行
1
2
3
4
5
6
7
8
9
10
11
12
13
public class BeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之前执行");
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之后执行");
return bean;
}
}

然后在 xml 配置文件中添加这个 bean

1
<bean id="beanpost" class="com.szx.spring.pojo.BeanPost"></bean>

添加了后置处理器bean后,会为xml中每一个配置的bean添加后置处理器。这样bean的声明周期会由原来的五个变成七个,分别如下

image-20220517195905028.png

配置XML读取配置文件

首先需要导入两个jar包

然后编写 xml 配置文件

1
2
3
4
5
6
7
<!--普通方式配置Druid连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/spring5" />
<property name="username" value="root" />
<property name="password" value="abc123" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

测试是否连接成功

Snipaste_2022-05-19_09-25-36.png

除了直接写在xml文件中之外,我们也可以吧连接数据库的信息写在配置文件中,然后在xml中通过读取配置文件的方式来获取连接

新建一个配置文件 mysql.properties,内容如下

1
2
3
4
prop.url=jdbc:mysql://127.0.0.1:3306/spring5
prop.name=root
prop.pwd=abc123
prop.class=com.mysql.jdbc.Driver

然后在 xml 配置文件的 bean 空间里面添加如下属性

1
2
3
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

Snipaste_2022-05-19_10-45-55.png

然后引入外部文件

1
2
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:mysql.properties"/>

接着配置连接池,连接池的属性值可以通过 ${xxx} 的方式直接从配置文件中读取,大括号里面的内容就是在配置文件中定义的key

1
2
3
4
5
6
7
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${prop.url}" />
<property name="username" value="${prop.name}" />
<property name="password" value="${prop.pwd}" />
<property name="driverClassName" value="${prop.class}" />
</bean>

修改后再来测试连接是否成功

Snipaste_2022-05-19_10-46-24.png

Bean 管理配置注解的方式

创建对象

首先需要一个依赖包

然后再xml 的命名空间中添加 context

1
2
3
4
5
6
7
8
<?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
http://www.springframework.org/schema/context/spring-context.xsd" >

Snipaste_2022-05-21_14-28-49.png

开启组件扫描配置

1
2
<!--配置自动扫描包-->
<context:component-scan base-package="com.szx.spring" />

base-package 表示要扫描的包的上一级,这样会自动扫描这个目录下的所有类,发现有添加注解的类会自动完成类创建

在poji包下面新建一个 UserServer 类

1
2
3
4
5
6
@Component(value = "userServer")
public class UserServer {
public void add(){
System.out.println("add...");
}
}

添加一个注释 @Component(value = "userServer")

其中 value 表示我们要给这个类起的名字,和 bean 标签中的 id 相同。value 也可以不写,不写默认会将类的首字母小写来作为对象实例名

编写测试类,来测试是否创建成功

Snipaste_2022-05-21_14-49-25.png

通过运行可以看到add方法成功调用,表示类创建成功

除了设置 Component 注解外,可以设置其他注解来完成类的创建,具体有:

  • @Component
  • @Service
  • @Controller
  • @Repository

这几个的注解功能是一样的,都是用来创建对象

注意:这里推荐使用 jdk1.8 版本。因为我原本使用的是 17 版本,但是添加组件扫描配置后,启动会报错切换到1.8后错误消失

组件扫描配置

开启组件扫描时除了默认扫描方式外,也可以开启自动以扫描配置

配置一:

1
2
3
4
<!--设置只扫描配置了Controller注解的包-->
<context:component-scan base-package="com.szx.spring.pojo" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

配置二:

1
2
3
4
<!--设置不扫描配置了Controller注解的类-->
<context:component-scan base-package="com.szx.spring.pojo" use-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

两个区别:一个是 include,一个是 exclude

注入属性@Autowired和@Qualifier的使用

注入属性的注解有四个

  • @Autowired 根据类型注入
  • @Qualifier 根据名称注入
  • @Resource 可以根据类型注入,也可以根据名称注入
  • @Value 注入普通类型属性

添加 UserDao,使用 @Repository 注解完成对象创建

1
2
3
4
5
6
@Repository
public class UserDao {
public void add(){
System.out.println("dao...");
}
}

然后在 UserServer 类中添加 UserDao 类型的属性,并使用 @Autowired 完成属性赋值

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserServer {
// @Autowired 根据类型自动赋值
@Autowired
public UserDao userDao;

public void add(){
System.out.println("server...");
userDao.add();
}
}

@Qualifier 要和 @Autowired 一起使用

当一个接口有多个实现类时,就不能在通过类型注入,而是要通过名称注入。比如先给 UserDao 设置一个实例名

1
2
3
4
5
6
@Repository(value = "userDao1")
public class UserDao {
public void add(){
System.out.println("dao...");
}
}

然后使用 @Qualifier 注入值

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class UserServer {
// @Autowired 根据类型自动赋值
@Autowired
// 使用Qualifier说明要使用那个实例来注入值
@Qualifier(value = "userDao1")
public UserDao userDao;

public void add(){
System.out.println("server...");
userDao.add();
}
}

编写测试方法,来测试是否注入成功。通过结果看到两个add方法都被成功执行

Snipaste_2022-05-21_15-51-55.png

注入属性 @Resource 和 @Value

@Resource 不设置name值时表示通过类型注入,设置了name值表示通过名称注入

注意:@Resource 是从 import javax.annotation.Resource; 包中导出来的,并不是 Spring 提供的,所以不推荐使用

1
2
@Resource(name = "userDao1")
public UserDao userDao;

@Value 表示普通值注入

1
2
@Value(value = "张三")
String name;

在add方法中将name值打印出来

1
2
3
4
public void add(){
System.out.println("server..." + name);
userDao.add();
}

编写测试方法

Snipaste_2022-05-21_16-06-42.png

完全注解开发

  • @Configuration // 添加注解,表示这个类是一个配置类
  • @ComponentScan(basePackages = {“com.szx.spring”}) // 添加自动扫描的注解

首先添加配置类,目的是替代 xml 配置文件

1
2
3
4
5
@Configuration // 添加注解,表示这个类是一个配置类
@ComponentScan(basePackages = {"com.szx.spring"}) // 添加自动扫描的注解
public class SpringConfig {

}

修改测试方法

使用 AnnotationConfigApplicationContext 类,传入配置类的 class

1
2
3
4
5
6
@Test
public void test1(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserServer userServer = context.getBean("userServer", UserServer.class);
userServer.add();
}

运行效果和之前相同

AOP概述

  1. 面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率
  2. 通俗描述:不通过修改源代码的方式,在主干功能里面添加新功能

AOP底层使用动态代理

有两种情况的动态代理

  1. 有接口情况下,使用JDK动态代理
    • 创建接口实现类的代理对象,做到对类的增强
  2. 没有接口情况下,使用CGLIB动态代理
    • 创建子类的代理对象,增强类的方法

JDK动态代理实现

首先创建一个接口

1
2
3
4
5
public interface UserDao {
int add(int a,int b);

String updateName(String name);
}

创建接口实现类

1
2
3
4
5
6
7
8
9
10
11
12
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
return a + b;
}

@Override
public String updateName(String name) {
return "传入的名字是" +
name;
}
}

创建代理类

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
package com.szx.spring.proxy;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
* @author songzx
* @create 2022-05-21 17:37
*/
public class UserProxy {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
// 获取UserDaoImpl的代理类
UserDao proxyUser = (UserDao) Proxy.newProxyInstance(UserProxy.class.getClassLoader(), interfaces, new MyInvocationHandler(userDao));
// 通过代理类调用方法
int add = proxyUser.add(1, 2);
System.out.println("add = " + add);
}
}

// 创建InvocationHandler接口实现类
class MyInvocationHandler implements InvocationHandler{
Object obj;
// 使用有参构造器传入需要代理的对象
public MyInvocationHandler(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法前触发");
System.out.println("调用的方法是" + method.getName());
System.out.println("调用的参数是" + Arrays.toString(args));

Object invoke = method.invoke(obj, args);

System.out.println("方法执行结束");

return invoke;
}
}

测试运行效果

Snipaste_2022-05-21_17-52-54.png

APO术语

  • 连接点
    • 类里面的那些方法可以被增强,这些方法被称为连接点
  • 切入点
    • 实际被增强的方法称为切入点
  • 通知
    • 实际增强的逻辑被称为通知
    • 通知有多种类型
      • 前置通知
      • 后置通知
      • 环绕通知
      • 异常通知
      • 最终通知
  • 切面
    • 切面是一个动作,把通知应用到切入点的操作被称为切面

AOP操作准备

  • Spring 框架一般都是基于 AspectJ 实现AOP操作

    • AspectJ 不是 Spring 组成部分,是一个独立的AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作
  • 基于AspectJ实现AOP操作

    • 基于XML配置文件实现
    • 基于注解的方式实现(使用)
  • 引入相关jar包

    • spring-aspects-5.2.6.RELEASE.jar
    • com.springsource.net.sf.cglib-2.2.0.jar
    • com.springsource.org.aopalliance-1.0.0.jar
    • com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
  • 切入点表达式

    • 切入点表达式的作用:知道对那个类里面的那个方法进行增强

    • 语法结构

      • ```java
        execution([权限修饰符][返回类型][类的全路径]方法名称)

        1
        2
        3
        4
        5

        - 举例一:对 com.szx.spring.dao.BookDao 类里面的 add 方法进行增强

        - ```java
        execution(* com.szx.spring.dao.BookDao.add(args))
      • * 表示一个通配符,表示对任何权限修饰的方法都生效,返回类型可以省略。权限修饰符要和类的全路径中间有一个空格

    • 举例二:对 com.szx.spring.dao.BookDao 类里面的所有方法进行增强

      • ```java
        execution(* com.szx.spring.dao.BookDao.*(args))
        1
        2
        3
        4
        5

        - 举例二:对 com.szx.spring.dao 包里面的所有类的所有方法进行增强

        - ```java
        execution(* com.szx.spring.dao.*.*(args))

AspectJ 注解开发1

首先配置xml配置文件,在命名空间中添加如下命名

1
2
3
4
5
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd

然后开启自动扫描和AspectJ代理对象

1
2
3
4
5
<!--开启自动扫描-->
<context:component-scan base-package="com.szx.spring.apoanno"></context:component-scan>

<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

完整的xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--开启自动扫描-->
<context:component-scan base-package="com.szx.spring.apoanno"></context:component-scan>

<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

然后我们在 apoanno 包下新建一个 User 类作为被增强的类

1
2
3
4
5
6
7
8
// 被增强的类
@Component
public class User {
// 被增强方法
public void add(){
System.out.println("User add...");
}
}

然后新建 UserProxy 作为 User 类的增强类

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
package com.szx.spring.apoanno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
* @author songzx
* @create 2022-05-22 10:46
*/
// 增强类
@Component
@Aspect
public class UserProxy {
// Before 前置通知
@Before(value = "execution(* com.szx.spring.apoanno.User.add(..))")
public void before(){
System.out.println("Before 前置通知");
}

// After 后置通知(最终通知)
@After(value = "execution(* com.szx.spring.apoanno.User.add(..))")
public void after(){
System.out.println("After 后置通知");
}

// Around 环绕通知
@Around(value = "execution(* com.szx.spring.apoanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("Around 环绕之前");

// 执行被增强的方法
proceedingJoinPoint.proceed();

System.out.println("Around 环绕之后");
}

// AfterThrowing 异常通知
@AfterThrowing(value = "execution(* com.szx.spring.apoanno.User.add(..))")
public void afterthrowing(){
System.out.println("AfterThrowing 异常通知");
}

// AfterReturning 方法正确执行后调用
@AfterReturning(value = "execution(* com.szx.spring.apoanno.User.add(..))")
public void afterreturning(){
System.out.println("AfterReturning 返回后通知");
}
}

增强类中有多个通知类型的注解

  • Before 前置通知
  • After 后置通知(最终通知),无论成功失败都会执行
  • Around 环绕通知,需要接收 ProceedingJoinPoint 类型的参数,然后在环绕之后执行
  • AfterThrowing 异常通知,在被增强的方法发生异常后调用
  • AfterReturning 方法正确执行后调用

编写测试方法运行查看效果

Snipaste_2022-05-22_15-30-42.png

可以看到异常通知没有触发,这是因为 User 类的 add 方法中没有发生异常,现在手动触发一个异常。修改 add 方法

Snipaste_2022-05-22_15-32-33.png

然后再次运行测试方法

Snipaste_2022-05-22_15-33-10.png

异常通知被执行,after 最终通知也被执行

AspectJ 注解开发2

切入点抽取

可以吧相同的切入点进行抽取,在多个通知方法上引调用同一个抽取方法

1
2
3
4
5
6
7
8
9
10
11
// 相同切入点的抽取
@Pointcut(value = "execution(* com.szx.spring.apoanno.User.add(..))")
public void pointcut(){

}

// Before 前置通知
@Before(value = "pointcut()")
public void before(){
System.out.println("Before 前置通知");
}

通知优先级

当有多个增强类对同一个类进行增强时,可以设置增强类的优先级,使用 @Order(数字) ,数字越小,优先级越高

新建一个增强类 UserProxy2,也对 User 类进行增强,设置 @Order 的优先级为1

1
2
3
4
5
6
7
8
9
@Component
@Aspect
@Order(1) // 设置增强类的优先级,数字越小,优先级越高
public class UserProxy2 {
@Before(value = "execution(* com.szx.spring.apoanno.User.add(..))")
public void before(){
System.out.println("第二个增强类的前置通知");
}
}

运行测试方法

Snipaste_2022-05-22_21-23-20.png

AspectJ 配置文件开发

新建被增强类 Book

1
2
3
4
5
public class Book {
public void add(){
System.out.println("book add ......");
}
}

新建增强类 BookProxy

1
2
3
4
5
public class BookProxy {
public void before(){
System.out.println("增强方法");
}
}

配置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
<?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"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--开启自动扫描-->
<context:component-scan base-package="com.szx.spring"></context:component-scan>

<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

<bean id="book" class="com.szx.spring.aopxml.Book"></bean>
<bean id="bookProxy" class="com.szx.spring.aopxml.BookProxy"></bean>

<!--配置aop增强-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="p" expression="execution(* com.szx.spring.aopxml.Book.add(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<!--增强作用具体的方法上,aop:after表示前置通知-->
<aop:after method="before" pointcut-ref="p"></aop:after>
</aop:aspect>
</aop:config>

</beans>

测试运行效果

1
2
3
4
5
6
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Book book = context.getBean("book", Book.class);
book.add();
}

Snipaste_2022-05-22_21-56-03.png

JdbcTemplate 概述和准备工作

什么是 jdbctemplate?JdbcTemplate 是 Spring 对 jdbc 的封装,可以很方便的对数据库进行增删改查操作

准备工作

导入相关jar包

  • druid-1.1.10.jar
  • mysql-connector-java-8.0.28.jar
  • spring-jdbc-5.2.6.RELEASE.jar
  • spring-orm-5.2.6.RELEASE.jar
  • spring-tx-5.2.6.RELEASE.jar

在xml配置文件中开启自动扫描

1
2
<!--开启自动扫描-->
<context:component-scan base-package="com.szx.spring"></context:component-scan>

配置DataSource

1
2
3
4
5
6
7
<!--配置数据连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/spring5" />
<property name="username" value="root" />
<property name="password" value="abc123" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

配置jdbcTemplate,注入DataSource

1
2
3
4
5
<!--配置jdbcTemplate 对象,注入DataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入DataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>

新建UserDao接口

1
2
3
public interface UserDao {

}

添加接口实现类,在dao中注入JdbcTemplate模板

1
2
3
4
5
6
@Component
public class UserDaoImpl implements UserDao {
// 注入JdbcTemplate模板
@Autowired
JdbcTemplate jdbcTemplate;
}

添加service类,注入dao

1
2
3
4
5
6
@Component
public class UserService {
// 注入DAO
@Autowired
UserDao userDao;
}

至此,前期准备工作完成

使用 JdbcTemplate 操作数据库

添加数据

首先创建数据库实体类User

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
package com.szx.spring.jdbc.pojo;

/**
* @author songzx
* @create 2022-05-22 23:07
*/
public class User {
String id;
String name;
String age;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

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

public String getAge() {
return age;
}

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

在UserDAO接口中增加UserDao方法

1
2
3
public interface UserDao {
void addUser(User user);
}

实现addUser方法,调用 jdbcTemplate.update 方法,返回受影响的行数

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class UserDaoImpl implements UserDao {
// 注入JdbcTemplate模板
@Autowired
JdbcTemplate jdbcTemplate;

@Override
public void addUser(User user) {
String sql = "insert into t_user values(?,?,?)";
int update = jdbcTemplate.update(sql, user.getId(), user.getName(), user.getAge());
System.out.println(update);
}
}

然后在UserService中调用addUser方法

1
2
3
4
5
6
7
8
9
10
@Component
public class UserService {
// 注入DAO
@Autowired
UserDao userDao;

public void addUser(User user){
userDao.addUser(user);
}
}

编写测试方法

Snipaste_2022-05-22_23-20-56.png

查看数据库内容

Snipaste_2022-05-22_23-21-21.png

修改和删除数据

修改和删除和添加调用的方法一样,都是调用 jdbcTemplate 的 update 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void updateUser(User user) {
String sql = "UPDATE t_user SET name = ?";
int update = jdbcTemplate.update(sql, user.getName());
System.out.println(update);
}

@Override
public void deleteUser(String id) {
String sql = "DELETE FROM t_user WHERE id = ?";
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}

查询数据

查询某个值

调用 jdbcTemplate.queryForObject 方法

  • 第一个参数传递sql
  • 第二个参数传递要返回的类型

例如获取表中数据的总条数

1
2
3
4
5
public Integer getCount() {
String sql = "select count(*) from t_user";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
return integer;
}

测试查看返回

Snipaste_2022-05-23_14-33-53.png

Snipaste_2022-05-23_15-57-44.png

查询返回对象

还是调用 jdbcTemplate.queryForObject 方法。参数有三个,分别意思是

  • 第一个,SQL 语句
  • 第二个,RowMapper 接口的实现类,但是 Spring 已经帮我们做了封装,直接传入 new BeanPropertyRowMapper<>(User.class) 实例
  • 第三个,参数

例如:根据id查询user信息

1
2
3
4
5
public User getUserById(String id) {
String sql = "select * from t_user where id = ?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), id);
return user;
}

测试查询结果,返回的是一个User对象

Snipaste_2022-05-23_16-24-35.png

查询返回集合

返回集合时需要调用 jdbcTemplate.query 方法,方法参数和获取对象的参数相同,没有查询的参数时可以不传

例如:获取所有的User信息放到一个集合中

1
2
3
4
5
6
@Override
public List<User> getUserList() {
String sql = "select * from t_user";
List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
return userList;
}

测试查询结果,返回的是一个User集合

Snipaste_2022-05-23_16-26-45.png

批量添加

调用 jdbcTemplate.batchUpdate 方法,方法接收两个参数

  • 第一个参数:SQL 语句
  • 第二个参数:传入一个集合,里面接受一个Object类型的数组,将多条数据放到集合中,完成批量添加

方法运行逻辑是遍历集合,循环调用SQL语句完成批量新增

例如:完成一个批量添加用户的功能

1
2
3
4
5
6
7
// 批量添加
@Override
public void batchAdd(List<Object[]> userList) {
String sql = "insert into t_user values(?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, userList);
System.out.println("ints = " + Arrays.toString(ints));
}

在service里面调用DAO中定义的方法

1
2
3
4
// 批量新增
public void batchAddUser(List<Object[]> userList){
userDao.batchAdd(userList);
}

编写测试方法,测试批量新增功能

1
2
3
4
5
6
7
8
9
@Test
public void testBatchAdd(){
List<Object[]> userList = new ArrayList<>();
Object[] o1 = {"3","王五","23"};
Object[] o2 = {"4","赵六","15"};
userList.add(o1);
userList.add(o2);
userService.batchAddUser(userList);
}

注意:数组中的顺序要和SQL语句中的问号顺序一致

查看数据库,两条数据成功添加

Snipaste_2022-05-23_17-07-45.png

批量删除

批量删除和批量添加方法一致,只是执行的SQL语句不同

1
2
3
4
5
6
7
// 批量更新
@Override
public void batchUpdate(List<Object[]> userList) {
String sql = "update t_user set name = ?,age = ? where id = ?";
int[] ints = jdbcTemplate.batchUpdate(sql, userList);
System.out.println(Arrays.toString(ints));
}

测试调用

1
2
3
4
5
6
7
8
9
@Test
public void testBatchUpdate(){
List<Object[]> userList = new ArrayList<>();
Object[] o1 = {"王五111","23","3"};
Object[] o2 = {"赵六222","15","4"};
userList.add(o1);
userList.add(o2);
userService.batchUpdate(userList);
}

查看效果

Snipaste_2022-05-23_17-31-18.png

批量删除

1
2
3
4
5
6
7
// 批量删除
@Override
public void batchDel(List<Object[]> ids) {
String sql = "delete from t_user where id = ?";
int[] ints = jdbcTemplate.batchUpdate(sql, ids);
System.out.println(Arrays.toString(ints));
}

测试调用

1
2
3
4
5
6
7
8
9
@Test
public void testBatchDel(){
List<Object[]> ids = new ArrayList<>();
Object[] o1 = {"3"};
Object[] o2 = {"4"};
ids.add(o1);
ids.add(o2);
userService.batchDel(ids);
}

查看效果,数据被删除

Snipaste_2022-05-23_17-32-48.png

事务概念

事务是数据库操作的最基本单元,逻辑上一组操作,要么都成功,如果有一个操作失败,则操作都失败

事务的特征:

  • 原子性;体现要成功都成功,有一个失败,全部失败
  • 一致性;例如转账操作,转账前和转账后的总金额不变
  • 隔离性;多个事务之间不互相影响
  • 持久性;数据是真正的被保存起来的

搭建事务准备环境

编写程序模拟转账过程

首先创建 t_accout 表,并添加两条数据

Snipaste_2022-05-23_20-21-38.png

添加配置文件 bean.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
<?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"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--开启自动扫描-->
<context:component-scan base-package="com.szx.spring"></context:component-scan>

<!--配置数据连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/spring5" />
<property name="username" value="root" />
<property name="password" value="abc123" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!--配置jdbcTemplate 对象,注入DataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入DataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>

</beans>

然后新建 Account 接口

1
2
3
4
5
6
7
8
9
10
11
package com.szx.spring.dao;

/**
* @author songzx
* @create 2022-05-23 20:07
*/
public interface Account {
void addMoney();

void subMoney();
}

创建 Account 实现类

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
package com.szx.spring.dao.impl;

import com.szx.spring.dao.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

/**
* @author songzx
* @create 2022-05-23 20:08
*/
// 自动创建实例
@Component
public class AccountImpl implements Account {
// 自动注入
@Autowired
public JdbcTemplate jdbcTemplate;

// 增加金额
@Override
public void addMoney() {
String sql = "update t_account set money = money + ? where name = ?";
jdbcTemplate.update(sql,100,"lucy");
}

// 减金额
@Override
public void subMoney() {
String sql = "update t_account set money = money - ? where name = ?";
jdbcTemplate.update(sql,100,"jack");
}
}

编写 service 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.szx.spring.service;

import com.szx.spring.dao.impl.AccountImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* @author songzx
* @create 2022-05-23 20:14
*/
@Component
public class AccountService {
@Autowired
public AccountImpl accountImpl;

// 转账操作
public void accountMoney(){
accountImpl.addMoney();
accountImpl.subMoney();
}
}

编写测试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.szx.spring.test;

import com.szx.spring.dao.impl.AccountImpl;
import com.szx.spring.service.AccountService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* @author songzx
* @create 2022-05-23 20:16
*/
public class TestAccount {
@Test
public void testaccount(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
AccountService accountService = context.getBean("accountService", AccountService.class);
accountService.accountMoney();
}
}

目录结构

Snipaste_2022-05-23_20-23-52.png

查看运行效果

Snipaste_2022-05-23_20-24-49.png

查看表数据

Snipaste_2022-05-23_20-25-24.png

Spring 事务管理介绍

  • 事务添加到 JavaEE 三层结构里面的 Service 层最合适
  • 在 Spring 进行事务管理操作
    • 编程式事务管理
    • 声明式事务管理(推荐使用)
  • 声明式事务管理
    • 基于注解方式(使用)
    • 基于xml配置文件方式

注解式声明事务管理

首先在 xml 中创建事务管理器

1
2
3
4
5
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>

xml 头部添加 tx 命名空间

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

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

Snipaste_2022-05-23_21-27-59.png

开启事务注解

1
2
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

在 Service 类上面添加注解 @Transactional,这个注解可以添加到类上和方法上

  • 如果添加类上则为这个类中的所有方法都添加事务
  • 如果添加到方法上则只为这个方法开启事务

Snipaste_2022-05-23_21-31-18.png

在运行测试类之前先把表中的数据重置为各 1000 元

Snipaste_2022-05-23_21-32-25.png

运行测试方法

Snipaste_2022-05-23_21-33-09.png

再次看表数据,查看是否有异常

Snipaste_2022-05-23_21-34-01.png

事务管理注解的参数管理

主要的参数有如下

Snipaste_2022-05-23_21-54-41.png

  • propagation 传播行为
  • isolation 事务隔离级别
  • timeout 超时时间
  • readOnly 只读
  • rollbackFor 回滚
  • boRollbackFor 不回滚

传播行为 propagation

什么是事务传播行为:多事务方法直接进行调用,这个过程中的事务是如何进行管理的,被称为事务传播

事务传播有七种方式

Snipaste_2022-05-23_22-01-22.png

详解:https://blog.csdn.net/qq_34115899/article/details/115602002

隔离级别 isolation

Snipaste_2022-05-23_22-23-28.png

其中 mysql 中默认使用的是可重复读

超时时间 timeout

  • 事务需要在一定时间内进行提交,如果不提交进行回滚
  • 默认值是-1,表示不超时,设置的时间以秒为单位

只读 readOnly

读:表示查询操作;写:增加、删除、修改

  • readOnly 默认值为 false,表示可以进行增删改查操作
  • 设置 readOnly 为 true 时,改方法只能进行查询操作

回滚 rollbackFor

设置出现那些异常后进行事务回滚

不回滚 boRollbackFor

设置出现那些异常后不进行回滚

配置文件方式声明事务管理

  • xml 文件头信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <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"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    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
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
  • 准备:开启自动扫描,和配置数据库连接池以及配置jdbcTemplate 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!--开启自动扫描-->
    <context:component-scan base-package="com.szx.spring"></context:component-scan>

    <!--配置数据连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/spring5" />
    <property name="username" value="root" />
    <property name="password" value="abc123" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>

    <!--配置jdbcTemplate 对象,注入DataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入DataSource-->
    <property name="dataSource" ref="dataSource"></property>
    </bean>
  • 第一步 创建事务管理器

    1
    2
    3
    4
    5
    <!--创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource" ref="dataSource"></property>
    </bean>
  • 第二步 开启事务通知

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!--2.配置通知-->
    <tx:advice id="txAdvice">
    <!--配置事务参数-->
    <tx:attributes>
    <!--在accountMoney方法上添加事务管理-->
    <tx:method name="accountMoney"/>
    <!--在以account开头的方法上添加事务管理-->
    <tx:method name="account*"/>
    </tx:attributes>
    </tx:advice>
  • 第三步 配置切面和切入点

    1
    2
    3
    4
    5
    6
    7
    <!--3.配置切面和切入点-->
    <aop:config>
    <!--切入点-->
    <aop:pointcut id="tx" expression="execution(* com.szx.spring.service.AccountService.*(..))"/>
    <!--切面-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="tx"/>
    </aop:config>

    至此完成通过xml的方式声明事务管理的操作

Spring5新特性

整合日志功能 log4j2

首先导入 jar 包

  • log4j-api-2.11.2.jar
  • log4j-core-2.11.2.jar
  • log4j-slf4j-impl-2.11.2.jar
  • slf4j-api-1.7.30.jar

新建 log4j2.xml 配置文件,名字是固定的,内容也比较固定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
<!--先定义所有的appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>

然后运行测试方法在控制台中查看日志打印

Snipaste_2022-05-24_15-21-21.png

除了被动打印日志外,也可以手动的输入一段日志

1
2
3
4
5
6
7
public class logs {
public static Logger logger = LoggerFactory.getLogger(logs.class);
public static void main(String[] args) {
logger.info("hello logs");
logger.error("hello logs");
}
}

Snipaste_2022-05-24_15-27-24.png

@Nullable 注解

@Nullable 注解可以用在方法、属性、参数上

用在方法上,表示这个方法的返回值可以为空

1
2
3
4
5
// Nullable 注解放在方法上表示返回值可以为空
@Nullable
public String getName(){
return name;
}

用在属性上表示这个属性可以为空

1
2
3
// 属性可以为空
@Nullable
String name;

用在参数中表示这个参数可以为空

1
2
3
4
// 用在参数上表示这个参数可以为空
public void setName(@Nullable String name1){
this.name = name1;
}

函数式风格创建对象

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testGenericApplicationContext(){
// 1.创建GenericApplicationContext对象
GenericApplicationContext context = new GenericApplicationContext();
// 2.调用context的方法对象注册
context.refresh();
context.registerBean("user1", User.class,()->new User());
// 3.获取在spring注册的对象
User user1 = context.getBean("user1", User.class);
System.out.println(user1);
}

Snipaste_2022-05-24_16-26-28.png

整合JUnit5单元测试框架

导入相关jar包

spring-test-5.2.6.RELEASE.jar

首先整合 Junit4,编写测试类,用到注解有两个

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(“classpath:bean.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
package com.szx.spring.test;

import com.szx.spring.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
* @author songzx
* @create 2022-05-24 17:33
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean.xml")
public class JTest4 {
@Autowired
private AccountService accountService;

@Test
public void test(){
accountService.accountMoney();
}
}

这里的 @Test 注解用的是 JUnit4

Snipaste_2022-05-24_21-23-38.png

整合 Junit5,编写测试类

用到的注解是:

@SpringJUnitConfig(locations = “classpath:bean.xml”)

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


import com.szx.spring.service.AccountService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

/**
* @author songzx
* @create 2022-05-24 21:24
*/
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class JTest5 {
@Autowired
private AccountService accountService;

@Test
public void testAccount(){
accountService.accountMoney();
}
}

这里的 @Test 注解用到的是 JUnit5.7.0

Snipaste_2022-05-24_21-28-41.png

SpringWebflux的使用

等我先去学习SpringMVC和SpringBoot再来补充……