Spring6

概述

Spring是什么

Spring 是一款主流的 Java EE 轻量级开源框架 ,Spring 由“Spring 之父”Rod Johnson 提出并创立,其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring 框架除了自己提供功能外,还提供整合其他技术和框架的能力。

Spring 自诞生以来备受青睐,一直被广大开发人员作为 Java 企业级应用程序开发的首选。时至今日,Spring 俨然成为了 Java EE 代名词,成为了构建 Java EE 应用的事实标准。

自 2004 年 4 月,Spring 1.0 版本正式发布以来,Spring 已经步入到了第 6 个大版本,也就是 Spring 6。本课程采用Spring当前最新发布的正式版本6.0.2

Spring的广义和狭义

在不同的语境中,Spring 所代表的含义是不同的。下面我们就分别从“广义”和“狭义”两个角度,对 Spring 进行介绍。

广义的 Spring:Spring 技术栈

广义上的 Spring 泛指以 Spring Framework 为核心的 Spring 技术栈。

经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。

这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。

狭义的 Spring:Spring Framework

狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。

Spring 框架是一个分层的、面向切面的 Java 应用程序的一站式轻量级解决方案,它是 Spring 技术栈的核心和基础,是为了解决企业级应用开发的复杂性而创建的。

Spring 有两个最核心模块: IoC 和 AOP。

IoC:Inverse of Control 的简写,译为“控制反转”,指把创建对象过程交给 Spring 进行管理。

AOP:Aspect Oriented Programming 的简写,译为“面向切面编程”。AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等。

SpringFrameWork特点

  • 非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。

  • 控制反转:IoC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。

  • 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。

  • 容器:Spring IoC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。

  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。

  • 一站式:在 IoC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且 Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。

Spring模块组成

image-20221207142746771

2097896352

①Spring Core(核心容器)

spring core提供了IOC,DI,Bean配置装载创建的核心实现。核心概念: Beans、BeanFactory、BeanDefinitions、ApplicationContext。

  • spring-core :IOC和DI的基本实现

  • spring-beans:BeanFactory和Bean的装配管理(BeanFactory)

  • spring-context:Spring context上下文,即IOC容器(AppliactionContext)

  • spring-expression:spring表达式语言

②Spring AOP

  • spring-aop:面向切面编程的应用模块,整合ASM,CGLib,JDK Proxy
  • spring-aspects:集成AspectJ,AOP应用框架
  • spring-instrument:动态Class Loading模块

③Spring Data Access

  • spring-jdbc:spring对JDBC的封装,用于简化jdbc操作
  • spring-orm:java对象与数据库数据的映射框架
  • spring-oxm:对象与xml文件的映射框架
  • spring-jms: Spring对Java Message Service(java消息服务)的封装,用于服务之间相互通信
  • spring-tx:spring jdbc事务管理

④Spring Web

  • spring-web:最基础的web支持,建立于spring-context之上,通过servlet或listener来初始化IOC容器
  • spring-webmvc:实现web mvc
  • spring-websocket:与前端的全双工通信协议
  • spring-webflux:Spring 5.0提供的,用于取代传统java servlet,非阻塞式Reactive Web框架,异步,非阻塞,事件驱动的服务

⑤Spring Message

  • Spring-messaging:spring 4.0提供的,为Spring集成一些基础的报文传送服务

⑥Spring test

  • spring-test:集成测试支持,主要是对junit的封装

入门开发

程序开发

引入依赖

添加依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>

<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>

查看依赖:

image-20221201105416558

创建java类

1
2
3
4
5
6
7
8
package com.atguigu.spring6.bean;

public class HelloWorld {

public void sayHello(){
System.out.println("helloworld");
}
}

创建配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--
配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理
通过bean标签配置IOC容器所管理的bean
属性:
id:设置bean的唯一标识
class:设置bean所对应类型的全类名
-->
<bean id="helloWorld" class="com.atguigu.spring6.bean.HelloWorld"></bean>

</beans>

创建测试类测试

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

@Test
public void testHelloWorld(){
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld");
helloworld.sayHello();
}
}

程序分析

  1. 底层是通过反射机制调用无参数构造方法

  2. 把创建好的对象存储到一个什么样的数据结构当中了呢?

    bean对象最终存储在spring容器中,在spring源码底层就是一个map集合,存储bean的map在DefaultListableBeanFactory类中:

    1
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    Spring容器加载到Bean类时 , 会把这个类的描述信息, 以包名加类名的方式存到beanDefinitionMap 中,
    Map<String,BeanDefinition> , 其中 String是Key , 默认是类名首字母小写 , BeanDefinition , 存的是类的定义(描述信息) , 我们通常叫BeanDefinition接口为 : bean的定义对象。

启用Log4j2日志框架

Log4j2日志概述

在项目开发中,日志十分的重要,不管是记录运行情况还是定位线上问题,都离不开对日志的分析。日志记录了系统行为的时间、地点、状态等相关信息,能够帮助我们了解并监控系统状态,在发生错误或者接近某种危险状态时能够及时提醒我们处理,同时在系统产生问题时,能够帮助我们快速的定位、诊断并解决问题。

Apache Log4j2是一个开源的日志记录组件,使用非常的广泛。在工程中以易用方便代替了 System.out 等打印语句,它是JAVA下最流行的日志输入工具。

Log4j2主要由几个重要的组件构成:

(1)日志信息的优先级,日志信息的优先级从高到低有TRACE < DEBUG < INFO < WARN < ERROR < FATAL
TRACE:追踪,是最低的日志级别,相当于追踪程序的执行
DEBUG:调试,一般在开发中,都将其设置为最低的日志级别
INFO:信息,输出重要的信息,使用较多
WARN:警告,输出警告的信息
ERROR:错误,输出错误信息
FATAL:严重错误

这些级别分别用来指定这条日志信息的重要程度;级别高的会自动屏蔽级别低的日志,也就是说,设置了WARN的日志,则INFO、DEBUG的日志级别的日志不会显示

(2)日志信息的输出目的地,日志信息的输出目的地指定了日志将打印到控制台还是文件中

(3)日志信息的输出格式,而输出格式则控制了日志信息的显示内容。

引入Log4j2依赖

1
2
3
4
5
6
7
8
9
10
11
<!--log4j2的依赖-->
<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-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>

加入日志配置文件

