Spring详细总结
文章目录
- 1.Spring简介
- 1.1 Spring概述
- 1.2 Spring Framework
- 1.2.1、Spring Framework特性
- 1.2.2 Spring Framework五大功能模块
- 2.IOC
- 2.1 IOC容器
- 2.1.1 IOC思想
- 2.1.2 IOC容器在Spring中的实现
- 2.2 基于XML管理bean
- 2.2.1 入门案例
- 2.2.2 获取bean
- 2.2.3 依赖注入
- 2.2.3.1 依赖注入之setter注入
- 2.2.3.2 依赖注入之构造器注入
- 2.2.4 特殊值处理
- 2.2.5 为类类型属性赋值
- 2.2.6 为数组类型属性赋值
- 2.2.7 为集合类型属性赋值
- 2.2.7.1 为List集合类型属性赋值
- 2.2.7.2 为Map集合类型属性赋值
- 2.2.8 p命名空间、c命名空间
- 2.2.8.1 p命名空间
- 2.2.8.2 c命名空间注入
- 2.2.9 引入外部属性文件
- 2.2.10 bean的作用域
- 2.2.11 基于xml的自动装配
- 2.3 Spring IoC注解式开发
- 2.3.1 标记与扫描
- 2.3.2 声明Bean的注解
- 2.3.3 给Bean的属性赋值
- 2.3.3.1 Value注解
- 2.3.3.2 Autowired和Qualifier注解
- 2.3.3.3 Resource注解
- 2.3.4 全注解式开发(@Configuration,@Bean)
- 2.4 Bean的实例化方式
- 2.4.1 通过构造方法实例化
- 2.4.2 通过简单工厂模式实例化
- 2.4.3 通过factory-bean实例化
- 2.4.4 通过FactoryBean接口实例化
- 2.5 BeanFactory和FactoryBean的区别
- 2.6 bean的生命周期
- 3.AOP
- 3.1 AOP概念及相关术语
- 3.1.1 概述
- 3.1.2 相关术语
- 3.3.3、作用
- 3.4 基于注解的AOP
- 3.4.1 技术说明
- 3.4.2 准备工作
- 3.4.3 创建切面类并配置
- 3.4.4 各种通知
- 3.4.5 切入点表达式语法
- 3.4.6 重用切入点表达式
- 3.4.7 获取通知的相关信息
- 3.4.8 环绕通知
- 3.4.9 切面的优先级
- 3.5 基于XML的AOP(了解)
- 4 声明式事务
- 4.2 声明式事务概念
- 4.2.1 编程式事务
- 4.2.2、声明式事务
- 4.3 基于注解的声明式事务
- 4.3.1 加入事务
- 4.3.1 @Transactional注解标识的位置
- 4.3.2 事务属性:只读
- 4.3.3 事务属性:超时
- 4.3.4 事务属性:回滚策略
- 4.4.5 事务属性:事务隔离级别
1.Spring简介
1.1 Spring概述
官网地址:https://spring.io/
(1)Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用Spring 框架
来创建性能好(spring为我们提供对象的创建)、易于测试(整合了Junit)、可重用的代码(例如把事务的代码放到切面中,
再把切面作用于方法中)。
(2)Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首次在
Apache 2.0 许可下发布。
(3)Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。
(4)Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应
用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO
编程模型来促进良好的编程实践。
1.2 Spring Framework
Spring Framework就是Spring 的基础框架,可以视为 Spring 基础设施,基本上任何其他 Spring 项目都是以
Spring Framework为基础的。
1.2.1、Spring Framework特性
(1)非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。
对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会
破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序
时结构清晰、简洁优雅。Spring是非侵入式的,Spring应用中的对象不依赖于其他特定类
(2)控制反转:IOC(Inversion of Control),反转资源获取方向。把自己创建资源、向环境索取资源
变成环境将资源准备好,我们享受资源注入。
(3)面向切面编程:AOP(Aspect Oriented Programming),在不修改源代码的基础上增强代码功能。
(4)容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化
的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发
效率。
(5)组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML
和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭
建超大型复杂应用系统。
(6)声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
(7)一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架(如mybatis)和优秀的第三方类库。而且
Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基
础上全部使用 Spring 来实现
1.2.2 Spring Framework五大功能模块
2.IOC
2.1 IOC容器
2.1.1 IOC思想
(1)IOC:Inversion of Control,翻译过来是控制反转。是面向对象编程中的一种设计思想,可以用来降低代码之间的耦合度,符合依赖倒置原则。
(2)控制反转的核心是:将对象的创建权交出去,将对象和对象之间关系的管理权交出去,由第三方容器来负责创建与维护。
- 获取资源的传统方式
①就像自己做饭:买菜、洗菜、择菜、改刀、炒菜,全过程参与,费时费力,必须清楚了解资源创建整个
过程中的全部细节且熟练掌握。
②在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的
模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。 - 控制反转方式获取资源
①就像点外卖:下单、等、吃,省时省力,不必关心资源创建过程的所有细节。
②控制反转的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主
动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源
的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
(3)DI(Dependency Injection),翻译过来是依赖注入。DI 是 IOC 的具体实现方式:即组件以一些预先定义好的方式
(例如:set 注入,构造器注入)接收来自于容器的资源注入。总结就是:IOC 是一种控制反转的思想, 而 DI
是对 IOC 的一种具体实现。
2.1.2 IOC容器在Spring中的实现
Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件叫做 bean。在创建bean 之前,
首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式:
①BeanFactory
这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
②ApplicationContext
BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用ApplicationContext
而不是底层的 BeanFactory。ApplicationContext的主要实现类如下:
2.2 基于XML管理bean
2.2.1 入门案例
(1)创建maven工程
(2)引入依赖
<dependencies>
<!-- 导入spring-context依赖是spring的基础依赖
如果想使用spring的jdbc或者其他,那么还需要导入其他依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
spring-context结构如下:
(3)创建Student类
public class Student {
private Integer sid;
private String name;
private Integer age;
private String gender;
// 全参,无参构造器
// Get/Set方法,toString方法
}
(4)创建Spring配置文件
(5)在Spring中配置bean
applicationContext.xml
<!--
配置Student所对应的bean,即将Student的对象交给Spring的IOC容器管理;通过bean标签配置IOC容器所管理的bean
属性:
id:设置bean的唯一标识
class:设置bean所对应类型的全类名
-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.atguigu.spring.Student"></bean>
</beans>
(6)创建测试类测试
public class IocTest {
@Test
public void testIoc(){
// 第一步:获取Spring容器对象
// ApplicationContext 翻译为:应用上下文,其实就是Spring容器;ApplicationContext是一个接口
// ApplicationContext 接口下有很多实现类,其中有一个实现类叫做:ClassPathXmlApplicationContext
// ClassPathXmlApplicationContext是专门从类路径当中加载spring配置文件的一个spring上下文对象
// 下面这行代码就相当于启动了spring容器,解析spring.xml文件,并且实例化所有的bean对象,放到spring容器当中
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
// 第二步:获取bean(在IOC容器中,通过 xml解析+反射机制+工厂模式 来创建对象)
// dom4j解析beans.xml文件,从中获取class的全限定类名
// 默认情况下spring会通过反射机制,调用类的无参构造方法来实例化对象,实现原理如下:
// Class clazz = Class.forName("类的全路径");
// Object obj = clazz.newInstance();
Student student = ioc.getBean(Student.class);
System.out.println(student);
} }
(7)注意
①Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,这一点需要注意。如果在需要
无参构造器时,没有无参构造器,则会抛出异常
②在spring的配置文件中id是不能重名的
③把创建好的对象存储到一个什么样的数据结构当中了呢?
④spring的配置文件可以有多个,在ClassPathXmlApplicationContext构造方法的参数上传递文件路径即可。这是为什么呢?通过源码可以看到:
⑤在spring配置文件中配置的bean可以是任意类,包括jdk中的类,只要这个类不是抽象的,并且提供了无参数构造方法。
⑥BeanFactory是Spring容器的超级接口。ApplicationContext是BeanFactory的子接口。
⑦配置文件如果没有在类路径当中的话,需要使用FileSystemXmlApplicationContext类进行加载配置文件。
ApplicationContext applicationContext2 = new FileSystemXmlApplicationContext("d:/spring6.xml");
Vip vip = applicationContext2.getBean("vipBean2", Vip.class);
System.out.println(vip);
2.2.2 获取bean
(1)方式一:根据id获取
由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。
(2)方式二:根据类型获取
@Test
public void testHelloWorld(){
ApplicationContext ac = newClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld bean = ac.getBean(HelloWorld.class);
bean.sayHello();
}
(3)方式三:根据id和类型
@Test
public void testHelloWorld(){
ApplicationContext ac = newClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld bean = ac.getBean("helloworld", HelloWorld.class);
bean.sayHello();
}
(4)注意
当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个。当IOC容器中一共配置了两个,
那么根据类型获取时就会抛出异常
(5)扩展
①如果组件类实现了接口,根据接口类型可以获取 bean 吗?
可以,前提是bean唯一
②如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?
不行,因为bean不唯一
(6)结论
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』
的返回结果,只要返回的是true就可以认定为和类型匹配,就能够获取到。
2.2.3 依赖注入
2.2.3.1 依赖注入之setter注入
(1)创建学生类Student
public class Student {
private Integer id;
private String name;
private Integer age;
private String sex;
//构造,Getter,Setter, toSting方法
(2)配置bean时为其属性赋值
<bean id="studentOne" class="com.atguigu.spring.bean.Student">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)-->
<!-- value属性:指定属性值 -->
<property name="id" value="1001"></property>
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
<property name="sex" value="男"></property>
</bean>
(3)测试
@Test
public void testDIBySet(){
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");
Student studentOne = ac.getBean("studentOne", Student.class);
System.out.println(studentOne);
}
(4)实现原理:
通过property标签获取到属性名:userDao
通过属性名推断出set方法名:setUserDao
通过反射机制调用setUserDao()方法给属性赋值
property标签的name是属性名。
property标签的ref是要注入的bean对象的id。(通过ref属性来完成bean的装配,这是bean最简单的一种装配方式。装配指的是:创建系统组件之间关联的动作)
2.2.3.2 依赖注入之构造器注入
核心原理:通过调用构造方法来给属性赋值
(1)在Student类中添加有参构造
public Student(Integer id, String name, Integer age, String sex) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
}
(2)配置bean
<bean id="studentTwo" class="com.atguigu.spring.bean.Student">
<constructor-arg value="1002"></constructor-arg>
<constructor-arg value="李四"></constructor-arg>
<constructor-arg value="33"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
</bean>
注意:
constructor-arg标签还有两个属性可以进一步描述构造器参数:
①index属性:指定参数所在位置的索引(从0开始)
②name属性:指定参数名
2.2.4 特殊值处理
(1)字面量赋值
什么是字面量?
int a = 10;
①声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a
的时候,我们实际上拿到的值是10。
②而如果a是带引号的:‘a’,那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面量。
所以字面量没有引申含义,就是我们看到的这个数据本身。
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="name" value="张三"/>
(2)注入null值
①第一种方式:不给属性赋值
②第二种方式:使用<null/>
<property name="name">
<null/>
</property>
注意:以下写法,为name所赋的值是字符串null
<property name="name" value="null"></property>
(3)注入空字符串
<?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="vipBean" class="com.powernode.spring6.beans.Vip">
<!--空串的第一种方式-->
<!--<property name="email" value=""/>-->
<!--空串的第二种方式-->
<property name="email">
<value/>
</property>
</bean>
</beans>
(4)XML中有5个特殊字符,分别是:<、>、’、”、&
以上5个特殊符号在XML中会被特殊对待,会被当做XML语法的一部分进行解析,如果这些特殊符号直接出现在注入的字符串当中,会报错
解决方案包括两种:
● 第一种:特殊符号使用转义字符代替。
● 第二种:将含有特殊符号的字符串放到:<![CDATA[]]> 当中。因为放在CDATA区中的数据不会被XML文件解析器解析。
①使用转义字符代替
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a < b"/>
②CDATA节
<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>
2.2.5 为类类型属性赋值
2.2.5.1 创建相关的类
①创建班级类Clazz
public class Clazz {
private Integer clazzId;
private String clazzName;
//有参,无参构造器
//Get/Set方法,toString
②修改Student类
public class Student {
private Integer sid;
private String name;
private Integer age;
private String gender;
private Clazz clazz;//添加Clazz属性
//有参,无参构造器
//Getter,Setter方法,toString
}
2.2.5.2 实现方式
(1)方式1:引入外部已声明的bean
①配置Clazz类型的bean:
<bean id="clazzOne" class="com.atguigu.spring.bean.Clazz">
<property name="clazzId" value="1111"></property>
<property name="clazzName" value="财源滚滚班"></property>
</bean>
②为Student中的clazz属性赋值:
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>
</bean>
(2)方式二:注入内部bean
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<property name="clazz">
<!-- 在一个bean中再声明一个bean就是内部bean -->
<!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
<bean id="clazzInner" class="com.atguigu.spring.bean.Clazz">
<property name="clazzId" value="2222"></property>
<property name="clazzName" value="远大前程班"></property>
</bean>
</property>
</bean>
(3)方式三:级联属性赋值(一般不使用)
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="name" value="赵六"></property>
<!-- 使用级联属性赋值需要注意两点:
1.配置的顺序不能颠倒,必须如下顺序
2.clazz属性必须提供getter方法 -->
<property name="clazz" ref="clazzOne"></property>
<property name="clazz.clazzName" value="最强王者班"></property>
</bean>
<bean id="clazzOne" class="com.atguigu.spring.bean.Clazz"></bean>
2.2.6 为数组类型属性赋值
(1)修改Student类,在Student类中添加以下代码:
private String[] hobbies; // Get/Set方法
(2)配置bean
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="hobbies">
<array>
<value>跳舞</value>
<value>唱歌</value>
<value>蹦迪</value>
</array>
</property>
</bean>
2.2.7 为集合类型属性赋值
2.2.7.1 为List集合类型属性赋值
(1)在Clazz类中添加以下代码:
private List<Student> students; // Get/Ste方法
(2)配置bean
<bean id="clazzTwo" class="com.atguigu.spring.bean.Clazz">
<property name="students">
<list>
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</list>
</property>
</bean>
注意:若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可
2.2.7.2 为Map集合类型属性赋值
(1)创建教师类Teacher:
public class Teacher {
private Integer teacherId;
private String teacherName;
}
(2)在Student类中添加以下代码:
private Map<String, Teacher> teacherMap;
(3)配置bean
<bean id="teacherOne" class="com.atguigu.spring.bean.Teacher">
<property name="teacherId" value="10010"></property>
<property name="teacherName" value="大宝"></property>
</bean>
<bean id="teacherTwo" class="com.atguigu.spring.bean.Teacher">
<property name="teacherId" value="10086"></property>
<property name="teacherName" value="小宝"></property>
</bean>
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="teacherMap">
// key:是字面量用key,是类类型用key-ref
// value:是字面量用value,是类类型用value-ref
<map>
<entry key="10086" value-ref="teacherOne"></entry>
<entry key="10010" value-ref="teacherTwo"></entry>
</map>
</property>
</bean>
2.2.8 p命名空间、c命名空间
2.2.8.1 p命名空间
(1)p命名空间是简化setter注入的
(2)使用p命名空间注入的前提条件包括两个:
● 第一:在XML头部信息中添加p命名空间的配置信息:xmlns:p=“http://www.springframework.org/schema/p”
● 第二:p命名空间注入是基于setter方法的,所以需要对应的属性提供setter方法。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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="customerBean" class="com.powernode.spring6.beans.Customer" p:name="zhangsan" p:age="20"/>
</beans>
2.2.8.2 c命名空间注入
(1)c命名空间是简化构造方法注入的。
(2)使用c命名空间的两个前提条件:
第一:需要在xml配置文件头部添加信息:xmlns:c=“http://www.springframework.org/schema/c”
第二:需要提供构造方法。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
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="myTimeBean" class="com.powernode.spring6.beans.MyTime" c:year="1970" c:month="1" c:day="1"/>-->
<bean id="myTimeBean" class="com.powernode.spring6.beans.MyTime" c:_0="2008" c:_1="8" c:_2="8"/>
</beans>
2.2.9 引入外部属性文件
我们都知道编写数据源的时候是需要连接数据库的信息的,例如:driver url username password等信息。这些信息可以单独写到一个属性配置文件中吗,这样用户修改起来会更加的方便。
(1)加入依赖
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
(2)在类路径下创建外部属性文件jdbc.properties,并配置信息
(3)在spring配置文件中引入context命名空间。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
(4)引入属性文件
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
(5)配置bean
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
(5)测试
@Test
public void testDataSource() throws SQLException {
ApplicationContext ac = new ClassPathXmlApplicationContext("springdatasource.xml");
DataSource dataSource = ac.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
2.2.10 bean的作用域
(1)scope属性的值
● singleton:默认的,单例。在IOC容器初始化时创建对象
● prototype:原型。这个bean在IOC容器中有多个实例,每调用一次getBean()方法的时候,实例化该Bean对象。
● request:一个请求对应一个Bean。仅限于在WEB应用中使用。
● session:一个会话对应一个Bean。仅限于在WEB应用中使用。
● global session:portlet应用中专用的。如果在Servlet的WEB应用中使用global session的话,和session一个效果。(portlet和servlet都是规范。servlet运行在servlet容器中,例如Tomcat。portlet运行在portlet容器中。)
● application:一个应用对应一个Bean。仅限于在WEB应用中使用。
● websocket:一个websocket生命周期对应一个Bean。仅限于在WEB应用中使用。
(2)自定义scope:(自定义一个类实现Scope接口即可使用)
接下来咱们自定义一个Scope,线程级别的Scope,在同一个线程中,获取的Bean都是同一个;跨线程则是不同的对象。
①第一步:自定义Scope。
○ spring内置了线程范围的类:org.springframework.context.support.SimpleThreadScope(该类已经实现了Scope接口,可以直接用)
②第二步:将自定义的Scope注册到Spring容器中
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="myThread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
③第三步:使用Scope
<bean id="sb" class="com.powernode.spring6.beans.SpringBean" scope="myThread" />
④测试
@Test
public void testCustomScope(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class);
SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb1);
System.out.println(sb2);
// 启动线程
new Thread(new Runnable() {
@Override
public void run() {
SpringBean a = applicationContext.getBean("sb", SpringBean.class);
SpringBean b = applicationContext.getBean("sb", SpringBean.class);
System.out.println(a);
System.out.println(b);
}
}).start();
}
2.2.11 基于xml的自动装配
(1)Spring还可以完成自动化的注入,自动化注入又被称为自动装配。它可以根据名字进行自动装配,也可以根据类型进行自动装配。
(2)配置bean
可以通过bean标签中的autowire属性设置自动装配的策略
(3)自动装配的策略
①no,default:都表示不装配,即bean中的属性不会自动匹配某个bean,为属性赋值,此时属性使用默认值
②byType:根据要赋值的属性的类型,在IOC容器中匹配某个bean,为属性赋值
注意:
a>若通过类型没有找到任何一个类型匹配的bean,此时不装配,属性使用默认值
b>若通过类型找到了多个类型匹配的bean,此时会抛出异常:NoUniqueBeanDefinitionException
<!-- 根据类型自动装配是基于set方式实现的-->
<bean id="userController" class="com.atguigu.autowire.xml.controller.UserController" autowire="byType">
</bean>
<bean id="userService" class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byType">
</bean>
<bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>
③byName
byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值。如果根据名称装配(byName),底层会调用set方法进行注入。
<!-- id一般也叫做bean的名称,根据名字进行自动装配的时候,被注入的对象的bean的id不能随便写,
set方法的方法名去掉set,剩下单词首字母小写 -->
<bean id="userController" class="com.atguigu.autowire.xml.controller.UserController" autowire="byName">
</bean>
<bean id="userService" class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName">
</bean>
<bean id="userServiceImpl" class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName">
</bean>
<bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoImpl" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl">
</bean>
2.3 Spring IoC注解式开发
2.3.1 标记与扫描
(1)注解
①和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测
到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
②本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。
③举例:元旦联欢会要布置教室,蓝色的地方贴上元旦快乐四个字,红色的地方贴上拉花,黄色的地方贴
上气球。班长做了所有标记,同学们来完成具体工作。墙上的标记相当于我们在代码中使用的注解,后面
同学们做的工作,相当于框架的具体操作。
(2)扫描组件
Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注
解进行后续操作。
①情况一:最基本的扫描方式
<context:component-scan base-package="com.atguigu"></context:component-scan>
②情况二:指定要排除的组件
<context:component-scan base-package="com.atguigu">
<!-- 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.controller.UserController"/>-->
</context:component-scan>
③情况三:仅扫描指定组件
<context:component-scan base-package="com.atguigu" use-default-filters="false">
<!-- context:include-filter标签:包含扫描 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--<context:include-filter type="assignable" expression="com.atguigu.controller.UserController"/>-->
</context:component-scan>
2.3.2 声明Bean的注解
(1)负责声明Bean的注解,常见的包括四个:
①@Component:将类标识为普通组件
②@Controller:将类标识为控制层组件
③@Service:将类标识为业务层组件
④@Repository:将类标识为持久层组件
(2)通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解
的基础上起了三个新的名字。
对于Spring使用IOC容器管理这些组件来说没有区别。所以@Controller、@Service、@Repository这
三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。
注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。
(3)组件所对应的bean的id
在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识,便于在其他地方引用。现在使用
注解后,每个组件仍然应该有一个唯一标识。
①默认情况
类名首字母小写就是bean的id。例如:UserController类对应的bean的id就是userController。
②自定义bean的id
可通过标识组件的注解的value属性设置自定义的bean的id
@Service("userService")//默认为userServiceImpl
public class UserServiceImpl implementsUserService {
}
2.3.3 给Bean的属性赋值
2.3.3.1 Value注解
当属性的类型是简单类型时,可以使用@Value注解进行注入
@Component
public class User {
@Value(value = "zhangsan")
private String name;
@Value("20")
private int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2.3.3.2 Autowired和Qualifier注解
(1)Autowired注解可以用来注入非简单类型,在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。我们在项目中的正式用法就是这样。
@Controller
public class UserController {
@Autowired
private UserService userService;
public void saveUser(){
userService.saveUser();
} }
注意:@Autowired注解也可以标记在构造器和set方法上
①@Autowired注解标记在构造器上
@Controller
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService){
this.userService = userService;
}
public void saveUser(){
userService.saveUser();
} }
②@Autowired注解标记在set方法上
@Controller
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService){
this.userService = userService;
} }
(2)@Autowired工作流程
补充:如果和所需类型匹配的bean不止一个
①没有@Qualifier注解:根据@Autowired标记位置成员变量的变量名作为bean的id进行匹配
能够找到:执行装配;找不到:装配失败
②使用@Qualifier注解:根据@Qualifier注解中指定的名称作为bean的id进行匹配。
能够找到:执行装配;找不到:装配失败
@Controller
public class UserController {
@Autowired
@Qualifier("userServiceImpl")
private UserService userService;
public void saveUser(){
userService.saveUser();
} }
2.3.3.3 Resource注解
(1)@Resource注解也可以完成非简单类型注入。那它和@Autowired注解有什么区别?
①Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
②Autowired注解是Spring框架自己的。
③Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
④Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
⑤Resource注解用在属性上、setter方法上。
⑥Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
(2)引入依赖
Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。】
①如果你是Spring6+版本请使用这个依赖
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
一定要注意:如果你用Spring6,要知道Spring6不再支持JavaEE,它支持的是JakartaEE9。(Oracle把JavaEE贡献给Apache了,Apache把JavaEE的名字改成JakartaEE了,大家之前所接触的所有的 javax.*
包名统一修改为 jakarta.*
包名了。)
②如果你是spring5-版本请使用这个依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
2.3.4 全注解式开发(@Configuration,@Bean)
所谓的全注解开发就是不再使用spring配置文件了。写一个Java配置类来代替配置文件。在这个Java类中可以创建Java对象,把对象放入到spring容器中(注入到容器)
①@Configuration:放在一个类的上面,表示这个类是作为配置文件使用的
②@Bean:声明对象,把对象注入到容器中
@Configuration //org.springframework.context.annotation.Configuration;
@ComponentScan({
"com.powernode.spring6.dao", "com.powernode.spring6.service"})
public class Spring6Config {
/**
创建方法,方法的返回值是对象。在方法的上面加上@Bean注解
方法的返回值对象会注入到容器中
@Bean:把对象注入到spring容器中。作用相当于<bean>
(1)位置:方法的上面
(2)说明:@Bean,不指定对象的名称,bean的id默认是:方法名
@Bean的name属性,指定对象的名称(id)
*/
@Bean
public Student creatStudent(){
Student s = new Student();
s.setName("张三");
s.setAge(26);
s.setSex("男");
return s;
} }
@Test
public void test(){
ApplicationContext ctx = new AnnotationConfigApplicationContext(Spring6Config.class);
Student stu = ctx.getBean("creatStudent");
}
2.4 Bean的实例化方式
Spring为Bean提供了多种实例化方式,通常包括4种方式(也就是说在Spring中为Bean对象的创建准备了多种方案,目的是:更加灵活)
● 第一种:通过构造方法实例化
● 第二种:通过简单工厂模式实例化
● 第三种:通过factory-bean实例化
● 第四种:通过FactoryBean接口实例化
2.4.1 通过构造方法实例化
(1)创建类构造方法
package com.powernode.spring6.bean;
public class User {
public User() {
System.out.println("User类的无参数构造方法执行。");
}
}
(2)xml
<?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="userBean" class="com.powernode.spring6.bean.User"/>
</beans>
(3)测试程序
public class SpringInstantiationTest {
@Test
public void testConstructor(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("userBean", User.class);
System.out.println(user);
}
}
2.4.2 通过简单工厂模式实例化
(1)第一步:定义一个Bean
public class Vip {
}
(2)第二步:编写简单工厂模式当中的工厂类
public class VipFactory {
public static Vip get(){
return new Vip();
}
}
(3)第三步:在Spring配置文件中指定创建该Bean的方法(使用factory-method属性指定)
spring.xml
XML
<bean id="vipBean" class="com.powernode.spring6.bean.VipFactory" factory-method="get"/>
(4)测试程序
@Test
public void testSimpleFactory(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Vip vip = applicationContext.getBean("vipBean", Vip.class);
System.out.println(vip);
}
2.4.3 通过factory-bean实例化
这种方式本质上是:通过工厂方法模式进行实例化。
(1)第一步:定义一个Bean
package com.powernode.spring6.bean;
public class Order {
}
(2)第二步:定义具体工厂类,工厂类中定义实例方法
package com.powernode.spring6.bean;
public class OrderFactory {
public Order get(){
return new Order();
}
}
(3)第三步:在Spring配置文件中指定factory-bean以及factory-method
<bean id="orderFactory" class="com.powernode.spring6.bean.OrderFactory"/>
<bean id="orderBean" factory-bean="orderFactory" factory-method="get"/>
(4)测试程序
@Test
public void testSelfFactoryBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Order orderBean = applicationContext.getBean("orderBean", Order.class);
System.out.println(orderBean);
}
2.4.4 通过FactoryBean接口实例化
以上的第三种方式中,factory-bean是我们自定义的,factory-method也是我们自己定义的。
在Spring中,当你编写的类直接实现FactoryBean接口之后,factory-bean不需要指定了,factory-method也不需要指定了。
factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject()方法。
(1)第一步:定义一个Bean
package com.powernode.spring6.bean;
public class Person {
}
(2)第二步:编写一个类实现FactoryBean接口
package com.powernode.spring6.bean;
public class PersonFactoryBean implements FactoryBean<Person> {
@Override
public Person getObject() throws Exception {
return new Person();
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
// true表示单例
// false表示原型
return true;
}
}
(3)第三步:在Spring配置文件中配置FactoryBean
<bean id="personBean" class="com.powernode.spring6.bean.PersonFactoryBean"/>
(4)测试程序
@Test
public void testFactoryBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Person personBean = applicationContext.getBean("personBean", Person.class);
System.out.println(personBean)
}
2.5 BeanFactory和FactoryBean的区别
(1)BeanFactory
Spring IoC容器的顶级对象,BeanFactory被翻译为“Bean工厂”,在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。
BeanFactory是工厂。
(2) FactoryBean
FactoryBean:它是一个Bean,是一个能够辅助Spring实例化其它Bean对象的一个Bean。
FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个
FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是
getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都
屏蔽起来,只把最简洁的使用界面展示给我们。将来我们整合Mybatis时,Spring就是通过FactoryBean
机制来帮我们创建SqlSessionFactory对象的。
FactoryBean是一个接口,需要创建一个类去实现该接口,其中有三个方法:
①getObject():提供一个对象交给IOC容器管理
②getObjectType():设置所提供的对象的类型
③isSingleton():所提供的对象是否单例
当把FactoryBean的实现类配置为bean时,会把当前类中getObject()所返回的对象交给IOC容器管理
在Spring中,Bean可以分为两类:
● 第一类:普通Bean
● 第二类:工厂Bean(记住:工厂Bean也是一种Bean,只不过这种Bean比较特殊,它可以辅助Spring实例化其它Bean对象。)
2.6 bean的生命周期
(1)具体的生命周期过程
①实例化(bean对象的创建,调用无参构造器)
②依赖注入,给bean对象设置属性
③bean对象初始化之前操作(后置处理器的postProcessBeforeInitialization)
④初始化,需要通过bean的init-method属性指定初始化方法
⑤bean对象初始化之后操作(后置处理器的postProcessAfterInitialization)
⑥IOC容器关闭时销毁,需要通过bean的destroy-method属性指定销毁的方法
(2)配置bean
<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.atguigu.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod">
<property name="id" value="1001"></property>
<property name="username" value="admin"></property>
<property name="password" value="123456"></property>
<property name="age" value="23"></property>
</bean>
(3)bean的后置处理器
bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,
且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容
器中所有bean都会执行
(4)创建bean的后置处理器:
public class MyBeanProcessor implements BeanPostProcessor {
@Override // 此方法在bean的生命周期初始化 之前 执行
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("☆☆☆" + beanName + " = " + bean);
return bean;
}
@Override // 此方法在bean的生命周期初始化 之后 执行
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("★★★" + beanName + " = " + bean);
return bean;
} }
(5)在IOC容器中配置后置处理器
<!-- bean的后置处理器要放入IOC容器才能生效 -->
<bean id="myBeanProcessor" class="com.atguigu.spring.process.MyBeanProcessor"/>
3.AOP
3.1 AOP概念及相关术语
3.1.1 概述
AOP,即面向切面编程,它是面向对象编程的一种补充和完善,它通过预编译方式和运行期动态代理的方式实现在不修改源代码的情况下给程序动态统一添加额外功能。它将公共逻辑(比如事务管理、日志、缓存等)封装成切面,跟业务代码进行分离。
3.1.2 相关术语
(1)横切关注点
①从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方
法进行多个不同方面的增强。
②这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横
切关注点。
(2)通知
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
①前置通知:在被代理的目标方法前执行
②返回通知:在被代理的目标方法成功结束之后执行
③异常通知:在被代理的目标方法的catch子句中执行
④后置通知:在被代理的目标方法的finally子句中执行
⑤环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
(3)切面
封装通知方法的类
(4)目标
被代理的目标对象(非核心业务代码的对象)
(5)代理
向目标对象应用通知之后创建的代理对象。
(6)连接点
①这也是一个纯逻辑概念,不是语法定义的。
②把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点
就是连接点。就是我们抽取横切关注点的位置。
③连接点就是我们抽取横切关注点的位置,比如我们的通知方法是在核心代码之前抽取出来的,这就是一个连接点。
(7)切入点
①定位连接点的方式。
②每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。
③如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
④Spring 的 AOP 技术可以通过切入点定位到特定的连接点。
⑤切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
3.3.3、作用
(1)简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,
提高内聚性。
(2)代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就
被切面给增强了。
3.4 基于注解的AOP
3.4.1 技术说明
(1)动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因
为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
(2)cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
(3)AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最
终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
3.4.2 准备工作
(1)添加依赖(在IOC所需依赖基础上再加入下面依赖即可)
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
(2)准备被代理的目标资源
①接口:
public interface Calculator {
int add(int i, int j);
int div(int i, int j);
}
②实现类:
package com.zqg.spring;
@Component
public class CalculatorPureImpl implements Calculator {
@Override
public int add(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;
} }
3.4.3 创建切面类并配置
(1)创建切面类
@Aspect//表示该类是一个切面类
@Component// 保证这个切面类能够放入IOC容器
//在切面中,需要通过指定的注解将方法标识为通知方法
public class LogAspect {
// 第一个*表示任意的访问修饰符和返回值类型
// 第二个*表示类中任意的方法
// execution是固定写法
@Before("execution(* com.zqg.spring.CalculatorImpl.*(int,int))")
public void beforeAdviceMethod(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();//获取连接点所对应方法的签名信息
String args = Arrays.toString(joinPoint.getArgs());//获取连接点所对应方法的参数
System.out.println("前置通知" + signature.getName() + args);
}
(2)在spring的配置文件中配置
<!-- AOP的主要事项:
切面类和目标类都需要交给IOC容器管理
切面类必须通过@Aspect注解标识为一个切面
-->
<context:component-scan base-package="com.zqg.spring"></context:component-scan>
<!-- 开启基于注解的AOP -->
<aop:aspectj-autoproxy/>
3.4.4 各种通知
①前置通知:使用@Before注解标识,在被代理的目标方法前执行
②返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
③异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
④后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
⑤环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包
括上面四种通知对应的所有位置
(1)各种通知的执行顺序:
1)Spring版本5.3.x以前:
①前置通知
②目标操作
③后置通知
④返回通知或异常通知
2)Spring版本5.3.x以后:
①前置通知
②目标操作
③返回通知或异常通知
④后置通知
3.4.5 切入点表达式语法
(1)用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
(2)在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
例如:*.Hello匹配com.Hello,不匹配com.spring.Hello
(3)在包名的部分,使用“*..”表示包名任意、包的层次深度任意
(4)在类名的部分,类名部分整体用*号代替,表示类名任意
(5)在类名的部分,可以使用*号代替类名的一部分
例如:*Service匹配所有名称以Service结尾的类或接口
(6)在方法名部分,可以使用*号表示方法名任意
(7)在方法名部分,可以使用*号代替方法名的一部分
例如:*Operation匹配所有方法名以Operation结尾的方法
(8)在方法参数列表部分,使用(..)表示参数列表任意
(9)在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
(10)在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
(11)在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
例如:execution(public int ..Service.*(.., int)) 正确
例如:execution(* int ..Service.*(.., int)) 错误
3.4.6 重用切入点表达式
(1)声明
@Pointcut("execution(* com.aop.annotation.*.*(..))")
public void pointCut(){
}
(2)在同一个切面中使用
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
(3)在不同切面中使用
@Before("com.spring.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);
}
3.4.7 获取通知的相关信息
(1)获取连接点信息
获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
@Before("execution(public int com.spring.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);
}
(2)获取目标方法的返回值
@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值
@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);
}
(3)获取目标方法的异常
@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常
@AfterThrowing(value = "execution(* com.spring.aop.annotation.CalculatorImpl.*
(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}
3.4.8 环绕通知
@Around("execution(* com.spring.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;
}
3.4.9 切面的优先级
(1)相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
①优先级高的切面:外面
②优先级低的切面:里面
(2)使用@Order注解可以控制切面的优先级:
①@Order(较小的数):优先级高
②@Order(较大的数):优先级低
3.5 基于XML的AOP(了解)
<context:component-scan base-package="com.atguigu.aop.xml"></context:componentscan>
<aop:config>
<!--配置切面类-->
<aop:aspect ref="loggerAspect">
<aop:pointcut id="pointCut" expression="execution(*com.spring.aop.xml.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:after-throwing method="afterThrowingMethod" throwing="ex" pointcutref="pointCut">
</aop:after-throwing>
<aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
<aop:aspect ref="validateAspect" order="1">
<aop:before method="validateBeforeMethod" pointcut-ref="pointCut"></aop:before>
</aop:aspect>
</aop:config>
4 声明式事务
4.2 声明式事务概念
4.2.1 编程式事务
(1)编程式事务功能的相关操作全部通过自己编写代码来实现
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
(2)编程式的实现方式存在缺陷:
①细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
②代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
4.2.2、声明式事务
(1)既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出
来,进行相关的封装。封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
①好处1:提高开发效率
②好处2:消除了冗余的代码
③好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个
方面的优化
(2)所以,我们可以总结下面两个概念:
①编程式:自己写代码实现功能
②声明式:通过配置让框架实现功能
4.3 基于注解的声明式事务
4.3.1 加入事务
<!--事务的所有代码都是通过连接对象来设置的,事务必须依赖于数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
</bean>
<!-- 开启事务的注解驱动:
通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就
是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
注意:导入的名称空间需要 tx 结尾的那个。
4.3.1 @Transactional注解标识的位置
(1)@Transactional标识在方法上,只会影响该方法
(2)@Transactional标识的类上,会影响类中所有的方法
4.3.2 事务属性:只读
(1)介绍
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这
样数据库就能够针对查询操作来进行优化。
(2)使用方式
@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
}
(3)注意
对增删改操作设置只读会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification
are not allowed
4.3.3 事务属性:超时
(1)介绍
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间
占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常
程序可以执行。
概括来说就是一句话:超时回滚,释放资源
(2)使用方式
@Transactional(timeout = 3) //超过3秒就会超时回滚
public void buyBook(Integer bookId, Integer userId) {
}
4.3.4 事务属性:回滚策略
(1)介绍
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。可以通过@Transactional中相关属性设置回滚策略
1.因为什么而回滚:
①rollbackFor属性:需要设置一个Class类型的对象
②rollbackForClassName属性:需要设置一个字符串类型的全类名
2.因为什么而不回滚:
③noRollbackFor属性:需要设置一个Class类型的对象
④noRollbackForClassName属性:需要设置一个字符串类型的全类名
(2)使用方式
@Transactional(noRollbackFor = ArithmeticException.class)
public void buyBook(Integer bookId, Integer userId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
System.out.println(1/0);
}
(3)观察结果
虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当
出现ArithmeticException不发生回滚,因此购买图书的操作正常执行
4.4.5 事务属性:事务隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事
务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同
的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
还没有评论,来说两句吧...