您现在的位置是:首页 >学无止境 >Spring框架网站首页学无止境

Spring框架

红尘不染 2024-06-14 17:19:22
简介Spring框架

Spring简介

Spring主要内容

Spring 的主要特点在于简化开发,降低开发的复杂性,提高开发效率。Spring可以进行框架整合,高效整合其他技术。

Spring 为了简化开发,提供了两个大的核心技术 IOC 和 AOP。对于Spring的学习主要学习 IOC和AOP。Spring 还提供了事务处理,事务处理是Spring 中AOP的具体应用。Spring可以进行框架整合,可以整合几乎所有主流框架。

对于Spring的学习,主要学习四块内容:IOC,AOP,事务处理,框架整合。

Spring介绍

Spring 官网 https://spring.io
Spring 能做什么:用以开发web、微服务以及分布式系统等。
Spring并不是单一的一个技术,而是一个大家族,可以从官网的Projects中查看其包含的所有技术。
Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能。
在众多的项目中,需要重点关注Spring Framework、SpringBoot和 SpringCloud。
Spring Framework:Spring框架,是Spring中最早最核心的技术,也是所有其他技术的基础。
SpringBoot:Spring是来简化开发,而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发。
SpringCloud:这个是用来做分布式之微服务架构的相关开发。

spring 就是指的 Spring Framework。Spring Framework是 Spring 家族中其他框架的底层基础,学好Spring 可以为其他 Spring 框架的学习打好基础。

Spring 4 的架构图。
请添加图片描述

(1)核心层
Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
(2)AOP层
AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
Aspects:AOP是思想,Aspects是对AOP思想的具体实现
(3)数据层
Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容
(4)Web层
这一层的内容将在SpringMVC框架具体学习
(5)Test层
Spring主要整合了Junit来完成单元测试和集成测试

Spring 学习路线

Spring的学习主要包含四部分内容,分别是:

  • Spring的IOC/DI
  • Spring的AOP
  • AOP的具体应用,事务管理
  • IOC/DI的具体应用,整合Mybatis
    请添加图片描述

Spring 核心容器

核心容器有关的核心概念主要包含 IOC/DI,IOC容器和Bean。

IOC,IOC容器,Bean,DI 介绍

IOC(Inversion of control)控制反转:使用对象时,由主动new 产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转
IOC容器:Spring技术对IOC思想进行了实现,Spring提供了一个容器,称为IOC容器,用来充当IOC思想中的外部。
IOC容器负责对象的创建,初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为Bean,IOC容器中存放就是一个个的Bean对象。
DI(Dependency Injection)依赖注入:在IOC中,Bean是互相独立的,并没有任何关系。依赖注入就是在容器中建立Bean与Bean之间的依赖关系。建立依赖关系的过程称为依赖注入。

IOC和DI的目标就是做到充分解耦,使用 IOC容器来管理Bean,在容器中将有依赖关系的Bean进行依赖注入。这样在使用对象的时候,就能从容器中获取一个绑定了所有的依赖的对象。

配置文件管理Bean

Spring 是使用容器来管理Bean对象的,spring有两种方式来将想要被管理的Bean对象告知IOC容器,分别是使用配置文件和注解,本节先解释使用配置文件来管理Bean对象。

示例,通过配置文件来管理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标签标示配置bean id属性标示给bean起名字 class属性表示给bean定义类型 -->
	<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
	<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>
</beans>

使用配置文件初始化容器,获取容器中被管理的Bean

public class App {
	public static void main(String[] args) {
		//获取IOC容器,通过配置文件初始化IOC容器。
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		// 获取 bean
		BookService bookService = (BookService) ctx.getBean("bookService");
		bookService.save();
	}
}

Bean的基础配置使用bean标签来进行配置。
bean标签需要掌握的属性如下:

  • id是bean在容器中的唯一标识,通过id来获取一个唯一的bean。
  • class是bean的类型,即配置bean的全路径类名。
  • name为bean的别名配置,别名可定义多个,可以使用逗号(,)分号(;)空格( )分隔。
  • scope来定义bean的作用范围,可以选择的值为 :singleton(单例),prototype(非单例)。
<bean id="" class="" name="" scope=""/>

如果bean定义了别名,可以通过别名来获取Bean和依赖注入Bean。Bean的作用范围默认是单例的,即多次获取Bean的时候,都是同一个,如果设置Bean为非单例的,则每一次获取,会重新创建一个Bean对象。

Bean的实例化

在配置文件中,使用bean标签就可以将对象交给IOC容器管理,IOC是如何来创建对象的?
实例化Bean的三种方式,构造方法,静态工厂和实例工厂。
在IOC容器中默认使用的时无参的构造方法来创建对象的,如果类没有无参构造方法,则就不能创建对象。IOC是通过反射来获取类的无参构造方法来进行创建对象的。

静态工厂实例化Bean
静态工厂类,有一个静态方法,实例化bean

public class OrderDaoFactory {
	public static OrderDao getOrderDao(){
		return new OrderDaoImpl();
	}
}

配置bean,id为bean的唯一标识,class为工厂的全路径类名,factory-method具体工厂类中创建对象的方法名。就可以在IOC容器中通过工厂类的创建对象的方法创建一个id为orderDao的bean对象。

<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>

实例工厂实例化Bean
工厂类,提供一个普通方法。

public class UserDaoFactory {
	public UserDao getUserDao(){
		return new UserDaoImpl();
	}
}

bean标签配置,先实例化工厂对象,再使用工厂对象中的方法实例化bean。
factory-bean:工厂的实例对象
factory-method:工厂对象中的具体创建对象的方法名

<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

FactoryBean的使用
Spring为了简化工厂的配置方式,提供了一种FactoryBean的方式简化开放。
FactoryBean接口其实会有三个方法,getObject(),被重写后,在方法中进行对象的创建并返回。getObjectType(),被重写后,主要返回的是被创建类的Class对象。isSingleton() 用来指明Bean的作用范围,是单例还是非单例。

T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() {
	return true;
}

使用FactoryBean接口,来实例化bean