在类的根路径下提供log4j2.xml配置文件(文件名固定为:log4j2.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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<loggers>
<!--
level指定日志级别,从低到高的优先级:
TRACE < DEBUG < INFO < WARN < ERROR < FATAL
trace:追踪,是最低的日志级别,相当于追踪程序的执行
debug:调试,一般在开发中,都将其设置为最低的日志级别
info:信息,输出重要的信息,使用较多
warn:警告,输出警告的信息
error:错误,输出错误信息
fatal:严重错误
-->
<root level="DEBUG">
<appender-ref ref="spring6log"/>
<appender-ref ref="RollingFile"/>
<appender-ref ref="log"/>
</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>

<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
<File name="log" fileName="d:/spring6_log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>

<!-- 这个会打印出所有的信息,
每次大小超过size,
则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
作为存档-->
<RollingFile name="RollingFile" fileName="d:/spring6_log/app.log"
filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="50MB"/>
<!-- DefaultRolloverStrategy属性如不设置,
则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</appenders>
</configuration>

使用日志

1
2
3
4
5
6
7
8
9
10
11
12
public class HelloWorldTest {

private Logger logger = LoggerFactory.getLogger(HelloWorldTest.class);

@Test
public void testHelloWorld(){
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld");
helloworld.sayHello();
logger.info("执行成功");
}
}

IOC

IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序

Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。

IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。

IoC概述

控制反转(IoC)

  • 控制反转是一种思想。

  • 控制反转是为了降低程序耦合度,提高程序扩展力。

  • 控制反转,反转的是什么?

    • 将对象的创建权利交出去,交给第三方容器负责。
    • 将对象和对象之间关系的维护权交出去,交给第三方容器负责。
  • 控制反转这种思想如何实现呢?

    • DI(Dependency Injection):依赖注入

依赖注入

DI(Dependency Injection):依赖注入,依赖注入实现了控制反转的思想。

依赖注入:

  • 指Spring创建对象的过程中,将对象依赖属性通过配置进行注入

依赖注入常见的实现方式包括两种:

  • 第一种:set注入
  • 第二种:构造注入

所以结论是:IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现。

Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。

IoC容器在Spring的实现

Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现。IoC容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建IoC 容器。Spring 提供了IoC 容器的两种实现方式:

①BeanFactory

这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

②ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。

③ApplicationContext的主要实现类

img005.png

类型名 简介
ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContext ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

基于XML管理Bean

实验一:获取Bean

方式一:根据ID获取

1
2
3
4
5
6
7
8
9
10
11
@Test 
public void test01(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

// 通过id获取
User user1 = (User) applicationContext.getBean("user");


System.out.println("ID : " + user1);

}

方式二:根据类型获取

1
2
3
4
5
6
7
8
9
@Test 
public void test01(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

// 通过类获取
User user2 = applicationContext.getBean(User.class);

System.out.println("Class : " + user2);
}

方式三:根据类型和ID

1
2
3
4
5
6
7
8
9
@Test 
public void test01(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

// 通过类和ID获取
User user3 = applicationContext.getBean("user", User.class);

System.out.println("ID && Class : " + user3);
}

注意的地方

当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个

当IOC容器中一共配置了两个:

1
2
<bean id="helloworldOne" class="com.atguigu.spring6.bean.HelloWorld"></bean>
<bean id="helloworldTwo" class="com.atguigu.spring6.bean.HelloWorld"></bean>

根据类型获取时会抛出异常:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.atguigu.spring6.bean.HelloWorld’ available: expected single matching bean but found 2: helloworldOne,helloworldTwo

如果出现上面这种情况的话,则需要通过ID获取或者通过ID和类名获取

扩展知识

如果组件类实现了接口,根据接口类型可以获取 bean 吗?

可以,前提是bean唯一

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.hgu.User" />
<bean id="userImpl" class="com.hgu.bean.UserImpl" />
</beans>
1
2
3
4
5
6
public void test02(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserBean bean = applicationContext.getBean(UserBean.class);
// 成功 输出UserImpl
System.out.println(bean);
}

如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?

不行,因为bean不唯一

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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.hgu.User" />
<bean id="userImpl" class="com.hgu.bean.UserImpl" />
<bean id="personImpl" class="com.hgu.bean.PersonImpl" />
</beans>
1
2
3
4
5
6
7
public void test02(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserBean bean = applicationContext.getBean(UserBean.class);
// 失败
// No qualifying bean of type 'com.hgu.bean.UserBean' available: expected single matching bean but found 2: userImpl,personImpl
System.out.println(bean);
}

结论

根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。

java中,instanceof运算符用于判断前面的对象是否是后面的类,或其子类、实现类的实例。如果是返回true,否则返回false。也就是说:用instanceof关键字做判断时, instanceof 操作符的左右操作必须有继承或实现关系

实验二:依赖注入之通过Setter注入

通过Setter注入,一定要写set方法

第一步. 创建实例类

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
package com.hgu;

public class User {
private String like;
private String grade;

public User() {
}

public String getLike() {
return like;
}

public void setLike(String like) {
this.like = like;
}

public String getGrade() {
return grade;
}

public void setGrade(String grade) {
this.grade = grade;
}
}

第二步. 在Spring配置文件配置

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.hgu.User" >
<property name="grade" value="A" />
<property name="like" value="B" />
</bean>
</beans>

第三步. 验证结果

1
2
3
4
5
6
7
8
9
10
@Test 
public void test01(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

// 通过id获取
User user1 = (User) applicationContext.getBean("user");

System.out.println(user1);
// User{like='B', grade='A'}
}

实验三:依赖注入之通过构造器注入

第一步. 创建实例类

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
package com.hgu;

public class User {
private String like;
private String grade;

public User() {
}

public User(String like, String grade) {
System.out.println("有参构造器");
this.like = like;
this.grade = grade;
}

public String getLike() {
return like;
}

public void setLike(String like) {
this.like = like;
}

public String getGrade() {
return grade;
}

public void setGrade(String grade) {
this.grade = grade;
}

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


第二步. 在Spring配置文件配置

通过constructor-arg设置,name为属性名,value为属性值

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="user" class="com.hgu.User" >
<constructor-arg name="grade" value="A" />
<constructor-arg name="like" value="B" />
</bean>

</beans>

第三步. 验证结果

1
2
3
4
5
6
7
8
9
10
11
@Test 
public void test01(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

// 通过id获取
User user1 = (User) applicationContext.getBean("user");

System.out.println(user1);
// 有参构造器
// User{like='B', grade='A'}
}

实验四:特殊值处理

字面量赋值

什么是字面量?

int a = 10;

声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a的时候,我们实际上拿到的值是10。

而如果a是带引号的:‘a’,那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的这个数据本身。

1
2
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="name" value="张三"/>

null值

1
2
3
<property name="name">
<null />
</property>

注意:

1
<property name="name" value="null"></property>

以上写法,为name所赋的值是字符串null

xml实体

在xml里面大于小于需要使用特殊符号

1
2
3
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a &lt; b"/>

CDATA节

因为我们在XML里面有很多特殊字符,所以我们需要使用< ![CDATA[a < b ]] >

1
2
3
4
5
6
7
<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>

实验五:为对象类型属性赋值

外部bean

第一步:创建两个对象

Person

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
package com.hgu;

public class Person {
private String sex;
private String name;

public Person() {
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getName() {
return name;
}

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

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

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
50
51
package com.hgu;

public class User {
private String like;
private String grade;

private Person person;

public User() {
}

public User(String like, String grade) {
System.out.println("有参构造器");
this.like = like;
this.grade = grade;
}

public String getLike() {
return like;
}

public void setLike(String like) {
this.like = like;
}

public String getGrade() {
return grade;
}

public void setGrade(String grade) {
this.grade = grade;
}

public Person getPerson() {
return person;
}

public void setPerson(Person person) {
this.person = person;
}

@Override
public String toString() {
return "User{" +
"like='" + like + '\'' +
", grade='" + grade + '\'' +
", person=" + person +
'}';
}
}

第二步:配置XML文件

不再使用value赋值,而是使用ref指向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.hgu.Person" >
<property name="name" value="张三" />
<property name="sex" value="男" />
</bean>
<bean id="user" class="com.hgu.User" >
<property name="grade" value="A" />
<property name="like" value="B" />
<property name="person" ref="person" />
</bean>
</beans>

第三步:测试结果

1
2
3
4
5
6
7
8
9
public void test01(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

// 通过id获取
User user1 = (User) applicationContext.getBean("user");

System.out.println(user1);
// User{like='B', grade='A', person=Person{sex='男', name='张三'}}
}

内部bean

区别在于xml里面的配置在bean里面

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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.hgu.User" >
<property name="grade" value="A" />
<property name="like" value="B" />
<!--内部bean-->
<property name="person" >
<!-- 内部bean只能用于属性赋值,不能再外部通过IOC容器获取,因此可以省略掉ID属性 -->
<bean id="person" class="com.hgu.Person" >
<property name="name" value="张三" />
<property name="sex" value="男" />
</bean>
</property>
</bean>
</beans>

级联属性赋值

主要区别还是在于XML文件,该方法可以在赋值的时候再给对应的对象属性赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.hgu.Person" >
<property name="name" value="张三" />
<property name="sex" value="男" />
</bean>
<bean id="user" class="com.hgu.User" >
<property name="grade" value="A" />
<property name="like" value="B" />
<property name="person" ref="person" />
<property name="person.name" value="李四" />
</bean>
</beans>

实验六:为数组类型属性赋值

还是在xml文件中进行设置,使用array方式来给数组赋值

Person

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.hgu;

import java.util.Arrays;

public class Person {
private String[] loves;
public Person() {
}

public String[] getLoves() {
return loves;
}

public void setLoves(String[] loves) {
this.loves = loves;
}

@Override
public String toString() {
return "Person{" +
"loves=" + Arrays.toString(loves) +
'}';
}
}

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.hgu.Person" >
<property name="loves">
<array>
<value>吃饭</value>
<value>睡觉</value>
<value>打代码</value>
</array>
</property>
</bean>
</beans>

实验七:为集合类型属性赋值

List集合类型赋值

区别还是在于XML

Person

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
package com.hgu;

import java.util.Arrays;
import java.util.List;

public class Person {
private List<User> loves;
public Person() {
}

public List<User> getLoves() {
return loves;
}

public void setLoves(List<User> loves) {
this.loves = loves;
}

@Override
public String toString() {
return "Person{" +
"loves=" + loves +
'}';
}
}

XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.hgu.Person" >
<property name="loves">
<list>
<ref bean="user1"/>
<ref bean="user2"/>
</list>
</property>
</bean>
<bean id="user1" class="com.hgu.User" >
<property name="grade" value="A" />
<property name="like" value="B" />
<property name="person" ref="person" />
</bean>
<bean id="user2" class="com.hgu.User" >
<property name="grade" value="B" />
<property name="like" value="A" />
<property name="person" ref="person" />
</bean>
</beans>

Map集合类型赋值

Person

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

import java.util.Map;

public class Person {
private Map<String,User> test;
public Person() {
}

public Map<String, User> getTest() {
return test;
}

public void setTest(Map<String, User> test) {
this.test = test;
}
}

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.hgu.Person" >
<property name="test">
<map>
<entry>
<key>
<value>123</value>
</key>
<ref bean="user1" ></ref>
</entry>
<entry>
<key>
<value>1234</value>
</key>
<ref bean="user2" ></ref>
</entry>
</map>
</property>
</bean>
<bean id="user1" class="com.hgu.User" >
<property name="like" value="aa"/>
</bean>
<bean id="user2" class="com.hgu.User" >
<property name="like" value="bb"/>
</bean>
</beans>

测试

1
2
3
4
5
6
7
@Test
public void test03(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
// Person{test={123=User{like='aa', grade='null', person=null}, 1234=User{like='bb', grade='null', person=null}}}
}

引用集合类型bean

Person

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 com.hgu;

import java.util.List;
import java.util.Map;

public class Person {
private Map<String,User> test;
private List<User> myList;
public Person() {
}

public Map<String, User> getTest() {
return test;
}

public void setTest(Map<String, User> test) {
this.test = test;
}

public List<User> getMyList() {
return myList;
}

public void setMyList(List<User> myList) {
this.myList = myList;
}

@Override
public String toString() {
return "Person{" +
"test=" + test +
", myList=" + myList +
'}';
}
}

beansXML

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

<bean id="person" class="com.hgu.Person" >
<property name="myList" ref="stringList" />
<property name="test" ref="userList" />
</bean>
<bean id="user1" class="com.hgu.User" >
<property name="like" value="aa"/>
</bean>
<bean id="user2" class="com.hgu.User" >
<property name="like" value="bb"/>
</bean>

<util:list id="stringList">
<ref bean="user1" ></ref>
<ref bean="user2" ></ref>
</util:list>

<util:map id="userList">
<entry>
<key>
<value>100</value>
</key>
<ref bean="user1"></ref>
</entry>
<entry>
<key>
<value>100</value>
</key>
<ref bean="user2"></ref>
</entry>
</util:map>
</beans>

测试

1
2
3
4
5
6
7
@Test
public void test03(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
// Person{test={100=User{like='bb', grade='null', person=null}}, myList=[User{like='aa', grade='null', person=null}, User{like='bb', grade='null', person=null}]}
}

实验八:p命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
https://www.springframework.org/schema/util/spring-util.xsd">
<beans>
<bean id="person" class="com.hgu.Person" p:test-ref="userList" p:myList-ref="stringList"></bean>
</beans>

测试:

1
2
3
4
5
6
7
@Test
public void test03(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
// Person{test={100=User{like='bb', grade='null', person=null}}, myList=[User{like='aa', grade='null', person=null}, User{like='bb', grade='null', person=null}]}
}

实验九:引入外部文件

步骤:

  1. 引入数据库相关依赖
  2. 创建外部文件properties格式,定义数据信息:用户名,密码,地址
  3. 创建Spring配置文件,引入Context命名空间,引入属性文件,使表达式完成注入

加入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
 <!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>

<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>

创建外部文件

jdbc.properties

1
2
3
4
user=root
password=admin
url=jdbc:mysql://localhost:3306/test?CharacterEncoding=utf8&useUnicode=true
driverClass=com.mysql.cj.jdbc.Driver

创建XML文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="user" class="com.hgu.User">
<property name="url" value="${url}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
<property name="driverClass" value="${driverClass}" />
</bean>
</beans>

测试

1
2
3
4
5
6
7
8
@Test
public void test04(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-jdbc.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user);
// User{url='jdbc:mysql://localhost:3306/test?CharacterEncoding=utf8&useUnicode=true', password='admin', user='root', driverClass='com.mysql.cj.jdbc.Driver'}

}

实验十:bean作用域

概念

在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:

使用sinleton的话,整个项目中只会存在这一个对象,但是使用prototype的时候,我们使用一次getBean就会创建一个对象

取值 含义 创建对象的时机
singleton(默认) 在IOC容器中,这个bean的对象始终为单实例 IOC容器初始化时
prototype 这个bean在IOC容器中有多个实例 获取bean时

如果是在WebApplicationContext环境下还会有另外几个作用域(但不常用):

取值 含义
request 在一个请求范围内有效
session 在一个会话范围内有效

创建类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
50
package com.hgu;

public class User {
private String url;
private String password;
private String user;
private String driverClass;

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getUser() {
return user;
}

public void setUser(String user) {
this.user = user;
}

public String getDriverClass() {
return driverClass;
}

public void setDriverClass(String driverClass) {
this.driverClass = driverClass;
}

@Override
public String toString() {
return "User{" +
"url='" + url + '\'' +
", password='" + password + '\'' +
", user='" + user + '\'' +
", driverClass='" + driverClass + '\'' +
'}';
}
}

测试 prototype

XML

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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.hgu.User" scope="prototype" >

</bean>
</beans>

JAVA

可以看出是两个不同的对象

1
2
3
4
5
6
7
8
9
10
@Test
public void test05(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-scope.xml");
Object user = applicationContext.getBean("user");
System.out.println(user);
Object user1 = applicationContext.getBean("user");
System.out.println(user1);
// com.hgu.User@7776ab
// com.hgu.User@79179359
}

测试 singleton

XML

默认是singleton

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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.hgu.User">

</bean>
</beans>

JAVA

1
2
3
4
5
6
7
8
9
10
@Test
public void test05(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-scope.xml");
Object user = applicationContext.getBean("user");
System.out.println(user);
Object user1 = applicationContext.getBean("user");
System.out.println(user1);
// com.hgu.User@7ac0e420
// com.hgu.User@7ac0e420
}

实验十一:bean生命周期

具体的生命周期过程

  • bean对象创建(调用无参构造器)

  • 给bean对象设置属性

  • bean的后置处理器(初始化之前)

  • bean对象初始化(需在配置bean时指定初始化方法)

  • bean的后置处理器(初始化之后)

  • bean对象就绪可以使用

  • bean对象销毁(需在配置bean时指定销毁方法)

  • IOC容器关闭

创建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
package com.hgu.life;

public class LifeBean {
private String name;
public LifeBean() {
System.out.println("1. bean对象创建调用无参构造器");
}

public String getName() {
return name;
}

public void setName(String name) {
System.out.println("2. 给bean对象赋值调用set方法");
this.name = name;
}

public void initMethod(){
System.out.println("4. bean对象初始化");
}
public void destroyMethod(){
System.out.println("7. bean对象销毁");
}
}

bean的后置处理器

bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行

创建bean的后置处理器:

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

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("3.bean后置处理器,初始化之前执行,调用postProcessBeforeInitialization方法");
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("5.bean后置处理器,初始化之后执行,调用postProcessAfterInitialization方法");
return bean;
}
}

创建XML文件

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="life" class="com.hgu.life.LifeBean" init-method="initMethod" destroy-method="destroyMethod" >
<property name="name" value="张三" />
</bean>
<!--配置Bean后缀处理器 -->
<bean id="myBeanProcessor" class="com.hgu.life.MyPost" />
</beans>

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test06(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans-life.xml");
applicationContext.getBean("life");
System.out.println("6. bean对象已经就绪");
applicationContext.close();
}
// 1. bean对象创建调用无参构造器
// 2. 给bean对象赋值调用set方法
// 3.bean后置处理器,初始化之前执行,调用postProcessBeforeInitialization方法
// 4. bean对象初始化
// 5.bean后置处理器,初始化之后执行,调用postProcessAfterInitialization方法
// 6. bean对象已经就绪
// 7. bean对象销毁

实验十二:FactoryBean

简介

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

/**
* Interface to be implemented by objects used within a {@link BeanFactory} which
* are themselves factories for individual objects. If a bean implements this
* interface, it is used as a factory for an object to expose, not directly as a
* bean instance that will be exposed itself.
*
* <p><b>NB: A bean that implements this interface cannot be used as a normal bean.</b>
* A FactoryBean is defined in a bean style, but the object exposed for bean
* references ({@link #getObject()}) is always the object that it creates.
*
* <p>FactoryBeans can support singletons and prototypes, and can either create
* objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean}
* interface allows for exposing more fine-grained behavioral metadata.
*
* <p>This interface is heavily used within the framework itself, for example for
* the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the
* {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for
* custom components as well; however, this is only common for infrastructure code.
*
* <p><b>{@code FactoryBean} is a programmatic contract. Implementations are not
* supposed to rely on annotation-driven injection or other reflective facilities.</b>
* {@link #getObjectType()} {@link #getObject()} invocations may arrive early in the
* bootstrap process, even ahead of any post-processor setup. If you need access to
* other beans, implement {@link BeanFactoryAware} and obtain them programmatically.
*
* <p><b>The container is only responsible for managing the lifecycle of the FactoryBean
* instance, not the lifecycle of the objects created by the FactoryBean.</b> Therefore,
* a destroy method on an exposed bean object (such as {@link java.io.Closeable#close()}
* will <i>not</i> be called automatically. Instead, a FactoryBean should implement
* {@link DisposableBean} and delegate any such close call to the underlying object.
*
* <p>Finally, FactoryBean objects participate in the containing BeanFactory's
* synchronization of bean creation. There is usually no need for internal
* synchronization other than for purposes of lazy initialization within the
* FactoryBean itself (or the like).
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 08.03.2003
* @param <T> the bean type
* @see org.springframework.beans.factory.BeanFactory
* @see org.springframework.aop.framework.ProxyFactoryBean
* @see org.springframework.jndi.JndiObjectFactoryBean
*/
public interface FactoryBean<T> {

/**
* The name of an attribute that can be
* {@link org.springframework.core.AttributeAccessor#setAttribute set} on a
* {@link org.springframework.beans.factory.config.BeanDefinition} so that
* factory beans can signal their object type when it can't be deduced from
* the factory bean class.
* @since 5.2
*/
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
* <p>As with a {@link BeanFactory}, this allows support for both the
* Singleton and Prototype design pattern.
* <p>If this FactoryBean is not fully initialized yet at the time of
* the call (for example because it is involved in a circular reference),
* throw a corresponding {@link FactoryBeanNotInitializedException}.
* <p>As of Spring 2.0, FactoryBeans are allowed to return {@code null}
* objects. The factory will consider this as normal value to be used; it
* will not throw a FactoryBeanNotInitializedException in this case anymore.
* FactoryBean implementations are encouraged to throw
* FactoryBeanNotInitializedException themselves now, as appropriate.
* @return an instance of the bean (can be {@code null})
* @throws Exception in case of creation errors
* @see FactoryBeanNotInitializedException
*/
@Nullable
T getObject() throws Exception;

/**
* Return the type of object that this FactoryBean creates,
* or {@code null} if not known in advance.
* <p>This allows one to check for specific types of beans without
* instantiating objects, for example on autowiring.
* <p>In the case of implementations that are creating a singleton object,
* this method should try to avoid singleton creation as far as possible;
* it should rather estimate the type in advance.
* For prototypes, returning a meaningful type here is advisable too.
* <p>This method can be called <i>before</i> this FactoryBean has
* been fully initialized. It must not rely on state created during
* initialization; of course, it can still use such state if available.
* <p><b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return
* {@code null} here. Therefore it is highly recommended to implement
* this method properly, using the current state of the FactoryBean.
* @return the type of object that this FactoryBean creates,
* or {@code null} if not known at the time of the call
* @see ListableBeanFactory#getBeansOfType
*/
@Nullable
Class<?> getObjectType();

/**
* Is the object managed by this factory a singleton? That is,
* will {@link #getObject()} always return the same object
* (a reference that can be cached)?
* <p><b>NOTE:</b> If a FactoryBean indicates to hold a singleton object,
* the object returned from {@code getObject()} might get cached
* by the owning BeanFactory. Hence, do not return {@code true}
* unless the FactoryBean always exposes the same reference.
* <p>The singleton status of the FactoryBean itself will generally
* be provided by the owning BeanFactory; usually, it has to be
* defined as singleton there.
* <p><b>NOTE:</b> This method returning {@code false} does not
* necessarily indicate that returned objects are independent instances.
* An implementation of the extended {@link SmartFactoryBean} interface
* may explicitly indicate independent instances through its
* {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean}
* implementations which do not implement this extended interface are
* simply assumed to always return independent instances if the
* {@code isSingleton()} implementation returns {@code false}.
* <p>The default implementation returns {@code true}, since a
* {@code FactoryBean} typically manages a singleton instance.
* @return whether the exposed object is a singleton
* @see #getObject()
* @see SmartFactoryBean#isPrototype()
*/
default boolean isSingleton() {
return true;
}
}

创建类UserFactoryBean

1
2
3
4
5
6
7
8
9
10
11
12
package com.atguigu.spring6.bean;
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}

