1、Spring简介
1.1、概述
Spring : 春天,它给软件行业带来了春天。现代化Java
的开发,就是基于Spring
的开发!
很难想象Rod Johnson
的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring理念 : 让现有技术更容易使用。它本身就是一个大杂烩,也是一种融合剂。它整合了现有的框架技术。
1.2、优点和缺点
(1)优点:
Spring
是一个开源免费的容器(框架)。
Spring
是一个轻量级的、非侵入式的框架。
- 两大特性:控制反转(IOC)、面向切面编程(Aop)。
- 支持事务的处理。
- 支持各种框架的整合。
- …
**(2)缺点:**经过长期的发展,它的配置变得十分繁琐,违背了原来的理念。人称:”配置地狱“!直到SPring Boot
的出现。
1.3、总结成一句话
一句话概括:Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的容器(框架)。
1.4、Spring框架组成
Spring
框架是一个分层架构,它由7大模块组成。
Spring
模块构建在核心容器(Spring Core
)之上,核心容器定义了创建、配置和管理bean
的方式 。
组成Spring
框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。
每个模块的功能如下:
- 核心容器:核心容器提供
Spring
框架的基本功能。核心容器的主要组件是BeanFactory
,它是工厂模式的实现。BeanFactory
使用控制反转(IOC
) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- Spring AOP:通过配置管理特性,
Spring AOP
模块直接将面向切面的编程功能 , 集成到了Spring
框架中。所以,可以很容易地使Spring
框架管理任何支持AOP
的对象。Spring AOP
模块为基于Spring
的应用程序中的对象提供了事务管理服务。通过使用Spring AOP
,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:
JDBC DAO
抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO
的面向JDBC
的异常遵从通用的DAO
异常层次结构。
- Spring ORM(Object-Relational Mapping,对象关系映射):Spring 框架插入了若干个
ORM
框架,从而提供了ORM
的对象关系工具,其中包括 JDO
、Hibernate
和iBatis SQL Map
。所有这些都遵从Spring
的通用事务和DAO
异常层次结构。
- Spring上下文(Context):
Spring
上下文是一个配置文件,向Spring
框架提供上下文信息。Spring
上下文包括企业服务,例如 JNDI
、EJB
、电子邮件、国际化、校验和调度功能。
- Spring Web 模块:
Web
上下文模块建立在应用程序上下文模块之上,为基于Web
的应用程序提供了上下文。所以,Spring
框架支持与Jakarta Struts
的集成。Web
模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:
MVC
框架是一个全功能的构建Web
应用程序的MVC
实现。通过策略接口,MVC
框架变成为高度可配置的,MVC
容纳了大量视图技术,其中包括 JSP
、Velocity
、Tiles
、iText
和POI
。
1.5、Spring Boot与Spring Cloud
Spring Boot
是Spring
的一套快速配置脚手架,我们可以基于Spring Boot
快速开发单个微服务;
Spring Cloud
是基于Spring Boot
实现的;
Spring Boot
专注于快速、方便集成的单个微服务个体,Spring Cloud
关注全局的服务治理框架;
Spring Boot
使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 。Spring Cloud
很大一部分是基于Spring Boot
来实现,Spring Boot
可以离开Spring Cloud
独立使用开发项目,但是Spring Cloud
离不开Spring Boot
,属于依赖的关系。
SpringBoot
在SpringClound
中起到了承上启下的作用,如果你要学习SpringCloud
必须要学习SpringBoot
。
- 学好
Spring Boot
的前提是:需要完全掌握Spring
和Spring MVC
。
2、IOC理论推导(重点)
2.1、引例
2.1.1、以前实现一个业务的步骤
(1)新建一个普通的Maven
项目,(具体步骤参考《Maven详解》),项目名称为Spring-Study
。
(2)删除系统自动生成的src
目录,所得到的空项目就是我们的Maven父项目,我们可以在其中创建多个子项目(即:Module
)。
(3)在父项目的pom.xml
文件中导入父/子工程需要的Maven依赖。
pom.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies>
|
(4)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-01-IOC1
。
(5)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(6)在子项目的java
目录下新建一个com.atangbiji.dao
包(用于存放我们自己编写的持久层代码),在该包下新建一个UserDao
接口,并在其中创建接口函数。
UserDao.java
文件:
1 2 3 4 5 6 7
| package com.atangbiji.dao;
public interface UserDao { void getUser(); }
|
(7)然后,在子项目的com.atangbiji.dao
包下新建一个UserDaoImpl
接口实现类,并在其中重写接口中的方法。
UserDaoImpl.java
文件:
1 2 3 4 5 6 7 8 9 10
| package com.atangbiji.dao;
public class UserDaoImpl implements UserDao{ @Override public void getUser() { System.out.println("获取用户数据"); } }
|
(8)在子项目的java
目录下新建一个com.atangbiji.service
包(用于存放我们自己编写的业务层代码),在该包下新建一个UserService
接口,并在其中创建接口函数。
UserService.java
文件:
1 2 3 4 5 6 7
| package com.atangbiji.service;
public interface UserService { void getUser(); }
|
(9)然后,在子项目的com.atangbiji.service
包下新建一个UserServiceImpl
接口实现类,并在其中重写接口中的方法。
UserServiceImpl.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.atangbiji.service;
import com.atangbiji.dao.UserDao; import com.atangbiji.dao.UserDaoImpl;
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImpl(); @Override public void getUser() { userDao.getUser(); } }
|
注:此时,我们是在service
接口实现类中直接新建(new
)了一个Dao
接口实现类的对象。
(10)在子项目的src/test/java
目录下,新建一个MyTest
测试类,用于用户调用业务层接口。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| import com.atangbiji.service.UserService; import com.atangbiji.service.UserServiceImpl; import org.junit.Test;
public class MyTest { @Test public void test(){ UserService service = new UserServiceImpl(); service.getUser(); } }
|
运行测试程序,执行结果如下图所示:
以上就是我们以前处理一个业务的方式。
2.1.2、以前业务处理方式存在的问题
按照以前的业务处理方式,如果此时用户需要将需求修改为“从MySQL
中获取用户数据”,那么程序员就需要修改Dao
层和Service
层代码,具体步骤如下:
(1)先在子项目的com.atangbiji.dao
包下新建一个UserDaoMySqlImpl
接口实现类,并在其中重写接口中的方法。
UserDaoMySqlImpl.java
文件:
1 2 3 4 5 6 7 8 9 10
| package com.atangbiji.dao;
public class UserDaoMysqlImpl implements UserDao { @Override public void getUser() { System.out.println("MySql获取用户数据"); } }
|
(2)然后,去service
接口实现类中修改业务层对应的实现。
UserServiceImpl.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.atangbiji.service;
import com.atangbiji.dao.UserDao; import com.atangbiji.dao.UserDaoMysqlImpl;
public class UserServiceImpl implements UserService{ private UserDao userDao = new UserDaoMysqlImpl(); @Override public void getUser() { userDao.getUser(); } }
|
(3)在不修改测试程序的情况下,重新运行测试程序,执行结果如下图所示:
如果此时用户又需要将需求修改为“从Oracle
中获取用户数据呢?”,那么程序员就又需要重复上述步骤,重新修改Dao
层和Service
层代码。
实际项目中,假设用户的这种需求非常多 , 程序员可能需要修改几十个地方,这种方式就很反人类。这种设计的耦合性太高了,程序不能适应用户的需求变化,牵一发而动全身。 因此,每次用户需求发生变化 , 程序员都需要修改大量service
层代码,此时程序的控制权掌握在程序员手上。【虽然他不想掌握(改代码)!】
2.1.3、解决方式
为了解决上述问题,我们不再在service
层直接创建(new
)Dao
层接口实现类的对象,而是将Dao接口类的对象定义为service
接口实现类的成员变量,并通过Set
的方式把接口暴露给用户,进而动态实现值得注入。这样,在不修改底层代码的情况下,用户就可以根据自己的需求灵活调用底层接口了。因此,我们只需对service
接口实现类做如下修改:
UserServiceImpl.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.atangbiji.service;
import com.atangbiji.dao.UserDao;
public class UserServiceImpl implements UserService{
private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; }
@Override public void getUser() { userDao.getUser(); } }
|
通过这种方式,用户若想在测试类中根据需要灵活调用各种业务层接口,就不需要程序员修改底层代码的了。如:
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import com.atangbiji.dao.UserDaoImpl; import com.atangbiji.dao.UserDaoMysqlImpl; import com.atangbiji.service.UserServiceImpl; import org.junit.Test;
public class MyTest { @Test public void test(){ UserServiceImpl service = new UserServiceImpl(); service.setUserDao(new UserDaoImpl()); service.getUser(); service.setUserDao(new UserDaoMysqlImpl()); service.getUser(); } }
|
运行测试程序,执行结果如下图所示:
大家发现两种业务处理方式的区别没有 ? 可能很多人说没啥区别,但其实它们已经发生了根本性的变化。
-
以前的业务处理方式中,用户的需求变化会影响我们原来的代码,当用户的需求发生变化时,程序员需要根据用户的需求去修改原来的代码。此时,业务层代码主动创建对象,程序的控制权掌握在程序员手上。
-
而现在的业务处理方式中,用户的需求变化不会影响我们原来的代码,当用户的需求发生变化时,用户只需要在调用业务层接口时通过set
方法注入(new
)相应的Dao
接口实现类的对象即可。此时,业务层代码不再具有主动性,程序的控制权反转到了用户(调用者)的手上。
这种思想,从本质上解决了上述问题,我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现。系统的耦合性大大降低,这也就是控制反转(IOC
)的原型 !Spring
的底层使用的都是这种控制反转(IOC
)机制,底层接口如果没有set
方法是无法正常运行的。
2.2、IOC容器和Bean
-
所谓**IOC
容器**,指的就是:Spring
实现控制反转(IOC
)的容器。
IOC
容器负责创建、定位、配置应用程序中的对象及建立这些对象间的依赖。这样,应用程序无需直接在代码中new
相关的对象,应用程序由IOC
容器进行组装。在Spring中BeanFactory是IOC容器的实际代表者。
-
所谓**Bean
**,指的就是:由IOC
容器创建、组装和管理的那些组成我们应用程序的Java
对象。
2.3、IOC容器的工作原理
IOC容器的工作原理如下图所示:
(1)Spring IOC
容器在初始化时,首先通过读取**配置文件(或注解)**的方式获取其中的配置元数据;
(2)然后再通过元数据对应用中的各个对象(Bean
)进行创建和组装,并存入到容器中。
(3)当程序需要使用相关对象时,直接从IOC
容器中获取相关的对象(Bean
)即可。
2.3、IOC的本质
控制反转IOC(Inversion of Control)
是一种设计思想,其本质还是为了程序解耦。
理想的系统希望各模块(层)之间相互独立,如下图3所示。
- 在没有
IOC
的程序中 , 我们使用面向对象编程 , 对象的创建(new
)与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制(如下图1所示)。
Spring
通过IOC
容器实现控制反转。实现控制反转后,我们便将对象的创建(new
)转移给了第三方,即:IOC
容器(如下图2所示)。
所谓控制反转就是:获得依赖对象的方式发生了反转。
注:
-
所谓IOC,对于Spring框架来说,就是由Spring的IOC容器来负责管理对象的生命周期和对象间的关系。
-
控制反转的实现方式有很多种,DI(Dependency Injection,依赖注入)只是其中的一种。
-
IOC
是Spring
框架的核心内容,Spring
使用多种方式完美的实现了IOC
。我们可以使用XML
配置,也可以使用注解配置,新版本的Spring
也可以零配置实现IOC
。
- 采用
XML
方式配置Bean
的时候,Bean
的定义信息是和实现分离的;
- 而采用注解的方式可以把两者合为一体,
Bean
的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
综上所述:
-
控制反转是一种通过描述(XML或注解)并通过第三方(Spring中IOC容器)去生产或获取特定对象的方式。
-
在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入(Dependency Injection,DI)。
3、HelloSpring
3.1、创建普通的Maven项目
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-02-helloSpring
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
3.2、创建实体类
(3)在子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类),并在该包下新建一个Hello
实体类。
Hello.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.atangbiji.pojo;
public class Hello { private String str;
public String getStr() { return str; }
public void setStr(String str) { this.str = str; }
@Override public String toString() { return "Hello{" + "str='" + str + '\'' + '}'; } }
|
3.3、创建Beans的XML配置文件(重点)
(4)在子项目的resources
目录下,新建一个beans.xml
配置文件,用于配置Beans
的元数据。
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| <?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="hello" class="com.atangbiji.pojo.Hello"> <property name="str" value="Hello Spring!"/> </bean> </beans>
|
注:
- XML配置文件的名称可以自定义,官方建议使用
applicationContext.xml
。
Spring
给8种基本类型和String
类型的成员变量赋值时用value
;
Spring
给引用类型的成员变量赋值时用ref
。
3.4、测试
(5)在子项目的src/test/java
目录下,新建一个MyTest
测试类,用于测试Spring
。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import com.atangbiji.pojo.Hello; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); Hello hello = (Hello) context.getBean("hello"); System.out.println(hello.toString()); } }
|
运行测试程序,执行结果如下图所示:
3.5、结果分析
分析上述代码,可以发现:
Hello
类的对象是由Spring
创建的。
Hello
类对象的属性是由Spring
容器设置的。
这个过程就叫做“控制反转” :
- 控制:谁来控制对象的创建(
new
),传统应用程序的对象是由程序本身控制创建的;使用Spring
后 , 对象是由Spring
来创建的。
- 反转:程序本身不创建对象,而变成被动的接收对象。
依赖注入:就是Spring
实例化对象时通过实体类的set
方法来进行数据注入的。
因此, IOC
是一种编程思想,由主动的创建对象变成被动的接收对象。
**注:**将xml
配置文件配置为Application Context
后,如果我们在xml
配置文件中配置了Bean
,那么与之对应实体类的旁边会出现相应的小图标(如下图所示),我们可以点击它们进行快速导航。
此时,若用户需求发生变化,我们只需要在xml配置文件中进行修改即可,程序员就彻底不用修改底层代码了。
结论:所谓IoC
,一句话搞定就是:对象由Spring
来创建、管理、装配 ! (以后我们再也不用通过new
来创建对象了!)
4、IOC容器创建对象的方式
4.1、通过无参构造方法来创建
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-03-ioc2
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类),并在该包下新建一个User
实体类。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.atangbiji.pojo;
public class User { private String name; public User() { System.out.println("user的无参构造方法!"); }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public void show(){ System.out.println("name = " + name); } }
|
(4)在子项目的resources
目录下,新建一个beans.xml
配置文件,用于配置Beans
的元数据。
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| <?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="User" class="com.atangbiji.pojo.User"> <property name="name" value="阿汤笔迹"/> </bean> </beans>
|
(5)在子项目的src/test/java
目录下,新建一个MyTest
测试类。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import com.atangbiji.pojo.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("User"); user.show(); } }
|
运行测试程序,执行结果如下图所示:
由此我们可以发现:在调用show
方法之前,User
对象已经通过无参构造初始化了!
4.2、通过有参构造方法来创建
(1)UserT.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.atangbiji.pojo;
public class UserT { private String name; public UserT(String name) { this.name = name; System.out.println("user的有参构造方法!"); }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public void show(){ System.out.println("name = " + name); } }
|
(2)beans.xml
中给有参构造函数传参的方式有以下几种:
方式一:根据构造函数参数索引(index
)给参数赋值。
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| <?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="UserT" class="com.atangbiji.pojo.UserT"> <constructor-arg index="0" value="张三"/> </bean> </beans>
|
注:index
从0开始。
方式二:通过构造函数参数类型匹配给参数赋值。(不推荐)
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| <?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="UserT" class="com.atangbiji.pojo.UserT"> <constructor-arg type="java.lang.String" value="张三"/> </bean> </beans>
|
**注:**当构造函数有多个同类型的参数时,该方式将按照constructor-arg
标签书写顺序依次给参数赋值。因此,为了防止出错,不推荐使用此方式。
方式三:通过构造函数参数名称给参数赋值。
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| <?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="UserT" class="com.atangbiji.pojo.UserT"> <constructor-arg name="name" value="张三"/> </bean> </beans>
|
附:当构造函数参数为引用类型时,constructor-arg
标签使用ref
属性给参数传值。如:
XXX.java
文件:
1 2 3 4 5 6
| public class ThingOne { public ThingOne(ThingTwo thingTwo, ThingThree thingThree) { } }
|
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10
| <beans> <bean id="beanOne" class="x.y.ThingOne"> <constructor-arg ref="beanTwo"/> <constructor-arg ref="beanThree"/> </bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/> </beans>
|
(3)测试。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import com.atangbiji.pojo.UserT; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void testT(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserT user = (UserT) context.getBean("UserT"); user.show(); } }
|
运行测试程序,执行结果如下图所示:
结论:在xml
配置文件加载的时候,容器中配置的所有对象都已经被初始化了!
5、Spring配置
5.1、别名
我们可以通过alias
标签给Bean
设置别名 。这样,我们在获取Bean
的时候便可以通过别名获取到这个对象了。如:
beans.xml
文件:
1 2
| <alias name="fromName" alias="toName"/>
|
**注:**别名区分大小写。
5.2、Bean的基本配置
**Bean
就是java
对象,由Spring
创建和管理。**我们在Bean
的XML配置文件中对其进行配置。如:
beans.xml
文件:
1 2 3 4 5 6 7 8
|
<bean id="UserT" name="UserTwo user2,u3;u4" class="com.kuang.pojo.UserT"> <property name="name" value="Spring"/> </bean>
|
注:
- 如果我们没有给
bean
配置id
,那么name
就是其默认标识符。
- 如果我们给
bean
既配置了id
,又配置了name
,那么name
就是别名。
name
中可以设置多个别名,可以用逗号、分号或空格隔开。
- 如果不配置
id
和name
,那么我们可以通过applicationContext.getBean(.class)
获取对象;
5.3、import(导入)
import
标签一般用于团队开发,它可以将多个xml
配置文件中的beans
导入,合并为一个配置文件。如:
beans.xml
文件:
1 2 3 4 5 6 7 8 9
| <beans> <import resource="beans2.xml"/> <import resource="resources/beans3.xml"/> <import resource="/resources/beans4.xml"/> <bean id="bean1" class="..."/> <bean id="bean2" class="..."/> </beans>
|
**注:**注意xml
配置文件的导入顺序。当不同配置文件中都定义了同一个bean
时,后导入的会覆盖前面的。
至此,就算入门Spring
了,认真体会Spring
的好处吧!
6、依赖注入
依赖注入(Dependency Injection
,DI
)指的是Spring
在创建对象时,IOC
容器把对象依赖的属性注入到对象中的过程。
- 依赖:
Bean
对象的创建依赖于容器。
- 注入:
Bean
对象的所有属性由容器注入。
6.1、通过构造器注入
在4、IOC容器创建对象的方式
一节中已经介绍了。
6.2、通过Set方式注入(重点)
6.2.1、搭建测试环境
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-04-di
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类),并在该包下新建两个名称分别为Student
和Address
的实体类。
Address.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.atangbiji.pojo;
public class Address { private String address;
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
@Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; } }
|
Student.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| package com.atangbiji.pojo;
import java.util.*;
public class Student { private String name; private Address address; private String[] books; private List<String> hobbies; private Map<String,String> card; private Set<String> games; private String wife; private Properties info;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
public String[] getBooks() { return books; }
public void setBooks(String[] books) { this.books = books; }
public List<String> getHobbies() { return hobbies; }
public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; }
public Map<String, String> getCard() { return card; }
public void setCard(Map<String, String> card) { this.card = card; }
public Set<String> getGames() { return games; }
public void setGames(Set<String> games) { this.games = games; }
public String getWife() { return wife; }
public void setWife(String wife) { this.wife = wife; }
public Properties getInfo() { return info; }
public void setInfo(Properties info) { this.info = info; }
@Override public String toString() { return "Student{" + "name='" + name + '\'' + '\n' + ", address=" + address.toString() + '\n' + ", books=" + Arrays.toString(books) + '\n' + ", hobbies=" + hobbies + '\n' + ", card=" + card + '\n' + ", games=" + games + '\n' + ", wife='" + wife + '\'' + '\n' + ", info=" + info + '\n' + '}'; } }
|
(4)在子项目的resources
目录下,新建一个beans.xml
配置文件,用于配置Beans
的元数据。
(5)在子项目的src/test/java
目录下,新建一个MyTest
测试类。
6.2.2、常量注入
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="student" class="com.atangbiji.pojo.Student"> <property name="name" value="阿汤"/> </bean> </beans>
|
6.2.3、Bean注入
beans.xml
文件:
1 2 3 4 5 6 7
| <bean id="address" class="com.atangbiji.pojo.Address"> <property name="address" value="湖北省武汉市"/> </bean> <bean id="student" class="com.atangbiji.pojo.Student"> <property name="address" ref="address"/> </bean>
|
注:Bean
注入时,value
是一个引用,使用ref
属性注入。
6.2.4、数组注入
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11
| <bean id="student" class="com.atangbiji.pojo.Student"> <property name="books"> <array> <value>红楼梦</value> <value>西游记</value> <value>水浒传</value> <value>三国演义</value> </array> </property> </bean>
|
6.2.5、List注入
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10
| <bean id="student" class="com.atangbiji.pojo.Student"> <property name="hobbies"> <list> <value>听歌</value> <value>编程</value> <value>看电影</value> </list> </property> </bean>
|
6.2.6、Map注入
beans.xml
文件:
1 2 3 4 5 6 7 8 9
| <bean id="student" class="com.atangbiji.pojo.Student"> <property name="card"> <map> <entry key="身份证" value="888888888"/> <entry key="学生卡" value="123456789"/> </map> </property> </bean>
|
6.2.7、Set注入
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10
| <bean id="student" class="com.atangbiji.pojo.Student"> <property name="games"> <set> <value>英雄联盟</value> <value>王者荣耀</value> <value>绝地求生</value> </set> </property> </bean>
|
6.2.8、Null注入
beans.xml
文件:
1 2 3 4 5 6
| <bean id="student" class="com.atangbiji.pojo.Student"> <property name="wife"> <null/> </property> </bean>
|
6.2.9、Properties(属性)注入
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10
| <bean id="student" class="com.atangbiji.pojo.Student"> <property name="info"> <props> <prop key="学号">20221125</prop> <prop key="用户名">root</prop> <prop key="密码">123456</prop> </props> </property> </bean>
|
6.2.10、测试结果
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import com.atangbiji.pojo.Student; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void testDI(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); Student student = (Student)context.getBean("student"); System.out.println(student.toString()); } }
|
运行测试程序,依赖注入成功。执行结果如下图所示:
6.3、p命名空间和c命名空间注入
首先,在子项目的com.atangbiji.pojo
包下新建一个名称为User
的实体类。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.atangbiji.pojo;
public class User { private String name; private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
|
注:此时User
实体类没有有参构造器,各属性依然要设置set
方法。
然后,再在子项目的resources
目录下,新建一个userBeans.xml
配置文件,用于配置Beans
的元数据。
6.3.1、p命名空间注入
通过P
(properties
,属性)命名空间注入,可以直接注入属性的值。它对应的就是Set
方式注入,具体使用步骤如下:
(1)在xml
配置文件的头部导入约束。
1
| xmlns:p="http://www.springframework.org/schema/p"
|
(2)在bean
标签中使用p
命名空间注入。
userBeans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| <?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.atangbiji.pojo.User" p:name="阿汤" p:age="18"/> </beans>
|
(3)测试。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import com.atangbiji.pojo.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void testDI2(){ ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml"); User user = (User)context.getBean("user"); System.out.println(user.toString()); } }
|
运行测试程序testDI2
,执行结果如下图所示:
6.3.2、c命名空间注入
通过C
(Constructor
,构造器)命名空间注入,也可以直接注入属性的值。它对应的就是构造器注入,具体使用步骤如下:
(1)在实体类中添加有参构造函数。
User.java
文件:
1 2 3 4 5
| public User(String name, int age) { this.name = name; this.age = age; }
|
注:c
命名空间注入的对象,其实体类必须要有有参构造函数。
(2)在xml
配置文件的头部导入约束。
1
| xmlns:c="http://www.springframework.org/schema/c"
|
(3)在bean
标签中使用c
命名空间注入。
userBeans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| <?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:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.atangbiji.pojo.User" c:name="阿汤" c:age="20"/> </beans>
|
(4)再次运行测试程序testDI2
,执行结果如下图所示:
7、Bean的作用域
在Spring
中,那些组成应用程序的主体及由Spring IoC
容器所管理的对象,被称之为bean
。简单地讲,bean
就是由IoC
容器初始化、装配及管理的对象。
Spring中Bean的作用域有以下6种:
作用域 |
描述 |
singleton |
每次从IOC 容器中获取Bean 时,返回的都是同一个实例。Bean 以单例模式存在。Spring 中的Bean 默认以单例模式存在。 |
prototype |
每次从IOC 容器中获取Bean 时,返回的都是一个新的实例。 |
request |
Bean 的对象只在同一次HTTP请求中有效。(仅用于Web开发) |
session |
Bean 的对象只在同一次会话中有效。(仅用于Web开发) |
application |
Bean 的对象在**同一个Web应用(项目)**中有效,即服务器开启过程中一直有效。(仅用于Web开发) |
websocket |
类似于application 作用域。 |
7.1、singleton(单例模式)
如果一个bean
的作用域为Singleton
,那么Spring IoC
容器中只会存在一个共享的bean
实例。当获取bean
时,只要id
与该bean
相匹配,就会返回同一bean
的实例。Singleton
是单例模式,就是在创建容器时就同时自动创建了一个bean
的对象,不管你是否使用,它都已经存在了,且每次获取到的对象都是同一个对象。
Singleton
作用域是Spring
中bean
的默认作用域。我们也可以在XML
中显示地将bean
定义成singleton
。如:
userBeans.xml
文件:
1 2 3 4
| <bean id="user" class="com.atangbiji.pojo.User" scope="singleton"> <property name="name" value="阿汤"/> <property name="age" value="18"/> </bean>
|
**附:**测试。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import com.atangbiji.pojo.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void testDI3(){ ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml"); User user1 = (User)context.getBean("user"); User user2 = (User)context.getBean("user"); System.out.println(user1.hashCode()); System.out.println(user2.hashCode()); System.out.println(user1 == user2); } }
|
运行测试程序testDI3
,执行结果如下图所示:
7.2、prototype(原型模式)
若一个bean
的作用域为Prototype
,则表示一个bean
的定义可以对应多个对象实例。Prototype
作用域的bean
会导致在每次对该bean
请求(将其注入到另一个bean
中,或者以程序的方式调用容器的getBean()
方法)时都会创建一个新的bean
实例。
Prototype
是原型模式,它在我们创建容器的时候并没有实例化,而是当我们获取bean
的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。在XML
中将bean
定义成prototype
,可以这样配置:
userBeans.xml
文件:
1 2 3 4
| <bean id="user" class="com.atangbiji.pojo.User" scope="prototype"> <property name="name" value="阿汤"/> <property name="age" value="18"/> </bean>
|
**附:**测试。重新运行测试程序testDI3
,执行结果如下图所示:
**注:**根据经验,对有状态的bean
应该使用prototype
作用域;而对无状态的bean
则应该使用singleton
作用域。
7.3、其它作用域
request
、session
、application
和websocket
作用域仅在web
开发中使用。
8、Bean的自动装配
8.1、自动装配说明
8.1.1、Bean的3种装配机制
在Spring
中,Bean
有3种装配机制,分别是:
- 通过
xml
配置文件(或注解)的方式手动地显式配置;
- 在
java
代码中显式配置;
- 通过
xml
配置文件(或注解)的方式隐式地自动装配。【重点】
第一种,我们前面已经学习了,第二种会在以后的学习中再介绍。我们这里主要讲解的是第三种:自动化的装配bean。
8.1.2、什么是Bean的自动装配
所谓Bean
的自动装配,即:Spring
会在应用上下文中自动寻找,并自动给Bean
装配属性的过程。
注:Spring
中既可以通过xml
配置文件的方式实现Bean
的自动装配,又可以通过注解的方式实现Bean
的自动装配。但由于在xml
中实现自动装配可能会出现:我们以为bean
的某些属性会被自动装配,但其实并没有,进而导致bean
的这些属性忘记手动配置的情况。因此,一般情况下,建议使用注解的方式实现Bean
的自动装配。
8.1.3、自动装配的两大步骤
Spring
的自动装配过程需要分为两大步骤:
- 组件扫描(
component scanning
):spring
会自动发现应用上下文中所创建的bean
;
- 自动装配(
autowiring
):spring
自动满足bean
之间的依赖,也就是我们说的IoC/DI
;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降到最少。
8.2、搭建测试环境
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-05-Autowired
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类),并在该包下新建两个名称分别为Cat
和Dog
的实体类。
Cat.java
文件:
1 2 3 4 5 6 7 8
| package com.atangbiji.pojo;
public class Cat { public void shout(){ System.out.println("喵喵!"); } }
|
Dog.java
文件:
1 2 3 4 5 6 7 8
| package com.atangbiji.pojo;
public class Dog { public void shout(){ System.out.println("汪汪!"); } }
|
(4)在子项目的com.atangbiji.pojo
包下新建一个实体类User
。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package com.atangbiji.pojo;
public class User { private Cat cat; private Dog dog; private String name;
public Cat getCat() { return cat; }
public void setCat(Cat cat) { this.cat = cat; }
public Dog getDog() { return dog; }
public void setDog(Dog dog) { this.dog = dog; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public String toString() { return "User{" + "cat=" + cat + ", dog=" + dog + ", name='" + name + '\'' + '}'; } }
|
(5)在子项目的resources
目录下,新建一个beans.xml
配置文件,用于配置Beans
的元数据。
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="cat" class="com.atangbiji.pojo.Cat"/> <bean id="dog" class="com.atangbiji.pojo.Dog"/>
<bean id="user" class="com.atangbiji.pojo.User"> <property name="cat" ref="cat"/> <property name="dog" ref="dog"/> <property name="name" value="阿汤"/> </bean> </beans>
|
(6)在子项目的src/test/java
目录下,新建一个MyTest
测试类。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import com.atangbiji.pojo.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User)context.getBean("user"); user.getCat().shout(); user.getDog().shout(); } }
|
运行测试程序test
,执行结果如下图所示:
8.3、方式一:在xml配置文件中实现自动装配
8.3.1、按名称自动装配(autowire byName)
由于在手动配置xml
文件的过程中,经常出现字母缺漏和大小写出错等问题,而无法对其进行检查,使得开发效率降低。
采用自动装配可以避免这些问题,并且使配置简单化。具体实现如下:
(1)修改配置文件,在bean
标签中增加一个属性autowire="byName"
。这样,我们就不用手动注入属性Cat
和Dog
了。
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="cat" class="com.atangbiji.pojo.Cat"/> <bean id="dog" class="com.atangbiji.pojo.Dog"/> <bean id="user" class="com.atangbiji.pojo.User" autowire="byName"> <property name="name" value="阿汤"/> </bean> </beans>
|
(2)再次运行测试程序test
,依赖依旧注入成功。执行结果如下图所示:
**注:**若我们将cat
的bean id
修改为catXXX
,再次运行测试程序test
,则会报空指针异常java.lang.NullPointerException
。这是因为按照byName
规则找不对应的set
方法,真正的setCat
方法就没执行,对象就没有初始化,所以调用时就会报空指针异常的错误。
结论:
当一个bean
节点(标签)配置了autowire="byName"
的属性时,Spring
将会:
- 自动查找其类中所有的
set
方法名,例如setCat
,并获得将set
去掉并且首字母小写的字符串,即cat
。
- 去
spring IOC
容器上下文中查找是否有Bean
的id
属性与该字符串相同。
- 如果有,就取出注入;如果没有,就报空指针异常。
8.3.2、按类型自动装配(autowire byType)
具体实现如下:
(1)修改配置文件,在bean
标签中增加一个属性autowire="byType"
。同样地,我们也不用手动注入属性Cat
和Dog
了。
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="cat" class="com.atangbiji.pojo.Cat"/> <bean id="dog" class="com.atangbiji.pojo.Dog"/> <bean id="user" class="com.atangbiji.pojo.User" autowire="byType"> <property name="name" value="阿汤"/> </bean> </beans>
|
(2)再次运行测试程序test
,依赖依旧注入成功。执行结果如下图所示:
注:
- 按类型自动装配时,必须要保证:在
spring
容器(xml
配置文件)上下文中一个实体类只对应一个bean
对象(即:bean
的class
属性唯一)。否则,会报Bean
不唯一(NoUniqueBeanDefinitionException
)异常的错误。
- 按类型自动装配时,即使没有配置
bean
的id
属性,bean
同样可以完成自动装配。
8.4、方式二:使用注解实现自动装配
jdk1.5
开始支持注解,spring2.5
开始全面支持注解。
8.4.1、准备工作
若要通过注解的方式注入属性,则需要:
(1)在xml
配置文件的头部引入context
约束的文件头。
beans.xml
文件:
1 2 3 4
| xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
|
(2)开启属性注解支持!
beans.xml
文件:
1 2
| <context:annotation-config/>
|
8.4.2、使用@Autowired注解实现自动装配(重点)
@Autowired
注解的执行过程为:
先按类型自动装配的方式查找是否存在class
与其注解的实体类成员变量类型相同的bean
。
- 若没找到,则直接报错。
- 若找到多个该类型的
bean
对象,则再按名称自动装配的方式查找上述结果中是否存在id
与其注解的实体类成员变量名称相同的bean
;若仍然没找到,则直接报错。
测试步骤:
(1)在User
类的bean
属性上使用@Autowired
注解。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package com.atangbiji.pojo;
import org.springframework.beans.factory.annotation.Autowired;
public class User { @Autowired private Cat cat; @Autowired private Dog dog; private String name;
public Cat getCat() { return cat; }
public void setCat(Cat cat) { this.cat = cat; }
public Dog getDog() { return dog; }
public void setDog(Dog dog) { this.dog = dog; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public String toString() { return "User{" + "cat=" + cat + ", dog=" + dog + ", name='" + name + '\'' + '}'; } }
|
(2)简化xml
配置文件。
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="cat" class="com.atangbiji.pojo.Cat"/> <bean id="cat2" class="com.atangbiji.pojo.Cat"/> <bean id="dog2" class="com.atangbiji.pojo.Dog"/> <bean id="user" class="com.atangbiji.pojo.User"/> </beans>
|
(3)再次运行测试程序test
,依赖依旧注入成功。执行结果如下图所示:
注:
-
@Autowired
注解既可以在实体类的bean
属性(依赖对象)上使用,也可以在实体类bean
属性对应的Set
方法上使用。
-
使用@Autowired
注解实体类的bean
属性时,其对应的Set
方法甚至可以省略不写。
-
默认情况下,@Autowired
注解要求依赖对象必须非空。如果要允许依赖对象为null
,需要将它的required
属性设置为false
(默认为:true
)。当然,从Spring5.0
开始,我们也可以在依赖对象上使用@Nullable
标签允许依赖对象为null
。如:
User.java
文件:
1 2 3 4 5 6 7
| @Autowired(required=false) private Cat cat;
@Autowired @Nullable private Dog dog;
|
-
@Autowired
注解默认是按类型自动装配的(属于spring
规范)。
8.4.3、使用@Autowired和@Qualifier注解实现按名称自动装配
由于@Autowired
注解默认是先按类型自动装配的。当一个实体类对应多个bean
对象时,若在@Autowired
注解后面再加上@Qualifier
注解,并在其中指定要装配对象的id
,则可实现按名称自动装配(autowire byName
)。
注:@Qualifier
不能单独使用。
测试步骤:
(1)修改配置文件内容,让一个实体类对应多个bean
对象,且id
不为实体类对应的属性名。
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="cat1" class="com.atangbiji.pojo.Cat"/> <bean id="cat2" class="com.atangbiji.pojo.Cat"/> <bean id="dog1" class="com.atangbiji.pojo.Dog"/> <bean id="dog2" class="com.atangbiji.pojo.Dog"/> <bean id="user" class="com.atangbiji.pojo.User"/> </beans>
|
(2)若没有@Qualifier
测试,则直接报错。
(3)若在User
类的bean
属性上再加上@Qualifier
注解,并在其中指定要装配对象的id
,则可实现按名称自动装配。
User.java
文件:
1 2 3 4 5 6
| @Autowired @Qualifier(value = "cat1") private Cat cat; @Autowired @Qualifier(value = "dog1") private Dog dog;
|
再次运行测试程序test
,依赖依旧注入成功。执行结果如下图所示:
8.4.4、使用@Resource注解实现自动装配
@Resource
注解的执行过程如下:
- 如果
@Resource
指定了name
属性,那么先根据name
属性的值,按名称自动装配的方式查找与之对应的bean
;若没找到,则直接报错。
- 如果
@Resource
没有指定name
属性,那么先按名称自动装配的方式查找是否存在id
与其注解的实体类成员变量名称相同的bean
;若没找到,则再按类型自动装配的方式查找,若仍然没找到或找到该类型的bean
对象不唯一,则直接报错。
注:
测试步骤:
(1)在User
类的bean
属性上使用@Resource
注解。
User.java
文件:
1 2 3 4
| @Resource(name = "cat2") private Cat cat; @Resource private Dog dog;
|
(2)修改配置文件内容。
beans.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="cat1" class="com.atangbiji.pojo.Cat"/> <bean id="cat2" class="com.atangbiji.pojo.Cat"/> <bean id="dog2" class="com.atangbiji.pojo.Dog"/> <bean id="user" class="com.atangbiji.pojo.User"/> </beans>
|
(3)再次运行测试程序test
,依赖依旧注入成功。执行结果如下图所示:
9、使用注解开发
准备工作:
(1)在spring4
之后,想要使用注解开发,必须要确保aop
的包已经被导入。
(2)此外,想要使用注解还需要在xml
配置文件的头部引入一个context
约束。
beans.xml
文件:
1 2 3 4
| xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
|
(3)开启属性注解支持!
beans.xml
文件:
1 2
| <context:annotation-config/>
|
9.1、搭建测试环境
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-06-Annotation
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类),并在该包下新建一个User
实体类。
User.java
文件:
1 2 3 4 5
| package com.atangbiji.pojo;
public class User { private String name; }
|
(4)在子项目的resources
目录下,新建一个applicationContext.xml
配置文件,用于配置Beans
的元数据。
applicationContext.xml
文件:
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> </beans>
|
(5)在子项目的src/test/java
目录下,新建一个MyTest
测试类。
9.2、使用@Component注解注入Bean
我们之前都是使用bean
的标签进行Bean
注入,但是实际开发中,我们一般都会使用注解!
(1)指定扫描哪些包下的注解。
applicationContext.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <context:component-scan base-package="com.atangbiji"/> </beans>
|
**注:**配置扫描包是为了扫描类上的@Component
注解,如果不配置扫描包,就需要手动配置bean
。
(2)在实体类上使用@Component
(组件)注解。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.atangbiji.pojo;
import org.springframework.stereotype.Component;
@Component("user")
public class User { private String name = "阿汤";
@Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } }
|
(3)测试。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import com.atangbiji.pojo.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void testAnnotation(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = context.getBean("user", User.class); System.out.println(user.toString()); } }
|
运行测试程序testAnnotation
,执行结果如下图所示:
9.3、使用@Value注解注入属性
(1)实体类可以不用提供set
方法,直接在属性名上添加@value("值")
注解。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.atangbiji.pojo;
import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;
@Component("user")
public class User { @Value("阿汤笔迹") private String name;
@Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } }
|
**注:**如果实体类提供了set
方法,那么@value("值")
注解同样可以在set
方法上添加。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.atangbiji.pojo;
import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;
@Component("user")
public class User {
private String name;
@Value("阿汤笔迹") public void setName(String name) { this.name = name; }
@Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } }
|
(2)测试。重新运行测试程序testAnnotation
,执行结果如下图所示:
9.4、@Component的衍生注解
为了更好的进行分层,Spring
提供了@Component
注解的三个衍生注解,它们的功能和@Component
一样:都表示将某一个实体类对应的bean
对象装配到Spring IOC
容器中,交由Spring
管理了。
@Controller
:controller
层
@Service
:service
(业务)层
@Repository
(仓库):dao
层
9.5、使用注解实现自动装配
在Bean的自动装配
中已经介绍,详见8.4节。
9.6、使用@scope注解配置Bean的作用域
直接在实体类上使用@scope
(作用域)注解即可。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.atangbiji.pojo;
import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component;
@Component("user") @Scope("singleton")
public class User { @Value("阿汤笔迹") private String name;
@Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } }
|
注:
singleton
(单例模式):就是在创建容器时就同时自动创建了一个bean
的对象,不管你是否使用,它都已经存在了,且每次获取到的对象都是同一个对象。Spring
会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会被销毁。(默认值!)
prototype
(原型模式):它在我们创建容器的时候并没有实例化,而是当我们获取bean
的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。关闭工厂 ,所有的对象不会销毁,所有对象由内部的垃圾回收机制会回收。
9.7、小结
(1)XML与注解比较
xml
可以适用任何场景 ,结构清晰,维护方便。
- 注解只能在被其注解的实体类中使用,开发简单方便,维护相对复杂。
(2)最佳实践
实际项目中,建议:
- 使用
xml
配置文件管理Bean
;
- 注解只负责完成属性的注入。
10、基于Java的容器配置(重点)
在Spring
中,Bean
除了前面学过的:通过**xml
配置文件(或注解)的方式“手动地显式配置”和“隐式地自动装配**”之外,我们还可以通过java
代码的方式对其进行显式地配置。
JavaConfig
原来是Spring
的一个子项目,它通过Java
类的方式提供Bean
的定义信息。在Spring 4
之后的版本,JavaConfig
已正式成为Spring
的一个核心功能了。
10.1、实现步骤
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-07-AppConfig
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类),并在该包下新建一个User
实体类。
- 通过
@Component
注解将该实体类标注为Spring
的一个组件,放到IOC
容器中。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.atangbiji.pojo;
import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;
@Component public class User { @Value("阿汤笔迹") private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } }
|
(4)在子项目的com.atangbiji
包下新建一个config
(配置)包,并在该包下新建一个AppConfig
类,用来配置Beans
。
- 先通过
@Configuration
注解,我们可以将原本普通的AppConfig
类配置成一个配置类。
- 然后再通过
@ComponentScan
注解指定扫描com.atangbiji
包下的@Component
注解。这样,我们便可以将该包下的所有被@Component
注解标记的bean
全部注入到IOC
容器中了。
注:@ComponentScan
注解往往需要和@Component
注解在一起搭配使用。如果配置类只配置了@Configuration
注解,但没有通过@ComponentScan
注解指定扫描包的话,我们也可以通过@Bean
注解把配置类的成员函数注册一个bean
。此时,该成员函数的返回值就Bean
的类型;该成员函数的方法名就是bean
的id
。配置扫描包是为了扫描实体类上的@Component
注解,如果不配置扫描包,就需要在配置类中通过@Bean
注解手动配置bean
。当然,@ComponentScan
注解和@Bean
注解也可以同时使用。
AppConfig.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.atangbiji.config;
import com.atangbiji.pojo.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.atangbiji") public class AppConfig { @Bean public User user(){ return new User(); } }
|
注:
- 上述配置后的
AppConfig
类就等价于以前beans.xml
配置文件中的:
1 2 3
| <beans> <bean id="user" class="com.atangbiji.pojo.User"/> </beans>
|
@ComponentScan("com.atangbiji")
注解就等价于以前beans.xml
配置文件中的:
1 2
| <context:component-scan base-package="com.atangbiji"/>
|
(5)在子项目的src/test/java
目录下,新建一个MyTest
测试类。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import com.atangbiji.config.AppConfig; import com.atangbiji.pojo.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest { @Test public void test(){ ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); User user = context.getBean("user", User.class); System.out.println(user.toString()); } }
|
**注:**此时,我们不再通过解析beans.xml
配置文件的方式获取ApplicationContext
对象,而是通过注解解析AppConfig
配置类的方式获取ApplicationContext
对象。
运行测试程序,依赖注入成功。执行结果如下图所示:
10.2、导入多个配置类
同样地,当一个项目中有多个配置类时,我们可以通过@Import
注解将多个配置类中的beans
导入,合并为一个配置类。如:
(1)在上述子项目的com.atangbiji.config
包下再新建一个配置类AppConfig2
。
AppConfig2.java
文件:
1 2 3 4 5 6 7 8
| package com.atangbiji.config;
import org.springframework.context.annotation.Configuration;
@Configuration public class AppConfig2 { }
|
(2)在之前的配置类中通过@Import
注解导入其它配置类的class
对象即可。
AppConfig.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.atangbiji.config;
import com.atangbiji.pojo.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan("com.atangbiji")
@Import(AppConfig2.class) public class AppConfig { @Bean public User user(){ return new User(); } }
|
注:@Import(AppConfig2.class)
注解就等价于以前beans.xml
配置文件中的:
1 2 3 4 5 6
| <beans> <import resource="beans2.xml"/> <import resource="resources/beans3.xml"/> <import resource="/resources/beans4.xml"/> </beans>
|
关于这种基于Java
的容器配置方式,我们在之后的SpringBoot
和SpringCloud
中随处可见,现在我们只需要理解这些注解的作用即可!
11、代理模式
(1)为什么要学习代理模式?
因为AOP
的底层机制就是动态代理!因此,学习AOP
之前 , 我们要先学习一下代理模式!
(2)什么是代理模式?
代理模式就是:代理对象(如:中介)具备真实对象(如:房东)的功能,并代替真实对象完成相应操作(如:租房相关事宜),并能够在操作执行的前后,对操作进行增强处理。(为真实对象提供代理,然后供其他对象通过代理访问真实对象)
租房问题就是典型的代理模式:租客想找房东租房,但房东比较忙,一般都让中介代理完成租房相关事宜。
(3)代理模式的分类:
11.1、静态代理
11.1.1、静态代理角色分析
- 抽象角色:一般使用接口或者抽象类来实现。
- 真实角色:被代理的角色(如:房东)
- 代理角色:代理真实角色(如:中介);代理真实角色后,一般会做一些附属的操作(如:收取代理费用等)。
- 客户角色:使用代理角色来进行一些操作的人。(如:租客)
11.1.2、代码实现
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-08-Proxy
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在子项目的java
目录下新建一个com.atangbiji.demo01
包,并在该包下新建一个Rent
接口,作为抽象角色。
Rent.java
文件:
1 2 3 4 5 6
| package com.atangbiji.demo01;
public interface Rent { public void rent(); }
|
(4)在子项目的com.atangbiji.demo01
包下新建一个Host
类,作为真实角色(房东)。
Host.java
文件:
1 2 3 4 5 6 7 8
| package com.atangbiji.demo01;
public class Host implements Rent{ public void rent() { System.out.println("房东想要出租房子!"); } }
|
(5)在子项目的com.atangbiji.demo01
包下新建一个Proxy
类,作为代理角色(中介)。
Proxy.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.atangbiji.demo01;
public class Proxy implements Rent{ private Host host; public Proxy() { } public Proxy(Host host) { this.host = host; } public void rent() { seeHouse(); host.rent(); sign(); fare(); } public void seeHouse(){ System.out.println("中介带你看房!"); } public void sign(){ System.out.println("中介与你签合同!"); } public void fare(){ System.out.println("中介收取中介费!"); } }
|
(6)在子项目的com.atangbiji.demo01
包下新建一个Client
类,作为客户角色(租客)。
Client.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| package com.atangbiji.demo01;
public class Client { public static void main(String[] args) { Host host = new Host(); Proxy proxy = new Proxy(host); proxy.rent(); } }
|
运行主程序,租客通过中介成功租到房东的房子。执行结果如下图所示:
**分析:**在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是通过代理你依旧租到了房东的房子,这就是所谓的代理模式。程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。
11.1.3、静态代理的优缺点
(1)优点:
- 可以使得我们的真实角色(如:房东)更加纯粹,不用再去关注一些公共的事情。
- 公共的业务由代理来完成,进而实现了业务的分工。
- 公共业务发生扩展时,更加方便,程序的耦合性更低。
(2)缺点:
- 一个真实角色就会产生一个代理角色;如果真实角色太多,那么代码量将会变得非常大,进而导致开发效率降低。
**注:**我们想要静态代理的优点,又不想要静态代理的缺点,所以 , 就有了动态代理 !
11.1.4、静态代理再理解
为了加深对静态代理的理解,我们结合业务再来举一个例子。
(1)在上述子项目的java
目录下新建一个com.atangbiji.demo02
包,并在该包下新建一个UserService
接口,作为抽象角色。
(如咱们平时做的用户业务,抽象出来就是增删改查!)
UserService.java
文件:
1 2 3 4 5 6 7 8
| package com.atangbiji.demo02;
public interface UserService { public void add(); public void delete(); public void update(); public void query(); }
|
(2)在子项目的com.atangbiji.demo02
包下新建一个UserServiceImpl
类,作为真实角色,去实现这些增删改查操作。
UserServiceImpl.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.atangbiji.demo02;
public class UserServiceImpl implements UserService { public void add() { System.out.println("增加了一个用户!"); }
public void delete() { System.out.println("删除了一个用户!"); }
public void update() { System.out.println("更新了一个用户!"); }
public void query() { System.out.println("查询了一个用户!"); } }
|
那么,现在需求来了:我们需要增加删改查操作的日志功能,如何实现呢?
- 思路1 :直接在接口的实现类(真实角色)中增加该功能 【改变了原来的代码,程序的耦合性高!】
- 思路2:使用代理来做,能够在不改变原来的代码情况下,增加该功能【程序的耦合性低!】。具体过程如下:
(3)在子项目的com.atangbiji.demo02
包下新建一个代理类UserServiceProxy
,作为代理角色,去实现增删改查操作中的日志功能。
UserServiceProxy.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.atangbiji.demo02;
public class UserServiceProxy implements UserService { private UserServiceImpl userServiceImpl; public void setUserServiceImpl(UserServiceImpl userServiceImpl) { this.userServiceImpl = userServiceImpl; }
public void add() { userServiceImpl.add(); log("add"); }
public void delete() { userServiceImpl.delete(); log("delete"); }
public void update() { userServiceImpl.update(); log("update"); }
public void query() { userServiceImpl.query(); log("query"); }
public void log(String msg){ System.out.println("执行了" + msg + "方法!"); } }
|
(4)在子项目的com.atangbiji.demo02
包下新建一个Client
类,作为客户角色。
Client.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.atangbiji.demo02;
public class Client { public static void main(String[] args) { UserServiceProxy proxy = new UserServiceProxy(); proxy.setUserServiceImpl(new UserServiceImpl()); proxy.add(); proxy.delete(); proxy.update(); proxy.query(); } }
|
运行主程序,通过代理实现类实现日志功能。执行结果如下图所示:
上述示例中,我们在不改变原来的代码的情况下,实现了对原有功能的增强。——这是AOP中最核心的思想。
11.2、动态代理(重点)
- 动态代理的角色和静态代理的一样,也是:抽象角色、真实角色、代理角色和客户角色。
- 动态代理的代理类是动态生成的;静态代理的代理类是我们提前写好的。
11.2.1、动态代理的分类
动态代理分为两大类:一类是基于接口的动态代理 , 一类是基于类的动态代理。
- 基于接口的动态代理,例如:
JDK
原生的动态代理。
- 基于类的动态代理,例如:
CGLib (Code Generation Library)
:一个强大的,高性能,高质量的Code
生成类库。
- 现在用的比较多的是
Javassist
(一个开源的分析、编辑和创建Java字节码的类库)来生成动态代理。
- 我们这里使用
JDK
的原生代码来实现,其余的道理都是一样的。
11.2.2、JDK原生的动态代理
JDK
中的动态代理指的是:Java
通过动态生成的代理类(代理角色)代替被代理类(真实角色)处理业务的过程。
想要分析JDK
的动态代理的实现过程,需要了解:Proxy
类和InvocationHandler
(调用处理程序)接口。
(1)通过Proxy类创建动态代理类
Proxy
类是java.lang.reflect
(反射包)下的一个类。
Proxy
类提供了一系列创建动态代理类和实例的静态方法,我们可以通过Proxy
类动态生成代理类。
Proxy
类是通过这些方法所创建的所有动态代理类的父类。
- 每一个(动态生成的)动态代理类都有一个与之关联的调用处理程序(即:
InvocationHandler
接口实现类的对象)。当然,该InvocationHandler
接口实现类也可以是动态代理类本身。
注:Proxy
类中的newProxyInstance
静态方法是动态生成代理类的核心方法。
1 2 3 4 5
| public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){ …… }
|
该方法的输入参数为:
loader
:(动态生成的)动态代理类的类加载器。
interfaces
:(动态生成的)动态代理类所实现的接口列表,即:要代理的抽象角色。
h
:指派(动态生成的)动态代理类对应的调用处理程序(即:InvocationHandler
接口实现类的对象)。
返回值为:指定接口(即:抽象角色)的动态代理类的实例。
(2)通过调用处理程序的invoke方法使用动态代理实例处理业务
InvocationHandler
是java.lang.reflect
(反射包)下的一个接口,且该接口中只有一个invoke
方法。
InvocationHandler.java
文件:
1 2 3 4 5 6
| package java.lang.reflect;
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
|
-
每一个(动态)代理实例都有一个关联的调用处理程序(InvocationHandler
接口实现类)。InvocationHandler
就是该(动态)代理实例所对应的调用处理程序需要实现的接口。
-
动态代理所对应的InvocationHandler
接口实现类可以是动态代理类本身。
-
**动态代理代替真实对象处理业务的底层是通过调用与之对应的InvocationHandler
接口实现类的invoke
方法实现的。**即:当我们想通过(动态)代理实例调用其中的一个方法时,将该方法调用将会被编码,并将其交给与之(即:该代理实例)对应的调用处理程序(InvocationHandler
接口实现类)的invoke
方法去处理。
-
动态代理实例本身不执行代理方法,而是交由其调用处理程序的invoke
方法去执行。
注:invoke
方法的作用是:处理(动态)代理实例的方法调用(即:调用XXX代理实例的XXX方法),并返回结果。
1 2
| public Object invoke(Object proxy, Method method, Object[] args)
|
该方法的输入参数为:
proxy
:被调用方法对应的(动态)代理实例(即:你要用哪个代理?)
method
:(动态)代理实例要调用的方法(即:你要调用代理的哪个方法?)
args
:(动态)代理实例要调用方法的参数列表。(即:你通过代理要调用的方法有哪些参数?)
返回值为:代理实例调用相应方法的返回值。
11.2.3、代码实现
(1)在上述子项目的java
目录下新建一个com.atangbiji.demo03
包,并在该包下新建一个Rent
接口,作为抽象角色。
Rent.java
文件:
1 2 3 4 5 6
| package com.atangbiji.demo03;
public interface Rent { public void rent(); }
|
(2)在子项目的com.atangbiji.demo03
包下新建一个Host
类,作为真实角色(房东)。
Host.java
文件:
1 2 3 4 5 6 7 8 9
| package com.atangbiji.demo03;
public class Host implements Rent { public void rent() { System.out.println("房东想要出租房子!"); } }
|
(3)在子项目的com.atangbiji.demo03
包下新建一个ProxyInvocationHandler
类,用于动态生成代理类,即:代理角色(中介)。
ProxyInvocationHandler.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.atangbiji.demo03;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler { private Rent rent;
public void setRent(Rent rent) { this.rent = rent; }
public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { seeHouse(); Object result = method.invoke(rent, args); sign(); fare(); return result; } public void seeHouse(){ System.out.println("中介带你看房!"); } public void sign(){ System.out.println("中介与你签合同!"); } public void fare(){ System.out.println("中介收取中介费!"); } }
|
**注:**动态代理类本身也可以实现InvocationHandler
接口,作为它自己的调用处理程序。
(4)在子项目的com.atangbiji.demo03
包下新建一个Client
类,作为客户角色(租客)。
Client.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.atangbiji.demo03;
public class Client { public static void main(String[] args) { Host host = new Host(); ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(); proxyInvocationHandler.setRent(host); Rent proxy = (Rent)proxyInvocationHandler.getProxy(); proxy.rent(); } }
|
运行主程序,租客通过中介(动态代理)成功租到房东的房子。执行结果如下图所示:
11.2.4、动态代理再理解
在demo03
示例的基础上,我们只需将上述代理角色的调用处理程序中的Rent
代理接口修改为Object
,便可将其封装成一个通用的动态代理实现类。
ProxyInvocationHandler.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.atangbiji.demo03;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler { private Object target;
public void setTarget(Object target) { this.target = target; }
public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(target, args); log(method.getName()); return result; } public void log(String msg){ System.out.println("执行了" + msg + "方法!"); } }
|
**测试:**我们使用动态代理来代理demo02
示例中的真实角色。
Client.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.atangbiji.demo03;
import com.atangbiji.demo02.UserService; import com.atangbiji.demo02.UserServiceImpl;
public class Client { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(); proxyInvocationHandler.setTarget(userService); UserService proxy = (UserService)proxyInvocationHandler.getProxy(); proxy.add(); proxy.delete(); proxy.update(); proxy.query(); } }
|
重新运行主程序,执行结果如下图所示:
11.2.5、动态代理的好处
静态代理有的它都有,静态代理没有的,它也有!即:
- 可以使得我们的真实角色(如:房东)更加纯粹,不用再去关注一些公共的事情。
- 公共的业务由代理来完成,进而实现了业务的分工。
- 公共业务发生扩展时,更加方便,程序的耦合性更低。
- 由于动态代理代理的是真实角色的接口(即:抽象角色),因此当多个真实角色都实现了同一个抽象角色时,一个动态代理便可以代理所有这些真实角色。
12、AOP
12.1、什么是AOP
AOP
(Aspect Oriented Programming
)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护(即:在不修改源代码的情况下,给程序动态统一添加某种特定功能)的一种技术。AOP
是OOP
(面向对象)的延续,是软件开发中的一个热点,也是Spring
框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP
可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
12.2、AOP的本质(重点)
同IOC
一样,AOP的本质还是为了程序解耦。
AOP
中最核心的思想就是:在不改变原来的代码的情况下,实现对原有功能的增强。
AOP
的实现过程说白了就是:在原先**“纵向开发”的项目的基础上,“横向切入”添加新的功能。**
AOP
的实现过程类似于给病人做一个外接手术,即:把病人(原来的代s码)的伤口(切入点)从四周(通知)切开,外接一个人造器官(切面)后再把伤口(连接点)缝(织入)起来。
12.3、AOP的术语
- 横切关注点:跨越应用程序多个模块的方法或功能。即:与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如:日志、安全、缓存、事务等。
- 切面(
Aspect
):横切关注点被模块化的特殊对象,它是一个类。如:日志类等。
- 通知(
Advice
):切面必须要完成的工作,它是类中的一个方法。如:日志类中的方法。
- 引入(
Introduction
):允许开发者在现有的类中添加自定义的类和方法,以增强现有类的功能。
- 切入点(
PointCut
):切面通知执行的位置。
- 连接点(
JointPoint
):与切入点匹配的执行点。
- 目标对象(
Target
):被通知的对象。即:动态代理中的抽象角色。(Spring
底层已经通过动态代理帮我们实现了!)
- 代理(
Proxy
):向目标对象应用通知之后创建的对象。即:动态生成的代理实例。(Spring
底层已经通过动态代理帮我们实现了!)
- 织入(
Weaving
):是一个生成代理对象的过程。
**注:**在Spring AOP
中,通过Advice
(通知)定义横切逻辑。Spring
中支持5种类型的Advice
(通知):
12.4、Spring对AOP的支持
Spring的底层已经使用动态代理帮助我们实现了对AOP的支持。
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP
代理可以直接使用容器中的其它bean
实例作为目标对象,这种关系可由IOC
容器的依赖注入提供。
Spring
创建动态代理的规则为:
(1)默认使用Java
动态代理来创建AOP
代理,这样就可以为任何接口实例创建动态代理了。
(2)当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB。
12.5、使用Spring实现AOP
需要先在父项目的pom.xml
文件中导入AOP
织入依赖包!
pom.xml
文件:
1 2 3 4 5 6
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
|
12.5.1、方式一:通过Spring API实现AOP(早期)
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-09-AOP
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在子项目的java
目录下新建一个com.atangbiji.service
包,并在该包下新建一个UserService
接口及其接口实现类UserServiceImpl
。
UserService.java
文件:
1 2 3 4 5 6 7 8
| package com.atangbiji.service;
public interface UserService { public void add(); public void delete(); public void update(); public void select(); }
|
UserServiceImpl.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.atangbiji.service;
public class UserServiceImpl implements UserService {
public void add() { System.out.println("增加了一个用户!"); }
public void delete() { System.out.println("删除了一个用户!"); }
public void update() { System.out.println("更新了一个用户!"); }
public void select() { System.out.println("查询了一个用户!"); } }
|
(4)在子项目的java
目录下新建一个com.atangbiji.log
包,并在该包下新建两个增强类:一个前置增强(Log
类);一个后置增强(AfterLog
类)。
Log.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.atangbiji.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName() + "类的" + method.getName() + "方法被执行了"); } }
|
AfterLog.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.atangbiji.log;
import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" + target.getClass().getName() + "类的" + method.getName() + "方法,返回值为:" + returnValue); } }
|
(5)在子项目的resource
目录下新建一个applicationContext.xml
配置文件,用于注册bean
和配置AOP
。
applicationContext.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userServiceImpl" class="com.atangbiji.service.UserServiceImpl"/> <bean id="log" class="com.atangbiji.log.Log"/> <bean id="afterLog" class="com.atangbiji.log.AfterLog"/>
<aop:config> <aop:pointcut id="pointCut" expression="execution(* com.atangbiji.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="log" pointcut-ref="pointCut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/> </aop:config> </beans>
|
**注:**注意在xml
配置文件的头部导入AOP
的约束。
1 2 3
| xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
|
附:expression
(表达式)的语法格式为:
1
| execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
|
其中:(以问号?
结束的部分都是可以省略)
-
modifiers-pattern
表示:方法的访问类型,如public
等;
-
ret-type-pattern
表示:方法的返回值类型,如String
表示返回类型是String
,*
表示所有的返回类型;
-
declaring-type-pattern
表示:方法的声明类,如com.atangbiji..*
表示com.atangbiji
包及其子包下面的所有类型;
-
name-pattern
表示:方法的名称,如add*
表示所有以add
开头的方法名;
-
param-pattern
表示:方法的参数类型,name-pattern(param-pattern)
一起表示方法及对应的参数类型,如add()
表示不带参数的add
方法,add(*)
表示带一个任意类型的参数的add
方法,add(*,String)
则表示带两个参数,且第二个参数是String
类型的add
方法;
-
throws-pattern
表示:异常类型。
(6)在子项目的src/test/java
目录下,新建一个MyTest
测试类。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import com.atangbiji.service.UserService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userServiceImpl = (UserService)context.getBean("userServiceImpl"); userServiceImpl.add(); userServiceImpl.delete(); userServiceImpl.update(); userServiceImpl.select(); } }
|
运行测试程序test
,切面织入成功。执行结果如下图所示:
**注:**由于动态代理代理的是接口(抽象角色),因此需要将UserServiceImpl
类型强制转换为UserService
类型。
12.5.2、方式二:通过自定义类实现AOP(推荐)
(1)保持spring-09-AOP
子项目com.atangbiji.service
包下的UserService
接口及其接口实现类UserServiceImpl
不变。
注:UserService.java
文件与UserServiceImpl.java
文件参见方式一。
(2)在spring-09-AOP
子项目的java
目录下新建一个com.atangbiji.diy
包,并在该包下新建一个DiyAspect
类,用于自定义切面。
DiyAspect.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| package com.atangbiji.diy;
public class DiyAspect { public void before(){ System.out.println("---------方法执行前---------"); } public void after(){ System.out.println("---------方法执行后---------"); } }
|
(3)在子项目resource
目录下的applicationContext.xml
配置文件中注册bean
,并配置AOP
。
applicationContext.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userServiceImpl" class="com.atangbiji.service.UserServiceImpl"/> <bean id="diy" class="com.atangbiji.diy.DiyAspect"/>
<aop:config> <aop:aspect ref="diy"> <aop:pointcut id="diyPointcut" expression="execution(* com.atangbiji.service.UserServiceImpl.*(..))"/> <aop:before method="before" pointcut-ref="diyPointcut"/> <aop:after method="after" pointcut-ref="diyPointcut"/> </aop:aspect> </aop:config> </beans>
|
(4)保持子项目的src/test/java
目录下的MyTest
测试类不变,重新运行测试程序test
,切面同样织入成功。执行结果如下图所示:
12.5.3、方式三:通过注解实现AOP(主流)
(1)保持spring-09-AOP
子项目com.atangbiji.service
包下的UserService
接口及其接口实现类UserServiceImpl
不变。
注:UserService.java
文件与UserServiceImpl.java
文件参见方式一。
(2)在spring-09-AOP
子项目的java
目录下新建一个com.atangbiji.annotation
包,并在该包下新建一个AnnotationAspect
类,用于自定义切面。
AnnotationAspect.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.atangbiji.annotation;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before;
@Aspect public class AnnotationAspect { @Before("execution(* com.atangbiji.service.UserServiceImpl.*(..))") public void before(){ System.out.println("===========方法执行前========="); }
@After("execution(* com.atangbiji.service.UserServiceImpl.*(..))") public void after(){ System.out.println("===========方法执行后========="); }
@Around("execution(* com.atangbiji.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("------------环绕前-----------"); jp.proceed(); System.out.println("------------环绕后-----------"); } }
|
**注:**在环绕增强(通知)中,我们可以传入一个连接点参数,代表我们要获取处理切入的点。
(3)在子项目resource
目录下的applicationContext.xml
配置文件中注册bean
,并开启注解支持。
applicationContext.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userServiceImpl" class="com.atangbiji.service.UserServiceImpl"/> <bean id="annotationAspect" class="com.atangbiji.annotation.AnnotationAspect"/> <aop:aspectj-autoproxy/> </beans>
|
**注:**我们也可以通过注解方式注册bean
,为了方便我们理解通过注解实现AOP
,这里我们仍然使用xml
配置文件的方式注册bean
。
(4)保持子项目的src/test/java
目录下的MyTest
测试类不变,重新运行测试程序test
,切面同样织入成功。执行结果如下图所示:
附:aop:aspectj-autoproxy(开启注解支持)说明
-
通过aop
命名空间的<aop:aspectj-autoproxy />
声明自动为spring
容器中那些配置@aspectJ
切面的bean
创建代理,织入切面。当然,spring
在内部依旧采用AnnotationAwareAspectJAutoProxyCreator
进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />
隐藏起来了。
-
<aop:aspectj-autoproxy />
有一个proxy-target-class
属性,默认为false
,表示使用jdk
动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>
时,表示使用CGLib
动态代理技术织入增强。不过即使proxy-target-class
设置为false
,如果目标类没有声明接口,则spring
将自动使用CGLib
动态代理。
13、整合MyBatis
13.1、回顾MyBatis
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-10-MyBatis
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在父项目的pom.xml
文件中导入父/子工程需要的Maven依赖。
pom.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.10.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> </dependencies>
|
(4)在Maven子项目的src/main/resources
目录下新建一个名称为mybatis-config.xml
的MyBatis核心配置文件。
mybatis-config.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration> <typeAliases> <package name="com.atangbiji.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <package name="com.atangbiji.dao"/> </mappers> </configuration>
|
(5)在子项目的java
目录下新建一个com.atangbiji.utils
包(用于存放我们自己封装的MyBatis
工具类),并在该包下新建一个MyBatisUtils
类,用于获取SqlSession
对象。
MyBatisUtils.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.atangbiji.utils;
import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream;
public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } }
public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); }
public static SqlSession getSqlSession(boolean autoCommit){ return sqlSessionFactory.openSession(autoCommit); } }
|
(6)在子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类(pojo
)),并在该包下新建一个User
类,用于实现关系型数据库和业务实体间的映射。
User.java
文件:
1 2 3 4 5 6 7 8 9 10
| package com.atangbiji.pojo;
import lombok.Data;
@Data public class User { private int id; private String name; private String pwd; }
|
(7)在子项目的java
目录下新建一个com.atangbiji.dao
包(用于存放我们自己编写的持久层代码),并在该包下新建一个UserMapper
映射器接口。
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9
| package com.atangbiji.dao;
import com.atangbiji.pojo.User; import java.util.List;
public interface UserMapper { public List<User> selectUser(); }
|
(8)在该子项目的com.atangbiji.dao
包下新建一个映射器配置文件UserMapper.xml
,并在其中使用标签实现SQL
语句与映射器接口函数的映射。
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atangbiji.dao.UserMapper"> <select id="selectUser" resultType="user"> select * from mybatis.user </select> </mapper>
|
(9)在子项目的src/test/java
目录下,新建一个MyTest
,用于MyBatis
功能测试。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import com.atangbiji.dao.UserMapper; import com.atangbiji.pojo.User; import com.atangbiji.utils.MyBatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test;
import java.util.List;
public class MyTest { @Test public void test(){ try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.selectUser();
for (User user : userList) { System.out.println(user); } } } }
|
**注:**由于Maven的“约定大于配置”思想,我们的映射器配置文件*.xml
写在了项目中的Java
目录下,因此出现无法被导出或者无法生效的问题。该问题的解决方法是:在父项目(或子项目)的pom.xml
的<build>……</build>
中配置resources
,修改Maven约定的过滤条件,来防止我们的资源导出失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
|
重新加载Maven项目,运行测试程序,执行结果如下图所示:
13.2、MyBatis-Spring介绍
13.2.1、什么是MyBatis-Spring?
MyBatis-Spring
是帮助我们将MyBatis
代码无缝地整合到Spring
中的工具包。
MyBatis-Spring
允许MyBatis
参与到Spring
的事务管理之中,创建映射器mapper
和 SqlSession
并注入到bean
中,以及将Mybatis
的异常转换为Spring
的 DataAccessException
。 最终,可以做到应用代码不依赖于MyBatis
,Spring
或MyBatis-Spring
。
13.2.2、MyBatis-Spring的使用
在选择MyBatis-Spring
的版本之前,我们需要先明确它与Spring
和MyBatis
版本之间的对应关系。如下图所示:
如果要使用MyBatis-Spring
,仅需要在项目的pom.xml
文件中加入Spring
整合MyBatis
的依赖即可:
1 2 3 4 5 6
| <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency>
|
13.3、方式1:使用sqlSessionTemplate整合
具体整合步骤如下:
(1)引入Spring配置文件
在spring-10-MyBatis
子项目的resources
目录下新建一个spring-dao.xml
配置文件,用于配置与MyBatis相关Beans
的元数据。
spring-dao.xml
文件:
1 2 3 4 5 6 7 8 9
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
|
**注:**我们可以使用import
标签,将spring-dao.xml
配置文件导入到我们的Spring
主配置文件(applicationContext.xml
)中去。
applicationContext.xml
文件:
1 2 3 4 5 6 7
| <beans> <import resource="spring-dao.xml"/> <bean id="bean1" class="..."/> <bean id="bean2" class="..."/> </beans>
|
(2)配置数据源
在spring-dao.xml
文件中使用Spring
的数据源替换MyBatis
核心配置文件(mybatis-config.xml
)中的数据源配置。
spring-dao.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?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 https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> </beans>
|
**注:**数据源的种类有非常多,可以使用第三方的,也可使使用Spring
的,我们这里使用Spring
的提供的JDBC
。
(3)配置SqlSessionFactory,关联MyBatis
在MyBatis-Spring
中,可使用SqlSessionFactoryBean
来创建 SqlSessionFactory
。 要配置这个工厂bean
,只需要把下面代码放在Spring
的XML
配置文件中。
spring-dao.xml
文件:
1 2 3 4 5 6 7
| <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/atangbiji/dao/*.xml"/> </bean>
|
注:
- 在
SqlSessionFactoryBean
中绑定了MyBatis
配置文件后,我们便可以将两者关联起来,一起配合使用了。
- 在
SqlSessionFactoryBean
中配置了mapperLocations
属性后,我们就不用再在MyBatis
核心配置文件(mybatis-config.xml
)中通过mappers
标签注册所有的映射器了。
(4)注册sqlSessionTemplate,关联sqlSessionFactory(关键)
在MyBatis
中,我们使用 SqlSessionFactory
来创建 SqlSession
。 一旦你获得一个session
之后,我们可以使用它来执行映射了的SQL
语句,提交或回滚连接,最后,当不再需要它的时候,我们需要关闭session
。
使用MyBatis-Spring
之后,我们不需要再从SqlSessionFactory
中获取SqlSession
对象了,因为我们的bean
可以被注入一个线程安全的 SqlSession
(即:SqlSessionTemplate
),它能基于Spring
的事务配置来自动提交、回滚、关闭session
。
spring-dao.xml
文件:
1 2 3 4 5
| <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
|
注:
SqlSessionTemplate
是 SqlSession
的一个实现,使用它可以无缝代替我们之前代码中已经在使用的 SqlSession
。
SqlSessionTemplate
是MyBatis-Spring
的核心。
SqlSessionTemplate
是线程安全的,可以被多个DAO
或映射器所共享使用。
- 我们可以使用
SqlSessionFactory
作为有参构造方法的参数来创建 SqlSessionTemplate
对象。
(5)增加Dao接口的实现类,私有化sqlSessionTemplate(关键)
在该子项目的com.atangbiji.dao
包下新建一个映射器配置文件UserMapperImpl
类,用于实现UserMapper
接口;并在其中添加一个 SqlSessionTemplate
(私有)属性及其set
方法。
UserMapperImpl.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.atangbiji.dao;
import com.atangbiji.pojo.User; import org.mybatis.spring.SqlSessionTemplate; import java.util.List;
public class UserMapperImpl implements UserMapper { private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; }
@Override public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } }
|
**注:**为了能够在Spring
中使用Dao
接口,我们需要将Dao
接口的实现类注入到IOC
容器中。因此,我们需要手动实现Dao
接口,并在其中使用sqlSession
(SqlSessionTemplate
对象)执行SQL
语句,并将执行结果返回供用户调用。
(6)在SpringIOC容器中注册Dao接口的实现类
spring-dao.xml
文件:
1 2 3 4 5
| <bean id="userMapper" class="com.atangbiji.dao.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean>
|
(7)测试
修改MyTest
测试类,用于MyBatis-Spring
功能测试。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import com.atangbiji.dao.UserMapper; import com.atangbiji.pojo.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void testMyBatisSpring(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml"); UserMapper userMapper = (UserMapper)context.getBean("userMapper"); for (User user : userMapper.selectUser()) { System.out.println(user); } } }
|
运行测试程序,SQL执行成功。执行结果如下图所示:
**注:**这样我们便完成了Spring
对Mybatis
的整合。
mybatis-config.xml
文件:
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration> <typeAliases> <package name="com.atangbiji.pojo"/> </typeAliases> </configuration>
|
13.4、方式2:使用SqlSessionDaoSupport整合
mybatis-spring
1.2.3以上版本,为我们提供了一个抽象的支持类——SqlSessionDaoSupport
类,它为我们提供了 SqlSession
。如果我们手动添加的Dao
接口实现类(如:UserMapperImpl
类)继承了该支持类,那么我们就不用再像方式1一样在Dao
接口实现类中添加一个 SqlSessionTemplate
(私有)属性及其set
方法了。此时,我们直接调用其 getSqlSession()
方法,便可以得到一个 SqlSessionTemplate
对象,然后再去执行 SQL 语句了。
注:
- 与方式1相比,方式2不需要管理
SqlSessionTemplate
, 而且对事务的支持更加友好,可跟踪查看源码。
- 方式2是对方式1的进一步优化。
具体整合步骤如下:
(1)增加Dao接口的实现类,继承SqlSessionDaoSupport(关键)
在保持方式1步骤(1)(2)(3)不变的基础上,在上述子项目的com.atangbiji.dao
包下新建一个映射器配置文件UserMapperImpl2
类,用于实现UserMapper
接口;并让该Dao
接口实现类继承SqlSessionDaoSupport
类。
**注:**方式1的步骤(4)可以省略,也可以保持不变。
UserMapperImpl.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.atangbiji.dao;
import com.atangbiji.pojo.User; import org.apache.ibatis.session.SqlSession; import org.mybatis.spring.support.SqlSessionDaoSupport; import java.util.List;
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{ @Override public List<User> selectUser() { SqlSession sqlSession = getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } }
|
(2)在SpringIOC容器中注册新的Dao接口实现类
spring-dao.xml
文件:
1 2 3 4 5
| <bean id="userMapper2" class="com.atangbiji.dao.UserMapperImpl2"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean>
|
注:SqlSessionDaoSupport
需要设置一个 sqlSessionFactory
或 SqlSessionTemplate
属性。如果两个属性都被设置了,那么 SqlSessionFactory
将会被忽略。
(3)测试
修改MyTest
测试类,用于MyBatis-Spring
功能测试。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import com.atangbiji.dao.UserMapper; import com.atangbiji.pojo.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void testMyBatisSpring2(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml"); UserMapper userMapper = (UserMapper)context.getBean("userMapper2"); for (User user : userMapper.selectUser()) { System.out.println(user); } } }
|
运行测试程序,SQL执行成功。执行结果如下图所示:
**注:**除了这些方式可以实现MyBatis
整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot
的时候还会测试整合。
14、Spring声明式事务
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
- 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
14.1、回顾事务
详见《事务》。
14.2、搭建测试环境
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-11-Transaction
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)检测在父项目的pom.xml
文件中是否导入父/子工程需要的Maven依赖。若未导入,则手动导入。
(4)在子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类(pojo
)),并在该包下新建一个User
类,用于实现关系型数据库和业务实体间的映射。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.atangbiji.pojo;
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String pwd; }
|
(5)在子项目的java
目录下新建一个com.atangbiji.dao
包(用于存放我们自己编写的持久层代码),并在该包下新建一个UserMapper
映射器接口。
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.atangbiji.dao;
import com.atangbiji.pojo.User; import java.util.List;
public interface UserMapper { public List<User> selectUser(); public int addUser(User user); public int deleteUser(int id); }
|
(6)在该子项目的com.atangbiji.dao
包下新建一个映射器配置文件UserMapper.xml
,并在其中使用标签实现SQL
语句与映射器接口函数的映射。
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atangbiji.dao.UserMapper"> <select id="selectUser" resultType="user"> select * from mybatis.user </select> <insert id="addUser" parameterType="user"> insert into mybatis.user (id, name, pwd) VALUES (#{id},#{name},#{pwd}) </insert> <delete id="deleteUser" parameterType="int"> delete from mybatis.user where id = #{id} </delete> </mapper>
|
(7)在Maven子项目的src/main/resources
目录下新建一个名称为mybatis-config.xml
的MyBatis核心配置文件。
mybatis-config.xml
文件:
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration> <typeAliases> <package name="com.atangbiji.pojo"/> </typeAliases> </configuration>
|
接下来,按照方式1整合MyBatis。具体地:
(8)在子项目的resources
目录下新建一个spring-dao.xml
配置文件,用于配置与MyBatis相关Beans
的元数据。
spring-dao.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?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 https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/atangbiji/dao/*.xml"/> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean> </beans>
|
(9)在该子项目的com.atangbiji.dao
包下新建一个映射器配置文件UserMapperImpl
类,用于实现UserMapper
接口;并让该Dao
接口实现类继承SqlSessionDaoSupport
类。
UserMapperImpl.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.atangbiji.dao;
import com.atangbiji.pojo.User; import org.apache.ibatis.session.SqlSession; import org.mybatis.spring.support.SqlSessionDaoSupport; import java.util.List;
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
@Override public List<User> selectUser() { SqlSession sqlSession = getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); }
@Override public int addUser(User user) { return getSqlSession().getMapper(UserMapper.class).addUser(user); }
@Override public int deleteUser(int id) { return getSqlSession().getMapper(UserMapper.class).deleteUser(id); } }
|
(10)在子项目的resources
目录下新建一个Spring
的applicationContext.xml
配置文件。使用import
标签,将spring-dao.xml
配置文件导入到我们的Spring
主配置文件(applicationContext.xml
)中去。
applicationContext.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?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 https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="spring-dao.xml"/>
<bean id="userMapper" class="com.atangbiji.dao.UserMapperImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> </beans>
|
(11)在子项目的src/test/java
目录下,新建一个MyTest
,用于MyBatis-Spring
功能测试。
MyTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import com.atangbiji.dao.UserMapper; import com.atangbiji.pojo.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = (UserMapper)context.getBean("userMapper"); for (User user : userMapper.selectUser()) { System.out.println(user); } } }
|
运行测试程序,SQL执行成功。执行结果如下图所示:
14.3、为什么需要管理事务
为了通过演示说明事务管理的重要性,我们在上述测试环境的基础上:
(1)故意把UserMapper.xml
文件中的delete
SQL语句的delete
写错为deletes
。
UserMapper.xml
文件:
1 2 3 4
| <delete id="deleteUser" parameterType="int"> deletes from mybatis.user where id = #{id} </delete>
|
(2)修改UserMapperImpl
接口实现类的selectUser
方法,在该方法中增加一个“先添加一个用户,再删除该用户”的操作,并且希望这两个操作要么都成功,要么都失败。
UserMapperImpl.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.atangbiji.dao;
import com.atangbiji.pojo.User; import org.apache.ibatis.session.SqlSession; import org.mybatis.spring.support.SqlSessionDaoSupport; import java.util.List;
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
@Override public List<User> selectUser() { SqlSession sqlSession = getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = new User(4,"小明","123456"); mapper.addUser(user); mapper.deleteUser(4);
return mapper.selectUser(); }
@Override public int addUser(User user) { return getSqlSession().getMapper(UserMapper.class).addUser(user); }
@Override public int deleteUser(int id) { return getSqlSession().getMapper(UserMapper.class).deleteUser(id); } }
|
(3)测试。
再次运行测试程序test
,我们可以发现:SQL删除语句执行出错,但新用户添加仍然成功。执行结果如下图所示:
测试结果分析:虽然我们希望“添加一个用户“和”删除新添加的用户”这两个操作要么都成功,要么都失败,但由于我们没有对其进行事务的管理,才会出现上述SQL
删除语句执行出错,但新用户添加仍然成功的现象。这显然不是我们想要的结果!
如果我们想让它们都成功时才成功,有一个失败,就都失败,那么我们就需要把它们当做事务来处理!
以前我们都需要自己手动管理事务,十分麻烦!
但是**Spring
给我们提供了事务管理,我们只需要配置即可。**一旦配置好了Spring
的事务管理器,我们就可以在Spring
中按照我们平时的方式来配置事务。并且支持 @Transactional
注解和 AOP 风格的配置。在事务处理期间,一个单独的 SqlSession
对象将会被创建和使用。当事务完成时,这个session
会以合适的方式提交或回滚。
14.4、Spring中的事务管理
Spring
在不同的事务管理API
之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API
就可以使用Spring
的事务管理机制。
Spring
为我们提供了 DataSourceTransactionManager
(事务管理器)来实现事务管理。
Spring
支持编程式事务管理和声明式事务管理。
14.4.1、编程式事务管理与声明式事务管理
(1)编程式事务管理:
- 将事务管理代码嵌到业务方法中来控制事务的提交和回滚。
- 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码。
(2)声明式事务管理:(推荐)
- 一般情况下比编程式事务好用。
- 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
- 将事务管理作为横切关注点,通过
AOP
方法模块化。
Spring
中通过Spring AOP
框架支持声明式事务管理。
注:
14.4.2、实现步骤
(1)开启Spring
的事务处理功能
在上述子项目的基础上,开启Spring
的事务处理功能,即:在Spring
整合MyBatis
的配置文件中创建一个 DataSourceTransactionManager
(事务管理器)对象(Bean
)。
spring-dao.xml
文件:
1 2 3 4 5
| <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSource" /> </bean>
|
接下来,使用AOP实现事务的织入。具体地:
(2)导入tx
和AOP
的约束
在Spring
整合MyBatis
的配置文件头部导入tx
和AOP
的约束。
spring-dao.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| <?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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
|
注:tx
表示事务。
(3)配置事务的通知
在Spring
整合MyBatis
的配置文件中配置事务通知对应的事务管理器,并在其中配置事务的传播特性,即:配置哪些方法使用什么样的事务。
spring-dao.xml
文件:
1 2 3 4 5 6 7 8 9 10 11
| <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED"/> <tx:method name="select*"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
|
附:spring事务的传播特性
**事务传播行为(propagation)**就是多个事务方法相互调用时,事务如何在这些方法之间传播。spring
支持7种事务传播行为:
requierd
:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
supports
:支持当前事务,如果没有当前事务,就以非事务方法执行。
mandatory
:使用当前事务,如果没有当前事务,就抛出异常。
required_new
:新建事务,如果当前存在事务,把当前事务挂起。
not_supported
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
never
:以非事务方式执行操作,如果当前事务存在则抛出异常。
nested
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与required
类似的操作。
注:Spring
默认的事务传播行为是:REQUIRED
,它适合于绝大多数的情况。
假设ServiveX#methodX()
都工作在事务环境下(即都被Spring
事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3()
,那么这3个服务类的3个方法通过Spring
的事务传播机制都工作在同一个事务中。
就好比,我们14.3节中的selectUser()
方法调用了addUser()
和deleteUser()
方法,所以它们会被Spring
放在一组事务当中进行处理!
(4)配置AOP
在Spring
整合MyBatis
的配置文件中,通过AOP
把事务织入到com.atangbiji.dao
包下所有类的所有方法中。
spring-dao.xml
文件:
1 2 3 4 5 6 7 8
| <aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.atangbiji.dao.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config>
|
**注:**注意在xml
配置文件的头部导入AOP
的约束。
(5)测试
删除刚才插入的用户数据,再次运行测试程序test
,我们可以发现:SQL删除语句执行仍然出错,但此时新用户并未添加成功。执行结果如下图所示:
通过测试结果我们可以发现:Spring
通过声明式事务可以在不改变原有代码的基础上实现对事务的管理,将事务管理代码从业务方法中分离出来。
(本讲完,系列博文持续更新中…… )
关注**“阿汤笔迹”** 微信公众号,获取更多学习笔记。
原文地址:http://www.atangbiji.com/2022/12/08/SpringInDetail
博主最新文章在个人博客 http://www.atangbiji.com/ 发布。