框架 - Spring框架
- 第1章:Spring 概述
- 第2章:IoC 控制反转
- 第3章:AOP面向切面编程
- 第4章:Spring 集成 MyBatis
- 第5章:Spring事务
- 第6章:Spring与Web
- 【思维导图】
- 传送门
第1章:Spring 概述
1.1 概述
Spring: 出现在2002年左右,兴起于2003年的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。
可以减轻对项目模块之间的管理、类和类之间的管理, 帮助开发人员创建对象、管理对象之间的关系。
2017出现新的流行框架Spring Boot,核心思想与Spring相同。
Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。能实现模块之间、类之间的解耦合。
Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,
不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring容器统一管理,自动“注入”, 注入即赋值。
而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,
而是由 Spring 容器统一完成“织入”。
Spring 是可以在 Java SE/EE 中使用的轻量级开源框架。
Maven是管理项目的外部依赖,而Spring是管理项目的内部依赖。
依赖:classA中使用classB的属性或者方法, 叫做classA依赖classB
Spring的资源和文档可从官网获取 https://spring.io/
1.2 优点
(1)轻量:
Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。
Spring框架运行占有资源少,运行效率高,不依赖其他jar。
(2)针对接口编程,解耦合
Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。
原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。
(3)AOP 编程的支持
通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
(4)方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,
Spring 提供了对各种优秀框架(如 Struts、Hibernate、MyBatis)等的直接支持。简化框架的使用。
Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。
需要使用哪个框架,就把这个插头放入插线板。不需要可以轻易的移除。
1.3 Spring 体系结构
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、Web、
面向切面编程(AOP, Aspects)、提供JVM的代理(Instrumentation)、消息发送(Messaging)、
核心容器(Core Container)和测试(Test)。
第2章:IoC 控制反转
2.1 概述
控制反转(IoC,Inversion of Control),是一个概念,是一种思想。
指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。
控制反转就是对 对象控制权的转移,从程序代码本身反转到了外部容器。
通过容器实现 对象的创建、属性赋值、依赖的管理。
控制: 创建对象,对象的属性赋值,对象之间的关系管理。
反转: 把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。
由容器代替开发人员管理对象。创建对象、给属性赋值。
正转:由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。
public static void main(String args[]){
Student student = new Student(); // 在代码中,创建对象 ————正转。
}
容器:是一个服务器软件, 一个框架(spring)
使用 IoC 目的 :减少对代码的改动, 也能实现不同的功能。 实现解耦合。
java中创建对象的方式:
1. 构造方法 , new Student()
2. 反射
3. 序列化
4. 克隆
5. 动态代理
6. IoC :容器创建对象
IoC 的体现: servlet
1)创建类继承HttpServelt
2)在web.xml 注册servlet,使用 <servlet-name> myservlet </servlet-name>
<servelt-class>com.yuming.controller.MyServlet1</servlet-class>
3)没有创建 Servlet对象。 没有 MyServlet myservlet = new MyServlet()
4)Servlet 是Tomcat服务器它为你创建的。 Tomcat也称为容器
Tomcat作为容器:里面存放的有Servlet对象、Listener、Filter对象
IoC的技术实现:
DI(Dependency Injection): 依赖注入 。
只需要在程序中提供要使用的对象名称就可以,
至于对象如何在容器中创建、赋值、查找,都由容器内部实现。
Spring 框架使用依赖注入(DI)技术实现 IoC。 spring底层创建对象,使用的是反射机制。
2.2 Spring 的第一个程序
项目【ch01-spring-first】:使用 IOC, 由spring创建对象
实现步骤:
1)创建Maven项目
2)pom文件加入spring的依赖(5.2.5版本)
3)创建接口和实现类
和没有使用框架一样, 就是普通的类
4)创建Spring的配置文件
声明类的信息,这些类由spring创建和管理
5)测试spring创建的对象
1、创建Maven项目
新建一个maven版普通java项目,选择模板:maven-archetype-quickstart
删除src下默认创建的App和TestApp类文件;
创建缺少的resources目录并标识。右键resources ———— Mark Directory as —————— Resources Root
pom文件中:删掉无用的一些标签:name和url标签、build标签;
pom文件中:将jdk版本1.7要修改为1.8;
2、pom文件加入spring的依赖(5.2.5版本)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency
3、创建接口和实现类
public interface SomeService {
void doSome();
}
public class SomeServiceImpl implements SomeService{
@Override
public void doSome() {
System.out.println("====业务方法doSome()被调用了===");
}
}
4、创建Spring的配置文件
如同在Servlet中我们需要在web.xml中注册我们希望服务器自动创建管理的servlet对象一样,
在Spring中也需要有类似的配置,来自动创建刚才的SomeServiceImpl对象
在 src/main/resources/目录,右键 - new - XML Configuration File - Spring Config
创建Spring的配置文件: applicationContext.xml
-----------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="someService" class="com.yuming.service.SomeServiceImpl"/>
</beans>
-----------------------------------------------------------------------
<bean />:用于定义一个实例对象。一个实例对应一个 bean 元素。
id: 该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,
Bean 与 Bean 间的依赖关系也是通过 id 属性关联的。
class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。
5、测试spring创建的对象
-----------------------------------------------------------------------
//自己手动在程序中创建对象的方式
@Test
public void test01() {
SomeService service = new SomeServiceImpl();
service.doSome();
}
-----------------------------------------------------------------------
/** 使用Spring创建对象:
* spring默认创建对象的时间:在创建spring的容器时,会创建配置文件中的所有的对象。
* spring创建对象:默认调用的是无参数构造方法
*/
@Test
public void test02() {
//1.指定spring配置文件的位置和名称
String config = "applicationContext.xml";
//2.创建spring容器对象
/* ApplicationContext就是表示Spring容器
ClassPathXmlApplicationContext:表示从类路径(target/classes)中加载spring的配置文件
创建Spring容器,会读取配置文件中的所有bean标签,并创建对应的对象(无参构造方法)。 */
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//3、从容器中获取某个对象。通过配置文件中的<bean>的id值
SomeService service = (SomeService)ac.getBean("someService");
//4、调用对象的方法
service.doSome();
}
-----------------------------------------------------------------------
【拓展】可以获取spring容器中 java 对象的信息
@Test
public void test03(){
String config="applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//使用spring提供的方法, 获取容器中定义的对象的数量
int nums = ac.getBeanDefinitionCount();
System.out.println("容器中定义的对象数量:"+nums);
//容器中每个定义的对象的名称
String names [] = ac.getBeanDefinitionNames();
for(String name:names){
System.out.println(name);
}
}
【拓展】可以使用 spring 创建 非自定义类对象
spring 配置文件加入 java.util.Date 定义:
<bean id="myDate" class="java.util.Date" />
MyTest 测试类中:
调用 getBean(“myDate”); 获取日期类对象。
2.3 基于XML的DI
本节项目【ch02-xml-di】
前面讲到:
Spring 框架使用 DI 技术实现 IoC。 DI(Dependency Injection): 依赖注入,表示创建对象、给属性赋值。
只需要在程序中提供要使用的对象名称就可以,至于对象如何在容器中创建、赋值、查找,都由容器内部实现。
DI依赖注入的实现有两种:
1)基于 XML 的DI实现:在spring的配置文件中,使用标签和属性给对象的属性进行赋值。
2)基于 注解 的DI实现:使用spring中的注解,完成属性赋值
注入的分类:
bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。
初始化是由容器自动完成的,称为注入。
根据注入方式的不同,常用的有两类:
① set 注入(设值注入) ② 构造注入
2.3.1 set注入
- spring调用类的 set方法,在set方法可以实现属性的赋值。使用较多。
- (1)简单类型的set注入
简单类型: spring中规定 java的基本数据类型和String都是 简单类型。
注入:就是赋值的意思
java类:
public class Student {
private String name; //简单类型
private int age; //简单类型
//set方法、toString方法....
}
Spring配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myStudent" class="com.yuming.bao1.Student">
<property name="name" value="张三"/> <!--setName("张三")-->
<property name="age" value="20"/> <!--setAge(20)-->
</bean>
</beans>
测试类中的测试方法:
@Test
public void test01() {
String config = "bao1/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
Student student = (Student) ac.getBean("myStudent");
System.out.println(student);
//Student{name='张三', age=20}
}
- (2)引用类型的set注入
当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref的值必须为某 bean 的 id 值。
java类:
public class School {
private String name;
private String address;
//set方法、toString方法...
}
public class Student {
private String name; //简单类型
private int age; //简单类型
private School school; //引用类型
//set方法、toString方法...
}
配置文件(部分):
......
<bean id="mySchool" class="com.yuming.bao2.School"> <!--声明school对象、给其属性赋值(set注入)-->
<property name="name" value="市一中"/>
<property name="address" value="上海"/>
</bean>
<!--写法1:ref作为属性-->
<bean id="myStudent" class="com.yuming.bao2.Student">
<property name="name" value="张三111"/>
<property name="age" value="20"/>
<!--引用类型:-->
<property name="school" ref="mySchool"/> <!--调用setSchool(mySchool)-->
</bean>
<!--写法2:ref作为子标签-->
<bean id="myStudent2" class="com.yuming.bao2.Student">
<property name="name" value="张三222"/>
<property name="age" value="20"/>
<!--引用类型:-->
<property name="school">
<ref bean="mySchool"></ref> <!--调用setSchool(mySchool)-->
</property>
</bean>
......
测试方法:
@Test
public void test02() {
String config = "bao2/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//从容器中获取 已经创建好并且初始化属性值了的对象
Student student = (Student) ac.getBean("myStudent");
System.out.println(student);
//Student{name='张三111', age=20, school=School{name='市一中', address='上海'}}
}
2.3.2 构造注入
- spring调用类的 有参数构造方法,创建对象的同时完成属性的赋值。
- < constructor-arg />标签中用于指定参数的属性有:
➢ name:指定参数名称。
➢ index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,
若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
- < constructor-arg />标签中用于指定参数的属性有:
java类:
public class School {
private String name;
private String address;
//set方法、toString方法...
}
public class Student {
private String name; //简单类型
private int age; //简单类型
private School school; //引用类型,指向上面那个类
//set方法、toString方法、无参构造、【有参构造方法】....
}
配置文件(部分):
......
<!--声明school对象-->
<bean id="mySchool" class="com.yuming.bao3.School">
<property name="name" value="市一中"/>
<property name="address" value="上海"/>
</bean>
<!--声明Student对象--> <!--写法一:使用name属性-->
<bean id="myStudent" class="com.yuming.bao3.Student">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="20"/>
<constructor-arg name="school" ref="mySchool"/>
</bean>
<!--声明Student对象--> <!--写法二:使用index属性-->
<bean id="myStudent2" class="com.yuming.bao3.Student">
<constructor-arg index="0" value="张三222"/>
<constructor-arg index="1" value="22"/>
<constructor-arg index="2" ref="mySchool"/>
</bean>
<!--还可以省略index-->
<bean id="myStudent3" class="com.yuming.bao3.Student">
<constructor-arg value="张三333"/>
<constructor-arg value="33"/>
<constructor-arg ref="mySchool"/>
</bean>
......
测试方法:
@Test
public void test01() {
String config = "bao3/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
Student student = (Student) ac.getBean("myStudent");
System.out.println(student);
//Student{name='张三', age=20, school=School{name='市一中', address='上海'}}
}
2.3.3 引用类型属性的自动注入
-
对于引用类型属性的注入(赋值),也可不在配置文件中显示的注入。
可以通过为 标签设置 autowire 属性值,为引用类型属性进行隐式的自动注入。
根据自动注入判断标准的不同,可以分为两种:- byName:按名称自动注入
- byType:按类型自动注入
-
(1)byName 方式自动注入
当配置文件中 被调用者bean的id值与代码中 调用者bean类的属性名相同时,且数据类型是一致的,
可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean,就能够赋值给引用类型。
容器是通过调用者的 bean类的属性名 与配置文件的被调用者 bean的id 进行比较而实现自动注入的。
<!--byName自动注入,添加属性autowire="byName"-->
<bean id="myStudent" class="com.yuming.bao4.Student" autowire="byName">
<property name="name" value="张三111"/>
<property name="age" value="20"/>
<!--引用类型:-->
<!--<property name="school" ref="mySchool"/>-->
</bean>
<!--声明School对象
这里的bean的id的值和上面的引用类型属性名(school)一样才行 -->
<bean id="school" class="com.yuming.bao4.School">
<property name="name" value="北京大学"/>
<property name="address" value="北京"/>
</bean>
- (2)byType 方式自动注入
使用 byType 方式自动注入,要求:
配置文件中 被调用者 bean的 class属性指定的类,要与代码中 调用者 bean类的某引用类型属性是类型同源的。
同源,即要么相同,要么有 is-a 关系(子类,或是实现类)。
但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了
<!--byType-->
<bean id="myStudent" class="com.yuming.bao5.Student" autowire="byType">
<property name="name" value="张三222"/>
<property name="age" value="22"/>
<!--引用类型:-->
<!--<property name="school" ref="mySchool"/>-->
</bean>
<!--第一种:声明School对象-->
<!--
<bean id="mySchool" class="com.yuming.bao5.School">
<property name="name" value="北京大学22"/>
<property name="address" value="北京22"/>
</bean>
-->
<!--第二种:声明School的子类对象-->
<bean id="xiaoxue" class="com.yuming.bao5.PrimarySchool">
<property name="name" value="希望小学"/>
<property name="address" value="深圳"/>
</bean>
注意:在xml配置文件使用byType,符合条件的对象只能有一个,多余一个是报错的。
2.3.4 为应用指定 多个Spring 配置文件
-
在实际应用里,随着应用规模的增加,系统中 Bean数量也大量增加,导致配置文件变得非常庞大、臃肿。
为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring 配置文件分解成多个配置文件。 -
包含关系的配置文件:
多个配置文件中有一个总文件,总配置文件将各其它子文件通过引入。
在 Java代码中只需要使用总配置文件对容器进行初始化即可。 -
多个配置文件的优势:
1)每个文件的大小比一个文件要小很多。效率高
2)避免多人竞争带来的冲突。 -
多文件的分配方式:
1)按模块,一个模块一个配置文件(考试管理模块一个配置文件,成绩管理模块一个配置文件。。。)
2)按类的功能(数据库相关的配置放一个配置文件, 做事务的功能放一个配置文件, 做service功能的放一个配置文件等) -
举例:
在src/main/resources/bao6下面,创建3个配置文件,其中1个是主配置文件,引入另外2个配置文件
【total.xml】
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
包含关系的配置文件:
total.xml表示主配置文件 : 包含其他的配置文件的,主配置文件一般是不定义对象的。
语法:<import resource="其他配置文件的路径" />
关键字:"classpath:" 表示类路径(target/class文件所在的目录),
在spring的配置文件中要指定其他文件的位置, 需要使用classpath,告诉spring到哪去加载读取文件。
-->
<!--加载的是文件列表-->
<!--
<import resource="classpath:bao6/spring-school.xml" />
<import resource="classpath:bao6/spring-student.xml" />
-->
<!--
在包含关系的配置文件中,可以使用通配符(*:表示任意字符)
注意: 主的配置文件名称不能包含在通配符的范围内
(比如这里,主配置文件命名不能叫做spring-total.xml)
否则将出现循环递归包含
-->
<import resource="classpath:bao6/spring-*.xml" />
</beans>
----------------------------------------------------------------------------------------------------------------
【spring-student.xml】
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Student模块的配置文件: Student 模块所有bean的声明 -->
<!--byType-->
<bean id="myStudent" class="com.yuming.bao6.Student" autowire="byType">
<property name="name" value="赵六"/>
<property name="age" value="26"/>
<!--引用类型:-->
<!--<property name="school" ref="mySchool"/>-->
</bean>
</beans>
---------------------------------------------------------------------------------
【spring-school.xml】
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--School模块的配置文件:School模块所有bean的声明 -->
<!--声明School对象-->
<bean id="mySchool" class="com.yuming.bao6.School">
<property name="name" value="6666大学"/>
<property name="address" value="山东"/>
</bean>
</beans>
2.4 基于注解的DI
本节项目【ch04-anno-di】
使用spring中的注解,完成java对象创建、属性赋值。
对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。
Spring 中使用注解,需要在原有 Spring 运行环境基础上再做一些改变。
需要在 Spring 配置文件中配置【组件扫描器】,用于在指定的基本包中扫描注解。
主要的注解:
1)@Component、@Respotory、@Service、@Controller
2)@Value
3)@Autowired、@Qualifier
4)@Resource
2.4.0 使用注解的步骤
- 1)创建Maven项目,在pom文件中加入spring-context依赖
在你加入spring-context的同时, 它会自动间接加入spring-aop的依赖。
使用注解必须使用spring-aop依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
- 2)在类中加入spring的注解(有多个不同功能的注解)
@Component(value = "myStudent")
public class Student {
private String name;
private int age;
//set方法、toString方法...
}
- 3)在spring的配置文件中,加入一个组件扫描器的标签,用于在指定的包中扫描注解。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--声明组件扫描器(component-scan),用于在指定的包中扫描注解。
component-scan工作原理:
spring会扫描遍历base-package指定的包,
找到包和子包中的所有类中的注解,按照注解的功能创建对象,或给属性赋值。
加入了component-scan标签后,配置文件的变化:
1.最上面加入一个新的约束文件spring-context.xsd
2.这个新的约束文件的命名空间的名称:context -->
<!--<context:component-scan base-package="com.yuming.bao1"/>-->
<!--指定扫描多个包-->
<!--第一种,使用多次扫描器,分别指定不同的包-->
<!--<context:component-scan base-package="com.yuming.bao1"/>-->
<!--<context:component-scan base-package="com.yuming.bao2"/>-->
<!--第二种,使用分隔符(,或者;)指定多个包-->
<!--<context:component-scan base-package="com.yuming.bao1 ; com.yuming.bao2"/>-->
<!--第三种,指定父包-->
<context:component-scan base-package="com.yuming" />
</beans>
- 4)测试 注解创建的对象
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac =new ClassPathXmlApplicationContext(config);
Student student = (Student) ac.getBean("myStudent");
System.out.println(student);
//输出了 Student{name='null', age=0}------>说明确实是通过注解创建了Student对象
}
2.4.1 定义Bean 的注解 @Component
@Component: 创建对象的, 等同于<bean>的功能
属性:value ,就是对象的名称,也就是该bean的id值,
value的值是唯一的,创建的对象在整个spring容器中就一个
位置: 在类的上面, 表示创建此类的对象。
@Component(value = "myStudent")
public class Student {
private String name;
private int age;
//set方法、toString方法...
}
有3种写法:
第一种: @Component(value = "myStudent") 标准写法
第二种: @Component("myStudent") 等同于上面那个
第三种: @Component 等同于 @Component(value = "student")
javaSE中学到过:如果一个注解只有一个属性的话,并且属性的名字是value,那么在使用的时候,该属性名可以省略。
如果 @Component 不指定 value属性,那么默认创建的对象名是:该类名的首字母小写。例如Student类的对象就是student
@Component(value = "myStudent") 等同于 <bean id="myStudent" class="com.yuming.bao1.Student" />
另外,Spring 还提供了 3 个创建对象的注解:
1) @Repository(用在持久层类上面): 放在dao的实现类上面,
表示创建dao对象,dao对象是能访问数据库的。
2) @Service (用在业务层类上面):放在service的实现类上面,
创建service对象,service对象是做业务处理,可以有事务等功能的。
3) @Controller(用在控制器上面):放在Controller 实现类的上面,
创建控制器对象的,能够接受用户提交的参数,显示请求的处理结果。
以上三个注解的使用语法和 @Component一样的。 都能创建对象,但是这三个注解还有额外的功能。
这三个注解是给项目的对象分层的,是对 @Component 注解的细化,标注不同层的对象。
即持久层对象,业务层对象,控制层对象。
2.4.2 简单类型属性注入 @Value
@Value: 简单类型的属性赋值(赋初始值)
属性: value 是String类型的,表示简单类型的属性值
位置:
第一种:在属性定义的上面,无需set方法,推荐使用。
第二种:在set方法的上面
--------------------------------------------------------------------------------
@Component("myStudent2")
public class Student {
@Value(value = "张三")
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
// @Value(value = "25")
//同样,因为该注解只有一个属性,并且属性的名字是value,那么该属性是可以省略的
@Value("25")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +"name='" + name + '\'' +", age=" + age +'}';
}
}
2.4.3 byType 自动注入 @Autowired
@Autowired: spring框架提供的注解,实现引用类型的赋值。
spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType
@Autowired 默认使用的是byType 按类型自动注入
位置: 1)在属性定义的上面,无需set方法, 推荐使用
2)在set方法的上面
--------------------------------------------------------
Student类:
@Component("myStudent")
public class Student {
@Value("张三")
private String name;
@Value("25")
private int age;
//byType 按类型自动注入
@Autowired
private School school; //引用类型
//set方法、toString方法...
}
School类:
@Component("mySchool")
public class School {
@Value("武汉大学")
private String name;
@Value("湖北武汉")
private String address;
//set方法、toString方法...
}
2.4.4 byName 自动注入 @Autowired 与 @Qualifier
如果要使用byName方式,需要做的是:
1) 在属性上面加入 @Autowired
2) 在属性上面加入 @Qualifier(value="bean的id") :表示使用指定名称的bean完成赋值。
-------------------------------------------------------------------------
//byName 按名称自动注入
@Autowired
@Qualifier(value = "mySchool") //与School类的 @Component注解value值相同
private School school; //引用类型
-------------------------------------------------------------------------
@Autowired 还有一个required属性,默认值为 true,表示当匹配失败后,会终止程序运行。
若将其值设置为 false,则当匹配失败时,会被忽略,未匹配的属性值为 null。
------------------------------------------
//byName自动注入
@Autowired(required = false)
@Qualifier(value = "mySchool")
------------------------------------------
注:引用类型的 @Autowired 的required属性值推荐使用true,有利于我们把程序中的问题尽早解决
2.4.5 JDK 注解 @Resource 自动注入
@Resource: 来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值
使用的也是自动注入原理,支持byName, byType .默认是byName
位置: 1.在属性定义的上面,无需set方法,推荐使用。
2.在set方法的上面
默认是byName: 先使用byName自动注入,如果byName赋值失败,再使用byType
如果想只使用byName方式,需要增加一个name属性 ,name的值是bean的id(名称)
-----------------------------------------------------------------------------------------
// @Resource //先byName,如果失败则byType
@Resource(name = "mySchool") //只使用byName, 与School类的 @Component注解value值相同
private School school; //引用类型
2.5 注解与 XML 的对比
-
注解优点:
⚫ 方便
⚫ 直观
⚫ 高效(代码少,没有配置文件的书写那么复杂)。注解缺点:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。
-
XML 方式优点:
⚫ 配置和代码是分离的
⚫ 在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。xml 方式缺点:编写麻烦,效率低,大型项目过于复杂。
-
【总结】注解 和 xml 比较用哪个?
不易发生变动 配置——————>>优先注解
易变更、第三方、环境相关 配置——————>>优先配置文件
2.6 拓展:使用.properties属性配置文件
使用属性配置文件中的属性值,来进行注解:
第一步:在src\main\resources下面创建属性配置文件:test.properties
myname=zhaoliu
myage=26
第二步:在Spring的配置文件中 加载属性配置文件:
<context:property-placeholder location="test.properties"/>
第三步:在类中使用属性配置文件中的内容:${属性配置文件中的属性名},来获取对应的属性值
public class Student {
// @Value("赵六")
@Value("${myname}") //使用test.properties中myname的属性值:myname=zhaoliu
private String name;
// @Value("26")
@Value("${myage}") //使用test.properties中myage的属性值:myage=26
private int age;
//set方法、toString方法...
}
第3章:AOP面向切面编程
3.1 动态代理
-
回顾动态代理:
jdk动态代理:
使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。
不用自己创建代理类的.java 源文件。
jdk动态代理要求 —————— 目标类必须实现接口cglib动态代理:
第三方的工具库,创建代理对象,原理是继承。
通过继承目标类,而子类是增强过的,这个子类对象就是代理对象。
使用CGLIB生成动态代理要求 —————— 目标类必须能够被继承,即不能是final 的动态代理的作用:
1)在目标类源代码不改变的情况下,增加功能。
2)减少代码的重复
3)专注业务逻辑代码
4)解耦合,让你的业务功能和日志、事务等非业务功能分离。 -
【实例】使用动态代理,创建代理对象,实现功能增强(不使用 AOP 的开发方式 )
动态代理: 可以在程序的执行过程中,创建代理对象。通过代理对象执行方法, 给目标类的方法增加额外的功能(功能增强),而不需要在目标类中进行修改 jdk动态代理实现步骤: 1)创建接口,定义目标类要完成的功能:doSome, doOther等业务方法 2) 创建目标类 实现接口。SomeServiceImpl目标类,实现doSome, doOther等业务方法 3) 创建InvocationHandler接口(调用处理器)的实现类,在invoke方法中完成 代理类的功能: 1)调用目标方法 2)增强功能(比如输出时间、事务) 4)使用jdk中 Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型。 再通过代理对象执行方法 项目:【ch05-dynamic-proxy】
import java.util.Date;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
//目标对象
private Object target; //SomeServiceImpl类
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过代理对象执行方法时,会调用执行这个invoke()
/*
System.out.println("方法的执行时间:"+ new Date());//在目标方法之前,输出时间【增强功能】
//调用目标类的方法。通过Method类实现
Object res = method.invoke(target, args);//SomeServiceImpl 的 doSome()或者doOther()方法
System.out.println("方法执行完毕后,提交事务"); //在目标方法执行之后,提交事务【增强功能】
*/
//如果只想让SomeServiceImpl 的 doSome()方法执行时会增强功能,doOther()方法还是使用原来的呢?
String methodName = method.getName();
Object res = null;
if("doSome".equals(methodName)){
//在目标方法之前,输出时间【增强功能】
System.out.println("方法的执行时间:"+ new Date());
//调用目标类的方法。通过Method类实现
res = method.invoke(target, args);//SomeServiceImpl 的 doSome()方法
//在目标方法执行之后,提交事务【增强功能】
System.out.println("方法执行完毕后,提交事务");
} else {
res = method.invoke(target, args);//SomeServiceImpl 的 doOther()方法
}
return res;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class MyApp {
public static void main(String[] args) {
//调用doSome,doOther方法
/*
SomeService someService = new SomeServiceImpl();
someService.doSome();
System.out.println("------------------------------");
someService.doOther();
*/
//0、 创建目标对象
SomeService target = new SomeServiceImpl();
//1、获取——目标对象的类加载器
ClassLoader loader = target.getClass().getClassLoader();
//2、获取——目标对象实现的接口
Class<?>[] interfaces = target.getClass().getInterfaces();
//3、获取——自己写的调用处理器实现类
InvocationHandler handler = new MyInvocationHandler(target);
//4、使用Proxy类的静态方法newProxyInstance(),创建代理对象。并把返回值转为接口类型。
SomeService proxy = (SomeService) Proxy.newProxyInstance(loader, interfaces, handler);
/*SomeService proxy = (SomeService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target) ); //省略上面3步,一步到位*/
//5、通过代理执行 功能增强后的方法
proxy.doSome();
System.out.println("------------------------------");
proxy.doOther();
}
}
/*
方法的执行时间:Sat Sep 26 17:16:30 CST 2020
业务方法dosome执行了
方法执行完毕后,提交事务
------------------------------
业务方法doOther执行了
*/
3.2 AOP概述
-
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理。
Aop就是动态代理的【规范化】, 把动态代理的实现步骤、方式都定义好了,让开发人员用统一的方式使用动态代理。 -
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。
所谓交叉业务逻辑是指: 通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。
若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。 -
例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,
而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至还多。
它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑 —————— 转账。
3.3 AOP相关术语
-
(1)切面(Aspect )
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。
常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。 -
(2)连接点(JoinPoint )
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。 -
(3)切入点(Pointcut )
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。 -
(4)目标对象(Target )
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。
上例中的 SomeServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。
当然,不被增强,也就无所谓目标不目标了。 -
(5)通知(Advice )
通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。
换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。
通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
3.4 怎么理解面向切面编程 ?
(1)需要在分析项目功能时,找出切面。使用Aspect,切面干什么。
(2)合理的安排切面执行的时间。使用 Advice 表示时间,在目标方法之前,还是目标方法之后。
(3)合理的安排切面执行的位置。使用 Pointcut 表示切面执行的位置,在哪个类、哪个方法增加增强功能。
3.5 面向切面编程对有什么好处 ?
(1)减少重复;(2)专注业务;
注意:面向切面编程只是面向对象编程的一种补充。
使用 AOP 减少重复代码,专注业务实现
3.6 AOP 的实现
-
AOP是一个规范,是动态的一个规范化,一个标准
-
AOP的技术实现框架:
-
1.spring:
spring主要在事务处理时使用AOP。
spring的AOP比较笨重,项目开发中很少使用spring的AOP实现。 -
2.aspectJ:
一个开源、专门做AOP的框架。是Eclipse 的开源项目spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。
aspectJ框架 实现AOP有 2 种方式:
(1)使用xml配置文件,一般用于配置全局事务;
(2)使用注解。一般在项目开发中使用这种方式。 aspectj有5个注解。
-
3.7 AspectJ 对 AOP 的实现
3.7.1 AspectJ 的通知类型
-
通知(Advice),表示切面的执行时间,也叫增强。AspectJ 中常用的通知有五种类型:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知 -
在 AspectJ框架中可以使用xml配置文件中的标签表示,也可以使用注解表示:
(1) @Before
(2) @AfterReturning
(3) @Around
(4) @AfterThrowing
(5) @After
3.7.2 AspectJ 的切入点表达式
-
切入点(Pointcut)表示切面执行的位置。是声明了一个或多个连接点的集合,通过切入点指定一组方法。
-
AspectJ 定义了专门的表达式用于指定切入点,表达式的原型是:
execution(modifiers-pattern ? ret-type-pattern
declaring-type-pattern ? name-pattern(param-pattern)
throws-pattern ? )
解释:
modifiers-pattern 访问权限类型
ret-type-pattern 返回值类型 ————必选项
declaring-type-pattern 包名类名
name-pattern(param-pattern) 方法名(参数类型和参数个数) ————必选项
throws-pattern 抛出异常类型
? 表示可省略的部分
以上表达式主要 4 个部分:execution(访问权限 方法返回值 方法声明(参数) 异常类型)
-
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。
-
在其中可以使用以下符号:
符号 | 说明 |
---|---|
* | 0至多个 任意字符 |
. . | 1)用在方法参数中,表示任意多个参数; 2)用在包名后,表示当前包及其子包路径 |
+ | 1)用在类名后,表示当前类及其子类; 2)用在接口名后,表示当前接口及其实现类 |
- 举例:
execution(public * *(..))
指定切入点为:任意公共方法。
execution(* set*(..))
指定切入点为:任何一个以“set”开始的方法。
execution(* com.xyz.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。
注意:“..”出现在包名类名中时,后面必须跟“*”,表示包、子包下的所有类。
execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
例如:com.service.implA、 cn.abc.service.implB
3.8 AspectJ 基于注解的 AOP 实现
AspectJ 对于 AOP 的实现有注解和xml配置文件2种方式,常用是注解方式。
项目【ch06-aop-aspectj】: 用aspectj框架实现aop。
目的是给已经存在的一些类和方法,增加额外的功能。 前提是不改变原来的类的代码。
3.8.1 基本步骤
(1)新建maven项目(quickstart模板), 加入依赖
junit单元测试依赖、spring依赖、aspectj依赖;
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
(2)创建业务接口和其实现类
希望是给目标类中的方法增加功能,但不改变该类的代码。
--------------------------------------------------------------------------------
//接口
public interface SomeService {
void doSome(String name , Integer age);
}
//目标类
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
//给doSome方法增加一个功能:在方法执行之前, 输出方法的执行时间
System.out.println("====目标方法doSome()执行了====");
}
}
--------------------------------------------------------------------------------
(3)创建切面类
类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。
1)在类的上面加入 @Aspect
2)在类中定义方法, 方法就是切面要执行的功能代码
3)在方法的上面加入aspectj中的通知注解,例如 @Before,表示前置通知
注解中需要指定切入点表达式execution()
---------------------------------------------------------------------------------------------------------
@Aspect
public class MyAspect {
@Before(value = "execution(public void com.yuming.service01.SomeServiceImpl.doSome(String ,Integer))")
public void myBefore(){
System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());
}
}
---------------------------------------------------------------------------------------------------------
(4)创建spring的配置文件:声明目标对象与切面类对象、注册AspectJ的自动代理
1)声明对象,把对象交给容器统一管理。声明对象可以使用注解或者xml配置文件<bean>
2)注册 AspectJ 框架中的【自动代理生成器】:<aop:aspectj-autoproxy/>
自动代理生成器,会自动扫描到 @Aspect注解 定义的切面类,
切面类中的方法上面有通知类型和切入点,根据切入点可找到目标类中的目标方法,
而通知类型则可以确定具体切入的时间,然后生成“目标类+切面”的代理对象。
-----------------------------------------------------------------------------------------------------------------------------------
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="someService" class="com.yuming.service01.SomeServiceImpl"/>
<bean id="myAspect" class="com.yuming.service01.MyAspect"/>
<aop:aspectj-autoproxy/>
</beans>
-----------------------------------------------------------------------------------------------------------------------------------
(5)创建测试类,从spring容器中使用目标对象的id 获取目标对象(实际上是经过aspectj生成的代理对象)
通过代理对象执行方法,实现aop的功能增强。
-------------------------------------------------------------------------------------------
@Test
public void test01() {
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象(实际上是经过aspectj生成的代理对象)
SomeService proxy = (SomeService) ac.getBean("someService");
//System.out.println(proxy.getClass().getName());
//com.sun.proxy.$Proxy8: 目标类有接口,使用的是jdk的动态代理
// 通过代理对象执行方法,实现aop的功能增强。
proxy.doSome("lisi", 25);
}
/*程序输出结果:
前置通知, 切面功能:在目标方法之前输出执行时间:Tue Aug 25 21:06:54 CST 2020
====目标方法doSome()执行了====
*/
-------------------------------------------------------------------------------------------
3.8.2 @Before前置通知-方法有JoinPoint参数
在目标方法执行之前执行。被注解为前置通知的方法,可以指定一个 JoinPoint 类型参数。
注:不光前置通知的方法,所有的通知方法均可包含一个 JoinPoint 类型参数。
(1) @Before: 前置通知注解
属性: value ,是切入点表达式,表示切面的功能执行的位置。
特点:
1.在目标方法之前先执行的
2.不会改变目标方法的执行结果
3.不会影响目标方法的执行。
(2) 指定通知方法中的参数 : JoinPoint
JoinPoint: 表示连接点的方法 ,是要加入切面功能的业务方法
作用: 可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参。
如果你的切面功能中需要用到方法的信息,就加入JoinPoint.
要求:这个JoinPoint参数的值是由框架赋予, 必须是在第一个位置的参数
(3) 前置通知 ———— 方法的定义格式:
1.公共方法 public
2.方法没有返回值
3.方法名称自定义
4.方法可以有参数,也可以没有参数。
如果有参数,参数不是自定义的,有几个参数类型可以使用
举例:
--------------------------------------------------------------------------------------------
@Aspect
public class MyAspect {
@Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
public void myBefore(JoinPoint jp){
//获取方法的完整定义
System.out.println("方法的签名(定义):"+jp.getSignature());
System.out.println("方法的名称:"+jp.getSignature().getName());
System.out.println("方法的参数个数:"+jp.getArgs().length);
//获取方法的实参
Object args [] = jp.getArgs();
for (Object obj: args) {
System.out.println("实际参数:"+obj);
}
//切面要执行的功能代码:
System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());
}
}
--------------------------------------------------------------------------------------------
调用doSome方法: proxy.doSome("lisi", 25),执行的结果:
--------------------------------------------------------------------------------------------
方法的签名(定义):void com.yuming.service01.SomeService.doSome(String,Integer)
方法的名称:doSome
方法的参数个数:2
实际参数:lisi
实际参数:25
前置通知, 切面功能:在目标方法之前输出执行时间:Wed Aug 26 13:17:50 CST 2020
====目标方法doSome()执行了====
--------------------------------------------------------------------------------------------
3.8.3 @AfterReturning 后置通知-注解有 returning 属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。
该注解的 returning 属性就是用于指定接收方法返回值的变量名的。
所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。
该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
(1) @AfterReturning:后置通知注解
属性: 1.value 切入点表达式
2.returning 自定义的变量,表示目标方法的返回值的。
自定义变量名必须和该通知方法的形参名一样!!!
特点:
1. 在目标方法之后执行的。
2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
Object res = doOther();
3. 可以修改这个返回值
(2) 后置通知的执行
Object res = doOther();
myAfterReturning(res){
修改res的内容,是否会影响 最后的方法调用结果?
}
注意:参数传递的2种方式:
1) 值传递, 并不会改变原来方法的返回结果
2) 引用传递(地址传递),会改变原来方法的返回结果
(3) 后置通知 —— 方法的定义格式:
1.公共方法 public
2.方法没有返回值
3.方法名称自定义
4.方法有参数的, 推荐是Object ,参数名自定义
举例:
--------------------------------------------------------------------------------------------
@Aspect
public class MyAspect {
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
returning="res")
public void myAfterReturning(Object res){
System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);
// Object res:是目标方法执行后的返回值,可以根据返回值做切面的不同功能处理
if(res.equals("abcd")){
//做一些功能
} else{
//做其它功能
}
//修改目标方法的返回值, 是否会影响 最后的方法调用结果? ————》并不会
if( res != null){
String st = (String)res;
st = "Hello Aspectj";
}
}
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther2(..))",
returning = "res")
public void myAfterReturing2(Object res){
System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);
//修改目标方法的返回值,属性值等,是否会影响最后的调用结果呢-------》会
if (res != null){
Student student= (Student)res;
student.setName("lisi2222");
student.setAge(2222);
}
}
}
--------------------------------------------------------------------------------------------
调用 doOther 和 doOther 2 方法后:
--------------------------------------------------------------------------------------------
====目标方法doOther()执行了====
后置通知:在目标方法之后执行的,获取的返回值是:abcd
最后的调用结果:abcd
------------------------------------
====目标方法doOther 2()执行了====
后置通知:在目标方法之后执行的,获取的返回值是:Student{name='lisi', age=20}
最后的调用结果:Student{name='LISI2222', age=2222}
--------------------------------------------------------------------------------------------
3.8.4 @Around 环绕通知-增强方法有 ProceedingJoinPoint 参数
在目标方法执行之前之后执行。
被注解为环绕增强的方法要有返回,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。
接口 ProceedingJoinPoint 继承于JoinPoint,因此可以根据它获取方法的信息。其有一个 proceed()方法,用于执行目标方法。
若目标方法有返回值,则该方法的返回值就是目标方法的返回值。
最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
(1) @Around: 环绕通知注解
属性: value 切入点表达式
特点:
1.它是功能最强的通知
2.在目标方法的前和后都能增强功能。
3.可以控制目标方法是否被调用执行
4.可以修改原来的目标方法的执行结果、影响最后的调用结果
(2) 环绕通知 —— 方法的定义格式:
1.public
2.【必须有一个返回值,推荐使用Object】
3.方法名称自定义
4.方法有参数,固定的参数 ProceedingJoinPoint
(3) 环绕通知,等同于jdk动态代理的 InvocationHandler接口实现类 执行的功能
(4) 参数: ProceedingJoinPoint 就等同于 jdk动态代理的 Method
作用:执行目标方法的
(5) 返回值: 目标方法的执行结果,可以被修改。
(6) 环绕通知: 经常做事务,在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务
举例:
-------------------------------------------------------------------------------------------------
接口方法:
String doFirst(String name,int age);
在目标类中实现此方法:
@Override
public String doFirst(String name, Integer age) {
System.out.println("====目标方法doFirst()执行了====");
return "first";
}
切面类:
@Aspect
public class MyAspect {
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//【1.目标方法 之前 添加功能】
System.out.println("***环绕通知:在目标方法之前,输出时间:"+ new Date());
//【2.目标方法调用】
//因为 ProceedingJoinPoint 继承 JoinPoint,所以它也可以获取方法执行时的信息。
String name = "";
Object[] args = pjp.getArgs();
if(args!= null && args.length > 1){
Object arg= args[0]; //获取第一个参数值
name =(String)arg;
}
Object result = null;
if ("lisi".equals(name)){ //【控制目标方法是否被调用】
result = pjp.proceed(); //执行目标方法doFirst()。等同于jdk动态代理的method.invoke();
}
//【3.目标方法 之后 添加功能】
System.out.println("***环绕通知:在目标方法之后,提交事务");
//还可以修改目标方法的执行结果, 影响方法最后的调用结果
if( result != null){
result = "Hello AspectJ AOP";
}
//返回目标方法的执行结果
return result;
}
}
测试调用方法:
@Test
public void test01() {
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService) ac.getBean("someService");
String str = proxy.doFirst("lisi",23); //实际调用的是切面类的myAround()方法
System.out.println("目标方法的最终执行结果:"+str);
}
运行结果:
***环绕通知:在目标方法之前,输出时间:Wed Aug 26 17:43:45 CST 2020
====目标方法doFirst()执行了====
***环绕通知:在目标方法之后,提交事务
目标方法的最终执行结果:Hello AspectJ AOP
-------------------------------------------------------------------------------------------------
3.8.5 @AfterThrowing 异常通知-注解中有 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。
当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
(1) @AfterThrowing: 异常通知注解
属性: 1. value 切入点表达式
2. throwinng 自定义的变量,表示目标方法抛出的异常对象。
变量名必须和通知方法的参数名一样
特点:
1. 在目标方法抛出异常时执行的
2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。
如果有异常,可以发送邮件,短信进行通知
(2) 异常通知 —— 方法的定义格式:
1.public
2.没有返回值
3.方法名称自定义
4.方法可以没有参数,如果有,是一个Exception,如果还有就是JoinPoint参数
(3) 在执行效果上,相当于一个【try...catch语句】。目标方法在try语句块中,而切面方法放在了catch子句中。
try{
SomeServiceImpl.doSecond(..)
}catch(Exception e){
myAfterThrowing(e);
}
举例:
--------------------------------------------------------------------------------------------
接口方法:
void doSecond();
目标类实现方法:
@Override
public void doSecond() {
// 10/0,一定会有异常
System.out.println("===业务方法doSecond()执行===" + (10/0));
}
定义切面:
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",
throwing = "ex")
public void myAfterThrowing(Exception ex) {
//把异常发生的时间、位置、原因记录到数据库,日志文件等等
//可以在异常发生时,把异常信息通过邮件、短信,发送给开发人员
System.out.println("***异常通知:在目标方法发生异常时执行,异常原因:"+ex.getMessage());
}
调用doSecond()方法运行后:
***异常通知:在目标方法发生异常时执行,异常原因:/ by zero
java.lang.ArithmeticException: / by zero
at com.yuming.service04.SomeServiceImpl.doSecond(SomeServiceImpl.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
......
--------------------------------------------------------------------------------------------
3.8.6 @After最终通知
无论目标方法是否抛出异常,该增强均会被执行。
(1) @After: 最终通知注解
属性: value 切入点表达式
特点:
1.总是会执行
2.在目标方法之后执行的
(2) 最终通知 —— 方法的定义格式:
1.public
2.没有返回值
3.方法名称自定义
4.方法没有参数, 如果有就是JoinPoint,
(3) 在执行效果上,相当于将 切面方法 放在了【try...catch...finally...语句】的 finally子句中
try{
SomeServiceImpl.doThird(..)
}catch(Exception e){
}finally{
myAfter()
}
举例:
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfterThrowing( ) {
System.out.println("***最终通知:总是会被执行的代码***");
//一般做资源清除工作的。
}
3.8.7 定义切入点 @Pointcut
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。
AspectJ 提供了 @Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将 @Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。
其代表的就是 @Pointcut 定义的切入点。
这个使用 @Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。方法体内部无需添加代码。
切面类:
@Aspect
public class MyAspect {
// @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
@After(value = "mypc()")
public void myAfterThrowing( ) {
System.out.println("最终通知:在最后总是会被执行的");
}
// @Before(value = "execution(* *..SomeServiceImpl.doThird(..))")
@Before(value = "mypc()")
public void myBefore(){
System.out.println("前置通知,在目标方法之前先执行的");
}
// 定义切入点
@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
private void mypc(){
//无需代码
}
}
3.8.8 使用 cglib 动态代理
使用CGLIB生成动态代理要求是:目标类必须能够被继承,即不能是final 的。
无论有没有实现接口,都可以使用cglib的动态代理。
在Spring配置文件中,通过<aop:aspectj-autoproxy/>的 proxy-target-class属性
可以设置选择通过【JDK动态代理】还是【cglib动态代理】实现AOP。
------------------------------------------------------------------------------------
<!--注册aspectj框架中的【自动代理生成器】:生成“目标类+切面”的代理对象
<aop:aspectj-autoproxy/>表示:
目标类有接口,默认使用jdk动态代理。
目标类没有接口,默认使用cglib动态代理
目标类有接口,也可以使用cglib动态代理,需要设置proxy-target-class="true":
<aop:aspectj-autoproxy proxy-target-class="true" />
-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
------------------------------------------------------------------------------------
第4章:Spring 集成 MyBatis
4.1 实现思路
-
使用Spring的IoC技术,把MyBatis框架中使用的对象交给Spring创建、管理、赋值
-
为什么IoC能把 MyBatis和 Spring集成在一起,像一个框架一样使用?
是因为ioc能创建对象。
可以把MyBatis框架中的对象交给Spring统一创建,开发人员从Spring中获取对象。
开发人员就不用同时面对两个或多个框架了,只用面对一个Spring -
回顾MyBatis使用步骤,和需要的对象:
1)定义实体类,Student ————保存表中的一行数据的
2)定义dao接口,StudentDao ————定义操作数据库的方法
3)定义mapper映射文件, StudentDao.xml
---------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yuming.dao.StudentDao">
<select id="selectStudents" resultType="com.yuming.entity.Student">
select * from student order by id
</select>
</mapper>
---------------------------------------------------------------------
4)定义MyBatis的主配置文件, mybatis.xml
-----------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--1、数据库的连接信息-->
<environments default="myFirst">
<environment id="myFirst">
<transactionManager type="JDBC"/>
<dataSource type="POOLED"> <!--数据源的类型: POOLED 表示使用数据库的连接池-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/springdb"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</dataSource>
</environment>
</environments>
<!--2、 mapper映射文件的位置信息-->
<mappers>
<mapper resource="com/yuming/dao/StudentDao.xml"/>
</mappers>
</configuration>
-----------------------------------------------------------------------
注:我们会使用独立的连接池类,替换上面MyBatis默认自己带的, 把连接池类也交给Spring创建。
所以后面不需要<environments/>标签了
5)创建并获取dao接口的实现类对象(代理对象),然后进行相关操作
-----------------------------------------------------------------------
// 读取主配置文件
InputStream in = Resources.getResourceAsStream("mybatis.xml");
// 创建 SqlSessionFactory 对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 创建 SqlSession对象
SqlSession sqlSession = factory.openSession();
//创建并获取dao接口的实现类对象(代理对象)
StudentDao dao = sqlSession.getMapper(StudentDao.class);
// 查询数据库中所有student信息
List<Student> studentList = dao.selectStudents();
studentList.forEach( student -> System.out.println(student));
-
通过以上的说明,我们需要让Spring创建以下对象:
(1)独立的连接池类的对象。替换上面MyBatis默认自己带的,使用阿里的druid连接池,更加高效;
(2)SqlSessionFactory对象
(3)dao接口的实现类对象(代理对象)需要学习就是上面三个对象的创建语法,这里使用Spring的xml配置文件的bean标签(因为目前知识量的原因,没有使用注解的方式)。
4.2 实现步骤
项目【ch07-spring-mybatis】
1、创建mysql数据库和表student
数据库名 springdb ; 表名 student
2、新建maven项目,pom文件加入maven的依赖
1)spring依赖
2)spring的事务依赖
3)mybatis依赖
4)mysql驱动
5)阿里公司的数据库连接池
6)mybatis和spring集成的依赖:
是mybatis官方提供的,用来在spring项目中创建mybatis的SqlSesissonFactory对象、dao对象的
另外,记住要在pom文件中设置扫描xml文件,结果输出到target/classes中
3、创建实体类
4、创建dao接口
5、创建mapper映射文件
6、创建mybatis主配置文件
7、创建Service接口和实现类,实现类的属性是dao接口。
在实际项目中,我们在对数据库进行相关操作前需要一些其他的业务代码,
例如逻辑判断、身份认证等,这些放在Service中。
8、【重点】创建 Spring的配置文件, 声明mybatis的对象交给Spring创建:
1)声明数据源DataSource对象
2)声明SqlSessionFactoryBean, 用来创建SqlSessionFactory对象
3) 声明MyBatis的扫描器MapperScannerConfigurer,用来创建Dao接口的实现类对象
4)声明自定义的service对象。把3)中的Dao对象注入给service的属性
9、创建测试类,从spring容器中获取Service对象,调用Service的方法,完成数据库的操作
- 【源码】
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yuming</groupId>
<artifactId>ch07-spring-mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source> <!--记住修改jdk版本为1.8-->
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--单元测试的依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--spring的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring的事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!--阿里公司的数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!--mybatis和spring集成的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
<build>
<!--目的是把src/main/java目录中的xml文件和properties文件包含到输出结果中。输出到target/classes目录中-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
实体类
public class Student {
//定义属性, 推荐是属性名和数据库表的列名一样。
private Integer id;
private String name;
private String email;
private Integer age;
//set和get方法,无参和有参构造方法,toString方法
}
dao接口:
public interface StudentDao {
//添加
int insertStudent(Student student);
//查询
List<Student> selectStudents();
}
mapper映射文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yuming.dao.StudentDao">
<insert id="insertStudent">
insert into student values(#{id},#{name},#{email},#{age})
</insert>
<select id="selectStudents" resultType="com.yuming.entity.Student">
select * from student order by id desc
</select>
</mapper>
mybatis主配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--加入日志配置,可以在控制台输出执行的 sql 语句和参数-->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<!--设置类型别名-->
<typeAliases>
<!--name是包名, 这个包中的所有类,类名就是别名。就可以使用Student表示com.yuming.entity.Student了-->
<package name="com.yuming.entity"/>
</typeAliases>
<!--由于使用阿里的druid数据库连接池,所以不需要<environments/>标签-->
<!-- sql mapper(sql映射文件)的位置-->
<mappers>
<!--name:是包名, 这个包中的所有xml文件都能一次加载给mybatis-->
<package name="com.yuming.dao"/>
</mappers>
</configuration>
创建Service接口和实现类,实现类的属性是dao接口。
在实际项目中,我们在对数据库进行相关操作前需要一些其他的业务代码,
例如逻辑判断、身份认证等,这些放在Service中。
public interface StudentService {
//添加
int addStudent(Student student);
//查询
List<Student> queryStudents();
}
public class StudentServiceImpl implements StudentService {
//引用类型
private StudentDao studentDao ;
//为了set注入来赋值
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public int addStudent(Student student) {
int nums = studentDao.insertStudent(student);
return nums;
}
@Override
public List<Student> queryStudents() {
List<Student> students = studentDao.selectStudents();
return students;
}
}
Spring的配置文件:
【重点】创建 Spring的配置文件, 声明mybatis的对象交给Spring创建:
1)声明数据源DataSource对象
2)声明SqlSessionFactoryBean, 用来创建SqlSessionFactory对象
3)声明MyBatis的扫描器MapperScannerConfigurer,用来创建Dao接口的实现类对象
4)声明自定义的service对象。把3)中的Dao对象注入给service的属性
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 把数据库的配置信息,写在一个独立的文件,便于修改数据库的配置内容
让spring知道jdbc.properties文件的位置-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置阿里的Druid数据库连接池-->
<!--声明数据源DataSource对象, 作用是连接数据库,创建数据库连接对象的
这里的内容代替了之前使用mybatis时,主配置文件mybatis.xml的 <environments/>标签 -->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!--set注入.用来给DruidDataSource类的对象的属性赋值,提供连接数据库信息 -->
<!--注:Druid会自动根据url识别驱动类名,所以说无需配置driver驱动-->
<!-- 使用属性配置文件中的数据,语法 ${key}来获取对应的属性值 -->
<property name="url" value="${jdbc.url}"/> <!--setUrl()方法-->
<property name="username" value="${jdbc.username}"/> <!--setUsername()方法-->
<property name="password" value="${jdbc.passwd}"/> <!--setPassword()方法-->
<property name="maxActive" value="${jdbc.max}"/> <!--setMaxActive()方法。连接池支持的最大连接数,取值为20同时最多有20个数据库连接-->
</bean>
<!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部是创建SqlSessionFactory的。-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--引用类型的set注入.把数据库连接池赋给了dataSource属性-->
<property name="dataSource" ref="myDataSource"/>
<!--mybatis主配置文件的位置
configLocation属性是Resource类型,用来读取配置文件
它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置 -->
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!-- 声明MyBatis的扫描器,用来创建Dao接口的实现类对象(动态代理对象)-->
<!--创建dao对象,需要使用SqlSession的getMapper(StudentDao.class)方法
声明MyBatis的扫描器MapperScannerConfigurer: 在内部调用getMapper()生成每个dao接口的代理对象。-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定SqlSessionFactory对象,能获取SqlSession-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<!--指定基本扫描包,即Dao接口的包名,框架会把这个包中的所有接口一次创建出对应的dao代理对象
创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是:接口名首字母小写 -->
<property name="basePackage" value="com.yuming.dao"/>
</bean>
<!--声明自定义的service-->
<bean id="studentService" class="com.yuming.service.StudentServiceImpl">
<!--引用类型的set注入。把上面创建的dao对象赋值给service对象的属性-->
<property name="studentDao" ref="studentDao" />
</bean>
</beans>
属性配置文件:
jdbc.url=jdbc:mysql://localhost:3306/zt
jdbc.username=root
jdbc.passwd=123456
jdbc.max=30
创建测试类,从spring容器中获取Service对象,调用Service的方法,完成数据库的操作
public class MyTest {
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
String[] names = ac.getBeanDefinitionNames();
for (String name:names) {
System.out.println("容器中对象的名称:"+name+"|"+ac.getBean(name));
}
}
@Test
public void testDaoInsert(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//获取spring容器中的dao对象
StudentDao dao = (StudentDao) ctx.getBean("studentDao");
Student student = new Student(1028,"王五","ww@qq.com",25);
int nums = dao.insertStudent(student);
//spring和mybatis整合在一起使用,事务是自动提交的。 无需执行SqlSession.commit();
System.out.println("nums="+nums);
}
@Test
public void testServiceInsert(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//获取spring容器中的Service对象
StudentService service = (StudentService) ctx.getBean("studentService");
Student student = new Student(1228,"赵六","zl@qq.com",26);
int nums = service.addStudent(student);
System.out.println("nums="+nums);
}
@Test
public void testServiceSelect(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//获取spring容器中的Service对象
StudentService service = (StudentService) ctx.getBean("studentService");
List<Student> studentList = service.queryStudents();
studentList.forEach(student -> System.out.println(student));
}
}
第5章:Spring事务
5.1 Spring 的事务管理
-
在学习mysql数据库的时候,提出了事务。
事务是指一组sql语句的集合,集合中有多条sql语句,可能是insert , update ,select ,delete
我们希望这多个sql语句都能成功,或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。 -
【典型例子】:转账业务,从A账户向B账户转账,需要执行两条update更新语句,
这两条语句必须同时成功,或者同时失败,不允许出现一条成功,一条失败。 -
事务原本是数据库中的概念,在 Dao 层。
但一般情况下,需要将事务提升到业务层,即 Service 层。
这样做是为了能够使用事务的特性来管理具体的业务。
另外,业务方法一般会调用多个dao方法,执行多个sql语句,就需要用到事务。 -
不同数据库的访问技术,有不同的事务处理的机制、对象、方法。(jdbc、mybatis、hibernate…)
Spring提供了一种处理事务的统一模型,能使用统一步骤、方式完成多种不同数据库访问技术的事务处理。 -
在 Spring 中通常可以通过以下两种方式来实现对事务的管理:
(1)使用 Spring 的事务注解 管理事务
(2)使用 AspectJ 的 AOP 配置管理事务
5.2 事务管理器接口
-
事务管理器是 PlatformTransactionManager 接口对象
其主要用于完成事务的提交、回滚,及获取事务的状态信息。PlatformTransactionManager 接口有两个常用的实现类:
➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
5.3 事务定义接口
-
事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:
事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。 -
(1)定义了五个【事务隔离级别】常量
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
➢ DEFAULT: 采用 DB 默认的事务隔离级别。
MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。
➢ READ_UNCOMMITTED: 读未提交。未解决任何并发问题。
➢ READ_COMMITTED: 读已提交。解决脏读;存在不可重复读与幻读。
➢ REPEATABLE_READ: 可重复读。解决脏读、不可重复读;存在幻读。
➢ SERIALIZABLE: 串行化。不存在并发问题。 -
(2)定义了七个【事务传播行为】常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。
如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。
事务传播行为是加在方法上的。 事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS 前三个是需要掌握的
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED1) PROPAGATION_REQUIRED:
指定的方法必须在事务内执行。
若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。
这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。如该传播行为加在 doOther()方法上:
若 doSome()方法在调用 doOther()方法时 就是在事务内运行的,则 doOther()方法的执行也加入到该事务内执行。
若 doSome()方法在调用 doOther()方法时 没有在事务内执行的,则 doOther()方法会创建一个事务,并在其中执行。
2) PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
3)PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
- (3)定义了默认【事务超时时限】
超时时限:表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
单位是秒,整数值,默认是 -1.
常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。
注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。
所以,该值一般就使用默认值即可。
5.4 Spring 的默认回滚方式
(1)当业务方法执行成功,没有异常抛出,Spring在方法执行完毕后 提交事务。【事务管理器commit】
(2)当业务方法抛出 运行时异常 或ERROR, Spring执行回滚事务。【事务管理器rollback】
运行时异常,是 RuntimeException 类及其子类,是在运行时才出现的异常。
例如NullPointException , NumberFormatException
(3)当业务方法抛出 非运行时异常,主要是受查异常时,Spring默认提交事务。【事务管理器commit】
不过,对于受查异常,程序员也可以手工设置其回滚方式。
受查异常,也叫编译时异常,在写代码中,必须处理的异常,若不处理,则无法通过编译。
例如IOException, SQLException
5.5 总结 Spring的事务
(1)管理事务的是:事务管理器和它的实现类
(2)Spring的事务是一个统一模型
1)指定要使用的事务管理器实现类,使用< bean>
2)指定哪些类,哪些方法需要加入事务的功能
3)指定方法需要的隔离级别、传播行为、超时
5.6 程序实例:电商购买商品项目
-
项目 【ch08-spring-transaction】
本例要实现购买商品,模拟用户下订单,向订单表添加销售记录,同时从商品表减少库存。 -
实现步骤:
(和上面那个spring集成mybatis项目大致相同)
1、创建mysql数据库和表
数据库名 springdb ; 表名 商品表goods、销售表sale2、新建maven项目,pom文件加入maven的依赖
1)spring依赖
2)spring的事务依赖
3)mybatis依赖
4)mysql驱动
5)阿里公司的数据库连接池
6)mybatis和spring集成的依赖
另外,记住要在pom文件中设置扫描xml文件,结果输出到target/classes中3、创建实体类:Sale, Goods
4、创建dao接口和mapper映射文件
SaleDao接口 ,GoodsDao接口
SaleDao.xml , GoodsDao.xml5、创建mybatis主配置文件
6、创建一个运行时异常: 购买商品时,如果商品库存不够,抛出此异常
Java中怎么自定义异常呢?
第一步:编写一个类继承Exception或者RuntimeException.
第二步:提供两个构造方法,一个无参数的,一个带有String参数的。
7、创建Service接口和实现类,实现类的属性是saleDao, goodsDao。
public class BuyGoodsServiceImpl implements BuyGoodsService {
private SaleDao saleDao;
private GoodsDao goodsDao;
//set方法,为了spring配置文件中set注入
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("=====buy方法的开始====");
// 【1、记录销售信息,向sale表添加记录】
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
saleDao.insertSale(sale);//添加销售信息------------------------
//【2、更新库存,更新商品表中库存数量】
//注:数据的验证放到这里,是为了演示,当上面销售表添加数据了,这里抛异常的话,下面的商品表库存就不会修改了。即 数据不一致了
Goods goods = goodsDao.selectGoods(goodsId); //根据商品编号查询商品信息------------------------
if( goods == null){
//商品不存在
throw new NullPointerException("编号是:"+goodsId+",商品不存在");
} else if( goods.getAmount() < nums){
//商品库存不足
throw new NotEnoughException("编号是:"+goodsId+",商品库存不足"); //这个是自定义的异常
}
Goods buyGoods = new Goods();
buyGoods.setId( goodsId);
buyGoods.setAmount(nums);
goodsDao.updateGoods(buyGoods);//修改库存----------------------------
System.out.println("=====buy方法的结束====商品购买成功====");
}
}
8、创建 Spring的配置文件, 声明mybatis的对象交给Spring创建:
1)声明数据源DataSource对象
2)声明SqlSessionFactoryBean, 用来创建SqlSessionFactory对象
3) 声明MyBatis的扫描器MapperScannerConfigurer,用来创建Dao接口的实现类对象
4)声明自定义的service对象。把3)中的Dao对象注入给service的属性
9、创建测试类,从spring容器中获取Service对象,调用Service的方法,完成数据库的操作
public class MyTest {
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(config);
//从容器获取service对象
BuyGoodsService service = (BuyGoodsService) ac.getBean("buyService");
//调用方法,购买商品
//service.buy(1001,10); //可以正常购买商品的
//service.buy(1005,10); //异常,1005商品不存在---销售信息表数据增加了,但是商品表库存没有减少
service.buy(1001,100000);//异常,1001的商品库存不足100000---销售信息表数据增加了,但是商品表库存没有减少
}
}
- 通过测试可以发现,上面的程序是有问题的:
出现了数据库表中数据不一致的情况,即顾客购买商品没有成功,但销售记录却增加了
解决方法:通过AOP技术,给service实现类的 buy()方法 添加事务功能。
5.7 使用 Spring 的事务注解 管理事务
-
适合中小项目使用的 —————— 注解方案
-
Spring框架自己用 AOP 实现给业务方法增加事务的功能,使用 @Transactional注解增加事务。
@Transactional注解 是Spring框架自己的注解,放在public方法的上面,表示当前方法具有事务。
可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等@Transactional 的所有可选属性如下所示:
➢ propagation: 用于设置事务的传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。 ➢ isolation: 用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。 ➢ timeout: 用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。在实际业务开发中一般不设置!!! ➢ readOnly: 用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。 ➢ rollbackFor: 指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。 ➢ rollbackForClassName: 指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。 ➢ noRollbackFor: 指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。 ➢ noRollbackForClassName: 指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
需要注意的是, @Transactional 若用在方法上,只能用于 public 方法上。
对于其他非 public 方法,如果加上了注解 @Transactional,
虽然 Spring 不会报错,但不会将指定事务织入到该方法中。
因为 Spring 会忽略掉所有非 public 方法上的 @Transaction 注解。
若 @Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。 -
使用 @Transactional的步骤:
复制上面“电商购买商品”项目【ch08-spring-transaction】修改为【ch09-spring-transaction-anno】,添加事务功能
(1)pom文件中加入spring-tx依赖
<!--spring的事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
(2)Spring配置文件中:声明事务管理器对象
<!--1、声明事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定连接的数据库,ref为自己配置的数据源对象的id值-->
<property name="dataSource" ref="myDataSource"/>
</bean>
(3)Spring配置文件中:开启事务注解驱动
<!--2、开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象
transaction-manager:上面事务管理器对象的id
注意,下面输入annotation-driven后,有4个同名的,来源要选择以tx结尾的,tx是表示事务的
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
声明事务注解驱动,就是告诉Spring框架,要使用注解的方式管理事务。
Spring使用AOP机制,创建 @Transactional所在的类代理对象,给方法加入事务的功能。
Spring给业务方法加入事务:
在业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务。使用的是AOP的环绕通知
相当于:
@Around("需要增加事务功能的业务方法名称")
Object myAround(){
Spring开启事务
try{
buy(1001,10);
Spring的事务管理器.commit();
}catch(Exception e){
Spring的事务管理器.rollback();
}
}
(4)service业务层 public 方法上加入事务注解 @Transactional 及相关属性
/* @Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor = {
NullPointerException.class, NotEnoughException.class
}
)*/
//使用的是事务控制的默认值:
@Transactional //等同于上面那种写法,因为上面那种写法后面的属性值,本身就是事务控制的默认值
@Override
public void buy(Integer goodsId, Integer nums) {
//.....多条sql语句的执行
}
--------------------------------------------------------------------------------
* rollbackFor:表示发生指定的异常一定回滚.
* 处理逻辑是:
* 1) spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中
* 如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。
* 2) 如果你的抛出的异常不在rollbackFor列表中,spring会判断异常是不是 运行时异常
* 如果是就一定回滚。【Spring 的默认回滚方式】
=================================================================================
通过测试可以发现,添加了事务功能之后,解决了之前数据库表中数据不一致的情况(顾客购买商品没有成功,但销售记录却增加了)
现在要么购买成功:订单表添加销售记录,商品表减少库存。
要么购买失败,抛出异常:订单表、商品表数据一致,没有发生变化。
另外通过测试发现:在测试了几次失败的商品购买之后,虽然数据库表表面上看起来没有发生变化,但是再成功购买一次商品之后,
会看到销售记录表的 设置为自动递增的主键id值,和上一条记录的id值相差几位,并不是连续的,这就说明:
销售记录表确实是添加了一些记录,只不过因为添加了事务功能,在方法出现异常之后就被【回滚】了,
这就造成了这几个id值被用过了,再次添加数据之后,就会往后递增id值
5.8 使用 AspectJ 的 AOP配置 管理事务
-
适合大型项目,有很多的类、方法,需要大量的配置事务,使用AspectJ框架功能,
在Spring的 XML配置文件中声明类、方法需要的事务。
这种方式使业务方法和事务配置完全分离。在不更改源代码的条件下管理事务。 -
实现步骤
复制上面“电商购买商品”项目【ch08-spring-transaction】修改为【ch10-spring-transaction-aspectj】,添加事务功能
1)pom文件中加入spring-aspects依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2)Spring的配置文件中:声明事务管理器对象
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定连接的数据库,ref为自己配置的数据源对象的id值-->
<property name="dataSource" ref="myDataSource"/>
</bean>
3)Spring的配置文件中: 配置事务通知(切面)————声明方法的事务属性【隔离级别、传播行为、超时、回滚等】
为事务通知配置相关属性。用于指定要将事务以什么方式织入给哪些方法。
例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务。
<!--2.配置事务通知(切面)——声明业务方法的事务属性(隔离级别,传播行为,超时时间,回滚等)
id:自定义名称,表示这一个事务通知整体; transaction-manager:事务管理器对象的id -->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
name: 方法名称, 1)方法名称,不带有包和类;
2)方法可以使用通配符, * 表示任意字符
propagation:传播行为,枚举值
isolation:隔离级别
rollback-for:你指定的异常类名,全限定类名。 发生异常一定回滚 -->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException , com.yuming.excep.NotEnoughException"/>
<!--使用通配符,指定很多的方法-->
<!--指定添加方法-->
<tx:method name="add*" propagation="REQUIRES_NEW" />
<!--指定修改方法-->
<tx:method name="modify*" />
<!--删除方法-->
<tx:method name="remove*" />
<!--查询方法,有query,search,find多种-->
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
4) Spring的配置文件中: 配置AOP(配置切入点表达式、配置增强器)
配置切入点表达式:指定在第三步中事务管理的方法 所在的类和包
配置增强器:关联adivce和pointcut
<!--配置aop-->
<aop:config>
<!--1、配置切入点表达式:指定哪些包中类,要使用事务
id:切入点表达式的名称,唯一值
expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象 -->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!--2、配置增强器:关联adivce和pointcut
advice-ref:通知,上面tx:advice那里的配置 ; pointcut-ref:切入点表达式的id -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt" />
</aop:config>
第6章:Spring与Web
在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指 Servlet)中获取到 Spring容器的问题。
只要在 web 层获取到了 Spring 容器,便可从容器中获取到 Service 对象。
6.1 Web 项目使用 Spring 存在的问题
项目【ch11-spring-web】
在web项目中使用spring,完成学生注册功能
实现步骤:
1.创建maven版web项目(maven-archetype-webapp模板)
2.加入依赖
1)拷贝【ch7-spring-mybatis】Spring集成MyBatis项目中的依赖;
2)添加jsp、servlet依赖
3.拷贝ch7-spring-mybatis的代码和配置文件
4.创建一个jsp,学生注册页面,发起请求,有参数id、 name、email、age.
5.创建Servlet,接收请求参数, 调用 Service方法 ,完成注册
6.创建一个jsp,作为显示结果页面
7.发布网站(部署项目)
注:因为使用模板生成的web.xml的版本是2.3的,较低,目前很多功能不能实现,比如EL表达式
所以需要我们把版本提升一下:
1)方法见下图
2)或者也可以直接拷贝之前的项目是4.0版本的web.xml拷贝一下。
提示web.xml版本:
创建Servlet,接收请求参数, 调用 Service方法 ,完成注册
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//接收请求参数
String strId = request.getParameter("id");
String strName = request.getParameter("name");
String strEmail = request.getParameter("email");
String strAge = request.getParameter("age");
//=================================================================================================
//创建spring的容器对象
String config= "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
System.out.println("容器对象的信息:"+ctx);
// 容器对象的信息:org.springframework.context.support.ClassPathXmlApplicationContext@34af5268
// 容器对象的信息:org.springframework.context.support.ClassPathXmlApplicationContext@3ce1b683
//......
//=================================================================================================
//获取service
StudentService service = (StudentService) ctx.getBean("studentService");
Student student = new Student();
student.setId(Integer.valueOf(strId));
student.setName(strName);
student.setEmail(strEmail);
student.setAge(Integer.parseInt(strAge));
service.addStudent(student); //注册学生
//返回一个结果页面
request.getRequestDispatcher("/result.jsp").forward(request,response); //学生注册成功
}
}
-
结果分析:
通过测试发现,启动tomcat之后,每注册一次学生信息(发送一次请求),都会重新创建一个Spring容器对象,
而每创建一次Spring容器对象,就会把spring配置文件中所有对象都创建一次!!!
如果同时有很多人发起请求,那么就会创建非常多重复的对象,浪费内存,且响应时间长,显然是有问题的。
对于一个应用来说,只需要一个 Spring 容器即可。
所以,将 Spring 容器的创建语句放在 Servlet 的 doGet()或 doPost()方法中是有问题的。 -
解决方法:
web项目中Spring容器对象只需要创建一次, 把容器对象放入到 ServletContext【全局作用域】对象中。
6.2 使用 Spring 的监听器 ContextLoaderListener
-
对于 Web 应用来说,ServletContext 全局作用域对象是唯一的,
一个 Web 应用,只有一个ServletContext 对象,该对象是在 Web 应用装载时初始化的。
若将 Spring 容器的创建时机,放在 ServletContext 初始化时,就可以保证Spring容器的创建只会执行一次,
也就保证了Spring 容器在整个应用中的唯一性。 -
当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被访问的。
即,Spring 容器应具有全局性。 放入 ServletContext 对象的属性,就具有应用的全局性。
所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就保证了 Spring 容器的全局性。 -
若要在 ServletContext 初始化时创建 Spring 容器 ,就需要使用监听器对 ServletContext 进行监听。
回顾之前学习Servlet中的【监听器】的作用:
监控【作用域对象-生命周期变化时刻】以及【作用域对象-共享数据变化时刻】
ServletContext: 全局作用域对象
HttpSession : 会话作用域对象
HttpServletRequest: 请求作用域对象
- 所以我们可以使用监听器,当全局作用域对象被创建时,创建Spring容器,并存入ServletContext中
1)创建容器对象。执行 ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);
2)把容器对象放入到ServletContext。 ServletContext.setAttribute(key,ctx)
- 监听器可以自己创建,也可以使用框架中提供好的ContextLoaderListener,这里使用ContextLoaderListener
实现步骤:
1、添加Maven依赖
<!--spring-web依赖:有监听器ContextLoaderListener-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2、在web.xml中 注册监听器 ContextLoaderListener
监听器被创建对象后,会寻找/WEB-INF/applicatoinContext.xml,为什么找这个文件?
在监听器的初始方法中,会创建spring的容器对象, 在创建容器对象时,需要读取配置文件,
监听器默认是找/WEB-INF/applicatoinContext.xml。
但,我们一般会将spring配置文件放置于项目的 classpath 下,即 src 下,
所以需要在 web.xml 中对 Spring 配置文件的位置及名称进行指定。
=======================================================================================
<!--自定义spring配置文件的位置和名称-->
<context-param>
<!-- contextConfigLocation:表示是配置文件的路径的 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--注册监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
=======================================================================================
通过观察ContextLoaderListener源码,在其初始方法内部是这样的:
//创建Spring容器对象
this.context = this.createWebApplicationContext(servletContext);
//将容器对象放到ServletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
即 key= WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
value= this.context
3、在Sevlet中获取 Spring 容器对象
在 Servlet 中获取容器对象的常用方式有两种:
(1)直接获取容器对象
从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 的中存放的
key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
所以,可以直接通过 ServletContext 的 getAttribute()方法,按照指定的 key 将容器对象获取到。
=======================================================================================
WebApplicationContext ctx = null;
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
Object attribute = getServletContext().getAttribute(key); //通过key获取value
if( attribute != null){
ctx = (WebApplicationContext)attribute;
}
System.out.println("容器对象的信息:"+ctx);
=======================================================================================
其中webApplicationContext是ApplicationContext的子类,是在web项目中使用的Spring容器对象。
(2)通过 WebApplicationContextUtils 工具类
为了不使用框架给出的难记的key值获取webApplicationContext,这个框架还提供了一个工具类。
使用工具类获取webApplicationContext:
=======================================================================================
ServletContext sc = getServletContext();
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
System.out.println("容器对象的信息:"+ctx);
=======================================================================================
以上两种方式,无论使用哪种获取容器对象,注册多次学生(发送多次请求),获取到的Spring 容器均为同一个对象!!!
【思维导图】
框架怎么学: 框架是一个软件,其它人写好的软件。
1)知道框架能做什么, MyBatis–访问数据库, 对表中的数据执行增删改查。
2)框架的语法, 框架要完成一个功能,需要一定的步骤支持的,
3)框架的内部实现, 框架内部怎么做。 原理是什么。
4)通过学习,可以实现一个框架。
这4个阶段是通过不断学习一步一步达到的
传送门
上一章:框架 - MyBatis框架
下一章:框架 - SpringMVC框架