@Override
public Class<?> getObjectType() {
return User.class;
}
}

配置bean

1
<bean id="user" class="com.atguigu.spring6.bean.UserFactoryBean"></bean>

测试

虽然我们在XML文件中配置的是UserFactoryBean对象,但是我们的得到的是User对象,因为UserFactoryBean对象实现了FactoryBean对象并重写了getObject方法,所以我们通过xml文件调用的时候是返回User对象

1
2
3
4
5
6
7
@Test
public void testUserFactoryBean(){
//获取IOC容器
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-factorybean.xml");
User user = (User) ac.getBean("user");
System.out.println(user);
}

实验十三:基于xml自动装配

通过模拟JavaWeb中的controller Service 和 DAO之间的关系来进行xml自动转配

场景模拟

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hgu.web.controller;

import com.hgu.web.service.UserService;

public class UserController {
private UserService userService;

public UserService getUserService() {
return userService;
}

public void setUserService(UserService userService) {
this.userService = userService;
}

public void UserControllerFunction(){
System.out.println("UserControllerFunction中的方法调用了");
userService.userServiceFunction();
}
}

Service.Impl

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

import com.hgu.web.dao.UserDAO;

public class UserServiceImpl implements UserService{
private UserDAO userDAO;

public UserDAO getUserDAO() {
return userDAO;
}

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

@Override
public void userServiceFunction() {
System.out.println("UserServiceFunction中的方法调用了");
userDAO.userDAOFunction();
}
}

