Spring
- 1、概述
- 1.1、优点
- 1.2、组成
- 2. IOC概述
- 2.1 什么是IOC
- 2.1.1 推导过程
- 2.1.2 IOC本质
- 2.2 HelloSpring
- 2.2.1 导入Jar包
- 2.2.2 编写代码
- 2.2.2 思考
- 2.3 IOC过程
- 2.4 IOC 接口
- 3. Bean 管理
- 3.1 基于xml方式——set方法注入
- 3.2 FactoryBean
- 3.3 bean 作用域
- 3.4 bean 生命周期
- 4. 自动装配
- 4.1 ByName
- 4.2 byType
- 4.3 使用注解
- @Autowired
- @Qualifier
- @Resource
- @Value
- 4.4 小结
- 5. 使用注解开发
- 5.1 Bean的实现
- 5.2 属性注入
- 5.3 衍生注解
- 5.4 作用域
- 5.5 小结
- 5.6 基于Java类进行配置
- 6. AOP
- 6.1 什么是AOP
- 6.2 Aop在Spring中的作用
- 6.3 使用Spring实现Aop
- 7. 事务
- 7.1 什么是事务
- 7.2 事务四个特性(ACID)
- 7.3 事务操作(搭建事务操作环境)
- 7.3.1 没有事务时
- 7.3.2 加入事务
- 7.3.3 声明式事务管理参数配置
- 7.3.4 propagation——事务传播行为
- 7.3.5 ioslation——事务隔离级别
- 7.3.6 其他
- 7.4 XML 声明式事务管理
- 7.5 完全注解声明式事务管理
- 7.4 XML 声明式事务管理
- 7.5 完全注解声明式事务管理
官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html
1、概述
1.1、优点
- 开源框架(容器)
- 轻量级、非入侵式的框架
- 控制反转(IOC)、面向切面编程(AOP)
- IOC:控制反转,把创建对象过程交给Spring进行管理
- Aop:面向切面,不修改源代码进行功能增强
- 支持事务处理,对框架整合的支持
Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
1.2、组成
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式
组成 Spring 框架的每个模块都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
2. IOC概述
2.1 什么是IOC
- 把对象创建和对象之间的调用过程,交给Spring进行管理
- 使用IOC目的:为了降低耦合度
2.1.1 推导过程
传统的new对象方法:当UserDao类的路径和方法改变时,UserService类也要跟着相应变化。
- 先写一个UserDao接口
public interface UserDao {
public void getUser();
}
- 再去写Dao的实现类
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("获取用户数据");
}
}
- 然后去写UserService的接口
public interface UserService {
public void getUser();
}
- 最后写Service的实现类
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
- 测试
@Test
public void test(){
UserService service = new UserServiceImpl();
service.getUser();
}
当把Userdao的实现类增加一个时
public class UserDaoMySqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("MySql获取用户数据");
}
}
紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现 .
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
假设我们再增加一个Userdao的实现类 ,再修改service实现类
再添加再修改……一直这样下去。最后……
这种方式耦合性太高,牵一发而动全身
如何解决
我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下 .
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 利用set实现
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
@Test
public void test(){
UserServiceImpl service = new UserServiceImpl();
service.setUserDao( new UserDaoMySqlImpl() );
service.getUser();
//那我们现在又想用Oracle去实现呢
service.setUserDao( new UserDaoOracleImpl() );
service.getUser();
}
从程序控制创建对象 到 自行控制创建对象 ,把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .
这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !
其中涉及到迪米特原则(LoD):又叫作最少知识原则(The Least Knowledge Principle),一个类应当对其它类有尽可能少的了解,类与类之间的了解的越多,关系越密切,耦合度越大,当一个类发生改变时,还有一个类也可能发生变化。
迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间尽可能少存在依赖关系。
迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。
2.1.2 IOC本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
IOC底层原理:xml解析、工厂模式、反射
2.2 HelloSpring
2.2.1 导入Jar包
注 : spring 需要导入commons-logging进行日志记录 . 利用maven 自动下载对应的依赖项 .
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
2.2.2 编写代码
- 编写一个Hello实体类
public class Hello {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("Hello,"+ name );
}
}
- 编写spring文件 , 命名为beans.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就是java对象 , 由Spring创建和管理-->
<bean id="hello" class="com.kuang.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
</beans>
- 测试
@Test
public void test(){
//解析beans.xml文件 , 生成管理相应的Bean对象(获取Spring容器)
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//getBean : 参数即为spring配置文件中bean的id .
Hello hello = (Hello) context.getBean("hello");
hello.show();
}
2.2.2 思考
- Hello 对象是谁创建的 ? 【 hello 对象是由Spring创建的 】
- Hello 对象的属性是怎么设置的 ? 【hello 对象的属性是由Spring容器设置的 】
这个过程就叫控制反转 :
- 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
- 反转 : 程序本身不创建对象 , 而变成被动的接收对象
依赖注入 : 就是利用set方法来进行注入的.
IOC是一种编程思想,由主动的编程变成被动的接收
上面一通依赖注入操作,不知道在写啥玩意!!!转头扎进了百度……
问题一:为什么不用new而用xml
- new的方法对后期维护不友好。当实体类有新增,修改,更换时,需要找到写在各个位置的实例并进行修改,不利于扩展。
如:连接mysql数据库后,想要换一个数据库,就得把所有new的全部改一遍
引入工厂模式
工厂模式的实质是“定义一个创建对象的接口,让子类决定实例化哪个类。”
通过工厂模式来管理对象
例: 为了创建不同的类型的车,需要三个不同的工厂去创建,把创建的任务交给工厂去完成.
/**
* @Project: spring
* @description: 造车工厂的接口
* @author: sunkang
* @create: 2018-08-30 22:33
* @ModificationHistory who when What
**/
public interface Factory {
Car getCar();
}
/**
*生产奥迪的工厂
*/
public class AudiFactory implements Factory {
@Override
public Car getCar() {
return new Audi();
}
}
/**
*生产奔驰的工厂
*/
public class BenzFactory implements Factory {
@Override
public Car getCar() {
return new Benz();
}
}
/**
* 生产宝马的工厂
*/
public class BmwFactory implements Factory {
@Override
public Car getCar() {
return new Bmw();
}
}
/**
* 工厂方法的测试类
*/
public class FactoryTest {
public static void main(String[] args) {
//1.首先先创建一个奥迪工厂出来
Factory factory = new AudiFactory();
//2.然后根据工厂得到奥迪车,具体的造车工厂交给工厂来完成
System.out.println(factory.getCar());
factory = new BmwFactory();
System.out.println(factory.getCar());
}
}
将对象创建提取到工厂类中,由工厂类负责创建对象,调用者无需考虑对象的创建,只管从工厂中拿,在修改被调用者是也无需改动太多的代码,只需要修改工厂类。(原来的两个依赖类解耦,但和工厂类耦合)
但是,工厂模式只是将创建对象的责任分离到工厂实体上,对工厂产生了依赖,并未解除依赖,只是有多个依赖转为一个“工厂”的依赖,创建对象还是不够灵活。
比如:制造一辆电动车时,需要轮胎,轮胎需要轮毂,若干螺丝,外胎,内胎,刹车…… , 那么创建车的对象时,工厂就需要先创建一系列依赖的对象。但当我要生产汽车时,轮胎就得换掉了,总不能把电动车的轮胎装在汽车上面吧,这时我们就得重新开一个工厂做汽车。我又想做其他的,那又得搞个工厂……(无情的工厂制造机)
引入依赖注入DI
在依赖注入的情况下,Object只知道依赖关系,它对容器或工厂一无所知。
看到一篇 依赖倒转 的文章,写的非常清晰,我转载过来了,希望不要介意,大佬实在太厉害,终于看懂一点IOC了,推荐看看大佬的文章。
之前设计汽车的的思想:要创建汽车对象,就得先创建依赖对象。即先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。
这种设计可维护性低,如依赖对象改变时,导致依赖该对象的所有类都需要修改。
假设我们需要改动一下轮胎(Tire)类,把它的尺寸变成动态的,而不是一直都是30。我们需要这样改:
由此我们可以看到,仅仅是为了修改轮胎的构造函数,这种设计却需要修改整个上层所有类的构造函数!在软件工程中,这样的设计几乎是不可维护的——在实际工程项目中,有的类可能会是几千个类的底层,如果每次修改这个类,我们都要修改所有以它作为依赖的类,那软件的维护成本就太高了。
我们现在换一种思路。**假设所有依赖对象都已经创建好了,要用的时候直接调用就行了。**我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身, 车身依赖汽车。
这时候,如果要改动轮子的设计,我们就只需要改动轮子的设计,而不需要动底盘,车身,汽车的设计了。
这就是依赖倒置原则——把原本的高层建筑依赖底层建筑“倒置”过来,变成底层建筑依赖高层建筑。高层建筑决定需要什么,底层去实现这样的需求,但是高层并不用管底层是怎么实现的。这样就不会出现前面的“牵一发动全身”的情况。
所以我们需要进行控制反转(IoC),及上层控制下层,而不是下层控制着上层。我们用依赖注入(Dependency Injection)这种方式来实现控制反转。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。这里我们用构造方法传递的依赖注入方式重新写车类的定义:
这里我们再把轮胎尺寸变成动态的,同样为了让整个系统顺利运行,我们需要做如下修改:
这里**我只需要修改轮胎类就行了,不用修改其他任何上层类。这显然是更容易维护的代码。不仅如此,在实际的工程中,这种设计模式还有利于不同组的协同合作和单元测试:**比如开发这四个类的分别是四个不同的组,那么只要定义好了接口,四个不同的组可以同时进行开发而不相互受限制;而对于单元测试,如果我们要写Car类的单元测试,就只需要Framework类传入Car就行了,而不用把Framework, Bottom, Tire全部new一遍再来构造Car。
控制反转容器
上面的例子中,对车类进行初始化的那段代码发生的地方,就是控制反转容器。
因为采用了依赖注入,在初始化的过程中就不可避免的会写大量的new。这里IoC容器就解决了这个问题。这个容器可以自动对你的代码进行初始化,你只需要维护一个Configuration(可以是xml可以是一段代码),而不用每次初始化一辆车都要亲手去写那一大段初始化的代码。这是引入IoC Container的第一个好处。
IoC Container的第二个好处是:**我们在创建实例的时候不需要了解其中的细节。**在上面的例子中,我们自己手动创建一个车的实例时候,是从底层往上层new的:
这个过程中,我们需要了解整个Car/Framework/Bottom/Tire类构造函数是怎么定义的,才能一步一步new/注入。
而IoC Container在进行这个工作的时候是反过来的,它先从最上层开始往下找依赖关系,到达最底层之后再往上一步一步new(有点像深度优先遍历):
这里IoC Container可以直接隐藏具体的创建实例的细节,在我们来看它就像一个工厂:
我们就像是工厂的客户。我们只需要向工厂请求一个Car实例,然后它就给我们按照Config创建了一个Car实例。我们完全不用管这个Car实例是怎么一步一步被创建出来。
再次重申:IOC是一种编程思想,由主动的编程变成被动的接收
这个是大佬总结的spring ioc导图,很棒!!!
2.3 IOC过程
- xml配置文件,配置创建的对象dao
<!--
传统方式: 类型 变量名 = new 类型()
对应bean: id = 变量名
class = new 的对象
property : 给对象中的属性设置值
-->
<bean id="dao" class="com.xiong.UserDao">
<property name="str" value="Spring" />
</bean>
- 有service类和dao类,创建工厂类
class UserFactory {
public static UserDao getDao() {
String classValue = "com.xiong.UserDao"; //xml解析, 得到class属性值
Class clazz = Class.forName(classValue); //通过反射创建对象
return (UserDao)clazz.newInstance();
}
}
当改变路径时,只需要改变配置文件的值
2.4 IOC 接口
- IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
- Spring提供IOC容器实现两种方式:(两个接口)
- BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用
加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
- ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用
加载配置文件时候就会把在配置文件对象进行创建
BeanFactory使用时创建耗时响应慢但节省资源,ApplicationContext先创建耗费资源到响应快
3. Bean 管理
bean就是java对象
- Bean的配置
<!--bean就是java对象,由Spring创建和管理-->
<!--
id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
如果配置id,又配置了name,那么name是别名
name可以设置多个别名,可以用逗号,分号,空格隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
- Bean管理:
(1)Spring创建对象
(2)Spring注入属性
- 基于xml配置文件方式实现
- 基于注解方式实现
依赖注入DI:
- 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
- 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
3.1 基于xml方式——set方法注入
- 创建对象
// 配置User对象创建
<bean id="user" class="com.xiong.User"></bean>
(1)在spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建
在bean标签有很多属性,介绍常用的属性:
id属性:唯一标识
class属性:类全路径(包类路径)
(2)创建对象时,默认也是执行无参数构造方法完成对象创建
- 注入属性
//1.创建类,定义属性和对应的set方法
public class Book {
//创建属性
private String bname;
private String bauthor;
//创建属性对应的set方法
public void setBname(String bname) {
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
public void show(){
System.out.println("name="+ bname );
}
}
<!--2. 配置属性注入-->
<bean id="book" class="com.xiong.Book">
<!--使用property完成属性注入
name:类里面属性名称
value:向属性注入的值 -->
<property name="bname" value="易筋经"></property>
<property name="bauthor" value="达摩老祖"></property>
</bean>
//测试
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, book已经创建好了 , 通过无参构造
Book book = (Book) context.getBean("book");
//调用对象的方法
book.show();
}
- 使用有参数构造进行注入
//1. 创建类,定义属性,创建属性对应有参数构造方法
public class Orders {
//属性
private String oname;
private String address;
//有参数构造
public Orders(String oname,String address) {
this.oname = oname;
this.address = address;
}
public void show(){
System.out.println("name="+ oname );
}
}
<!--2. 在spring配置文件中进行配置-->
<!-- 有参数构造注入属性 可使用 index参数下标、name参数名、type参数类型-->
<bean id="orders" class="com.xiong.Orders">
<constructor-arg name="oname" value="电脑"></constructor-arg>
<constructor-arg name="address" value="China"></constructor-arg>
</bean>
//测试
@Test
public void testT(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Orders orders = (Orders) context.getBean("orders");
Orders.show();
}
xml 注入其他类型属性
1、字面量
(1) null值
<property name="address">
<null/>
</property>
(2) 属性值包含特殊符号
<!--1. 把特殊符号进行转义 < >
或
2. 把带特殊符号内容写到CDATA -->
<property name="address">
<value><![CDATA[<<南京>>]]></value>
</property>
2、外部bean
(1) 创建两个类 service类和dao类
(2) 在service调用dao里面的方法
(3) 在spring配置文件中进行配置
public class UserService {
//创建UserDao类型属性,生成set方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("service add..............."); userDao.update();
}
}
<!--1 service和dao对象创建-->
<bean id="userService" class="com.xiong.service.UserService">
<!--注入userDao对象
name属性:类里面属性名称,要对应
ref属性:引用Spring容器中创建好的对象 -->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.xiong.dao.UserDaoImpl"></bean>
3、内部bean
(1)一对多关系:部门和员工
一个部门有多个员工,一个员工属于一个部门;
(2)在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性进行表示
//部门类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
}
//员工类
public class Emp {
private String ename;
private String gender;
//员工属于某一个部门,使用对象形式表示
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
}
(3)在spring配置文件中进行配置
<!--内部bean-->
<bean id="emp" class="com.xiong.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.xiong.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
4、级联赋值
写法一
<!--级联赋值-->
<bean id="emp" class="com.xiong.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.xiong.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>
写法二
private Dept dept;
//生成dept的get方法
public Dept getDept() {
return dept;
}
public Dept setDept() {
this.dept = dept;
}
<!--级联赋值-->
<bean id="emp" class="com.xiong.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
<property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept" class="com.xiong.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>
- 集合属性
//(1)创建类,定义数组、list、map、set类型属性,生成对应set方法
public class Stu {
private String[] courses;
private List<String> list;
private Map<String,String> maps;
private Set<String> sets;
public void setSets(Set<String> sets) {
this.sets = sets;
}
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
}
<!--(2)在spring配置文件进行配置 -->
<!--1 集合类型属性注入-->
<bean id="stu" class="com.xiong.collectiontype.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<!--map类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--set类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>
在集合里面设置对象类型值
<!--创建多个course对象-->
<bean id="course1" class="com.xiong.collectiontype.Course">
<property name="cname" value="Spring5框架"></property>
</bean>
<bean id="course2" class="com.xiong.collectiontype.Course">
<property name="cname" value="MyBatis框架"></property>
</bean>
<!--注入list集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
- Properties注入
<property name="info">
<props>
<prop key="学号">20190604</prop>
<prop key="性别">男</prop>
<prop key="姓名">小明</prop>
</props>
</property>
3.2 FactoryBean
Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
- 普通 bean:在配置文件中定义 bean 类型就是返回类型
- 工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
//第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean
//第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
public class MyBean implements FactoryBean<Course> {
//定义返回 bean
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
@Test
public void test3() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
<bean id="myBean" class="com.xiong.factorybean.MyBean"></bean>
3.3 bean 作用域
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 。默认情况下,bean 是单实例对象
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
- 如何设置单实例还是多实例
(1)在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例
(2)scope 属性值
第一个值 默认值,singleton,表示是单实例对象
第二个值 prototype,表示是多实例对象
区别:设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象。 设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建 对象,而是在调用 getBean 方法时候创建多实例对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域
- Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求 都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web 的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="loginAction" class="cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
- Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
3.4 bean 生命周期
从对象创建到对象销毁的过程:
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(4)bean 可以使用了(对象获取到了)
(5)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
public class Orders {
//无参数构造
public Orders() {
System.out.println("第一步 执行无参数构造创建bean实例");
}
private String oname;
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步 调用set方法设置属性值");
}
//创建执行的初始化的方法
public void initMethod() {
System.out.println("第三步 执行初始化的方法");
}
//创建执行的销毁的方法
public void destroyMethod() {
System.out.println("第五步 执行销毁的方法");
}
}
@Test
public void testBean3() {
// ApplicationContext context =
// new ClassPathXmlApplicationContext("bean4.xml");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建bean实例对象");
System.out.println(orders);
//手动让bean实例销毁
context.close();
}
<bean id="orders" class="com.xiong.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>
bean的后置处理器,bean生命周期有七步
- 通过构造器创建bean实例(无参数构造)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 把bean实例传递bean后置处理器的方法postProcessBeforeInitialization
- 调用bean的初始化的方法(需要进行配置初始化的方法)
- 把bean实例传递bean后置处理器的方法 postProcessAfterInitialization
- bean可以使用了(对象获取到了)
- 当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)
//(1)创建类,实现接口BeanPostProcessor,创建后置处理器
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
}
<!--配置后置处理器-->
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>
4. 自动装配
- 自动装配是使用spring满足bean依赖的一种方法,spring会在应用上下文中为某个bean寻找其依赖的bean。
Spring中bean有三种装配机制:
- 在xml中显式配置;
- 在java中显式配置;
- 隐式的bean发现机制和自动装配。
自动化的装配bean
- 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
- 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
手动装配过程
新建两个实体类
public class Cat {
public void shout() {
System.out.println("miao~");
}
}
public class Dog {
public void shout() {
System.out.println("wang~");
}
}
新建一个用户类 User
public class User {
private Cat cat;
private Dog dog;
private String str;
}
编写Spring配置文件
<?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="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="str" value="qinjiang"/>
</bean>
</beans>
测试
public class MyTest {
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
user.getCat().shout();
user.getDog().shout();
}
}
自动装配
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。
4.1 ByName
autowire byName (按名称自动装配)
修改bean配置,增加一个属性 autowire=”byName”
<bean id="user" class="com.kuang.pojo.User" autowire="byName">
<property name="str" value="qinjiang"/>
</bean>
当我们将 cat 的bean id修改为 catXXX,再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结: 当一个bean节点带有 autowire byName的属性时。将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat, 去spring容器中寻找是否有此字符串名称id的对象, 如果有,就取出注入;如果没有,就报空指针异常。
4.2 byType
autowire byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException
测试:
- 将user的bean配置修改一下 :
autowire="byType"
- 测试,正常输出
- 在注册一个cat 的bean对象!
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User" autowire="byType">
<property name="str" value="qinjiang"/>
</bean>
测试,报错:NoUniqueBeanDefinitionException, 删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
4.3 使用注解
准备工作: 利用注解的方式注入属性。
在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
开启属性注解支持!
<context:annotation-config/>
@Autowired
- @Autowired是按类型自动转配的,不支持id匹配。
- 需要导入 spring-aop的包!
测试:将User类中的set方法去掉,使用@Autowired注解
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String str;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getStr() {
return str;
}
}
此时配置文件内容
<context:annotation-config/>
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>
@Autowired(required=false) 说明: false,对象可以为null;true,对象必须存对象,不能为null。
//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
@Qualifier
- @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
- @Qualifier不能单独使用。
测试实验步骤:
配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!
<bean id="dog1" class="com.kuang.pojo.Dog"/>
<bean id="dog2" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
没有加Qualifier测试,直接报错
在属性上添加Qualifier注解
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
测试,成功输出!
@Resource
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
实体类
public class User {
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String str;
}
beans.xml
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>
配置文件2:beans.xml , 删掉cat2
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
实体类上只保留注解
@Resource
private Cat cat;
@Resource
private Dog dog;
成功
结论:先进行byName查找,失败;再进行byType查找,成功。
@Value
注入普通类型属性
@Value(value = "abc")
private String name;
4.4 小结
@Autowired与@Resource异同:
- @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
- @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
- @Resource,默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
5. 使用注解开发
注:在spring4之后,想要使用注解形式,必须得要引入aop的包, 在配置文件当中,还得要引入一个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>
5.1 Bean的实现
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
开启组件扫描,配置扫描哪些包下的注解,如果扫描多个包,多个包使用逗号隔开
<!--指定注解扫描包-->
<context:component-scan base-package="com.kuang.pojo"/>
在指定包下编写类,增加注解
//在注解里面value属性值可以省略不写,
//默认值是类名称,首字母小写
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name = "秦疆";
}
测试
@Test
public void test(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("beans.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.name);
}
5.2 属性注入
使用注解注入属性
可以不用提供set方法,直接在直接名上添加@value(“值”)
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
@Value("秦疆")
// 相当于配置文件中 <property name="name" value="秦疆"/>
public String name;
}
如果提供了set方法,在set方法上添加@value(“值”);
@Component("user")
public class User {
public String name;
@Value("秦疆")
public void setName(String name) {
this.name = name;
}
}
5.3 衍生注解
注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。(只是用来做区分)
- @Controller:web层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
5.4 作用域
@scope
- singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
- prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
@Value("秦疆")
public String name;
}
5.5 小结
XML与注解比较
- XML可以适用任何场景 ,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便
xml与注解整合开发 :
- xml管理Bean
- 注解完成属性注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
<context:annotation-config/>
作用:
- 进行注解驱动注册,从而使注解生效
- 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
- 如果不扫描包,就需要手动配置bean
- 如果不加注解驱动,则注入的值为null!
5.6 基于Java类进行配置
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
测试:
- 编写一个实体类Dog
@Component //将这个类标注为Spring的一个组件,放到容器中!
public class Dog {
public String name = "dog";
}
- 新建一个config配置包,编写一个MyConfig配置类
@Configuration //代表这是一个配置类,替代xml配置文件
@ComponentScan("com.xiong.pojo")
public class MyConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public Dog getDog(){
return new Dog();
}
}
- 测试
@Test
public void test2(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = (Dog) applicationContext.getBean("getDog");
System.out.println(dog.name);
}
注:Component+ComponentScan注解应和Configration+Bean分开用,前者和后者都能实现Bean的注入。
@ComponentScan(“com.xiong.pojo”)扫描了包,默认id为Dog类的小写dog,等同于xml里面添加 context:component-scan base-package=“xxx.xxx”.
如果去掉包扫描, 这id必须一样!!!即 Dog dog = (Dog) applicationContext.getBean(“getDog”); 对应上方的bean的id
6. AOP
6.1 什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP的本质
AOP是实现分散关注的编程方法,将关注封装在切面中。如何分散关注呢?将需求功能从不相关的类中分离出来,同时使多个类共用一个行为,一旦行为发生变化,不必修改多个类,只修改行为即可。
AOP将软件系统划分为两个部分:核心关注点、横切关注点,业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的特点是经常发生在核心关注点的多个位置,而且它们功能基本相似。AOP的作用在于分离系统中的各个关注点,将核心关注点和横切关注点分离开来。
AOP技术利用一种称为“横切”的技术,剖解开封装对象的内部,将影响多个类的公共行为封装到一个可重用的模块中,并将其命名为Aspect
切面。所谓的切面,简单来说就是与业务无关,却为业务模块所共同调用的逻辑,将其封装起来便于减少系统的重复代码,降低模块的耦合度,有利用未来的可操作性和可维护性。
补充
OOP面向对象编程,主要是针对业务处理过程中的实体属性和行为的抽象与封装,以获得更加清晰高效地逻辑单元。
AOP面向切面编程,针对业务处理过程中的切面进行提取,它所面对的是处理过程中某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
AOP是OOP的补充和完善,OOP引入封装、继承、多态等概念建立了一种对象层次结构,用来模拟公共行为的一个集合。当需要为分散的对象引入公共行为的时候,OOP显得无能为力,也就是说,OOP允许定义从上到下的关系,但并不适合定义从左到右的关系。
6.2 Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
- 横切关注点:对哪些方法进行拦截,拦截后怎样处理。即是,与我们业务逻辑无关的,却为业务模块所共同调用的逻辑,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ….
- 切面(ASPECT):散落在系统各处通用的业务逻辑代码,如日志模块、权限模块、事务模块等。切面通常是一个类,可以定义切入点和通知。类是对物体特征的抽象,切面是对横切关注点的抽象。切面是业务流程运行的某个特定步骤,是应用运行过程中的关注点,关注点通常会横切多个对象,因此也被称为横切关注点。
- 通知(Advice):AOP在特定切入点上执行的增强处理,是拦截到连接点之后要执行的代码。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):带有通知的连接点,在程序中主要体现为编写切入点表达式。切入点是对连接点进行拦截的定义。切入点用于定义通知应该切入到哪些连接点上,不同的通知需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。
- 连接点(JointPoint):程序执行过程中明确的点,一般是类中方法的调用。连接点是程序在运行过程中能够插入切面的地点,比如方法调用、异常抛出、字段修改等。
简单解释一下上面的术语:
- 目标对象:即要被代理的类 , save()为对象中的方法(实现逻辑)
- 连接点:被代理类中哪些方法可以被增强,这些方法称为连接点
- 切入点:实际被增强的方法
- 通知: 即增强,实际增强的逻辑部分,包括多种类型:
- 前置通知:发生在方法前
- 后置通知:发生在方法后
- 环绕通知:发生在方法前后
- 异常通知:发生在方法抛出异常后
- 最终通知:方法后一定会发生的,类似于finally
- 切面:指把通知应用到切入点的过程
切入点表达式
- 切入点表达式作用:知道对哪个类里面的哪个方法进行增强
- 语法结构:语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
- 举例1:对com.atguigu.dao.BookDao类里面的add()方法进行增强
//权限修饰符默认public, * 表示返回类型任意, ..表示参数任意
execution(* com.atguigu.dao.BookDao.add(..))
- 举例2:对com.atguigu.dao.BookDao类里面的所有的方法进行增强
//后面的 * 表示类下的所有方法
execution(* com.atguigu.dao.BookDao.* (..))
- 举例3:对com.atguigu.dao包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.*.* (..))
6.3 使用Spring实现Aop
【重点】使用AOP,需要导入一个依赖包!
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
方式一:注解
- 创建类,在类里面定义方法
public class User {
public void add() {
System.out.println("add.......");
}
}
- 创建增强类(编写增强逻辑)
(1)在增强类里面,创建方法,让不同方法代表不同通知类型
//增强的类
public class UserProxy {
//前置通知
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
- 进行通知的配置
(1)在spring配置文件中,开启注解扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.atguigu.spring5.aopanno"></context:component-scan>
</beans>
(2)使用注解创建User和UserProxy对象
//被增强的类
@Component
public class User {
}
//增强的类
@Component
public class UserProxy {
}
(3)在增强类上面添加注解 @Aspect
//增强的类
@Component
@Aspect
//生成代理对象
public class UserProxy {
}
(4)在spring配置文件中开启生成代理对象
<!-- 开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 配置不同类型的通知
(1)在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
//增强的类
@Component
@Aspect
//生成代理对象
public class UserProxy {
//前置通知
//@Before注解表示作为前置通知
@Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void before() {
System.out.println("before.........");
}
//后置通知(返回通知)
@AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning.........");
}
//最终通知
@After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void after() {
System.out.println("after.........");
}
//异常通知
@AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing.........");
}
//环绕通知
@Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前.........");
//被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后.........");
}
}
打印测试结果:
方法存在异常结果:
- 相同的切入点抽取
//相同切入点抽取
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void pointdemo() {
}
//前置通知
//@Before注解表示作为前置通知
@Before(value = "pointdemo()")
public void before() {
System.out.println("before.........");
}
- 有多个增强类对同一个方法进行增强,设置增强类优先级
(1)在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
@Component
@Aspect
@Order(1)
public class PersonProxy{}
补充:配置类
- 完全使用注解开发 (1)创建配置类,不需要创建xml配置文件
@Configuration
@ComponentScan(basePackages = {"com.atguigu"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop { }
方式二:配置文件
- 创建两个类,增强类和被增强类,创建方法
- 在spring配置文件中创建两个类对象
<!--创建对象-->
<bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>
- 在spring配置文件中配置切入点
<!--配置aop增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopxml.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
7. 事务
7.1 什么是事务
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
典型场景:银行转账,不能单方面成功或失败
- lucy 转账100元 给mary
- lucy少100,mary多100
7.2 事务四个特性(ACID)
- 原子性
事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部执行,要么全部不执行。只要 其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
- 一致性
事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
- 隔离性
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被 其他事务的操作所干扰,多个并发事务之间要相互隔离。
对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才 开始,这样每个事务都感觉不到有其他事务在并发地执行。
- 持久性
当事务正确完成后,它对于数据的改变是永久性的。
7.3 事务操作(搭建事务操作环境)
7.3.1 没有事务时
1、创建数据库表,添加记录
2、创建service,搭建dao,完成对象创建和注入关系
(1)service注入dao,在dao注入JdbcTemplate,在JdbcTemplate注入DataSource
@Service
public class UserService {
//注入dao
@Autowired
private UserDao userDao;
}
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
}
3、在dao创建两个方法:多钱和少钱的方法,在service创建方法(转账的方法)
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//lucy转账100给mary
//少钱
@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?"; jdbcTemplate.update(sql,100,"lucy");
}
//多钱
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql,100,"mary");
}
}
@Service
public class UserService {
//注入dao
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
//lucy少100
userDao.reduceMoney();
//mary多100
userDao.addMoney();
}
}
上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,有问题
当出现异常时,
reduceMoney()
执行了,接着出现异常,导致后续代码都未执行,最终结果是一边钱少了,但另一边钱未增加。(银行说这个操作我喜欢……)
如何解决上述问题: 使用事务进行解决
7.3.2 加入事务
- 传统方式
既然会出现异常导致代码执行出现问题,那么我们将会出现异常的代码使用try
catch
捕获,内部实现事务
注:事务添加到JavaEE三层结构里面Service层(业务逻辑层)
在Spring进行事务管理操作有两种方式:
- 编程式事务管理:上面使用的方法,每开启一个事务按照上述四个步骤来写,代码冗余,修改不便
- 声明式事务管理
- 基于注解方式(使用)
- 基于xml配置文件方式
在Spring进行声明式事务管理,底层使用AOP原理 ,
Spring事务管理API
提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
事务操作(注解声明式事务管理)
- 在spring配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 在spring配置文件,开启事务注解
(1)在spring配置文件引入名称空间 tx
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
(2)开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 在service类上面(或者service类里面方法上面)添加事务注解
(1)@Transactional,这个注解添加到类上面,也可以添加方法上面
(2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务
(3)如果把这个注解添加方法上面,为这个方法添加事务
@Service
@Transactional
public class UserService {
}
7.3.3 声明式事务管理参数配置
在service类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数
7.3.4 propagation——事务传播行为
简单的理解就是多个事务方法相互调用时,事务如何在这些方法间传播。
举个栗子,方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
例:
dao层两个方法A和B(不同类下,同一类下要使用注入,不能直接使用this.方法名调用(这个是对象内部方法,不会通过Spring管理)),方法A执行会在A表插入一条数据,方法B执行会在B表插入一条数据
//将传入参数a存入A表
pubilc void A(a){
insertIntoATable(a);
}
//将传入参数b存入B表
public void B(b){
insertIntoBTable(b);
}
Service层测试
- REQUIRED —— 情况一
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
B(b1); //调用B入参b1
i = 10 / 0; //模拟异常
B(b2); //调用B入参b2
}
结果:数据库没有插入新的数据,数据库还是保持着执行testMain方法之前的状态,没有发生改变。
testMain上声明了事务,在执行testB方法时就加入了testMain的事务(当前存在事务,则加入这个事务),在执行testB方法抛出异常后事务会发生回滚,又testMain和testB使用的同一个事务,所以事务回滚后testMain和testB中的操作都会回滚,也就使得数据库仍然保持初始状态
- REQUIRED —— 情况二
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
B(b1); //调用B入参b1
i = 10 / 0; //模拟异常
B(b2); //调用B入参b2
}
结果:数据a1存储成功,数据b1和b2没有存储。
由于testMain没有声明事务,testB有声明事务且传播行为是REQUIRED,所以在执行testB时会自己新建一个事务(如果当前没有事务,则自己新建一个事务),testB抛出异常则只有testB中的操作发生了回滚,也就是b1的存储会发生回滚,但a1数据不会回滚,所以最终a1数据存储成功,b1和b2数据没有存储.
- SUPPORTS —— 情况一
当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){
B(b1); //调用B入参b1
i = 10 / 0; //模拟异常
B(b2); //调用B入参b2
}
这种情况下,执行testMain的最终结果就是,a1,b1存入数据库,b2没有存入数据库。由于testMain没有声明事务,且testB的事务传播行为是SUPPORTS,所以执行testB时就是没有事务的(如果当前没有事务,就以非事务方法执行),则在testB抛出异常时也不会发生回滚,所以最终结果就是a1和b1存储成功,b2没有存储。
- SUPPORTS —— 情况二
当前存在事务,则加入当前事务
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){
B(b1); //调用B入参b1
i = 10 / 0; //模拟异常
B(b2); //调用B入参b2
}
testMain上声明事务且使用REQUIRED传播方式的时候,满足当前存在事务,则加入当前事务,在testB抛出异常时事务就会回滚,最终结果就是a1,b1和b2都不会存储到数据库。
- REQUIRES_NEW
创建一个新事务,如果存在当前事务,则挂起该事务。
可以理解为设置事务传播类型为REQUIRES_NEW的方法,在执行时,不论当前是否存在事务,总是会新建一个事务。
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
i = 10 / 0; //模拟异常
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB(){
B(b1); //调用B入参b1
B(b2); //调用B入参b2
}
这种情形的执行结果就是a1没有存储,而b1和b2存储成功,因为testB的事务传播设置为REQUIRES_NEW,所以在执行testB时会开启一个新的事务,testMain中发生的异常时在testMain所开启的事务中,所以这个异常不会影响testB的事务提交,testMain中的事务会发生回滚,所以最终a1就没有存储,而b1和b2就存储成功了。
与这个场景对比的一个场景就是testMain和testB都设置为REQUIRED,那么上面的代码执行结果就是所有数据都不会存储,因为testMain和testMain是在同一个事务下的,所以事务发生回滚时,所有的数据都会回滚.
7.3.5 ioslation——事务隔离级别
- 事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
- 有三个读问题:脏读、不可重复读、幻读
- 脏读:一个未提交事务读取到另一个未提交事务的数据
- **不可重复读:**一个事务两次读取同一行的数据,结果得到不同状态的结果,中间正好另一个事务更新了该数据,两次数据内容不一致,不可被信任。针对update操作
- 幻读:一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。幻读是事务非独立执行时发生的一种现象。前后多次读取,数据总量不一致。 针对insert和delete操作
解决:设置事务隔离级别
READ UNCOMMITTED(读未提交)
该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
READ_COMMITTED (读提交)
该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
REPEATABLE_READ (可重复读)
该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
SERIALIZABLE (串行化)
所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 在该隔离级别下事务都是串行顺序执行的,MySQL 数据库的 InnoDB 引擎会给读操作隐式加一把读共享锁,从而避免了脏读、不可重读复读和幻读问题。
7.3.6 其他
- timeout:超时时间
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
- readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly默认值false,表示可以查询,可以添加修改删除操作
(3)设置readOnly值是true,设置成true之后,只能查询
- rollbackFor:回滚
设置出现哪些异常进行事务回滚 - noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
7.4 XML 声明式事务管理
在spring配置文件中进行配置
1. 配置事务管理器
1. 配置通知
1. 配置切入点和切面
<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(*com.atguigu.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
7.5 完全注解声明式事务管理
- 创建配置类,使用配置类替代xml配置文件
@Configuration
//配置类
@ComponentScan(basePackages = "com.atguigu")
//组件扫描
@EnableTransactionManagement
//开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db"); dataSource.setUsername("root");
dataSource.setPassword("root"); return dataSource;
}
//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到ioc容器中根据类型找到dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager
getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager =
new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource); return transactionManager;
}
}
间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
- readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly默认值false,表示可以查询,可以添加修改删除操作
(3)设置readOnly值是true,设置成true之后,只能查询
- rollbackFor:回滚
设置出现哪些异常进行事务回滚 - noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
7.4 XML 声明式事务管理
在spring配置文件中进行配置
1. 配置事务管理器
1. 配置通知
1. 配置切入点和切面
<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(*com.atguigu.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
7.5 完全注解声明式事务管理
- 创建配置类,使用配置类替代xml配置文件
@Configuration
//配置类
@ComponentScan(basePackages = "com.atguigu")
//组件扫描
@EnableTransactionManagement
//开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db"); dataSource.setUsername("root");
dataSource.setPassword("root"); return dataSource;
}
//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到ioc容器中根据类型找到dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager
getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager =
new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource); return transactionManager;
}
}
以上很多地方学的不是很透彻,写的有点饶,后面有时间在修改!!!