public class UserDaoFactoryBean implements FactoryBean<UserDao> {
	//代替原始实例工厂中创建对象的方法
	public UserDao getObject() throws Exception {
		return new UserDaoImpl();
	} 
	//返回所创建类的Class对象
	public Class<?> getObjectType() {
		return UserDao.class;
	}
}

使用bean标签配置bean,只需要指明工厂的全路径类名即可。

<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

Bean实例化小结
Spring 的IOC容器实例化对象的三种方式:

  • 构造方法
  • 静态工厂
  • 实例工厂(实现FactoryBean接口)

Bean的生命周期

生命周期:从创建到消亡的完整过程。
bean的生命周期:bean对象从创建到销毁的整体过程。
bean生命周期的配置,在创建后和销毁前,做一些事情。

public class BookDaoImpl implements BookDao {
	public void save() {
		System.out.println("book dao save ...");
	} 
	//表示bean初始化对应的操作
	public void init(){
		System.out.println("init...");
	} 
	//表示bean销毁前对应的操作
	public void destory(){
		System.out.println("destory...");
	}
}

在bean标签中,通过 init-method 和 destroy-method来指定初始化方法和销毁方法。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init"
destroy-method="destory"/>

Spring提供了两个接口来完成生命周期的控制,好处是可以不用再进行配置 init-method 和 destroy-method。
接下来在BookServiceImpl完成这两个接口的使用:
修改BookServiceImpl类,添加两个接口 InitializingBean, DisposableBean 并实现接口中的两个方法 afterPropertiesSet 和 destroy

public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
	private BookDao bookDao;
	public void setBookDao(BookDao bookDao) {
		this.bookDao = bookDao;
	}
	public void save() {
		System.out.println("book service save ...");
		bookDao.save();
	} 
	// 在销毁前执行
	public void destroy() throws Exception {
		System.out.println("service destroy");
	} 
	// 在初始化后执行
	public void afterPropertiesSet() throws Exception {
		System.out.println("service init");
	}
}

生命周期小结
Spring中对bean生命周期控制提供了两种方式:在配置文件中的bean标签中添加 init-method 和destroy-method 属性,类实现 InitializingBean 与 DisposableBean 接口。

对于bean的生命周期控制在bean的整个生命周期中所处的位置如下:
初始化容器

  • 1.创建对象(内存分配)
  • 2.执行构造方法
  • 3.执行属性注入(set操作)
  • 4.执行bean初始化方法

使用bean

  • 1.执行业务操作

关闭/销毁容器

  • 1.执行bean销毁方法

配置文件实现DI(依赖注入)

向一个类中传递数据的方式可以使用构造方法,和普通方法(set方法)。依赖注入描述了容器中建立bean与bean之间的依赖关系的过程。bean所需要的数据的类型可能有两种,引用类型和简单类型(基本数据类型与String)
综上,依赖注入共有两种方式,Setter注入和构造器注入,每一种方式都可以注入简单类型和引用类型。

setter注入

注入引用类型

在类中声明属性并提供对应的set方法。

public class BookServiceImpl implements BookService{
	private BookDao bookDao;
	private UserDao userDao;
	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	} 
	public void setBookDao(BookDao bookDao) {
		this.bookDao = bookDao;
	}
}

在配置文件中进行注入配置,在bean中,使用property 标签,name的值对应了类中属性的名字。ref的值是容器中bean的id。表示将引用的bean,注入到类的对应属性中。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
	<property name="bookDao" ref="bookDao"/>
	<property name="userDao" ref="userDao"/>
</bean>

注入简单数据类型

声明属性并提供setter方法。

public class BookDaoImpl implements BookDao {
	private String databaseName;
	private int connectionNum;
	public void setConnectionNum(int connectionNum) {
		this.connectionNum = connectionNum;
	} 
	public void setDatabaseName(String databaseName) {
		this.databaseName = databaseName;
	}
}

在配置文件中进行注入配置,使用property标签,name的值对应类中属性的名字,value的值就是注入的值。在注入的时候会自动进行数据类型转换。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<property name="databaseName" value="mysql"/>
	<property name="connectionNum" value="10"/>
</bean>

总结

对于引用数据类型使用的是 <property name="" ref=""/>
对于简单数据类型使用的是 <property name="" value=""/>

构造器注入

注入引用类型

声明属性名,并提供构造方法

public class BookServiceImpl implements BookService{
	private BookDao bookDao;
	public BookServiceImpl(BookDao bookDao) {
		this.bookDao = bookDao;
	}
}

配置文件中进行构造方式注入,使用constructor-arg标签,name的值为构造方法中参数名字。ref指向容器中的其他bean对象。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
	<constructor-arg name="bookDao" ref="bookDao"/>
</bean>

注入多个引用类型

public class BookServiceImpl implements BookService{
	private BookDao bookDao;
	private UserDao userDao;
	public BookServiceImpl(BookDao bookDao,UserDao userDao) {
		this.bookDao = bookDao;
		this.userDao = userDao;
	} 
}

配置文件多参数注入,使用多个constructor-arg标签。constructor-arg标签的顺序可以任意。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
	<constructor-arg name="bookDao" ref="bookDao"/>
	<constructor-arg name="userDao" ref="userDao"/>
</bean>

注入多个简单数据类型
声明一个类,添加多个简单属性并提供构造方法。

public class BookDaoImpl implements BookDao {
	private String databaseName;
	private int connectionNum;
	public BookDaoImpl(String databaseName, int connectionNum) {
		this.databaseName = databaseName;
		this.connectionNum = connectionNum;
	}
}

配置文件进行构造器注入。使用多个constructor-arg标签,name的值为参数的名字,value的值就是注入的值。constructor-arg标签的顺序可以任意。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<constructor-arg name="databaseName" value="mysql"/>
	<constructor-arg name="connectionNum" value="666"/>
</bean>

使用构造器注入,使用constructor-arg标签,name属性的值要和构造方法的参数名字对应,产生的耦合。可以考虑使用type属性,按照类型注入。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<constructor-arg type="int" value="10"/>
	<constructor-arg type="java.lang.String" value="mysql"/>
</bean>

如果有多个参数类型相同,也可以使用index属性,按照索引下标进行注入。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<constructor-arg index="1" value="100"/>
	<constructor-arg index="0" value="mysql"/>
</bean>