service.Inter

1
2
3
4
5
package com.hgu.web.service;

public interface UserService {
void userServiceFunction();
}

DAO.Impl

1
2
3
4
5
6
7
8
package com.hgu.web.dao;

public class UserDAOImpl implements UserDAO{
@Override
public void userDAOFunction() {
System.out.println("UserDAO中的方法调用了");
}
}

DAO.Inter

1
2
3
4
5
package com.hgu.web.dao;

public interface UserDAO {
void userDAOFunction();
}

配置bean byType

使用bean标签的autowire属性设置自动装配效果

自动装配方式:byType

byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值

若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null

若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException

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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userController" class="com.hgu.web.controller.UserController" autowire="byType"></bean>
<bean id="userService" class="com.hgu.web.service.UserServiceImpl" autowire="byType"></bean>
<bean id="userDAO" class="com.hgu.web.dao.UserDAOImpl" ></bean>
</beans>

配置bean byName

自动装配方式:byName

byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值

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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userController" class="com.hgu.web.controller.UserController" autowire="byName"></bean>
<bean id="userService" class="com.hgu.web.service.UserServiceImpl" autowire="byName"></bean>
<bean id="userDAO" class="com.hgu.web.dao.UserDAOImpl" ></bean>
</beans>

测试

1
2
3
4
5
6
@Test
public void testAutoWireByXML(){
ApplicationContext ac = new ClassPathXmlApplicationContext("autowire-xml.xml");
UserController userController = ac.getBean(UserController.class);
userController.saveUser();
}

基于注解管理bean(重点)

从 Java 5 开始,Java 增加了对注解(Annotation) 的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。

Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置。

Spring 通过注解实现自动装配的步骤如下:

  1. 引入依赖
  2. 开启组件扫描
  3. 使用注解定义 Bean
  4. 依赖注入

搭建子模块spring6-ioc-annotation

①搭建模块

搭建方式如:spring6-ioc-xml

②引入配置文件

引入spring-ioc-xml模块日志log4j2.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
<dependencies>
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.3</version>
</dependency>

<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>

<!--log4j2的依赖-->
<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-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
</dependencies>

开启组件扫描

Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 < context:component-scan > 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了@Component

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描功能-->
<context:component-scan base-package="com.atguigu.spring6"></context:component-scan>
</beans>

在使用 < context:component-scan > 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 < beans > 中添加 context 相关的约束

情况一:最基本情况扫描

1
2
<context:component-scan base-package="com.atguigu.spring6">
</context:component-scan>

情况二:指定要排除的组件

1
2
3
4
5
6
7
8
9
10
<context:component-scan base-package="com.atguigu.spring6">
<!-- context:exclude-filter标签:指定排除规则 -->
<!--
type:设置排除或包含的依据
type="annotation",根据注解排除,expression中设置要排除的注解的全类名
type="assignable",根据类型排除,expression中设置要排除的类型的全类名
-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--<context:exclude-filter type="assignable" expression="com.atguigu.spring6.controller.UserController"/>-->
</context:component-scan>

