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-spring1.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文件中的deleteSQL语句的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/ 发布。