依赖注入总结

  1. 强制依赖使用构造器进行,强制依赖指对象在创建过程中必须要注入的参数。
  2. 可选依赖使用setter注入进行,灵活性强,可选依赖是指在对象创建的时候参数可有可无。
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨。
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入。
setter注入
- 简单数据类型
<bean ...>
	<property name="" value=""/>
</bean>
- 引用数据类型
<bean ...>
	<property name="" ref=""/>
</bean>

构造器注入
- 简单数据类型
<bean ...>
	<constructor-arg name="" index="" type="" value=""/>
</bean>
- 引用数据类型
<bean ...>
	<constructor-arg name="" index="" type="" ref=""/>
</bean>

自动配置完成依赖注入

配置文件的编写过于麻烦,使用自动配置完成bean的创建,和依赖的注入。
IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配。
修改配置文件中的bean标签,在bean标签中添加autowire属性。

<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>

注意事项:需要注入属性的类中对应属性必须要有setter方法,被注入的对象必须要被spring的IOC容器管理,在按类型注入时,如果找到多个对象,则不能注入。

如果IOC中有多个对象,可以按名称注入

<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byName"/>

在按照名称注入时,名称是set方法去掉set后,首字母小写。如setBookDao方法,名称为bookDao。

自动装配注意事项:

  1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作。
  2. 使用按类型装配(byType)时,必须保障容器中相同类型的bean唯一,推荐使用。
  3. 使用按名称装配(byName)时,必须保障容器中具有指定名称的bean,按照名称装配,变量名会与配置耦合,不推荐使用。
  4. 自动装配优先级低于setter注入与构造器注入,同时出现时,自动装配配置失效。

集合注入

常见的集合类型有数组,List,Set,Map,Properties。接下来就针对这几种集合进行注入。

注入数组类型数据,set方法注入使用 property 标签,构造方法注入使用 constructor-arg标签。name属性的值对应类中的属性名。

set方法注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<property name="array">
		<array>
			<value>100</value>
			<value>200</value>
			<value>300</value>
		</array>
	</property>
</bean>
构造器方法注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<constructor-arg name="array">
		<array>
			<value>100</value>
			<value>200</value>
			<value>300</value>
		</array>
	</constructor-arg>
</bean>

注入List数据类型

set方法注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<property name="list">
		<list>
			<value>itcast</value>
			<value>itheima</value>
			<value>boxuegu</value>
			<value>chuanzhihui</value>
		</list>
	</property>
</bean>
构造器方法注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<constructor-arg name="list">
		<list>
			<value>itcast</value>
			<value>itheima</value>
			<value>boxuegu</value>
			<value>chuanzhihui</value>
		</list>
	</constructor-arg>
</bean>

注入Set类型数据

set方法注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<property name="set">
		<set>
			<value>itcast</value>
			<value>itheima</value>
			<value>boxuegu</value>
			<value>chuanzhihui</value>
		</set>
	</property>
</bean>
构造器方法注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<constructor-arg name="set">
		<set>
			<value>itcast</value>
			<value>itheima</value>
			<value>boxuegu</value>
			<value>chuanzhihui</value>
		</set>
	</constructor-arg>
</bean>

注入Map类型数据

set方法注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<property name="map">
		<map>
			<entry key="country" value="china"/>
			<entry key="province" value="henan"/>
			<entry key="city" value="kaifeng"/>
		</map>
	</property>
</bean>
构造器方法注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<constructor-arg name="map">
		<map>
			<entry key="country" value="china"/>
			<entry key="province" value="henan"/>
			<entry key="city" value="kaifeng"/>
		</map>
	</constructor-arg>
</bean>

注入Properties类型数据

set方法注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<property name="properties">
		<props>
			<prop key="country">china</prop>
			<prop key="province">henan</prop>
			<prop key="city">kaifeng</prop>
		</props>
	</property>
</bean>
构造器方法注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	<constructor-arg name="properties">
		<props>
			<prop key="country">china</prop>
			<prop key="province">henan</prop>
			<prop key="city">kaifeng</prop>
		</props>
	</constructor-arg>
</bean>

如果集合中是引用类型,只需要把value标签改为ref标签即可。

IOC/DI配置管理第三方bean

实现IOC容器管理Druid连接池对象。
导入依赖,在配置文件中将第三方Bean让IOC容器管理。
pom文件导入依赖。

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>1.1.16</version>
</dependency>

在配置文件中配置第三方Bean。driverClassName:数据库驱动,url:数据库连接地址,username:数据库连接用户名,password:数据库连接密码。

<!--管理DruidDataSource对象-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
	<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
	<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
	<property name="username" value="root"/>
	<property name="password" value="root"/>
</bean>

在上面的配置文件中,直接将数据库的一些信息写在配置文件中不利于后期维护,可以将这些值写在外部的properties中。

resources下创建一个jdbc.properties文件,并添加对应的属性键值对

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root

在配置文件中开启context命名空间,加载properties文件

<?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/springcontext.xsd">
		<!--加载配置文件-->
		<context:property-placeholder location="jdbc.properties"/>	
		<!--配置第三方bean-->
		<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
			<property name="driverClassName" value="${jdbc.driver}"/>
			<property name="url" value="${jdbc.url}"/>
			<property name="username" value="${jdbc.username}"/>
			<property name="password" value="${jdbc.password}"/>
		</bean>
</beans>

如果有多个配置文件需要加载,可以使用以下几种方式。
system-propertiesmode = “NEVER” 表示不加载系统的属性。

<!-- 方式一 -->
<context:property-placeholder location="jdbc.properties,jdbc2.properties" system-propertiesmode="NEVER"/>
<!--方式二-->
<context:property-placeholder location="*.properties" systemproperties-mode="NEVER"/>
<!--方式三 -->
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
<!--方式四-->
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>

方式一:可以实现,如果配置文件多的话,每个都需要配置
方式二:*.properties代表所有以properties结尾的文件都会被加载,可以解决方式一的问题,但是不标准
方式三:标准的写法,classpath: 代表的是从根路径下开始查找,但是只能查询当前项目的根路径
方式四:不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的 properties配置文件

加载properties配置文件小结。
开启context命名空间
请添加图片描述
加载配置文件