情况三:仅扫描指定组件

context:include-filter标签:指定在原有扫描规则的基础上追加的规则

此时必须设置use-default-filters=“false”,因为默认规则即扫描指定包下所有类

1
2
3
4
5
6
7
8
9
10
11
12
<context:component-scan base-package="com.atguigu" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
<!--
type:设置排除或包含的依据
type="annotation",根据注解排除,expression中设置要排除的注解的全类名
type="assignable",根据类型排除,expression中设置要排除的类型的全类名
-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--<context:include-filter type="assignable" expression="com.atguigu.spring6.controller.UserController"/>-->
</context:component-scan>

使用注解定义 Bean

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean

注解 说明
@Component 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller 该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

属性注解一:@Autowrite注入

属性注入

属性注入

源码中有两处需要注意:

  • 第一处:该注解可以标注在哪里?

    • 构造方法上
    • 方法上
    • 形参上
    • 属性上
    • 注解上
  • 第二处:该注解有一个required属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错。如果required属性设置为false,表示注入的Bean存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。

场景搭建 Controller

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

import com.hug.web.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller(value = "user")
public class UserController {
@Autowired
private UserService userService;

public void add(){
System.out.println("UserController.....");
userService.add();
}
}

场景搭建 Service

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

import com.hug.web.dao.UserDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDAO userDAO;
@Override
public void add() {
System.out.println("UserService....");
userDAO.add();
}
}

场景搭建 DAO

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.hug.web.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserDAOImpl implements UserDAO{

@Override
public void add() {
System.out.println("DAO....");
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
public class TestClass {
@Test
public void test01(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-spring6.xml");
UserController user = (UserController) applicationContext.getBean("user");
user.add();
// UserController.....
// UserService....
// DAO....
}
}

set注入

set注入

和属性注入使用大致相同,就是将@Autowrite放在set方法上即可

Controller

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.hug.web.controller;

import com.hug.web.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller(value = "user")
public class UserController {

private UserService userService;

public UserService getUserService() {
return userService;
}
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}

public void add(){
System.out.println("UserController.....");
userService.add();
}
}

构造方法注入

构造方法注入

和属性注入使用大致相同,就是将@Autowrite放在构造器方法上即可

Controller

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

import com.hug.web.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller(value = "user")
public class UserController {

private UserService userService;

@Autowired
public UserController(UserService userService){
this.userService = userService;
}

public void add(){
System.out.println("UserController.....");
userService.add();
}
}

形参上注入

形参上注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hug.web.controller;

import com.hug.web.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller(value = "user")
public class UserController {

private UserService userService;

public UserController(@Autowired UserService userService){
this.userService = userService;
}

public void add(){
System.out.println("UserController.....");
userService.add();
}
}

只有一个构造函数,无注解

只有一个构造函数,无注解

User

下面代码中只有一个有参构造器,那么就可以不用些注解,会自动的给你赋值

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

import com.hug.web.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller(value = "user")
public class UserController {

private UserService userService;

public UserController(UserService userService){
this.userService = userService;
}

public void add(){
System.out.println("UserController.....");
userService.add();
}
}

@Autowired注解和@Qualifier注解联合

当一个接口有两个实现类的时候,如果我们是用接口来进行实例化的时候会报错,因为不知道我们具体要实例化哪一个类

为解决上述方法我们通过使用@Qualifier注解来通过名称进行自动赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller(value = "user")
public class UserController {
@Autowried
@Qualifier(value="userServiceImpl")
private UserService userService;

public UserController(UserService userService){
this.userService = userService;
}

public void add(){
System.out.println("UserController.....");
userService.add();
}
}

属性注解二:@Resource注入

和@Autowired注解的区别

  • @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
  • @Autowired注解是Spring框架自己的。
  • @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
  • @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
  • @Resource注解用在属性上、setter方法上。
  • @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。

引入依赖

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

根据name注入

默认是通过ByName注入的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller(value = "userController")
public class UserController {

@Resource(name = "myUserService")
private UserService userService;

public UserController(UserService userService){
this.userService = userService;
}

public void add(){
System.out.println("MyUserController.....");
userService.add();
}
}

根据属性名注入

当我们name没有写的时候,默认通过属性名去寻找

1
2
3
4
5
6
7
8
9
10
@Service(value = "myUserService")
public class UserServiceImpl implements UserService {
@Resource
private UserDAO userDAO;
@Override
public void add() {
System.out.println("UserService....");
userDAO.add();
}
}

根据类型注入

根据类型注入,当我们既没有写name,根据属性名也找不到对应的属性,那我们通过类型获取

1
2
3
4
5
6
7
8
9
10
@Service(value = "myUserService")
public class UserServiceImpl implements UserService {
@Resource
private UserDAO myUserDAO;
@Override
public void add() {
System.out.println("UserService....");
userDAO.add();
}
}

总结:

@Resource注解:默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个

Spring全注解开发

通过创建配置类,来省略配置文件

配置类的创建

1
2
3
4
5
6
7
8
9
10
11
12
package com.hug;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

// 通过使用Configuration标签来表明这个类是配置类
@Configuration
// 通过使用Componentscan来配置我们要解析那个包下的文件
@ComponentScan("com.hug.resource")
public class SpringConfig {
}

创建测试类

通过使用AnnotationConfigApplicationContext(配置类的Class)这种方法来进行配置

1
2
3
4
5
6
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
UserController user = (UserController) applicationContext.getBean("userController");
user.add();
}

原理-手写IOC

反射

获取类名并实例化

通过类名.class获取

1
Person.class;

通过对象.getClass()方法获取

1
new Person().getClass();

通过Class.forName(“全路径”)

1
Class.forName("com.hgu.ioc.Person");

实例化

1
Pserson person = (Person)Class.forName("com.hgu.ioc.Person").getDeclaredConstructor().newInstance();

获取构造方法

getDeclaredConstructor() 和 getConstructor()的区别?

getConstructor()可以获取对应类中所有的public的构造方法

getDeclaredConstructor() 可以获取类中所有的构造方法,不管是public还是private

获取指定构造器的方法

默认是无参构造器

如果是public的方法的话

1
2
3
4
5
6
@Test
public void test01() throws Exception {
Constructor<User> declaredConstructor = User.class.getDeclaredConstructor(String.class);
User user1 = declaredConstructor.newInstance("张三");
System.out.println(user1);
}

如果是private

1
2
3
4
5
6
7
@Test
public void test01() throws Exception {
Constructor<User> declaredConstructor = User.class.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(true);
User user1 = declaredConstructor.newInstance("张三");
System.out.println(user1);
}

获取属性

通过getDeclaredFields()获取所有属性

private使用getDeclaredField

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test01() throws Exception {
Constructor<User> declaredConstructor = User.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
User user = declaredConstructor.newInstance();

Field name = User.class.getDeclaredField("name");
name.setAccessible(true);
name.set(user, "张三");

System.out.println(user);
}

public使用getField()

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test01() throws Exception {
Constructor<User> declaredConstructor = User.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
User user = declaredConstructor.newInstance();

Field name = User.class.getField("name");
name.set(user, "张三");

System.out.println(user);
}

获取方法

private使用getDeclaredMethods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test01() throws Exception {
Constructor<User> declaredConstructor = User.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
User user = declaredConstructor.newInstance();
user.setName("李四");

Method[] declaredMethods = User.class.getDeclaredMethods();
for(Method m: declaredMethods){
if(m.getName().equals("toString")){
m.setAccessible(true);
String invoke = (String) m.invoke(user);
System.out.println(invoke);
}
}
}

public使用getMethods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test01() throws Exception {
Constructor<User> declaredConstructor = User.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
User user = declaredConstructor.newInstance();
user.setName("李四");

Method[] declaredMethods = User.class.getMethods();
for(Method m: declaredMethods){
if(m.getName().equals("toString")){
String invoke = (String) m.invoke(user);
System.out.println(invoke);
}
}
}

实现Spring的IOC

实现过程

  1. 创建子模块

  2. 创建测试类

  3. 创建注解

    1. @Bean创建对象
    2. @Di 属性注入
  4. 创建Bean容器接口 ApplicationContext定义方法,返回对象

  5. 实现Bean容器接口

    1. 返回对象

    2. 根据包加载bean

      such as:包com.hgu,扫描com.hug这个包,和他的子包里面的所有类,看类上面是否有@Bean注解,如果有就把这个类通过反射进行实例化

创建注解

