Spring6
Spring6
环境准备
-
开发工具:
IDEA2022.3 -
JDK17JDK17
起步
-
问题
如下代码出现的问题 
-
OCP以上代码违背了
OCP开闭原则 -
什么是
OCPOCP是软件七大开发原则中最基本的一个原则: 开闭原则 -
OCP核心 只要你在扩展系统功能的时侯,没有修改以前写好的代码,那么就是符合
OCP原则 反之,如果在扩展系统功能的时候,如果动了之前修改的程序,那么这个设计就是失败的,违背了
OCP原则
-
-
依赖倒置原则
( DIP原则) -
什么是依赖倒置原则
面向接口编程、面向抽象编程、不要面向具体编程
1
2// 面向具体
(x)
private UserService userService = new UserServiceImpl(); -
依赖倒置原则的目的
降低程序的耦合度,提高扩展力
-
什么叫做符合依赖倒置
上
( Controller: UserService = new xx) 不依赖 下( Service: UserMapper = new xxx),就是符合 -
什么叫做违背依赖倒置
上依赖下,就是违背
只要
下一改动,上就受到牵连
-
-
-
控制反转
1
2
3private UserService userService = new UserServiceImpl();
=>
private UserService userService;-
反转是什么
反转是两件事:
- 第一件事: 我不在程序中采用硬编码的方式来
new对象了。( new对象的权力交出去了) - 第二件事: 我不在程序中采用硬编码的方式来维护对象的关系了
- 第一件事: 我不在程序中采用硬编码的方式来
-
控制反转
控制反转: 是一种编程思想。或者叫做一种新型的设计模式。由于出现的比较新,没有被纳入
GoF23种设计模式范围内。
-
Spring
Spring框架实现了控制反转 IoC这种思想 Spring框架可以帮你 new对象 Spring框架可以帮你维护对象和对象之间的关系
Spring是一个实现可 IoC思想的容器 - 控制反转的实现方式有多种,其中比较重要的叫做:
依赖注入,简称 DI - 控制反转是思想。依赖注入是这种思想的具体实现
- 依赖注入
( DI),包含常见的两种方式set注入 - 构造方法注入
- 依赖注入中
依赖是什么意思?注入是什么意思?- 依赖:
A对象和 B对象的关系 - 注入: 是一种手段,通过这种手段,可以让
A对象和 B对象产生关系。
- 依赖:
- 中文官网地址:
http://spring.p2hp.com
Hello-World
-
实现步骤
-
创建
maven项目 -
添加依赖
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<!--
https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Artifacts
属于里程碑还暂未发布,所以需要添加 仓库地址
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring6-002</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- 配置多个仓库 -->
<repositories>
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project> -
创建
spring的配置文件 1
2
3
4
5
6
7
8
9
10
11
<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: 是这个 bean 的唯一标识
class: 必须填写类的全路径,全限定类名(带包名的类名)
-->
<bean id="userBean" class="org.example.bean.User"/>
</beans> -
测试获取
bean1
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
27package com.example;
import org.example.bean.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FirstSpringTest {
/**
* ApplicationContext: 翻译为: 应用上下文,其实就是 spring 容器
* ApplicationContext 是一个接口
* ApplicationContext 接口下有很多实现类.其中有一个实现类叫做: ClassPathXmlApplicationContext
* ClassPathXmlApplicationContext: 专门从类路径中加载 spring 配置文件的一个 Spring 上下文对象
*
* ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
* 上面这行代码只要执行,就相当于开启了 Spring 容器,解析 spring.xml 文件,并且实例化所有的 bean 对象, 放到 spring 容器中
*/
public void testSayHello() {
// 第一步: 获取 Spring 容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 第二步: 根据 bean 的 id 从 spring 容器中获取这个对象
User user = ac.getBean("userBean", User.class);
System.out.println(user);
}
} -
Spring是怎么实例化对象的 ? 默认情况下
Spring会通过反射机制,调用类的无参构造方法来实例化对象 1
2
3// 实现原理
Class class = Clazz.forName("org.example.bean.User");
Object obj = class.newInstance();1
2
3
4
5public class User {
public User() {
System.out.println("无参构造被调用.................");
}
}无参构造被自动调用 
-
Spring6 启动 Log4j2 日志框架
从
Spring5之后, Spring框架支持继承的日志框架是 Log4j2
-
启用步骤
-
引入
Log4j2的依赖 1
2
3
4
5
6
7
8
9
10
11<!-- Spring6 的日志依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.19.0</version>
</dependency> -
在类路径下提供
log4j2.xml配置文件 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<!-- 文件名固定为: log4j2.xml 文件必须放到类的根路径下 -->
<configuration>
<loggers>
<!--
level指定日志级别,从低到高的优先级:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
-->
<root level="DEBUG">
<appender-ref ref="spring6log"/>
</root>
</loggers>
<appenders>
<!--输出日志信息到控制台-->
<console name="spring6log" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
</console>
</appenders>
</configuration> -
使用
1
2Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
logger.info("我是一条日志消息");
-
Spring 对 IoC 的实现
-
IoC控制反转 -
控制反转是一种思想
-
控制反转是为了降低程序耦合度,提高程序扩展力,达到
OCP原则,达到 DIP原则 -
控制反转,反转的是什么?
-
将对象的创建权力交出去,交给第三方容器负责
1
<bean id="userBean" class="org.example.bean.User"/>
-
将对象和对象之间关系的维护权交出去,交给第三方容器实现
-
-
控制反转这种思想是如何实现呢?
DI: 依赖注入
-
-
依赖注入
依赖注入实现了控制反转的思想
Spring通过依赖注入的方式来完成 Bean管理的 Bean管理说的是: Bean对象的创建,以及 Bean对象中属性的赋值 (或者叫做 Bean对象之间关系的维护) - 依赖注入
- 依赖指的是对象和对象之间的关联关系
- 注入指的是一种数据传递行为,通过注入行为来让对象和对象产生关系
- 依赖注入常见的实现方式包括两种
- 第一种:
set注入 - 第二种:
构造注入
- 第一种:
- 依赖注入
Set 注入
-
dao1
2
3
4
5
6
7
8
9
10
11
12
13package org.example.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert() {
logger.info("mysql 正在保存用户信息..............");
}
} -
serviceImpl1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package org.example.service.impl;
import org.example.dao.UserDao;
import org.example.service.UserService;
public class UserServiceImpl implements UserService {
private UserDao userDao;
// TODO: set 注入,必须提供 set 方法,spring 会自动调用构造方法
public void saveUser() {
userDao.insert();
}
}注意 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package org.example.service.impl;
import org.example.dao.UserDao;
import org.example.service.UserService;
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 提供 set 方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
// set 注入,必须提供 set 方法,spring 会自动调用构造方法
public void saveUser() {
userDao.insert();
}
} -
spring配置 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<!-- application.xml -->
<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">
<!-- dao -->
<bean id="userDaoBean" class="org.example.dao.UserDao"/>
<!-- service -->
<bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl">
<!--
property 标签属性:
name: set 方法的方法名,去掉 set, 然后把剩下的单次首字母变小写, 写到这里
ref: ref 是 references。ref 后面指定的是要注入的 bean 的 id
-->
<property name="userDao" ref="userDaoBean"/>
</bean>
</beans> -
测试
1
2
3
4
5
6
7
8public class UserTest {
public void userTest() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userServiceImpl = ac.getBean("userServiceImplBean", UserServiceImpl.class);
userServiceImpl.saveUser();
}
}
构造注入
-
serviceImpl1
2
3
4
5
6
7
8
9
10
11
12
13public class UserServiceImpl implements UserService {
private UserDao userDao;
// 构造方法
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
public void saveUser() {
userDao.insert();
}
} -
spring配置 1
2
3
4
5
6
7
8
9
10
11
12
13<!-- dao -->
<bean id="userDaoBean" class="org.example.dao.UserDao"/>
<!-- service -->
<bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl">
<!--
构造注入通过标签: constructor-arg
constructor-arg 属性:
index: 指定构造方法的第一个参数,下标是 0, 以此类推
ref: 用来指定注入的 bean 的 id
-->
<constructor-arg index="0" ref="userDaoBean"></constructor-arg>
</bean> -
为什么注入配置在
serviceImpl配置位置说明 
Set 注入专题
-
注入外部
Bean1
2
3
4
5
6
7
<!-- dao -->
<bean id="userDaoBean" class="org.example.dao.UserDao"/>
<!-- service -->
<bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl">
<property name="userDao" ref="userDaoBean"/>
</bean> -
注入内部
Bean1
2
3
4
5
6
7<!-- service -->
<bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl">
<property name="userDao">
<!-- dao -->
<bean id="userDaoBean" class="org.example.dao.UserDao"/>
</property>
</bean> -
注入简单类型
-
User.class1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package org.example.bean;
public class User {
private String username;
private Integer age;
public void setUsername(String username) {
this.username = username;
}
public void setAge(Integer age) {
this.age = age;
}
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
} -
Spring配置 1
2
3
4
5
<bean id="userBean" class="org.example.bean.User">
<property name="username" value="coder-itl"/>
<property name="age" value="18"/>
</bean> -
通过源码分析得知,简单类型包括:
- 基本数据类型
- 基本数据类型对应的包装类
String或其他的 CharSequence子类 Number子类 Date子类, 虽然它是简单类型, 但在使用时不推荐直接以简单类型使用,因为日期书写格式有要求 font> Enum子类 URIURLTemporal子类 LocaleClass- 另外还包括以上简单值类型对应的数组类型。
-
-
级联属性赋值
-
Student1
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
29package org.example.bean;
public class Student {
// 学生姓名
private String stuName;
// 所属班级
private Clazz clazz;
public void setStuName(String stuName) {
this.stuName = stuName;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
public Clazz getClazz() {
return clazz;
}
public String toString() {
return "Student{" +
"stuName='" + stuName + '\'' +
", clazz=" + clazz +
'}';
}
} -
Clazz1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package org.example.bean;
public class Clazz {
// 班级名称
private String clazzName;
public void setClazzName(String clazzName) {
this.clazzName = clazzName;
}
public String toString() {
return "Clazz{" +
"clazzName='" + clazzName + '\'' +
'}';
}
} -
Spring配置 1
2
3
4
5
6
7
8
9
10
11
12<!--
级联属性赋值注意点:
1. 配置的顺序不能颠倒,必须如下顺序
2. clazz 属性必须提供 getter 方法
-->
<bean id="studentBean" class="org.example.bean.Student">
<property name="stuName" value="coder-itl"/>
<property name="clazz" ref="clazzBean"/>
<!-- 级联属性赋值 clazz.clazzName 是通过提供的 get 方法 -->
<property name="clazz.clazzName" value="高三一班"/>
</bean>
<bean id="clazzBean" class="org.example.bean.Clazz"/> -
测试
1
2
3
4
5
6
7
public void stuClazzTest() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Student bean = ac.getBean("studentBean", Student.class);
// Student{stuName='coder-itl', clazz=Clazz{clazzName='高三一班'}}
System.out.println(bean);
}
-
-
注入数组
-
数组元素是简单类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package org.example.bean;
import java.util.Arrays;
public class Hobby {
private String[] hobby;
public void setHobby(String[] hobby) {
this.hobby = hobby;
}
public String toString() {
return "Hobby{" +
"hobby=" + Arrays.toString(hobby) +
'}';
}
}-
Spring配置 1
2
3
4
5
6
7
8<bean id="hobbyBean" class="org.example.bean.Hobby">
<property name="hobby">
<array>
<value>喝酒 </value>
<value>打麻将 </value>
</array>
</property>
</bean>
-
-
非简单类型
-
Hobby1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package org.example.bean;
import java.util.Arrays;
public class Hobby {
private String[] hobby;
private Women[] women;
public void setHobby(String[] hobby) {
this.hobby = hobby;
}
public void setWomen(Women[] women) {
this.women = women;
}
public String toString() {
return "Hobby{" +
"hobby=" + Arrays.toString(hobby) +
", women=" + Arrays.toString(women) +
'}';
}
} -
Women1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package org.example.bean;
public class Women {
private String girlName;
public void setGirlName(String girlName) {
this.girlName = girlName;
}
public String toString() {
return "Women{" +
"girlName='" + girlName + '\'' +
'}';
}
} -
Spring配置 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
<bean id="w1" class="org.example.bean.Women">
<property name="girlName" value="aa"/>
</bean>
<bean id="w2" class="org.example.bean.Women">
<property name="girlName" value="bb"/>
</bean>
<bean id="w3" class="org.example.bean.Women">
<property name="girlName" value="cc"/>
</bean>
<bean id="hobbyBean" class="org.example.bean.Hobby">
<!-- 简单类型 -->
<property name="hobby">
<array>
<value>喝酒 </value>
<value>打麻将 </value>
</array>
</property>
<!-- 非简单类型赋值 -->
<property name="women">
<array>
<ref bean="w1"/>
<ref bean="w2"/>
<ref bean="w3"/>
</array>
</property>
</bean>
-
-
-
List和 Set集合注入 -
Person1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package org.example.bean;
import java.util.List;
import java.util.Set;
public class Person {
private List<String> personName;
private Set<String> address;
public void setPersonName(List<String> personName) {
this.personName = personName;
}
public void setAddress(Set<String> address) {
this.address = address;
}
public String toString() {
return "Person{" +
"personName=" + personName +
", address=" + address +
'}';
}
} -
Spring配置 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<bean id="personBean" class="org.example.bean.Person">
<property name="personName">
<list>
<value>aa</value>
<value>bb</value>
<value>cc</value>
</list>
</property>
<property name="address">
<set>
<value>aa</value>
<value>bb</value>
<value>cc</value>
</set>
</property>
</bean>
-
-
Map和 Properties注入 -
Map-
Person1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package org.example.bean;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Person {
private Map<Integer, String> phones;
public void setPhones(Map<Integer, String> phones) {
this.phones = phones;
}
public String toString() {
return "Person{" +
"phones=" + phones +
'}';
}
} -
Spring配置 1
2
3
4
5
6
7
8
9
10
11<bean id="mapAndPropertiesBean" class="org.example.bean.Person">
<property name="phones">
<map>
<entry key="1" value="110"/>
<entry key="2" value="120"/>
<entry key="3" value="119"/>
</map>
</property>
</bean>
-
-
Properties-
Person1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package org.example.bean;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Person {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
public String toString() {
return "Person{" +
"properties=" + properties +
'}';
}
} -
Spring配置 1
2
3
4
5
6
7
8
9<bean id="mapAndPropertiesBean" class="org.example.bean.Person">
<property name="properties">
<props>
<prop key="Driver">mysql://localhost:3306</prop>
<prop key="username">root</prop>
<prop key="password">root</prop>
</props>
</property>
</bean>
-
-
-
注入
null和空字符串 -
Cat1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package org.example.bean;
public class Cat {
private String catName;
private Integer catAge;
public void setCatName(String catName) {
this.catName = catName;
}
public void setCatAge(Integer catAge) {
this.catAge = catAge;
}
public String toString() {
return "Cat{" +
"catName='" + catName + '\'' +
", catAge=" + catAge +
'}';
}
} -
Spring配置 1
2
3
4<!-- 如果只对一个属性赋值,默认为 null => Cat{catName='null', catAge=1} -->
<bean id="catBean" class="org.example.bean.Cat">
<property name="catAge" value="1"/>
</bean>1
2
3
4
5
<bean id="catBean" class="org.example.bean.Cat">
<property name="catName" value="null"/>
<property name="catAge" value="1"/>
</bean>1
2
3
4
5
6
7<!-- 手动注入: null -->
<bean id="catBean" class="org.example.bean.Cat">
<property name="catName">
<null/>
</property>
<property name="catAge" value="1"/>
</bean>
-
基于 XML 的自动装配
-
自动装配是基于
set注入的 -
根据名称自动装配
-
UserDao1
2
3
4
5
6
7public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert() {
logger.info("mysql 正在保存用户信息..............");
}
} -
ServiceImpl1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void saveUser() {
userDao.insert();
}
} -
配置
1
2
3
<bean id="userDao" class="org.example.dao.UserDao"/>
<bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl" autowire="byName"/>
-
-
根据类型自动装配
1
2
3
<bean class="org.example.dao.UserDao"/>
<bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl" autowire="byType"/>
引入外部的属性配置文件
-
外部属性文件
1
2
3jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=root-
配置使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--
引入外部的 properties 文件
第一步: 引入 context 命名空间
第二步: 使用标签 context:property-placeholder 的 location 属性来指定属性配置文件的路径
-->
<context:property-placeholder location="classpath*:jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="ds" class="org.example.bean.MyDataSource">
<!-- 属性取值: ${key} -->
<property name="driver" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans> -
测试
1
2
3
4
5
6
7
void dataSourceTest() {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-properties.xml");
MyDataSource bean = ac.getBean("ds", MyDataSource.class);
// MyDataSource{driver='com.mysql.cj.jdbc.Driver', username='root', password='root'}
System.out.println(bean);
}
-
Bean 的作用域
-
单例
-
SpringBean1
2
3
4
5public class SpringBean {
public SpringBean() {
System.out.println("SpringBean 的构造方法................");
}
} -
配置
1
<bean id="springBean" class="org.example.bean.SpringBean"/>
-
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SpringScopeTest {
/**
* Spring 默认情况下是如何管理这个 Bean 的
* 默认情况下 Bean 是单例的(单例: singleton)
* 在 Spring 上下文初始化的时候实例化
* 在每一次调用 getBean() 方法的时候,都返回那个单例的对象
*/
void springScopeTest() {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-scope.xml");
SpringBean bean1 = ac.getBean("springBean", SpringBean.class);
System.out.println("bean1 = " + bean1);
SpringBean bean2 = ac.getBean("springBean", SpringBean.class);
System.out.println("bean2 = " + bean2);
}
}
-
-
多例
1
<bean id="springBean" class="org.example.bean.SpringBean" scope="prototype"/>
1
2
3
4
5
6/**
* 当 bean 的 scope 属性设置为 prototype
* bean 是多例的
* Spring 上下文初始化的时候,并不会初始化这些 prototype 的 bean
* 每一次调用 getBean() 方法的时候,实例化该 bean 对象
*/
循环依赖
-
singleton+setter模式 -
输出
1
2Husband{name='张三', wife=
菊花}
Wife{name='菊花', husband=张三} -
Husband1
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
27package org.example.bean;
public class Husband {
private String name;
private Wife wife;
public void setName(String name) {
this.name = name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
public String getName() {
return name;
}
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
} -
Wife1
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
27package org.example.bean;
public class Wife {
private String name;
private Husband husband;
public void setName(String name) {
this.name = name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
public String getName() {
return name;
}
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
} -
配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--
singleton 表示在整个 Spring 容器中是单例的,独一无二的对象
在 singleton + setter 模式下,为什么循环依赖不会出现问题,Spring 是如何应对的?
主要的原因是,在这种模式下 Spring 对 Bean 的管理主要分为清晰的两个阶段
第一个阶段: 在 Spring 容器加载的时候,实例化 Bean, 只要其中任意一个 Bean 实例化之后,马上进行 “曝光 [不等属性赋值就曝光]”
第二个阶段: Bean 在 “曝光”之后,再进行属性的赋值 (调用 set 方法)
核心解决方案是: 实例化对象和对象属性赋值分为两个阶段来完成的
-->
<bean id="husbandBean" class="org.example.bean.Husband" scope="singleton">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="org.example.bean.Wife" scope="singleton">
<property name="name" value="菊花"/>
<property name="husband" ref="husbandBean"/>
</bean>
-
-
prototype+setter模式 1
2
3
4
5
6<!--
在 prototype + setter 模式下的循环依赖,存在问题,会出现异常
BeanCurrentlyInCreationException: 当前 Bean 正处于创建中
注意: 当只有两个 bean 的 scope 都是 prototype 的时候,才会出现异常
当两个中任意一个是 singleton 就不会出现异常
-->
Spring IoC 注解声明 Bean
-
使用步骤
-
第一步:加入
aop的依赖 1
2
3
4
5<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency> -
第二步:在配置文件中添加
context命名空间 1
2
3
4
5
6
7
8
9
10
11
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans> -
第三步:在配置文件中指定扫描的包
1
2<!-- 开启包扫描 -->
<context:component-scan base-package="org.example.bean"/> -
第四步:在
Bean类上使用注解 1
2
3
4// 普通类 value: bean 名称,
如果没有声明, 则默认为类名首字母小写
public class User {
}1
2
3
4// 持久层 => dao/mapper
public interface UserMapper {
}1
2
3
4
5// 服务层
public class UserServiceImpl implements UserService {
}1
2
3
4
5// 控制器
public class UserController {
} -
测试
1
2
3
4
5
6
public void springAnnTest() {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-anno.xml");
User userBean = ac.getBean("userBean", User.class);
System.out.println(userBean);
} -
如果有多个包,可以通过如下解决
-
第一种: 在配置文件中指定多个包,用逗号隔开
1
<context:component-scan base-package="org.example.bean,org.example.service.impl"/>
-
第二种: 指定多个包的父包
1
<context:component-scan base-package="org.example"/>
当前项目包结构 
-
-
选择性实例化 Bean
-
创建
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
42package org.example.bean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
class A {
public A() {
System.out.println("A的无参构造..........");
}
}
class B {
public B() {
System.out.println("B的无参构造..........");
}
}
class C {
public C() {
System.out.println("C的无参构造..........");
}
}
class D {
public D() {
System.out.println("D的无参构造..........");
}
}
class E {
public E() {
System.out.println("E的无参构造..........");
}
} -
配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启包扫描 -->
<!--
第一种解决方案
use-default-filters="false" 表示所有带有声明的 Bean 注解全部失效
-->
<context:component-scan base-package="org.example" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
...
</context:component-scan>
<!-- 只有这个失效 -->
<context:component-scan base-package="org.example">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
...
</context:component-scan>
</beans>
负责注入的注解
-
@Value1
// 简单类型注入值
-
@Autowired1
// 可以对非简单类型注入,单独使用默认是根据类型装配的 【byType】
1
2
3
4
5
6
7// 可以出现的位置
(源码)
public Autowired {
boolean required() default true;
} -
@Qualifier1
2
3
4
5
6
7
8
public Qualifier {
String value() default "";
} -
@Resource-
@Resource和 @Autowired的区别 -
@Resource注解是 JDK扩展包中的,也就是属于 JDK的一部分。所以该注解是标准注解,更加具有通用性。( JSR-250标准中指定的注解类型。 JSR是 Java规范提案) -
@Autowired注解是 Spring框架自己的 -
@Resource注解默认根据名称装配, 未指定 name时,使用属性名作为 name。通过name找不到的话会自动启动通过 byType装配 -
@Resource注解用在属性上, setter方法上 -
@Autowired注解用在属性上、 setter方法上、构造方法上、构造方法参数上 1
2
3
4
5
6
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
-
-
全注解开发
-
创建配置类
1
2
3
4
5
public class SpringConfig {
}
JDBCTemplate
-
添加依赖
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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring6-003</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- Spring6 的日志依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!-- spring jdbc 依赖: 简单封装了 jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.0-M2</version>
</dependency>
</dependencies>
</project> -
数据库表准备
-
创建数据库
1
CREATE DATABASE `spring6`;
-
创建表
1
2
3
4
5CREATE TABLE `tb_user` (
`id` INT NOT NULL AUTO_INCREMENT,
`real_name` VARCHAR ( 20 ) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY ( `id` )
)ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
-
-
实体类
1
2
3
4
5
6
public class User {
private Integer id;
private String realName;
} -
数据源
-
druid -
c3p0 -
自定义数据源实现
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
78package org.example.bean;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.*;
import java.util.logging.Logger;
public class MyDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password;
public Connection getConnection() throws SQLException {
try {
// 注册驱动
Class.forName(driver);
// 获取数据库连接对象
Connection connection = DriverManager.getConnection(url, username, password);
return connection;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
public PrintWriter getLogWriter() throws SQLException {
return null;
}
public void setLogWriter(PrintWriter out) throws SQLException {
}
public void setLoginTimeout(int seconds) throws SQLException {
}
public int getLoginTimeout() throws SQLException {
return 0;
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
-
-
配置类注入数据源
1
2
3
4
5
6
7
8
public class SpringConfig {
public JdbcTemplate jdbcTemplate(MyDataSource myDataSource) {
return new JdbcTemplate(myDataSource);
}
} -
测试
1
2
3
4
5
6
7
public void springAnnTest() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate",JdbcTemplate.class);
// org.springframework.jdbc.core.JdbcTemplate@299266e2
System.out.println(jdbcTemplate);
}1
2
3
4
5
6
7
8
9
10
public void springAnnTest() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate", JdbcTemplate.class);
// insert 语句
String sql = "insert into tb_user(real_name) values(?)";
// 注意在 JdbcTemplate 中,只要是 insert,update,delete 都调用 update 方法
int count = jdbcTemplate.update(sql, "aa");
System.out.println(count);
} -
查询
一个对象1
2
3
4String sql = "select * from tb_user where id=?";
// 查询 id 为 2 的用户
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 2);
System.out.println(user); -
查询多个对象
1
2
3String sql = "select * from tb_user";
List<User> user = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
System.out.println(user); -
查一个值
1
2
3String sql = "select count(*) from tb_user";
int count = jdbcTemplate.queryForObject(sql, int.class);
System.out.println(count); -
批量添加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void springAnnTest() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate", JdbcTemplate.class);
// 批量添加
String sql = "insert into tb_user(id,real_name) values(?,?)";
Object[] obj1 = {null, "Java"};
Object[] obj2 = {null, "JavaWeb"};
Object[] obj3 = {null, "Spring6"};
List<Object[]> list = new ArrayList<>();
Collections.addAll(list, obj1, obj2, obj3);
int[] count = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(count));
}批量插入 
-
批量更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void springAnnTest() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "update tb_user set real_name=? where id=?";
Object[] obj1 = {"spring-java", 4};
Object[] obj2 = {"lisi", 2};
Object[] obj3 = {"javaweb", 5};
List<Object[]> list = new ArrayList<>();
Collections.addAll(list, obj1, obj2, obj3);
int[] count = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(count));
} -
批量删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void springAnnTest() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "delete from tb_user where id=?";
Object[] obj1 = {4};
Object[] obj2 = {5};
Object[] obj3 = {6};
List<Object[]> list = new ArrayList<>();
Collections.addAll(list, obj1, obj2, obj3);
int[] count = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(count));
} -
回调函数:可以书写
JDBC代码
Druid 数据源
-
添加依赖
1
2
3
4
5<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency> -
配置类
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
35package org.example.config;
import com.alibaba.druid.pool.DruidDataSource;
import jakarta.annotation.Resource;
import org.example.bean.MyDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.time.Duration;
public class SpringConfig {
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
// mybatis 的化需要配置 sqlSessionFactoryBean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}1
2
3
4
5
6
7
8
9
10
11
12
public DataSource dataSource(
String driver
) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
GoF-代理模式
-
代理模式属于结构型设计模式
-
代理模式的作用
为其他对象提供一致代理以控制这个对象的访问。在某些情况下,一个客户端不想或者不能直接引用一个对象,此时可以通过一个称之为
代理的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户端不应该看到的内容和服务或者添加客户需要的额外服务。通过引入一个新的对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式机制。 -
代理模式中的角色
- 代理类
- 目标类
- 代理类和目标类的公共接口: 客户端在使用代理对象就是在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口
-
代理模式的形式
-
静态代理
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
36package org.example.service.impl;
import org.example.service.OrderService;
public class OrderServiceImpl implements OrderService {
public void generate() {
try {
Thread.sleep(100);
System.out.println("订单已生成.............");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public void modify() {
try {
Thread.sleep(200);
System.out.println("订单已修改.............");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public void detail() {
try {
Thread.sleep(300);
System.out.println("订单详情.............");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}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// 在上述业务的基础上添加需求: 统计耗时
package org.example.client;
import org.example.service.impl.OrderServiceImpl;
public class Sub extends OrderServiceImpl {
public void generate() {
Long start = System.currentTimeMillis();
super.generate();
Long end = System.currentTimeMillis();
System.out.println("订单生成耗时: " + (end - start));
}
public void modify() {
Long start = System.currentTimeMillis();
super.modify();
Long end = System.currentTimeMillis();
System.out.println("订单修改耗时: " + (end - start));
}
public void detail() {
Long start = System.currentTimeMillis();
super.detail();
Long end = System.currentTimeMillis();
System.out.println("查看订单详情耗时: " + (end - start));
}
}
// 虽然实现功能,但是冗余代码过多需求实现 
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// 代理实现
package org.example.client;
import org.example.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 代理对象: 代理对象和目标对象要具有相同的行为,那么就要实现同一个或者同一些接口
*/
public class StaticProxy implements OrderService {
private static final Logger logger = LoggerFactory.getLogger(StaticProxy.class);
/**
* 将目标对象作为代理对象的一个属性。这种关系叫做关联关系。比继承关系的耦合度低。
* 代理对象中还有目标对象的引用。关联关系 has a
* 注意: 这里一定要是一个公共接口的类型。因为公共接口的耦合度低。
* private OrderService target = null;
*/
private OrderService target;
// 创建代理对象的时候,传一个目标给代理对象
public StaticProxy(OrderService target) {
this.target = target;
}
public void generate() {
logger.error("StaticProxy generate.........................");
target.generate();
}
public void modify() {
logger.error("StaticProxy modify.........................");
target.modify();
}
public void detail() {
logger.error("StaticProxy detail.........................");
target.detail();
}
}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// 客户端
package org.example.client;
import org.example.service.OrderService;
import org.example.service.impl.OrderServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Client {
private static final Logger logger = LoggerFactory.getLogger(Client.class);
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
// 1. 项目创建后的运行状态测试
orderService.generate();
orderService.modify();
orderService.detail();
logger.info("....................................................\n\n");
// 2. 添加需求: 统计任务的耗时
OrderService sub = new Sub();
sub.generate();
sub.modify();
sub.detail();
logger.info("....................................................\n\n");
// 3. 静态代理实现
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
StaticProxy staticProxy = new StaticProxy(target);
// 调用代理对象的代理方法
staticProxy.generate();
staticProxy.detail();
staticProxy.modify();
}
} -
动态代理
在程序运行阶段,在内存中动态代理生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题
-
基于
JDK的动态代理实现 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
36package org.example.service.impl;
import org.example.service.OrderService;
public class OrderServiceImpl implements OrderService {
public void generate() {
try {
Thread.sleep(100);
System.out.println("订单已生成.............");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public void modify() {
try {
Thread.sleep(200);
System.out.println("订单已修改.............");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public void detail() {
try {
Thread.sleep(300);
System.out.println("订单详情.............");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}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
40package org.example.client;
import org.springframework.validation.ObjectError;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 1. 为什么强行要求你必须实现 InvocationHandler 接口?
* 因为一个类实现接口就必须实现接口中的方法
* 而 InvocationHandler 的方法 invoke 就是必须实现的方法。
* 因为 JDK 在底层调用 invoke() 方法的程序已经提前写好了
* 注意: invoke 方法不是我们程序负责调用的,而是 JDK 负责调用的
* 2. invoke 方法被调用的时机
* 当代理对象调用代理方法的时候,注册在 InvocationHandler 调用处理器当中的 invoke 方法将会被调用
*/
public class TimerInvocationHandler implements InvocationHandler {
private Object target;
public TimerInvocationHandler(Object target) {
this.target = target;
}
/**
* invoke 方法的三个参数:
* invoke 方式是 JDK 负责调用的,所以 JDK 调用方法的时候会自动给我们传过来这三个参数
* 第一个参数: Object proxy 代理对象的引用。这个参数使用比较少
* 第二个参数: Method method 目标对象上的目标方法(要执行的目标方法就是它)
* 第三个参数: Object[] args 目标方法上的实参
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("\n\ninvoke........................before");
Object returnValue = method.invoke(target, args);
System.out.println("invoke........................after\n\n");
// 注意这个 invoke 方法的返回值,如果代理对象调用代理方法之后,需要返回结果的化,invoke 方法必须将目标对象的目标方法执行结果继续返回
return returnValue;
}
}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
54package org.example.client;
import org.example.service.OrderService;
import org.example.service.impl.OrderServiceImpl;
import org.junit.jupiter.api.Order;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
private static final Logger logger = LoggerFactory.getLogger(Client.class);
public static void main(String[] args) {
// 创建目标
OrderService target = new OrderServiceImpl();
// 创建代理对象
/**
* 1. newProxyInstance: 翻译为 新建代理对象,也就是说,通过调用这个方法可以创建代理对象
* 本质上,这个 Proxy.newProxyInstance() 方法的执行,做了两件事:
* 1. 在内存中动态的生成了一个代理类的字节码 class
* 2. new 对象了。通过内存中生成的代理类这个代码。实例化了代理对象
*
* 2. 关于 newProxyInstance() 方法的三个重要参数
* 第一个参数: ClassLoader loader:
* 类加载器,作用是: 在内存中商城字节码也就是 class 文件,要执行也得先加载到内存中。加载类就需要类加载器。所以这里就需要指定类加载器
* 并且 JDK 要求,目标类的类加载器必须和代理类的加载器使用同一个
* 第二个参数: Class<?>[] insterfaces
* 代理类和目标类要实现用一个接口或者同一些接口
* 在内存中生成代理类的时候,这个代理类是需要你告诉他实现那些接口的
* 第三个参数: InvocationHandler h
* InvocationHandler 翻译为: 调用处理器。是一个接口
* 在调用处理器接口中编写的就是: 增强代码
* 因为具体要增强什么代码,JDK 动态代理计数他是才不到的。
* 他是一个接口,就需要有实现类。
*/
OrderService proxyInstance = (OrderService)Proxy.newProxyInstance(
// 类加载器
target.getClass().getClassLoader(),
// 公共接口
target.getClass().getInterfaces(),
// 增强需求
new TimerInvocationHandler(target)
);
// 调用代理对象的代理方法
proxyInstance.detail();
proxyInstance.generate();
proxyInstance.modify();
}
} -
基于
CGLIB的动态代理实现 CGLIB既可以做代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用 final修饰 -
实现步骤
-
添加依赖
1
2
3
4
5<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
-
-
-
-
面向切面编程 AOP
-
说明
IoC使软件组件松耦合。 AOP让你能够捕捉系统中建厂使用的功能,把他转化成组件。 AOP(Aspect Oroented Programming): 面向切面编程,面向方面编程( AOP是一种编程技术) AOP是对 OOP的补充延申 AOP底层是使用 动态代理来完成的Spring的 AOP使用的动态代理是: JDK+CGLIB。动态代理技术 Spring在这两种动态代理中灵活切换,如果代理是接口,会默认使用 JDK动态代理。如果要代理某个类,这个类没有实现接口,就会切换使用 CGLIB。 -
AOP总结: 将于核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为
AOP优点:
- 代码复用性强
- 代码易维护
- 使开发者更关注业务逻辑
-
七大术语
- 连接点:在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置
- 切点: 在程序执行流程中,真正织入切面的方法
- 通知
- 通知又叫增强,就是具体你要织入的代码
(增强功能的代码) - 通知包括
- 前置通知:
@Before目标方法执行之前的通知 - 后置通知:
@AfterReturning目标方法执行之后的通知 - 环绕通知:
@Around目标方法之前添加的通知,同时目标方法执行之后添加的通知 - 异常通知:
@AfterThrowing发生在异常之后执行的通知 - 最终通知:
@After放在 finally语句块中的通知
- 前置通知:
- 通知又叫增强,就是具体你要织入的代码
- 切面: 切点 + 通知就是切面
-
切入点表达式
切点表达式用来定义通知
( Advice)往那些方法上切入 -
语法
1
2
3
4execution([访问控制权限修饰符] 返回值类型 [全限定类名]
方法名 (形式参数列表) [异常])
// []: 可选项(可以写也可以不写)
// (): 必填项(必须出现)
-
-
Spring+AspectJ+Annotation(注解)的使用实现步骤 -
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!-- apo=>(spring-context 中) -->
<!-- spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.0-M2</version>
</dependency> -
spring的配置文件中添加命名空间 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"
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">
</beans>修改原理 
-
编写目标类并纳入
spring容器管理 1
2
3
4
5
6
7
8
9
10
11
12
13package org.example.service;
import org.springframework.stereotype.Service;
// 目标类
public class UserService {
// 目标方法
public void login() {
System.out.println("正在验证身份信息............");
}
} -
创建切面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package org.example.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
// 切面 = 通知 + 切点
public class LogAspect {
/**
* execution(* org.example.service.UserService.*(..))
* 权限修饰符: *
*
*/
public void buildUp() {
System.out.println("我是一个通知,我是一段增强代码...............");
}
} -
spring配置 1
2
3
4
5<!-- 开启包扫描 -->
<context:component-scan base-package="org.example.service,org.example.aspect"/>
<!-- 开启 aspectj 的自动代理 -->
<aop:aspectj-autoproxy/> -
测试
1
2
3
4
5
6
public void testAspect() {
ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
UserService userService = ac.getBean("userService", UserService.class);
userService.login();
}输出 
-
-
先后顺序
先后顺序 
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
34package org.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
// 切面 = 通知 + 切点
public class LogAspect {
public void buildUp() {
System.out.println("\t前置通知: 我是一个通知, 我是一段增强代码...............");
}
public void afterTest() {
System.out.println("\t后置通知..............");
}
// * org.example..*(..))
public void aroundTest(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("前环绕.........");
// 执行目标
joinPoint.proceed();
System.out.println("后环绕.........");
}
}1
2// 多个切面的执行顺序通过 Order 控制,数字越小,优先级越高。
-
通用切点表达式
1
2
3
4
5// 本类和其他类
(需要添加全限定) 都可以使用
// 定义通用切点
public void pointcut(){}1
2// 使用通用切点 通用切点方法的调用
AOP-基于全注解开发
-
创建配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package org.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* <context:component-scan base-package="org.example"/>
* <aop:aspectj-autoproxy/>
*/
// => application.xml
// <context:component-scan base-package="org.example"/>
// <aop:aspectj-autoproxy/>
public class SpringConfig {
} -
测试
1
2
3
4
5
6
7
public void testAspect() {
// 传入 配置类的字节码
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = ac.getBean("userService", UserService.class);
userService.login();
}
AOP-编程事务的解决方案
-
业务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package org.example;
import org.springframework.stereotype.Service;
public class AccountService {
// 转账
public void transfer() {
System.out.println("\t\t转账执行中...................................");
}
// 取款
public void withDraw() {
System.out.println("\t\t正在取款, 请稍后............................");
}
} -
切面
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
26package org.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
public class TransAspect {
// 环绕通知
public void aroundAspect(ProceedingJoinPoint joinPoint) {
try {
System.out.println("开启事务....................");
// 执行目标方法
joinPoint.proceed();
System.out.println("事务提交...................");
} catch (Throwable e) {
System.out.println("事务回滚.................");
throw new RuntimeException(e);
}
}
} -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import org.example.AccountService;
import org.example.aspect.TransAspect;
import org.example.config.SpringConfig;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestTransAspect {
public void testTransAspect() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService =ac.getBean("accountService", AccountService.class);
// 目标方法
accountService.transfer();
accountService.withDraw();
}
}
AOP-安全日志
-
需求
项目开发结束了,已经上线了。运行正常。客户提出了新需求: 凡是在系统中进行修改操作的、新增操作的,都要把这个人记录下来。因为这属于危险行为。
记录操作事件 
-
业务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package org.example;
import org.springframework.stereotype.Service;
public class UserService {
public void saveUser() {
System.out.println("新增用户.....................");
}
public void deleteUser() {
System.out.println("删除用户.....................");
}
public void updateUser() {
System.out.println("修改用户.....................");
}
public void showUser() {
System.out.println("查询用户.....................");
}
} -
配置
-
切面
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
35package org.example.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SecAspect {
public void savePointcut() {
}
public void updatePointcut() {
}
public void deletePointcut() {
}
public void beforeAdvice(JoinPoint joinPoint) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String nowTime = sdf.format(new Date());
System.out.println(nowTime + " coder-itl: " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
}
} -
测试
1
2
3
4
5
6
7
8
9// 测试安全日志
public void testSecurityAspect() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = ac.getBean("userService", UserService.class);
userService.deleteUser();
userService.saveUser();
userService.updateUser();
}
事务
使用步骤
- 事务的四个处理过程
- 开启事务
- 执行核心业务代码
- 提交事务
(如果核心业务处理过程中没有出现异常) - 回滚事务
(如果核心业务处理过程中出现异常)
事务案例演示
-
数据库表准备
-
数据库
spring6 -
表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE `tb_act`
(
`actno` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '账号',
`balance` double DEFAULT NULL COMMENT '余额',
PRIMARY KEY (`actno`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci
-- 测试数据
insert into tb_act values('act-001',50000);
insert into tb_act values('act-002',0);
-
-
三层
-
domain1
2
3
4
5
6
7
8
9
10package org.example.domain;
import lombok.Data;
public class Account {
private String actno;
private Double balance;
} -
dao1
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
29package org.example.dao.impl;
import jakarta.annotation.Resource;
import jakarta.annotation.Resources;
import org.example.dao.AccountDao;
import org.example.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public Account selectByActno(String acton) {
String sql = "select * from tb_act where actno=?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), acton);
}
public int update(Account act) {
String sql = "update tb_act set balance=? where actno=?";
return jdbcTemplate.update(sql, act.getBalance(), act.getActno());
}
} -
service1
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
55package org.example.service.impl;
import jakarta.annotation.Resource;
import org.example.dao.AccountDao;
import org.example.domain.Account;
import org.example.service.AccountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 转账业务
* 事务就在此处
*/
public class AccountServiceImpl implements AccountService {
private static final Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class);
private AccountDao accountDao;
public void transfer(String fromActno, String toActno, double money) {
// 查询转出账户的余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new RuntimeException("余额不足............");
}
// 余额充足
Account toAct = accountDao.selectByActno(toActno);
// 将内存中两个对象的余额先修改
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
// 数据库更新
int count = accountDao.update(fromAct);
// TODO: 模拟异常
logger.error("error: ...............................");
int num = 10 / 0;
logger.error("error: ................................");
// count = count + accountDao.update(toAct) => 成功一条为 1,两条成功记录就是 2
count += accountDao.update(toAct);
if (count != 2) {
throw new RuntimeException("转账失败................");
}
}
} -
配置
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// 全注解 Java 类配置
package org.example.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
public class SpringConfig {
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}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 配置 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="org.example"/>
<!-- 配置数据源 -->
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入数据源 -->
<property name="dataSource" ref="ds"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"/>
</bean>
<!-- 开启事务注解驱动器 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans> -
测试转账
1
2
3
4
5
6
7
8// 配置采用了 XML
public void testTransfer() {
ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
AccountServiceImpl accountServiceImpl = ac.getBean("accountServiceImpl", AccountServiceImpl.class);
accountServiceImpl.transfer("act-001", "act-002", 800);
}
-
-
模拟异常
添加异常代码 运行测试 

出现右边的结果是非常不理想的
-
Spring对事物的支持 -
Spring实现事务的两种方式 - 编程式事务: 通过编写代码的方式来实现事务的管理
- 声明式事务
- 基于注解方式
- 基于
XML配置方式
-
Spring事务管理 APISpring对事务的管理底层实现方式是基于 AOP实现的。采用 AOP的方式进行了封装。所以 Spring专门针对事务开发了一套 APIAPI:如果在 spring6中使用jdbcTemplate,就要使用DataSourceTransactionManager来管理事务 
-
声明式事务之注解实现方式
-
第一步:在
spring配置文件中配置事务管理器 1
2
3
4<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"/>
</bean> -
第二步:在
spring配置文件中引入 tx命名空间 1
2
3xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> -
第三步:在
spring配置文件中配置 “ 事务注解驱动器”,开始注解的方式控制事务1
2<!-- 开启事务注解驱动器 -->
<tx:annotation-driven transaction-manager="txManager"/> -
第四步:在
service类上或方法上添加 @Transactional注解
事务的传播行为
-
事务中的的重点属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14public Transactional {
// 传播行为
Propagation propagation() default Propagation.REQUIRED;
// 隔离级别
Isolation isolation() default Isolation.DEFAULT;
// 事务超时
int timeout() default -1;
// 只读事务
boolean readOnly() default false;
// 设置出现那些异常回滚事务
Class<? extends Throwable>[] rollbackFor() default {};
// 设置出现那些异常不回滚事务
Class<? extends Throwable>[] noRollbackFor() default {};
}-
传播行为是什么
在
service类中有 a方法和 b方法,两个方法各自都有事务,当 a方法在执行过程中调用了 b方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务? 这就是事务的传播行为。
-
-
一共七种传播行为
(* 是重要标记) - REQUIRED*: 支持当前事务,如果不存在就新建一个
(`默认`)【** 没有就新建,有就加入 **】 -
SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行【**
有就加入,没有就不管了 **】 -
MANDATORY: 必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【**
有就加入,没有就抛出异常 **】 -
REQUIRES_NEW*: 开启一个新事物,如果一个事务已经存在,则将这个存在的事务挂起【**
不管有没有,直接开启一个新事务,开启的新事物和之前的事务不存在嵌套关系,之前事务被挂起 **】 -
NOT_SUPPORTED: 以非事务方式运行,如果有事务存在
- NEVER: 以非事务方式运行,如果有事务存在
- NESTED: 如果当前正有一个事务正在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像 REQUIRED
一样 【** 有事务的话,就在这个事务里在嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和 REQUIRED 一样 **】
- REQUIRED*: 支持当前事务,如果不存在就新建一个
-
事务的隔离级别
-
事务的隔离级别类似于教室
A和教室 B之间的那道墙,隔离级别越高表示墙体越厚。隔音效果越好 -
数据库中数据存在的三大问题
- 脏读: 读取到没有提交到数据库的数据,叫做脏读
- 不可重复读: 在同一个事务当中,第一次和第二次读取的数据不一样
- 幻读: 读取到的数据是假的
-
事务的隔离级别包括四个隔离级别
-
读未提交:
READ_UNCOMMITED这种隔离界别,存在脏读问题,所谓的脏读表示能够读取到其他事务未提交的数据
-
读提交:
read_committed解决了脏读问题,其他事务提交之后才能读到,但存在不可重复度问题
-
可重复读:
repeatable_read解决了不可重复读,可以到可重读效果,只要当前事务不结束,读取到的数据一致都是一样的。但存在幻读问题
-
序列化:
Serializable解决了幻读问题,事务排队执行。不支持并发。
1
2
3
4
5
6
7
8
public enum Isolation {
DEFAULT(-1), // 默认的隔离级别
READ_UNCOMMITTED(1),
READ_COMMITTED(2), // ORACLE 的隔离级别
REPEATABLE_READ(4), // MYSQL 的隔离级别
SERIALIZABLE(8);
}
-
-
-
事务的超时
1
timeout = 10代表设置事务的超时时间为 10s表示超过
10s如果该事务中所有的 DML语句还没有执行完毕的话,最终结果会选择回滚 默认值
-1,表示没有时间限制-
细节注意
事务的超时时间指的是那段时间?
在当前事务当中,最后一条
DML语句执行之前的语句。如果最后一条 DML语句后面还有很多业务逻辑,这些业务代码执行的时间不计入超时时间。 1
2
3
4
5
6
7
8
9
10
11
12// 以下代码不会计入超时时间内
// 设置事务超时时间为10 秒。
public void save(Account act) {
accountDao.insert(act);
// 睡眠一会
try {
Thread.sleep(1000 * 15);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 是因为后续无相关 DML 语句执行
}1
2
3
4
5
6
7
8
9
10
11// 从上到下的执行,到 DML 语句执行结束都计入超时时间
// 设置事务超时时间为10 秒。
public void save(Account act) {
// 睡眠一会
try {
Thread.sleep(1000 * 15);
} catch (InterruptedException e) {
e.printStackTrace();
}
accountDao.insert(act);
}当然,如果想让整个方法的所有代码都计入超时时间的话,可以在方法最后一行添加一行无关紧要的
DML 语句。
-
-
只读事务
1
将当前事务设置为只读事务,在该事务执行过程中 只允许select语句执行, delete、insert、update均不可执行 该特性的作用是: 启动 spring 的优化策略。提高
select语句的执行效率 如果该事务中确实没有增删改操作,建议设置为只读事务
-
设置遇到那些异常回滚
1
2
// 表示只有发生 RuntimeException 异常或该异常的子类异常才回滚。 -
设置遇到那些异常不回滚
1
2
// 表示发生NullPointerException 或该异常的子类异常不回滚,其他异常则回滚。
事务的全注解开发
-
根据
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
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="org.example"/>
<!-- 配置数据源 -->
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入数据源 -->
<property name="dataSource" ref="ds"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"/>
</bean>
<!-- 开启事务注解驱动器 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans> -
对应的配置类
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// xml 配置到对应的 java 类的过程中,我们只需要明白返回值与参数的作用,就可以轻松实现转换
package org.example.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
// 1. application.xml
// 2. 开启包扫描
// 6. 开启事务注解驱动器
public class SpringConfig {
// 3. 配置数据源
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
// 4. jdbcTemplate 配置数据源
public JdbcTemplate jdbcTemplate(DruidDataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// 5. 配置事务管理器
public DataSourceTransactionManager dataSourceTransactionManager(DruidDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}xml与 config 类转换理解 
-
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
public void testTransfer() {
// ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
// 传入配置类的字节码等同于传入配置文件
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService = ac.getBean("accountService", AccountService.class);
try {
accountService.transfer("act-001", "act-002", 800);
System.out.println("转账成功");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
声明式事务之 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
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="org.example"/>
<!-- 配置数据源 -->
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入数据源 -->
<property name="dataSource" ref="ds"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 配置通知相关的属性 -->
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<!-- 可以采用通配符方式配置 -->
<tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 切点 -->
<aop:pointcut id="txPointcut" expression="execution(* org.example..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
Spring6 整合 JUnit5
Spring 对 JUnit4 的支持
-
添加依赖
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
<repositories>
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!-- junit4 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies> -
创建
Bean1
2
3
4
5
6
7
8
9
10
11
12
13package org.example.bean;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
public class User {
private String name;
} -
创建配置
1
<context:component-scan base-package="org.example"/>
-
创建测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 由于 spring 对 junit 的支持,可以简化如下代码
package org.example.test;
import org.example.bean.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class UserTest {
public void testUser() {
ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
User user = ac.getBean("user", User.class);
System.out.println(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// 简化后的测试
package org.example;
import org.example.bean.User;
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;
public class UserTest {
private User user;
public void testUser() {
System.out.println(user.getName());
}
}
Spring 对 JUnit5 的支持
-
添加依赖
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<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.0-M2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.0-M1</version>
<scope>test</scope>
</dependency>
</dependencies> -
在
JUnit的基础上,变动测试 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package org.example;
import org.example.bean.User;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
public class UserTest {
private User user;
// JUnit5 的包路径: import org.junit.jupiter.api.Test;
public void testUser() {
System.out.println(user.getName());
}
} -
配置类的加载
-
配置类
1
2
3
4
5
6
7
8
9
10
11package org.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
public class SpringConfig {
} -
简化
1
2
3
4
5
6
7
8
9
10
11
12
public class UserTest {
private User user;
// JUnit5 的包路径: import org.junit.jupiter.api.Test;
public void testUser() {
System.out.println(user.getName());
}
}
-
集成 Mybatis
-
实现步骤
-
第一步: 准备数据库表:
tb_act(账户表)1
2
3
4
5
6
7create database spring6;
create table tb_act
(
actno varchar(255) not null comment '账号' primary key,
balance double null comment '余额'
); -
第二步:
IDEA中创建一个模块,并引入依赖 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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springmybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<!-- 配置多个仓库 -->
<repositories>
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!-- start: 做 spring 事务 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!-- end: 做 spring 事务 -->
<!-- mysql 连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- mybatis 依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!-- spring 和 mybatis 整合 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!-- 简化实体类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>
<build>
<!-- 打包后的名称 -->
<finalName>coder-itl</finalName>
<!-- 资源文件扫描的配置 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
</project> -
第三步: 基于三层架构实现,
创建相关包结构 mapperservice,implentity
-
第四步: 编写
entity,实现JavaBean1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.example.entity;
import lombok.Data;
import java.io.Serializable;
/**
* @TableName tb_act
*/
public class Act implements Serializable {
private String actno;
private Double balance;
} -
第五步: 编写
mapper接口 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package com.example.mapper;
import com.example.entity.Act;
import java.util.List;
public interface ActMapper {
List<Act> findAll();
Act findByActNo(String actno);
// 添加账户信息
int createAct(Act act);
// 删除账户信息
int removeByActNo(String actno);
int updateByAct(Act act);
} -
第六步: 编写
mapper配置文件,在配置文件中配置命名空间,以及每个方法对应的 sql1
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
<mapper namespace="com.example.mapper.ActMapper">
<!-- List<Act> findAll(); -->
<select id="findAll">
select *
from tb_act;
</select>
<!-- Act findByActNo(String actno); -->
<select id="findByActNo" parameterType="string" resultType="com.example.entity.Act">
select *
from tb_act
where actno = #{actno}
</select>
<!-- int createAct(Act act); -->
<insert id="createAct" parameterType="com.example.entity.Act">
insert into tb_act
values (#{actno}, #{balacne});
</insert>
<!-- int removeByActNo(String actno); -->
<delete id="removeByActNo" parameterType="string">
delete
from tb_act
where actno = #{actno}
</delete>
<!-- int updateByAct(Act act); -->
<update id="updateByAct" parameterType="com.example.entity.Act">
update tb_act
set balance=#{balance}
where actno = #{actno}
</update>
</mapper> -
第七步: 编写
service和 service接口实现类 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 接口
package com.example.service;
import com.example.entity.Act;
import java.util.List;
public interface ActService {
int save(Act act);
int deletebyActNo(String actno);
int modify(Act act);
Act getByActNo(String actno);
List<Act> getAll();
void transfer(String fromACtno, String toActno, double monry);
}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
61package com.example.service.impl;
import com.example.entity.Act;
import com.example.mapper.ActMapper;
import com.example.service.ActService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public class ActServiceImpl implements ActService {
private ActMapper actMapper;
public int save(Act act) {
return actMapper.createAct(act);
}
public int deletebyActNo(String actno) {
return actMapper.removeByActNo(actno);
}
public int modify(Act act) {
return actMapper.updateByAct(act);
}
public Act getByActNo(String actno) {
return actMapper.findByActNo(actno);
}
public List<Act> getAll() {
return actMapper.findAll();
}
public void transfer(String fromACtNo, String toActNo, double money) {
Act fromAct = actMapper.findByActNo(fromACtNo);
if (fromAct.getBalance() < money) {
throw new RuntimeException("余额不足");
}
Act toAct = actMapper.findByActNo(toActNo);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
int count = actMapper.updateByAct(fromAct);
count += actMapper.updateByAct(toAct);
if (count != 2) {
throw new RuntimeException("转账失败");
}
}
} -
第八步: 编写
jdbc.properties配置文件,配置数据库连接池相关的信息 1
2
3
4
5# 8.0 resource/jdbc.properties
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring6?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8&autoReconnect=true
jdbc.username=root
jdbc.password=root -
第九步: 编写
mybatis-config.xml配置文件 1
2
3
4
5
6
7
8
9
10
<configuration>
<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
</configuration> -
第十步: 编写
spring.xml配置文件 -
组件扫描
-
引入外部属性文件
-
数据源
-
SqlSessionFactroyBean配置 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
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns="http://www.springframework.org/schema/beans"
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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 1. 组件扫描 -->
<context:component-scan base-package="com.example"/>
<!-- 2. 引入外部属性文件 -->
<context:property-placeholder location="classpath*:jdbc.properties"/>
<!-- 3. 配置数据源 -->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置数据源 -->
<property name="dataSource" ref="myDataSource"/>
<!--
注入 mybatis 核心配置文件路径
configLocation属性是 Resource 类型, 作用是: 读取配置文件
它的赋值,使用 value, 指定文件的路径, 使用 classpath: 表示文件的位置
-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!--
创建 dao 对象,使用 SqlSession 的 getMapper(XXXDao/XXXMapper.class)
MapperScannerConfigurer: 在内部调用 getMapper()生成每个 dao 接口的代理对象
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定 SqlSessionFactory 对象的 id -->
<property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
<!--
指定包名,包名是 dao/mapper 接口所在的包名
MapperScannerConfigurer 会扫描这个包中所有的接口,把每个接口都执行一次 getMapper() 方法, 得到每个接口的 dao 对象
创建好的 dao 对象放入到 spring 的容器中
-->
<property name="basePackage" value="com.example.mapper"/>
</bean>
<!-- 6. 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
<!-- 启用事务管理器 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans> -
转账测试
1
2
3
4
5
6
7
8
9
10
11
12public class TestSM {
public void testSM() {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
String[] definitionNames = ac.getBeanDefinitionNames();
for (String definitionName : definitionNames) {
System.out.println(definitionName);
}
ActService actService = ac.getBean("actService", ActService.class);
actService.transfer("act-001", "act-002", 800);
}
}事务控制测试 
-
-
Spring 中的八大设计模式
-
简单工厂模式
BeanFactory的 getBean()方法,通过唯一标识来获取 Bean对象。是典型的简单工厂模式 ( 静态工厂模式)
-
工厂方法模式
FactoryBean是典型的工厂方法模式。在配置文件中通过 factory-method属性来指定工厂方法,该方法是一个实例方法
-
单例模式
Spring用的是双重判断加锁的单例模式
-
代理模式
Spring的 AOP 就是使用了动态代理实现的
-
装饰器模式
-
JavaSE中的 IO流是非常典型的装饰器模式 Spring中配置 DataSource的时候,这些 dataSource可能是各种不同类型的,比如不同的数据库,也可能是不同的数据源。这时,能否在尽可能少修改原有类代码的情况下,做到动态切换不同的数据源?此时就可以用到装饰器模式。 Spring根据每次请求的不同,将 dataSource属性设置成不同的数据源,以达到切换数据源的目的。 Spring中类名中带有: Decorator和 Wrapper单次的类,都是装饰器模式。
-
-
观察者模式
定义对象间的一对多的关系,当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并自动更新。
Spring中观察者模式一般用在 listener的实现 Spring中的事件编程模型就是观察者模式的实现。在 Spring中定义了一个 ApplicationListener接口。用来监听 Application的事件, Application其实就是 ApplicationContext,ApplicationContext内置了几个事件,其中比较容易理解的是: ContextRefreshedEvent,ContextStartedEvent,ContextStoppedEvent,ContextCloseEvent -
策略模式
策略模式就是行为性模式,调用不同的方法,适应行为的变化,强调父类的调用子类的特性。
getHandler是 HandlerMapping接口中的唯一方法,用于根据请求封装找到匹配的处理器 比如我们自己写的
AccountDao接口,然后这个接口下有不同的实现类: AccountDaoForMySQL,AccountDaoOracle.对于 Service来说不需要关心底层具体的实现,只需要面向 AccountDao接口调用,底层可以灵活切换实现,这就是策略模式 -
模板方法模式
Spring中的 jdbcTemplate类九四一个模板类。他就是一个模板方法设计模式的体现。在模板类的模板方法 execute中编写核心算法,具体的实现步骤在子类中完成。