<context:property-placeholder location="" system-properties-mode="NEVER"/>

引入配置文件中的值

${key}

核心容器

容器的创建

// 通过类路径下的xml配置文件进行容器的创建
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 通过文件系统下的xml配置文件进行容器的创建,需要写清楚配置文件的路径
ApplicationContext ctx = new FileSystemXmlApplicationContext("applicationContext.xml");

获取bean的方式

// 通过getBean方法进行获取。需要进行类型转换
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// 在获取的时候,指明类型
BookDao bookDao = ctx.getBean("bookDao"BookDao.class);
// 通过类型来获取bean,需要确保IOC容器中该类型对应的bean只有一个。
BookDao bookDao = ctx.getBean(BookDao.class);

容器类层次结构
请添加图片描述
BeanFactory是延迟加载,只有在获取bean对象的时候才会去创建。
ApplicationContext是立即加载,容器加载的时候就会创建bean对象。
如果想要配置延迟加载,可以按照以下方式配置。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" lazy-init="true"/>

bean标签总结请添加图片描述
注入总结
请添加图片描述

IOC/DI 注解开发

使用注解管理Bean

bean标签定义bean。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

使用注解进行bean的管理。在类上加上Component注解。

@Component("bookDao")
public class BookDaoImpl implements BookDao {
	public void save() {
		System.out.println("book dao save ..." );
	}
}

请添加图片描述
为了让spring框架能够扫描到写在类上的注解,需要在配置文件上进行包扫描。

<context:component-scan base-package="com.itheima"/>

component-scan
component:组件,Spring将管理的bean视作自己的一个组件
scan:扫描
base-package指定Spring框架扫描的包路径,它会扫描指定包及其子包中的所有类上的注解。

@Component 注解如果不起名称,会有一个默认值就是当前类名首字母小写。
对于@Component注解,还衍生出了其他三个注解@Controller、@Service、@Repository。这三个注解和@Component注解的作用是一样的。只是为区分出类是属于表现层,业务层还是数据层的类。
在这里插入图片描述
上述中,依然使用了配置文件进行包的扫描。纯注解开发模式,可以将配置文件删除掉,用配置类来替换。
创建一个配置类。在配置类上添加@Configuration注解,将其标识为一个配置类。然后使用@ComponentScan在注解,来替换配置文件中的包扫描。

@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}

请添加图片描述
Java类替换Spring核心配置文件
@Configuration注解用于设定当前类为配置类
@ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式

@ComponentScan({com.itheima.service","com.itheima.dao"})

读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象

//加载配置文件初始化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

在这里插入图片描述
在这里插入图片描述
本节是使用注解完成bean的管理:

  1. 使用 @Component、@Controller、@Service、@Repository这四个注解
  2. 使用 @ComponentScan代替 applicationContext.xml中 context:component-san,扫描包的路径。
  3. @Configuration 标识该类为配置类,使用类替换applicationContext.xml文件、
  4. AnnotationConfigApplicationContext是加载配置类
  5. ClassPathXmlApplicationContext是加载XML配置文件

注解开发bean作用范围与生命周期管理

使用 @Scope注解可以设置bean的作用范围,@Scope(“prototype”) 非单例的,@Scope(“singleton”) 单例的。bean默认都是单例的。

@Repository
//@Scope设置bean的作用范围
@Scope("prototype")
public class BookDaoImpl implements BookDao {
	public void save() {
		System.out.println("book dao save ...");
	}
}

在这里插入图片描述
@PostConstruct 注解,用来指明一个方法,这个方法会在构造方法之后执行,用来替换bean标签中的 init-method属性。
@PreDestroy 注解,用来指明一个方法,这个方法会在销毁方法之前执行,用来替换bean标签中的destroy-method属性。

@Repository
public class BookDaoImpl implements BookDao {
	public void save() {
		System.out.println("book dao save ...");
	} 
	@PostConstruct //在构造方法之后执行,替换 init-method	
	public void init() {
		System.out.println("init ...");
	}
	@PreDestroy //在销毁方法之前执行,替换 destroy-method
	public void destroy() {
		System.out.println("destroy ...");
	}
}

在这里插入图片描述
在这里插入图片描述
小结
在这里插入图片描述

注解开发依赖注入

Spring 为了使用注解简化开发,并没有提供构造函数注入、setter注入对应的注解,只提供了自动装配的注解实现。
使用 Autowired 注解,注入。

@Service
public class BookServiceImpl implements BookService {
	@Autowired
	private BookDao bookDao;
	public void save() {
		System.out.println("book service save ...");
		bookDao.save();
	}
}

@Autowired可以写在属性上,也可也写在setter方法上,最简单的处理方式是写在属性上并将 setter方法删除掉。自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值,所以自动注入不需要setter方法。
@Autowired是按照类型注入,如果IOC容器中有多个相同类型的 bean,将会按照名称注入,如果找不到,将会报错。按照名称注入时,会找和属性名相同的bean的名字。
如果IOC容器中有多个相同类型的bean,又没有与属性名相同的bean,可以使用 @Qualifier注解,来指定想要注入的bean的名称。

@Service
public class BookServiceImpl implements BookService {
	@Autowired
	// 指明想要注入的bean的名称
	@Qualifier("bookDao1")
	private BookDao bookDao;
	public void save() {
		System.out.println("book service save ...");
		bookDao.save();
	}
}

@Qualifier不能独立使用,必须和@Autowired一起使用

简单类型的注入,简单类型注入的是基本数据类型或字符串类型。使用@Value 注解进行注入。在注入的时候,注意格式的。

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
	@Value("itheima")
	private String name;
	public void save() {
		System.out.println("book dao save ..." + name);
	}
}

注解读取properties配置文件,@Value 一般会被用在从 properties 配置文件中读取内容。
步骤如下:

  1. resource下准备properties文件
  2. 在配置类上添加@PropertySource注解
  3. 使用@Value读取配置文件中的内容

jdbc.properties文件

name=itheima888

配置类,加载 properties 文件

@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
public class SpringConfig {
}

使用@Value读取配置文件中的内容

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
	@Value("${name}")
	private String name;
	public void save() {
		System.out.println("book dao save ..." + name);
	}
}