1
2
3
4
5
6
7
8
9
10
11
12
package com.hgu.anno;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
1
2
3
4
5
6
7
8
9
10
11
12
package com.hgu.anno;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}

创建接口

1
2
3
4
5
6
package com.hgu.bean;

public interface ApplicationContext {
// 返回Bean对象
Object getBean(Class clazz);
}

创建接口实现类,并初始化beanMap方法

实现方法

  1. 先通过我们传入的包名的参数获取这个包对应的全路径
  2. 在通过截取获取我们的根路径
    1. 全路径:/E:/Project/JavaProject/Spring/hgu-spring/target/classes/com/hgu
    2. 根路径:/E:/Project/JavaProject/Spring/hgu-spring/target/classes/
  3. 通过创建leanBean方法填充beanMap
    1. 先判断文件是否为目录
    2. 获取该文件下面的所有文件,判断一下是否含有文件
    3. 如果含有的话,遍历整个子目录
    4. 如果子目录还是目录的的话,则递归调用
    5. 如果不是目录的话,则判断该目录是否是.class结尾的文件
    6. 如果是以.class结尾的文件的话,则通过字符串切割和替换获取对应的全类名
    7. 获取该全类名的class。判断这个class是否为interface(),
    8. 如果不是interface的话,则进行类的创建
    9. 类创建完毕之后判断该类是否实现了接口
    10. 如果实现了接口则beanMap中的k是以接口的名字命名
    11. 如果没有实现的话则k是以该类的class命名
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
package com.hgu.bean.impl;

import com.hgu.anno.Bean;
import com.hgu.bean.ApplicationContext;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class AnnotationApplicationContext implements ApplicationContext {
// 创建MAP,用于存放Map对象
private Map<Class, Object> beanFactory = new HashMap<>();
private String rootPath;

// 返回对象
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}

// 设置包扫描规则
// 当前包及其子包,那个类有@bean注解,就把这个类通过反射进行实例化
public AnnotationApplicationContext(String basePackage) throws Exception {
// 传入的参数是com.hgu
// 我们需要将这个包名转换成绝对路径
// 1. 将.替换成\
String packagePath = basePackage.replaceAll("\\.", "/");
// 2. 获取包的绝对路径
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(packagePath);
String decode = "";
while(resources.hasMoreElements()){
URL url = resources.nextElement();
decode = URLDecoder.decode(url.getFile(), "utf-8");
rootPath = decode.substring(0,decode.length() - packagePath.length());
}
loadBean(new File(decode));
}

private void loadBean(File file) throws Exception {
if(file.isDirectory()){
File[] files = file.listFiles();
if(files == null || files.length == 0){
return ;
}
for (File listFile : files) {
if(listFile.isDirectory()){
loadBean(listFile);
}else{
// 获取对应的路径的com.hgu.***.class,通过字符串截取
String realPath = listFile.getAbsolutePath().substring(rootPath.length() - 1);
String replace="";
// 判断对象是否是.class对象
if(realPath.endsWith(".class")){
// 如果是class对象的话,将路径中的\替换成.
replace = realPath.replace(".class", "").replaceAll("\\\\", "\\.");
}

// 获取对应类的Class对象
Class<?> aClass = Class.forName(replace);
// 判断是否为接口
if(!aClass.isInterface()){
// 判断该类中是否有对应的接口
Bean annotation = aClass.getAnnotation(Bean.class);
if(annotation != null ){
Object o = aClass.getDeclaredConstructor().newInstance();
// 判断该类是实现了接口,如果实现了接口则使用接口类当做参数
if(aClass.getInterfaces().length > 0){
beanFactory.put(aClass.getInterfaces()[0], o);
}else{
beanFactory.put(aClass, o);
}
}
}
}
}

}

}

public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new AnnotationApplicationContext("com.hgu");
}
}

创建注入属性

  1. 通过beanFactory遍历所有的类
  2. 通过反射获取类中的所有属性
  3. 判断该属性是否有对应的接口
  4. 如果有借口的话则进行实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void loadDi() throws IllegalAccessException {
//遍历beanMap里面的所有的类
Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
for (Map.Entry<Class, Object> entry : entries){
Object value = entry.getValue();
Field[] declaredFields = value.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
Di annotation = declaredField.getAnnotation(Di.class);
if(annotation != null ){
// 如果是私有属性的话则需要设置一下
declaredField.setAccessible(true);
declaredField.set(value, beanFactory.get(declaredField.getType()));
}
}
}
}

面向切面:AOP

场景模拟

创建一个计算器接口

Calculator

1
2
3
4
5
6
7
8
9
10
11
package com.hgu.example;

public interface Calculator {
int add(int i, int j);

int sub(int i, int j);

int mul(int i, int j);

int div(int i, int j);
}

实现实现类

CalculatorImpl

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 com.hgu.example.impl;

import com.hgu.example.Calculator;

public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {

int result = i + j;

System.out.println("方法内部 result = " + result);

return result;
}

@Override
public int sub(int i, int j) {

int result = i - j;

System.out.println("方法内部 result = " + result);

return result;
}

@Override
public int mul(int i, int j) {

int result = i * j;

System.out.println("方法内部 result = " + result);

return result;
}

@Override
public int div(int i, int j) {

int result = i / j;

System.out.println("方法内部 result = " + result);

return result;
}
}

创建带日志的实现类

CalculatorLogImpl

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
package com.hgu.example.impl;

import com.hgu.example.Calculator;

public class CalculatorLogImpl implements Calculator {
@Override
public int add(int i, int j) {

System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);

int result = i + j;

System.out.println("方法内部 result = " + result);

System.out.println("[日志] add 方法结束了,结果是:" + result);

return result;
}

@Override
public int sub(int i, int j) {

System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);

int result = i - j;

System.out.println("方法内部 result = " + result);

System.out.println("[日志] sub 方法结束了,结果是:" + result);

return result;
}

@Override
public int mul(int i, int j) {

System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);

int result = i * j;

System.out.println("方法内部 result = " + result);

System.out.println("[日志] mul 方法结束了,结果是:" + result);

return result;
}

@Override
public int div(int i, int j) {

System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);

int result = i / j;

System.out.println("方法内部 result = " + result);

System.out.println("[日志] div 方法结束了,结果是:" + result);

return result;
}
}

问题提出

针对带日志功能的实现类,我们发现有如下缺陷:

  • 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
  • 附加功能分散在各个业务功能方法中,不利于统一维护

解决思路

解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来

困难

解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。

代理模式

代理的概念

介绍

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

img016.png

使用代理后:

images

②生活中的代理

  • 广告商找大明星拍广告需要经过经纪人
  • 合作伙伴找大老板谈合作要约见面时间需要经过秘书
  • 房产中介是买卖双方的代理

③相关术语

  • 代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
  • 目标:被代理“套用”了非核心逻辑代码的类、对象、方法。

静态代理

我们在创建一个代理类

CalculatorStaticProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CalculatorStaticProxy implements Calculator {

// 将被代理的目标对象声明为成员变量
private Calculator target;

public CalculatorStaticProxy(Calculator target) {
this.target = target;
}

@Override
public int add(int i, int j) {

// 附加功能由代理类中的代理方法来实现
System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);

// 通过目标对象来实现核心业务逻辑
int addResult = target.add(i, j);

System.out.println("[日志] add 方法结束了,结果是:" + addResult);

return addResult;
}
}

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。

动态代理

images

步骤:

  1. 创建代理类

  2. 创建getProxy方法获取代理类

  3. 使用Proxy.newProxyInstance方法来获取代理类

  4. 第三步的方法需要三个参数

    1. 运行时类的类加载器

    2. 运行时类的实现的所有的接口

    3. invocationHandler类,用来表述代理的具体实现过程

      invoke方法有三个参数

      1. proxy需要被代理对象
      2. method需要被代理的方法
      3. 被代理方法的参数

生产代理对象的工厂类:

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
package com.hgu.example;

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

public class ProxyFactory {
private Object target;
private InvocationHandler invocationHandler;

// 创建构造方法
public ProxyFactory(Object target){
this.target = target;
}


/**
* 获取代理对象
* 使用 newProxyInstance方法
* newProxyInstance方法有三个参数
* 1、classLoader:加载动态生成的代理类的类加载器
* 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
* 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法
* @return
*/
public Object getProxy(){
// 获取类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
// 获取目标类的所有接口组成的数组
Class<?>[] interfaces = target.getClass().getInterfaces();
//设置代理目标实现代理的过程
invocationHandler = new InvocationHandler() {
/**
* 该方法有三个参数
* 1. proxy代理对象
* 2. method 代理对象所需要实现的方法
* 3. args:method所对应的方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
System.out.println("[动态代理][日志]" +method.getName()+",参数:"+ Arrays.toString(args));
// 通过反射调用对应的方法
result = method.invoke(target, args);
System.out.println("[动态代理][日志]" +method.getName()+",结果:"+ result);
}catch (Exception e){
e.printStackTrace();
System.out.println("[动态代理][日志]" +method.getName()+",错误:"+ e.getMessage());
}finally {
System.out.println("[动态代理][日志]" +method.getName()+",成功,方法执行完毕:");
}
return result;
}
};
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
}
}

AOP概念和相关术语

概念

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术 。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

相关术语

①横切关注点

分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。

从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。

这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。

images

②通知(增强)

增强,通俗说,就是你想要增强的功能,比如 安全,事务,日志等。

每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

  • 前置通知:在被代理的目标方法执行
  • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝
  • 异常通知:在被代理的目标方法异常结束后执行(死于非命
  • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论
  • 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

images

③切面

封装通知方法的类。

images

④目标

被代理的目标对象。

⑤代理

向目标对象应用通知之后创建的代理对象。

⑥连接点

这也是一个纯逻辑概念,不是语法定义的。

把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方

images

⑦切入点

定位连接点的方式。

每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。

如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。

Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法

切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

作用

  • 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
  • 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。

基于注解的AOP

images

image-20221216132844066

  • 动态代理分为JDK动态代理和cglib动态代理
  • 当目标类有接口的情况使用JDK动态代理和cglib动态代理没有接口时只能使用cglib动态代理.
  • JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
  • cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承 目标类
  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口 (兄弟两个拜把子模式)。
  • cglib:通过继承被代理的目标类 (认干爹模式)实现代理,所以不需要目标类实现接口。
  • AspectJ:是AOP思想的一种实现 。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解,spring使用Aspectj中的注解实现AOP

准备工作

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.2</version>
</dependency>

各种通知

  • 前置通知:使用@Before注解标识,在被代理的目标方法之前执行 .
  • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行 .
  • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行 .
  • 后置通知:使用@After注解标识,在被代理的目标方法最终执行 .
  • 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

各种通知的执行顺序:

  • Spring版本5.3.x以前:
    • 前置通知
    • 目标操作
    • 后置通知
    • 返回通知或异常通知
  • Spring版本5.3.x以后:
    • 前置通知
    • 目标操作
    • 返回通知或异常通知
    • 后置通知

切入点表达式语法

images

  • 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限

  • 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。

    • 例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
  • 在包名的部分,使用“*…”表示包名任意、包的层次深度任意

  • 在类名的部分,类名部分整体用*号代替,表示类名任意

  • 在类名的部分,可以使用*号代替类名的一部分

    • 例如:*Service匹配所有名称以Service结尾的类或接口 .
  • 在方法名部分,可以使用*号表示方法名任意

  • 在方法名部分,可以使用*号代替方法名的一部分

    • 例如:*Operation匹配所有方法名以Operation结尾的方法 .
  • 在方法参数列表部分,使用(..)表示参数列表任意.

  • 在方法参数列表部分,使用(int

  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的

    • 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
  • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符 .
    • 例如:execution(public int Service. (…, int)) 正确
      例如:execution(
      int *…Service. (…, int)) 错误

创建切面类并配置

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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描 -->
<context:component-scan base-package="com.hgu.annotation"></context:component-scan>
<!-- 开始aspectj自动代理,为目标对象生成代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package com.hgu.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect // 这个注解表明这个类是一个切面类即AOP
@Component // 这个注解保证这个切面类能够放入IOC容器
public class LogAspect {

/**
* 前置通知
* @param joinPoint
*/
@Before(value = "execution(* com.hgu.annotation.impl.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("前置方法..., 方法名称:"+name + "方法参数: "+ Arrays.toString(args));
}

/**
* 后置通知
* @param joinPoint
*/
@After(value = "execution(* com.hgu.annotation.impl.CalculatorImpl.*(..)))")
public void afterMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("后置方法..., 方法名称:"+name + "方法参数: "+Arrays.toString(args));
}

/**
* 返回通知,我们的方法如果有返回值的话,这个方法可以获取返回值
* 需要在注解中添加一个returning = “返回结果的名称”
* 在方法中添加Object 返回结果名称
* 这两个返回结果名称一定要对应起来
* @param joinPoint
*/
@AfterReturning(value = "execution(* com.hgu.annotation.impl.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("后置返回方法..., 方法名称:"+name + "方法参数: "+Arrays.toString(args) + ", 返回结果 :" + result);
}


/**
* 错误方法,当执行的方法错误的时候才会调用这个方法,否则不会调用
* 获取返回值信息,需要在注解中添加一个throwing的参数
* 在方法中添加一个和注解中同名的参数,类型为Throwable
* @param joinPoint
* @param error
*/
@AfterThrowing(value = "execution(* com.hgu.annotation.impl.CalculatorImpl.*(..)))", throwing = "error")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable error){
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("异常通知....方法名称:"+name + "方法参数: "+Arrays.toString(args) + ", 异常通知: "+error);
}

/**
* 环绕通知
* @param proceedingJoinPoint
* @return
*/
@Around(value = "execution(* com.hgu.annotation.impl.CalculatorImpl.*(..)))")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
String name = proceedingJoinPoint.getSignature().getName();
Object[] args = proceedingJoinPoint.getArgs();
Object result = null;
try{
System.out.println("环绕通知....在方法执行前执行");
// 执行方法
result = proceedingJoinPoint.proceed();

System.out.println("环绕通知.....在方法返回值之后执行");

}catch (Throwable e) {
e.printStackTrace();
System.out.println("环绕通知....在方法异常的时候执行");
} finally {
System.out.println("环绕通知....在方法执行完之后执行");
}
return result;
}
}

重用切入点表达式

①声明

1
2
@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){}

②在同一个切面中使用

1
2
3
4
5
6
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

③在不同切面中使用

1
2
3
4
5
6
@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

获取通知的相关信息

①获取连接点信息

获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参

1
2
3
4
5
6
7
8
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
//获取连接点的签名信息
String methodName = joinPoint.getSignature().getName();
//获取目标方法到的实参信息
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

②获取目标方法的返回值

@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值

1
2
3
4
5
@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}

③获取目标方法的异常

@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常

1
2
3
4
5
@AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}

环绕通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
Object result = null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标方法的执行,目标方法的返回值一定要返回给外界调用者
result = joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
} finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
}
return result;
}

切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

  • 优先级高的切面:外面
  • 优先级低的切面:里面

使用@Order注解可以控制切面的优先级:

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低

images

基于XML文件的AOP

配置文件

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
<?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
https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启组件扫描 -->
<context:component-scan base-package="com.hgu.aopxml"></context:component-scan>
<!-- 配置AOP -->
<aop:config>
<!-- 配置我们的Aspect类的指向 -->
<aop:aspect ref="logAspect">
<!-- 配置表达式 -->
<aop:pointcut id="pointcut" expression="execution(* com.hgu.aopxml.impl.CalculatorImpl.*(..)))"/>
<!-- 配置五种通知类型 -->
<!-- 前置通知 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
<!-- 后置通知 -->
<aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
<!-- 后置返回通知 -->
<aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointcut" ></aop:after-returning>
<!-- 环绕通知 -->
<aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowingMethod" throwing="error" pointcut-ref="pointcut"></aop:after-throwing>
</aop:aspect>

</aop:config>
</beans>

单元测试:JUnit

Junit5和4的整合的主要区别就是注解的区别

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--spring对junit的支持相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.2</version>
</dependency>

<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.0</version>
</dependency>

添加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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
https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启组件扫描 -->
<context:component-scan base-package="com.hgu.junit"/>

</beans>

创建准备类
User

1
2
3
4
5
6
7
8
9
10
package com.hgu.junit.junit5;

import org.springframework.stereotype.Component;

@Component
public class User {
public void run(){
System.out.println("User run....");
}
}

Junit5测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.hgu.junit.junit5;


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(locations = "classpath:aopxml.xml")
public class Junit5Test {
@Autowired
private User user;

@Test
public void test01(){
System.out.println(user);
user.run();
}
}

Junit4测试

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans.xml")
public class JUnit4Test {

@Autowired
private User user;

@Test
public void testUser(){
System.out.println(user);
}
}

事务

JdbcTemplate

准备

引入配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<!--spring jdbc Spring 持久化层支持jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
</dependencies>

创建配置文件

jdbc.properties

1
2
3
4
jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver

配置Spring配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 导入外部文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />

<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driver" value="${jdbc.driver}"/>
</bean>