如果读取的 properties 配置文件有多个,可以使用 @PropertySource 的属性来指定多个。

 @PropertySource({"jdbc.properties","xxx.properties"})

@PropertySource注解属性中可以把classpath:加上,代表从当前项目的根路径找文件

@PropertySource({"classpath:jdbc.properties"})

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

IOC/DI 注解开发管理第三方bean

完成 Druid 数据源的管理。
在 配置类中,增加一个方法,该方法的作用是返回一个 DataSource,然后在方法上加上@Value 注解。就可以把该方法的返回值加入到IOC容器中。
@Bean注解的作用是将方法的返回值制作为Spring管理的一个bean对象

@Configuration
public class SpringConfig {
	@Bean
	public DataSource dataSource(){
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName("com.mysql.jdbc.Driver");
		ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
		ds.setUsername("root");
		ds.setPassword("root");
		return ds;
	}
}

上面是把第三方bean直接写在了配置类中,可以把不同 bean 配置到不同的配置类中,便于代码阅读和管理。
使用一个新的配置类来配置。

public class JdbcConfig {
	@Bean
	public DataSource dataSource(){
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName("com.mysql.jdbc.Driver");
		ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
		ds.setUsername("root");
		ds.setPassword("root");
		return ds;
	}
}

然后在Spring配置类引入该配置类。

@Configuration
@Import({JdbcConfig.class})
public class SpringConfig {
}

@Import注解在配置类中只能写一次。如果要引入多个配置类,可以写成数组的形式。

@Import({JdbcConfig.class,xxx.class,yyy.class})

在这里插入图片描述
在这里插入图片描述

注解开发实现为第三方bean注入资源

在使用@Bean创建bean对象的时候,可能会需要一些其他资源,可能是简单数据类型,引用数据类型。

简单数据类型注入
使用 @Value,来为简单类型注入值。

public class JdbcConfig {
	@Value("com.mysql.jdbc.Driver")
	private String driver;
	@Value("jdbc:mysql://localhost:3306/spring_db")
	private String url;
	@Value("root")
	private String userName;
	@Value("password")
	private String password;
	@Bean
	public DataSource dataSource(){
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName(driver);
		ds.setUrl(url);
		ds.setUsername(userName);
		ds.setPassword(password);
		return ds;
	}
}

引用数据类型注入

public class JdbcConfig {
	@Bean
	public DataSource dataSource(BookDao bookDao){
		System.out.println(bookDao);
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName(driver);
		ds.setUrl(url);
		ds.setUsername(userName);
		ds.setPassword(password);
		return ds;
	}
}

引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象。

注解开发总结

在这里插入图片描述

Spring 整合

整合mybatis

Spring 整合 Mybatis,Spring要管理MyBatis中的SqlSessionFactory,Spring要管理Mapper接口的扫描。

步骤1: 项目中导入整合需要的jar包

<dependency>
	<!--
		Spring与Mybatis整合的jar包
		这个jar包mybatis在前面,是Mybatis提供的
	-->
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>1.3.0</version>
</dependency>

步骤2: 创建Spring的主配置类

//配置类注解
@Configuration
//包扫描,主要扫描的是项目中的AccountServiceImpl类
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}

步骤3: 创建数据源的配置类

public class JdbcConfig {
	@Value("${jdbc.driver}")
	private String driver;
	@Value("${jdbc.url}")
	private String url;
	@Value("${jdbc.username}")
	private String userName;
	@Value("${jdbc.password}")
	private String password;
	@Bean
	public DataSource dataSource(){
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName(driver);
		ds.setUrl(url);
		ds.setUsername(userName);
		ds.setPassword(password);
		return ds;
	}
}

步骤4: 创建Mybatis配置类并配置SqlSessionFactory

public class MybatisConfig {
	//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
	@Bean
	public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
		SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
		//设置模型类的别名扫描
		ssfb.setTypeAliasesPackage("com.itheima.domain");
		//设置数据源
		ssfb.setDataSource(dataSource);
		return ssfb;
	} 
	//定义bean,返回MapperScannerConfigurer对象
	@Bean
	public MapperScannerConfigurer mapperScannerConfigurer(){
		MapperScannerConfigurer msc = new MapperScannerConfigurer();
		msc.setBasePackage("com.itheima.dao");
		return msc;
	}
}

使用SqlSessionFactoryBean封装SqlSessionFactory需要的环境信息。
在这里插入图片描述
使用MapperScannerConfigurer加载Dao接口,创建代理对象保存到IOC容器中。
在这里插入图片描述
整合 mybatis 就已经完成了,主要就是将 SqlSessionFactoryBean 和 MapperScannerConfigurer 两个类交个 IOC 容器管理。

整合Junit

步骤1:引入依赖

<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.12</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>5.2.10.RELEASE</version>
</dependency>

步骤2: 编写测试类

//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = {SpringConfiguration.class}) //加载配置类
// @ContextConfiguration(locations={"classpath:applicationContext.xml"}) //加载配置文件
public class AccountServiceTest {
	//支持自动装配注入bean
	@Autowired
	private AccountService accountService;
	@Test
	public void testFindById(){
		System.out.println(accountService.findById(1));
	} 
	@Test
	public void testFindAll(){
		System.out.println(accountService.findAll());
	}
}

单元测试,如果测试的是注解配置类,则使用@ContextConfiguration(classes = 配置类.class)
单元测试,如果测试的是配置文件,则使用@ContextConfiguration(locations={配置文件名})
在这里插入图片描述
在这里插入图片描述

AOP

Spring有两个核心的概念,一个是IOC/DI,一个是AOP。
AOP是在不改原有代码的前提下对其进行增强。

AOP简介

AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。
AOP的作用:在不惊动原始设计的基础上为其进行功能增强。与设计模式中的代理模式,装饰模式类似。

AOP的核心概念:

  • 连接点:程序执行过程中的任意位置,粒度为执行方法,抛出异常,设置变量等。在Spring AOP中,理解为方法的执行。

  • 切入点:匹配连接点的式子,在Spring AOP中,一个切入点,可以描述一个具体的方法,也可以匹配多个方法。连接点比切入点的范围大,只有被匹配的连接点才是切入点。

  • 通知:在切入点处执行的操作,也就是要增强的操作,以方法的形式呈现。

  • 通知类:定义通知的类。

  • 切面(Aspect):描述通知与切入点的对应关系。

AOP案例

接口和实现类。类中的方法就是连接点。

public interface BookDao {
	public void save();
	public void update();
} 
@Repository
public class BookDaoImpl implements BookDao {
	public void save() {
		System.out.println(System.currentTimeMillis());
		System.out.println("book dao save ...");
	} 
	public void update(){
		System.out.println("book dao update ...");
	}
}

通知类和通知方法,还有切入点,切入点的定义需要依赖一个没有实际意义的方法进行,即无参数,无返回值,无方法体。切面,描述切入点和通知的关系。@Before 通知会在切入点之前执行。
将通知类注入容器,@Aspect 注解设置当前类为通知类。

@Component
@Aspect
public class MyAdvice {
	// 切入点
	@Pointcut("execution(void com.itheima.dao.BookDao.update())")
	private void pt(){}
	@Before("pt()")
	public void method(){
		System.out.println(System.currentTimeMillis());
	}
}

在Spring配置类中开启注解AOP功能。

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

以上的AOP配置就完成了,通知类中的通知方法和切入点进行了绑定,通知会在切入点之前执行。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

AOP 工作流程

AOP是基于Spring容器管理的bean做的增强,整个工作流程需要从Spring 加载bean开始。

流程1:spring容器启动,容器去加载bean,需要被增强的类,通知类。
流程2:读取通知类中所有切面配置中的切入点,没有被切面使用的切入点并不会读取。
流程3:初始化bean,要被实例化bean对象的类中的连接到与切入点进行匹配。匹配失败,没有进行增强,则创建原始对象。匹配成功,进行了增强,则根据原始对象,创建对应的代理对象。因为要对目标对象增强,低层采用的是动态代理,为其创建一个代理 对象,最终运行的是代理对象的方法,该方法会对原始方法进行功能增强。
流程4:获取bean执行方法,如果要进行增强,则会获取代理对象,进行执行。如果没有进行增强,获取的就是原始的对象。

目标对象(Target):原始功能去掉共性功能(增强的功能)对应的类产生的对象。
代理(proxy):目标对象无法直接完成工作,需要进行增强,通过原始对象的代理对象实现。

Spring AOP的本质或者说低层实现就是通过代理模式。

AOP配置管理

AOP切入点表达式

切入点:要进行增强的方法。
切入点表达式:要进行增强的方法的描述方式。

切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)

例子:

execution(public User com.itheima.service.UserService.findById(int))

execution:动作关键字,描述切入点的行为动作,execution表示执行到指定切入点
public:访问修饰符,还可以是public,private等,可以省略
User:返回值,写返回值类型
com.itheima.service:包名,多级包使用点连接
UserService: 类/接口名称
findById:方法名
int:参数,直接写参数的类型,多个类型用逗号隔开
异常名:方法定义中抛出指定异常,可以省略

切入点表达式就是要找到需要增强的方法,所以它就是对一个具体方法的描述,但是方法的定义会有
很多,所以如果每一个方法对应一个切入点表达式,就会很麻烦。所有可以使用通配符,一个切入点表达式可以匹配多个增强的方法。

通配符

*:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现。
 execution(public * com.itheima.*.UserService.find*(*))
 匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法。
.. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
+:专用于匹配子类类型
execution(* *..*Service+.*(..))
*Service+,表示所有以Service结尾的接口的子类中的任意方法。

对于切入点表达式的编写技巧:

  • 所有代码按照标准规范开发,否则以下技巧全部失效
  • 描述切入点通常描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了
  • 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用✳通配快速描述
  • 包名书写尽量不使用…匹配,效率过低,常用✳做单个包描述匹配,或精准匹配
  • 接口名/类名书写名称与模块相关的采用✳匹配,例如UserService书写成✳Service,绑定业务
    层接口名
  • 方法名书写以动词进行精准匹配,名词采用匹配,例如getById书写成getBy,selectAll书写成selectAll
  • 参数规则较为复杂,根据业务方法灵活调整
  • 通常不使用异常作为匹配规则

AOP通知类型

AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置。
AOP 提供了5种通知类型:

  • 前置通知
  • 后置通知
  • 环绕通知(重点)
  • 返回后通知(了解)
  • 抛出异常后通知(了解)
    在这里插入图片描述
    前置通知,追加功能到方法执行前,类似于在代码1或者代码2添加内容。
    后置通知,追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容。
    返回后通知,追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,如果方法执行抛出异常,返回后通知将不会被添加。
    抛出异常后通知,追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,只有方法抛出异常后才会被添加。
    环绕通知,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能。

接口和实现类。

public interface BookDao {
	public void update();
	public int select();
} 
@Repository
public class BookDaoImpl implements BookDao {
	public void update(){
		System.out.println("book dao update ...");
	} 
	public int select() {
		System.out.println("book dao select is running ...");
		return 100;
	}
}

Spring 配置类

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

通知类,切入点表达式,匹配update方法。
使用@Before注解实现前置通知,before方法将会在update方法之前执行。
使用@After注解实现后置通知,after方法将会在update方法之后执行。
使用@Around注解实现环绕通知,环绕通知需要在原始方法的前后进行增强,所以环绕通知就必须要能对原始操作进行调用。在环绕通知中,使用参数 pjp 来调用原始操作,在调用原始操作前后加入环绕操作,并接受原始方法的返回值,进行返回。
使用@AfterReturning注解实现返回后通知,返回后通知是在原始方法正常执行结束之后,进行执行。后置通知是不管原始方法有没有抛出异常,都会被执行。
@AfterReturning注解实现异常后通知,在原始方法抛出异常后执行。

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(void com.itheima.dao.BookDao.update())")
	private void pt(){}
	
	@Pointcut("execution(int com.itheima.dao.BookDao.select())")
	private void pt2(){}
	
	@Before("pt()")
	public void before() {
	System.out.println("before advice ...");
	} 
	
	@After("pt()")
	public void after() {
		System.out.println("after advice ...");
	} 
	
	@Around("pt2()")
	public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
		System.out.println("around before advice ...");
		//表示对原始操作的调用
		Object ret = pjp.proceed();
		System.out.println("around after advice ...");
		return ret;
	}
	
	@AfterReturning("pt2()")
	public void afterReturning() {
		System.out.println("afterReturning advice ...");
	} 
	
	@AfterReturning("pt2()")
	public void afterThrowing() {
		System.out.println("afterThrowing advice ...");
	}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