<!-- 配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
<!-- 装配数据源 -->
<property name="dataSource" ref="druidDataSource" />
</bean>
</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
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
@SpringJUnitConfig(locations = "classpath:beans.xml")
public class JdbcTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;

/**
* 添加操作
*/
@Test
public void test01(){
String sql = "INSERT INTO t_emp VALUES(null,?,?,?)";
int update = jdbcTemplate.update(sql, "狗男", 18, "未知");
System.out.println(update);
}

/**
* 删除操作
*/
@Test
public void test02(){
String sql = "DELETE FROM t_emp WHERE id=?";
int update = jdbcTemplate.update(sql, 1);
System.out.println(update);
}

/**
* 修改操作
*/
@Test
public void test03(){
String sql = "UPDATE t_emp SET name=? WHERE id=?";
int update = jdbcTemplate.update(sql, "狗策", 2);
System.out.println(update);
}


/**
* 查询操作1
* 1. 查询单个操作,并自己封装函数
*/
@Test
public void test04(){
String sql = "SELECT * FROM t_emp WHERE id = ?";
Emp emp1 = jdbcTemplate.queryForObject(sql,
(rs, rowNum) -> {
Emp emp = new Emp();
emp.setId(rs.getInt("id"));
emp.setName(rs.getString("name"));
emp.setAge(rs.getInt("age"));
emp.setSex(rs.getString("sex"));
return emp;
},
2
);
System.out.println(emp1);
}

/**
* 查询操作
* 2. 查询操作 使用已封装的配置类
* BeanPropertyRowMapper<>(目标类)
*/
@Test
public void test05(){
String sql = "SELECT * FROM t_emp WHERE id = ?";
Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 2);
System.out.println(emp);
}

/**
* 查询操作
* 3. 查询多个 使用已封装的配置类
*/
@Test
public void test06(){
String sql = "SELECT * FROM t_emp";
List<Emp> emps = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));
for(Emp emp : emps){
System.out.println(emp);
}
}

/**
* 查询操作
* 4. 查询单个的值
*/
@Test
public void test07(){
String sql = "SELECT COUNT(*) FROM t_emp";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println(integer);
}
}

事务概念

事务的基本概念

什么是事务

数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。

事务的特性

A:原子性(Atomicity)

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

C:一致性(Consistency)

事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。

如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。

如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。

I:隔离性(Isolation)

指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。

D:持久性(Durability)

指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

编程式事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Connection conn = ...;

try {

// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);

// 核心操作

// 提交事务
conn.commit();

}catch(Exception e){

// 回滚事务
conn.rollBack();

}finally{

// 释放数据库连接
conn.close();

}

编程式的实现方式存在缺陷:

  • 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
  • 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。

声明式事务

既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。

封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。

  • 好处1:提高开发效率
  • 好处2:消除了冗余的代码
  • 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化

所以,我们可以总结下面两个概念:

  • 编程式自己写代码实现功能
  • 声明式:通过配置框架实现功能

基于注解的声明式事务

准备

  1. 开启组件扫描

    1
    <context:component-scan base-package="com.hug.tx" />
  2. 创建的对应的表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    CREATE TABLE `t_book` (
    `book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    `book_name` varchar(20) DEFAULT NULL COMMENT '图书名称',
    `price` int(11) DEFAULT NULL COMMENT '价格',
    `stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)',
    PRIMARY KEY (`book_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
    insert into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100);
    CREATE TABLE `t_user` (
    `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    `username` varchar(20) DEFAULT NULL COMMENT '用户名',
    `balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',
    PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    insert into `t_user`(`user_id`,`username`,`balance`) values (1,'admin',50);
  3. 创建BookDAO BookDAOImpl BookService BookServiceImpl BookController

无事务情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BookServiceImpl implements BookService{
@Autowired
private BookDAO bookDAO;

@Override
public void buyBook(Integer bookId, Integer userId) {
// 库存减一
bookDAO.updateStock(bookId);
// 用户财产减一
// 获取该图书的价格
Double bookPrice = bookDAO.getBookPriceByID(bookId);
bookDAO.updateBalance(userId, bookPrice);

}
}

添加事务

引入配置

1
2
3
4
5
6
7
8
9
10
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>

<!--
开启事务的注解驱动
通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />

添加注解

@Transactional标识在方法上,则只会影响该方法

@Transactional标识的类上,则会影响类中所有的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class BookServiceImpl implements BookService{
@Autowired
private BookDAO bookDAO;

@Transactional
@Override
public void buyBook(Integer bookId, Integer userId) {
// 库存减一
bookDAO.updateStock(bookId);
// 用户财产减一
// 获取该图书的价格
Double bookPrice = bookDAO.getBookPriceByID(bookId);
bookDAO.updateBalance(userId, bookPrice);

}
}

Transaction配置参数

只读

介绍

对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。

代码

1
2
3
4
5
6
7
8
9
10
@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
//System.out.println(1/0);
}

注意

对增删改操作设置只读会抛出下面异常:

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

超时

介绍

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。

概括来说就是一句话:超时回滚,释放资源。

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//超时时间单位秒
@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
//System.out.println(1/0);
}

观察结果

执行过程中抛出异常:

org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022

回滚策略

介绍

声明式事务默认只针对运行时异常回滚,编译时异常不回滚。

可以通过@Transactional中相关属性设置回滚策略

  • rollbackFor属性:需要设置一个Class类型的对象

  • rollbackForClassName属性:需要设置一个字符串类型的全类名

  • noRollbackFor属性:需要设置一个Class类型的对象

  • rollbackFor属性:需要设置一个字符串类型的全类名

使用方式

1
2
3
4
5
6
7
8
9
10
11
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
System.out.println(1/0);
}

观察结果

虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚,因此购买图书的操作正常执行

隔离级别

  • 读未提交:READ UNCOMMITTED

    允许Transaction01读取Transaction02未提交的修改。

  • 读已提交:READ COMMITTED、

    要求Transaction01只能读取Transaction02已提交的修改。

  • 可重复读:REPEATABLE READ

    确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

  • 串行化:SERIALIZABLE

    确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

各个隔离级别解决并发问题的能力见下表:

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

各种数据库产品对事务隔离级别的支持程度:

隔离级别 Oracle MySQL
READ UNCOMMITTED ×
READ COMMITTED √(默认)
REPEATABLE READ × √(默认)
SERIALIZABLE

②使用方式

1
2
3
4
5
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

事务属性:传播行为

什么是传播行为:有两个事务方法相互调用的时候,各自的事务是如何传播的,是合并到一个事务中,还是从新开启一个事务

一共有七种传播行为:

  • REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行**【有就加入,没有就不管了】**
  • MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常**【有就加入,没有就抛异常】**
  • REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起**【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】**
  • NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务**【不支持事务,存在就挂起】**
  • NEVER:以非事务方式运行,如果有事务存在,抛出异常**【不支持事务,存在就抛异常】**
  • NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

测试

方法一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Transactional(propagation = Propagation.REQUIRED)
@Service
public class BookServiceImpl implements BookService{
@Autowired
private BookDAO bookDAO;

@Override
public void buyBook(Integer bookId, Integer userId) {
// 库存减一
bookDAO.updateStock(bookId);
// 用户财产减一
// 获取该图书的价格
System.out.println(1/0);
Double bookPrice = bookDAO.getBookPriceByID(bookId);
bookDAO.updateBalance(userId, bookPrice);

}
}

方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TestTx {
@Autowired
private BookController bookController;

@Test
@Transactional
public void testBuyBook(){
for(int i = 0; i < 2; i++){
bookController.buyBook(1, 1);
}
}
}

方法一和方法二都是事务方法,在方法二中调用方法一,因为方法一采用的是REQUIRED,方法二没有写,则默认也是REQUIRED有就加入没有就新建,方法二没有事务,则新建,在方法二中调用方法一,因为方法二中有事务,所以方法一会继续使用方法二中的事务,方法一中出错的话,则方法一和方法二都会回滚

全注解实现事务

创建配置类

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
@Configuration      // 该注解表明该类是配置类
@ComponentScan("com.hug.tx") // 开启自动扫描的包
@EnableTransactionManagement // 开启事务操作
public class SpringConfig {
@Bean // 创建bean容器
public DataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("admin");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?CharacterEncoding=utf8&useUnicode=true&serverTimezone=UTC");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
return dataSource;
}

@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}

@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}

测试:

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

@Test
@Transactional
public void testBuyBook(){
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
BookController controller = (BookController) annotationConfigApplicationContext.getBean("bookController");
controller.buyBook(1,1);
}
}

基于XML的声明式事务