环绕通知注意事项:

  1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知。
  2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
  3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为 Object类型
  4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成 Object
  5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常

AOP通知获取切入点方法的相关数据

获取切入点方法的参数,所有的通知类型都可以获取参数。
JoinPoint:适用于前置、后置、返回后、抛出异常后通知。
ProceedingJoinPoint:适用于环绕通知

获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,返回后通知,环绕通知有返回值。

获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,抛出异常后通知,环绕通知有异常信息

获取参数

非环绕通知获取方式,非环绕通知,使用JoinPoint类中的getArgs()方法获取参数,获取的参数类型为 Object类型的数组。因为参数的个数不固定,使用数组更匹配。

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	
	@Before("pt()")
	public void before(JoinPoint jp){
		Object[] args = jp.getArgs();
		System.out.println(Arrays.toString(args));
		System.out.println("before advice ..." );
	} 
	//...其他的略

环绕通知获取方式,环绕通知使用的是ProceedingJoinPoint类,类中也有getArgs()方法,可以获取参数。

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	@Around("pt()")
	public Object around(ProceedingJoinPoint pjp)throws Throwable {
		// 获取切入点方法的参数
		Object[] args = pjp.getArgs();
		System.out.println(Arrays.toString(args));
		// 执行切入点方法,并获取方法返回值
		Object ret = pjp.proceed();
		return ret;
	} 
	//其他的略
}

pjp.proceed()有两个,一个是无参数,一个是有参的。调用无参的proceed方法,当原始方法有参数,会在调用的过程中自动传入参数。如果想要修改原始方法的参数,只能采用带参数的proceed方法。

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	
	@Around("pt()")
	public Object around(ProceedingJoinPoint pjp) throws Throwable{
		// 获取原始参数。
		Object[] args = pjp.getArgs();
		System.out.println(Arrays.toString(args));
		// 修改参数。
		args[0] = 666;
		// 传入修改后的参数,执行原始方法。
		Object ret = pjp.proceed(args);
		return ret;
	} 
	//其他的略
}

可以在环绕通知中对原始方法的参数进行拦截过滤

获取返回值

对于返回值,只有返回后AfterReturing和环绕Around这两个通知类型可以获取。
环绕通知获取返回值

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	
	@Around("pt()")
	public Object around(ProceedingJoinPoint pjp) throws Throwable{
		Object[] args = pjp.getArgs();
		System.out.println(Arrays.toString(args));
		args[0] = 666;
		// ret 就是返回值
		Object ret = pjp.proceed(args);
		return ret;
	} 
	//其他的略
}

ret就是方法的返回值,可以直接获取,不但可以获取,如果需要还可以进行修改。

返回后通知获取返回值

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	
	@AfterReturning(value = "pt()",returning = "ret")
	public void afterReturning(Object ret) {
		System.out.println("afterReturning advice ..."+ret);
	} 
	// 其他的略
}

在这里插入图片描述
afterReturning方法参数类型的问题,参数类型可以写成String,但是为了能匹配更多的参数类型,建议写成Object类型。
在这里插入图片描述

获取异常

对于获取抛出的异常,只有抛出异常后AfterThrowing和环绕Around这两个通知类型可以获取。
环绕通知获取异常,只需要try,catch语句捕获就可以了。

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	@Around("pt()")
	public Object around(ProceedingJoinPoint pjp){
		Object[] args = pjp.getArgs();
		System.out.println(Arrays.toString(args));
		args[0] = 666;
		Object ret = null;
		try{
			ret = pjp.proceed(args);
		}catch(Throwable throwable){
			// 发生异常就可以捕获到。
			t.printStackTrace();
		} 
		return ret;
	} 
	//其他的略
}

抛出异常后通知获取异常

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	@AfterThrowing(value = "pt()",throwing = "t")
	public void afterThrowing(Throwable t) {
		System.out.println("afterThrowing advice ..."+t);
	} 
	// 其他的略
}

在这里插入图片描述

AOP总结

AOP(Aspect Oriented Programming)面向切面编程,一种编程范式。
作用:在不惊动原始设计的基础上为方法进行功能增强。

核心概念

  • 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
  • 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
  • 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
  • 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
  • 切面(Aspect):描述通知与切入点的对应关系
  • 目标对象(Target):被代理的原始对象成为目标对象

切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)

execution(* com.itheima.service.*Service.*(..))
切入点表达式描述通配符,作用:用于快速描述,范围描述
*:匹配任意符号(常用)
.. :匹配多个连续的任意符号(常用)
+:匹配子类类型

切入点表达式书写技巧

- 按标准规范开发 
- 查询操作的返回值建议使用*匹配 
- 减少使用..的形式描述包
- 对接口进行描述,使用*表示模块名,例如UserService的匹配描述为*Service 
- 方法名书写保留动词,例如get,使用*表示名词,例如getById匹配描述为getBy* 
- 参数根据实际情况灵活调整

五种通知类型

  • 前置通知
  • 后置通知
  • 环绕通知(重点),环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用,环绕通知可以隔离原始方法的调用执行,环绕通知返回值设置为Object类型,环绕通知中可以对原始方法调用过程中出现的异常进行处理
  • 返回后通知
  • 抛出异常后通知

通知中获取参数
获取切入点方法的参数,所有的通知类型都可以获取参数

  • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
  • ProceedingJoinPoint:适用于环绕通知

获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,返回后通知,环绕通知可以获取。
获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,抛出异常后通知,环绕通知可以获取。

AOP事务管理

事务作用:在数据层保障一系列的数据库操作同成功同失败
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败。
Spring为了管理事务,提供了一个平台事务管理器PlatformTransactionManager的接口。
请添加图片描述
commit是用来提交事务,rollback是用来回滚事务。
Spring还为其提供了一个具体的实现:
请添加图片描述
只需要给它一个DataSource对象,它就可以帮你去在业务层管理事务。其内部采用的是JDBC的事务。如果持久层采用的是JDBC相关的技术,就可以采用这个事务管理器来管理事务。

Spring事务管理具体的实现步骤

步骤1:在需要被事务管理的方法上添加注解

public interface AccountService {
	/**
	* 转账操作
	* @param out 传出方
	* @param in 转入方
	* @param money 金额
	*/
	//配置当前接口方法具有事务
	public void transfer(String out,String in ,Double money) ;
} 

@Service
public class AccountServiceImpl implements AccountService {
	@Autowired
	private AccountDao accountDao;
	@Transactional
	public void transfer(String out,String in ,Double money) {
		accountDao.outMoney(out,money);
		int i = 1/0;
		accountDao.inMoney(in,money);
	}
}

@Transactional 可以写在接口类上、接口方法上、实现类上和实现类方法上。

  • 写在接口类上,该接口的所有实现类的所有方法都会有事务
  • 写在接口方法上,该接口的所有实现类的该方法都会有事务
  • 写在实现类上,该类中的所有方法都会有事务
  • 写在实现类方法上,该方法上有事务
  • 建议写在实现类或实现类的方法上

步骤2:在配置类中配置事务管理器

public class JdbcConfig {
	@Value("${jdbc.driver}")
	private String driver;
	@Value("${jdbc.url}")
	private String url;
	@Value("${jdbc.username}")
	private String userName;
	@Value("${jdbc.password}")
	private String password;
	@Bean
	public DataSource dataSource(){
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName(driver);
		ds.setUrl(url);
		ds.setUsername(userName);
		ds.setPassword(password);
		return ds;
	} 
	// 配置事务管理器
	@Bean
	public PlatformTransactionManager transactionManager(DataSource dataSource){
		DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
		transactionManager.setDataSource(dataSource);
		return transactionManager;
	}
}

事务管理器根据使用技术进行选择, 使用的是JDBC事务,可以用 DataSourceTransactionManager进行管理。

步骤3:开启事务注解

在SpringConfig的配置类中开启

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

在这里插入图片描述
在这里插入图片描述

Spring事务角色

未开启Spring事务之前:
请添加图片描述
outMoney 和 inMoney 会分别开启一个事务。所有当其中一个事务出错时,另一个不会回滚。

开启Spring的事务管理后:
请添加图片描述

在transfer上添加了@Transactional注解,在该方法上就会有一个事务T,原来的两个独立的事务就会加入到transfer的事务T中。这样整个方法作为一个事务,出现错误的时候就会回滚。

事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法。
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法。

以上的事务管理是基于DataSourceTransactionManager和SqlSessionFactoryBean使用的是同一个数据源。

Spring事务属性

请添加图片描述
上面这些属性都可以在**@Transactional注解的参数上进行设置**。

  • readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
  • timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1 表示不设置超时时间。
  • rollbackFor:当出现指定异常进行事务回滚。
  • rollbackForClassName 等同于rollbackFor,只不过属性为异常的类全名字符串
  • noRollbackFor:当出现指定异常不进行事务回滚,设定对于指定的异常不回滚。
  • noRollbackForClassName 等同于noRollbackFor,只不过属性为异常的类全名字符串
  • isolation设置事务的隔离级别
    DEFAULT:默认隔离级别, 会采用数据库的隔离级别。
    READ_UNCOMMITTED : 读未提交
    READ_COMMITTED : 读已提交
    REPEATABLE_READ : 重复读取
    SERIALIZABLE: 串行化

在Spring事务中,并不是所有的异常都进行回滚。Spring的事务只会对 Error 异常和 RuntimeException 异常及其子类进行事务回顾,其他的异常类型是不会回滚的,对应 IOException 不符合上述条件所以不回滚。此时就可以使用rollbackFor属性来设置出现IOException异常回滚。

@Service
public class AccountServiceImpl implements AccountService {
	@Autowired
	private AccountDao accountDao;
	@Transactional(rollbackFor = {IOException.class})
	public void transfer(String out,String in ,Double money) throws IOException{
		accountDao.outMoney(out,money);
		// int i = 1 / 0; //这个异常事务会回滚
		if(true){
		 	// 这个异常事务默认就不会回滚,使用rollbackFor属性指定出现异常回滚。
			throw new IOException();
		} 
		accountDao.inMoney(in,money);
	}
}

事务的传播行为案例
在转账的时候,不论成功或失败,都要记录日志。
在 LogServiceImpl 的 log 方法上添加 @Transactional注解,设置事务的传播行为,设置为新建一个事务。

public interface LogService {
	void log(String out, String in, Double money);
} 
public interface LogDao {
	@Insert("insert into tbl_log (info,createDate) values(#{info},now())")
	void log(String info);
}
@Service
public class LogServiceImpl implements LogService {
	@Autowired
	private LogDao logDao;
	//propagation设置事务属性:传播行为设置为当前操作需要新事务
	@Transactional(propagation = Propagation.REQUIRES_NEW)	
	public void log(String out,String in,Double money ) {
		logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
	}
}

转账业务,在转账业务中,加入记录日志的功能。

public interface AccountService {
	/**
	* 转账操作
	* @param out 传出方
	* @param in 转入方
	* @param money 金额
	*/
	//配置当前接口方法具有事务
	public void transfer(String out,String in ,Double money)throws IOException ;
}
@Service
public class AccountServiceImpl implements AccountService {
	@Autowired
	private AccountDao accountDao;
	@Autowired
	private LogService logService;
	@Transactional()
	public void transfer(String out,String in ,Double money) {
		try{
			accountDao.outMoney(out,money);
			accountDao.inMoney(in,money);
		}finally {
			logService.log(out,in,money);
		}
	}
}

请添加图片描述

事务传播行为:事务协调员对事务管理员所携带事务的处理态度。
在上面案例中 transfer 开启一个新的事务 T,事务 T 为事务管理员,在transfer 方法中还有三个事务,为事务协调员,其中 inMoney,outMoney 会加入事务T,而 log事务设置了事务的传播行为属性,会新建一个事务。所有不管转账是否成功,日志都会被记录。

事务的传播行为可选值
请添加图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。