Spring
1、Spring 概述
1.1、什么是Spring
spring 就是一个java框架,使用java语言开发的、轻量级的、开源的框架,可以在 j2se、j2ee项目中都可以使用
Spring核心技术:IOC、AOP
Spring又叫做:容器。spring作为容器,装载的是java对象,可以让spring创建java对象,给属性赋值
Spring作用:实现解耦合,解决java对象之间的耦合,解决模块之间的耦合
Spring文档地址:https://spring.io
Tomcat 也是容器,管理的是 servlet、listener、filter 等对象(创建HelloServlet类,写web.xml)
Spring管理的是上面以外的对象,写配置文件
Spring 的工作方式:
1.2、Spring优点
Spring 是一个框架、半成品软件,是一个容器管理对象,容器装的是对象,Spring 是存储对象的容器
优点:
- 轻量
- 针对接口编程,解耦合
- AOP 编程的支持
- 方便集成各种优秀框架
2、 IOC 控制反转
2.1、概念
IOC ,Inversion Of Control:控制反转,是一个理论,一个指导思想,指导开发人员如何使用对象、管理对象的,把对象的创建、属性赋值、对象的生命周期都交给代码之外的容器管理
2.1.1、IOC 分为控制和反转
IOC 分为控制和反转
控制:对象创建、属性赋值、对象生命周期管理
反转:把开发人员管理对象的权限转移给代码之外的容器实现,由容器完成对象的管理
正转:开发人员在代码中,使用 new 构造方法创建对象。开发人员掌握对象的创建、属性赋值、对象从开始到销毁的全部过程,开发人员对对象全部控制
通过容器,可以使用容器中的对象(容器已经创建对象、对象属性已赋值、对象也组装好了)
Spring 就是一个日期,可以管理对象、创建对象、给属性赋值
2.1.2、IOC 的技术实现
IOC 的技术实现
DI (依赖注入):Dependency Injection,是 IOC 的一种技术实现。程序只需要提供要使用的对象的名称就可以了,对象如何创建、如何从容器中查找与获取 都由容器内部自己实现
依赖: 比如 Class A 使用了 Class B 的属性或方法,叫做 Class A 依赖 Class B
public class B{
public void createOrder(){}
}
public class A{
private ClassB b = new ClassB();
public void buy(){
b.createOrder();
}
}
执行Class A的buy()
ClassA a = new ClassA();
a.buy();
2.1.3、Spring 框架是使用 DI 实现 IOC
通过 Spring 框架,只需要提供要使用的对象名称就可以了,从容器中获取名称对应的对象
Spring 底层使用的是反射机制,通过反射创建对象,并属性赋值
2.2、创建容器对象
设置字符集 UTF-8
使用 Spring:Spring 作为容器管理对象,开发人员从 Spring 中获取要使用的对象
实现步骤:
-
新建 maven 项目
-
加入依赖,修改 pom.xml
spring-context :Spring依赖
junit :单元测试
-
定义类:接口、实现类
类也可以没有接口
接口、实现类:和没有Spring一样定义
-
创建 Spring 的配置文件,用于声明对象
把对象交给Spring创建和管理
使用
<bean>
标签表示对象声明,一个 bean 表示一个 java 对象 -
使用容器中的对象
创建一个表示 Spring 容器的对象 ApplicationContext
从容器中,根据名称获取对象,使用
getBean("对象名称")
2.2.1、pom.xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
</dependencies>
2.2.2、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: 自定义对象名称,唯一值(可以没有,spring可以提供默认的名称)
class: 类的全限定名称,spring 通过反射机制创建对象,不能是接口
spring根据 id、class 创建对象,把对象放入spring的一个map对象中
map.put(id,对象)
-->
<bean id="someService" class="com.service.impl.SomeServiceImpl"/>
</beans>
Spring标准的配置文件:
- 根标签是 beans
- beans 后面是约束文件说明
- beans 里面是 bean 声明
- 什么是bean: bean就是java对象,Spring容器管理的java对象,叫做bean
2.2.3、创建spring容器对象
容器对象特点:
-
容器对象 ApplicationContext :接口
通过 ApplicationContext 对象,获取要使用的 java 对象,执行 getBean(“id”)
-
Spring 默认是调用类的无参构造器创建对象
-
Spring 读取配置文件,一次创建好所有的 java 对象,都放到 map 中
Object object = this.factoryBeanObjectCache.get(beanName);
// 1、指定spring配置文件:从类路径(classpath)之下开始的路径
String config="beans.xml";
// 2、创建容器对象,ApplicationContext 表示spring容器对象,通过applicationContext获取某个java对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
// 3、从容器中获取指定名称的悐,使用 getBean("id")
SomeServiceImpl service = (SomeServiceImpl) applicationContext.getBean("someService");
// 4、调用实现类对象的方法,接口中的方法
service.doSome();
-
spring创建对象,调用类的哪个方法?
Spring默认使用无参构造方法创建对象
-
spring在什么时候创建的对象?
创建 Spring 容器对象的时候,会读取配置文件,创建文件中声明的java对象
所以在
new ClassPathXmlApplicationContext(config)
的时候调用无参构造器速度快,但是耗费占用内存
-
spring一次创建几个对象?
全部创建。
在创建容器对象(ApplicationContext)时,会把配置文件中所有对象都创建处理(Spring默认规则)
获取容器中对象信息:
获取容器中定义对象的数量: getBeanDefinitionCount()
获取容器中定义对象的名称(bean标签的id属性值):getBeanDefinitionNames()
配置文件:
<bean id="someService" class="com.service.impl.SomeServiceImpl"/>
<bean id="someService1" class="com.service.impl.SomeServiceImpl"/>
@Test
public void test1(){
ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");
//获取容器中定义对象的数量
int nums = appc.getBeanDefinitionCount();
System.out.println("容器中定义对象的数量==" + nums);
//获取容器中定义对象的名称(bean标签的id属性值)
String[] names = appc.getBeanDefinitionNames();
for (String name : names) {
System.out.println("容器中定义对象的名称==" + name);
}
}
spring创建非自定义类的对象
配置文件:
<!-- 创建非自定义类的对象 -->
<bean id="mydate" class="java.util.Date"/>
创建对象:
@Test
public void test2(){
ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");
// 让Spring创建非自定义类的对象,就是有Class就能让Spring创建对象
Date date = (Date) appc.getBean("mydate");
System.out.println(date);
}
2.3、DI:给属性赋值
Spring 调用类的无参构造器创建对象,对象创建后给属性赋值
属性的赋值有两个方式:1、xml 配置文件中的标签和属性;2、使用注解
DI 分类:1、set 注入,也叫做设值注入;2、构造注入
2.3.1、基于 xml 的DI
在 xml 配置文件中使用标签和属性,完成对象创建和属性赋值
2.3.1.1、set 注入
set 注入,也叫做设值注入
概念 :Spring调用类中的set方法,在set方法中可以完成属性赋值。(推荐使用)
使用<property name="构造器形参名" value="简单类型形参的值" ref="引用类型形参的值"/>
标签
-
简单类型:java中的基本数据类型和String
简单类型的设值注入,使用value <bean id="mystudent" class="com.domain01.Student"> <property name="name" value="小草莓"/> <!--调用setName()方法--> <property name="age" value="18"/> <!--调用setAge()方法--> </bean>
-
引用类型:自定义类对象作为属性
引用类型的set注入,使用ref <bean id="mystudent" class="com.domain01.Student"> <property name="name" value="小草莓"/> <!--调用setName()方法--> <property name="age" value="18"/> <!--调用setAge()方法--> <property name="school" ref="myschool"/> <!--调用setSchool()方法--> </bean> <bean id="myschool" class="com.domain01.School"> <property name="schoolname" value="家里蹲"/> <!--调用setSchoolname()方法--> </bean>
2.3.1.2、构造注入
概念:Spring调用类中的有参构造器,在创建对象的同时,给属性赋值
使用<constructor-arg name="构造器形参名" index="构造器参数位置" value="简单类型形参的值" ref="引用类型形参的值"/>
标签
-
使用 name 属性
<!--构造注入:使用 name 属性--> <bean id="mystudent" class="com.domain01.Student"> <constructor-arg name="name" value="小草莓"/> <constructor-arg name="age" value="18"/> <constructor-arg name="school" ref="myschool"/> </bean>
-
使用 index 属性
<!--构造注入:使用 index,参数位置从左往右是0,1,2--> <bean id="mystudent" class="com.domain01.Student"> <constructor-arg index="1" value="18"/> <constructor-arg index="0" value="小草莓"/> <constructor-arg index="2" ref="myschool"/> </bean>
也可以省略 index
<!-- 省略 index --> <bean id="mystudent" class="com.domain01.Student"> <constructor-arg value="小草莓"/> <constructor-arg value="18"/> <constructor-arg ref="myschool"/> </bean>
2.3.1.3、引用类型属性自动注入
概念:Spring可以根据某些规则给引用类型完成赋值,只对引用类型有效
byName(按名称注入)
byName(按名称注入):java类中引用类型属性名称和Spring容器中bean 的 id 属性值一样,且数据类型也是一样的,这样bean能够赋值给引用类型
示例:
java对象:
@Data
public class Student {
private String name;
private Integer age;
private School myschool;
}
配置文件:
<!--
引用类型自动注入:byName
java类中引用类型的属性名称和 bean 标签的 id 属性值一样,且数据类型一样
-->
<bean id="mystudent" class="com.domain01.Student" autowire="byName">
<property name="name" value="小草莓"/>
<property name="age" value="18"/>
</bean>
<!-- 下面bean标签的id值需要和Student类中的private School myschool;属性名一致 -->
<bean id="myschool" class="com.domain01.School">
<property name="schoolname" value="家里蹲"/>
</bean>
byType(按类型注入)
byType(按类型注入):java类中引用类型属性的数据类型和 Spring容器中bean 的 class 属性值是同源关系,这样bean能够赋值给引用类型
注意: 在配置文件中,符合条件的对象只能有一个,多余一个都会报错
同源关系:
- 引用类型属性和bean的class值 数据类型是一样的
- 引用类型属性和bean的class值 数据类型是父子类关系
- 引用类型属性和bean的class值 数据类型是借口和实现类关系
示例:
java对象:
@Data
public class Student {
private String name;
private Integer age;
private School myschool;
}
配置文件:
<!--
引用类型自动注入:byType
java类中引用类型属性和 bean 标签的 class 属性值的数据类型是同源关系
-->
<bean id="mystudent" class="com.domain01.Student" autowire="byType">
<property name="name" value="小草莓"/>
<property name="age" value="18"/>
</bean>
<!-- 声明School的子类 -->
<bean id="myprimaryschool" class="com.domain01.PrimarySchool">
<property name="primarySchoolname" value="家里蹲"/>
</bean>
2.3.1.4、使用多个Spring配置文件
分多个配置文件方式:
- 按功能模块分,一个模块一个配置文件
- 按类的功能分,数据库操作相关的类在一个文件,service类在一个配置文件,redis、事务等一个配置文件
Spring管理多个配置文件:
常用的是包含关系的配置文件。项目中有一个总的问文件,里面用 import 标签指向包含的其他多个配置文件
总的文件
<import resource="classpath:其他文件路径1"/>
<import resource="classpath:其他文件路径2"/>
关键字"claspath":
表示类路径,也就是类文件(class文件)所在的目录。Spring到类路径中加载文件
什么时候使用classpath:
在一个文件中要使用其他文件,需要使用classpath
示例:
总的文件beans.xml
,包含其他多个配置文件,一般不声明bean
<!--
当前是总的文件,用于指向其他多个配置文件,一般不声明 bean
语法:
<import resource="classpath:其他文件路径"/>
classpath:表示类路径,类文件所在目录,target/classes目录
-->
<import resource="classpath:spring-school.xml"/>
<import resource="classpath:spring-student.xml"/>
通配符
包含关系的配置文件,可使用通配符 *
,表示任意字符
注意:总的文件名称,不能包含在通配符范围内(beans.xml不能是spring-beans.xml)
示例: 总的文件beans.xml
可使用以下一行代码指代上面例子中的两行代码,使用通配符 * 替代了后面所有字符
<import resource="classpath:spring-*.xml"/>
2.3.2、基于注解的DI
基于注解的DI:使用 Spring 提供的注解,完成 java 对象创建、属性赋值
2.3.2.1、定义bean的 @Component
@Component:表示创建对象,对象放到容器中,作用是<bean/>
使用步骤:
-
在源代码加入注解 @Component
import lombok.Data; import org.springframework.stereotype.Component; /** * @Component:表示创建对象,对象放到容器中,作用是<bean/> * 属性: value ,表示对象名称,也就是bean的 id 属性值 * 位置: 在类的上面,表示创建此类的对象 * @Component(value = "mystudent") 等同于 * <bean id="mystudent" class="com.domain01.Student"/> * * 和 @Component 功能相同的创建对象的注解: * 1) @Repository: 放在dao接口的实现类上面,表示创建dao对象、持久层对象,能访问数据库 * 2) @Service: 放在业务层接口的实现类上面,表示创建业务层对象,业务层对象有事务的功能 * 3) @Controller: 放在控制器类的上面,表示创建控制器对象,属于表层对象, * 控制器对象接收请求、将处理结果显示给用户 * * 以上四个注解都能创建对象,但是 @Repository、@Service、@Controller 有角色说明, * 能够表示对象是分层的,表示对象是属于不同层的,具有额外的功能 */ //使用value指定对象的名称 //@Component(value = "mystudent") //可以省略value,默认就是首字母小写的类名 @Component @Data public class Student { private String name; private Integer age; }
-
在 Spring 的配置文件,加入注解扫描器标签
<!-- 声明组件扫描器:使用注解必须加入这个语句 component-scan: 组件扫描器,组件是java对象 属性: base-package ,注解在项目中的包名 框架会扫描此包及子包的所有类,找类中的所有注解 找到注解后,按照注解表示的功能,去创建对象,给属性赋值 --> <context:component-scan base-package="com"/>
-
context:exclude-filter
标签用于设置哪些内容不进行扫描<context:component-scan base-package="com"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
扫描多个包的三种方式:
<!-- 扫描多个包的三种方式 -->
<!-- 第一种:使用多次组件扫描区 -->
<context:component-scan base-package="com.domain01"/>
<context:component-scan base-package="com.domain02"/>
<!-- 第二种:使用分隔符(, ;),指定多个包 -->
<context:component-scan base-package="com.domain01,com.domain02;com.domain03"/>
<!-- 第三种:指定父包 -->
<context:component-scan base-package="com"/>
2.3.2.2、简单类型注入的 @Value
简单类型属性赋值: @Value
示例:
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
public class Student {
/**
* @Value:简单类型属性赋值
* 属性: value 简单类型属性值
* 位置: 1) 在属性定义的上面,无需 set 方法,推荐
* 2) 在set方法的上面
*/
@Value(value = "小草莓")
private String name;
private Integer age;
}
@Value 使用外部属性配置文件
-
创建外部属性配置文件
resources/myconf.properties
myname=小草莓 myage=18
-
在 Spring 的配置文件,加入外部属性配置文件指定标签
<!-- 读取外部的属性配置文件 property-placeholder:读取properties之类的文件 --> <context:property-placeholder location="classpath:myconf.properties"/>
-
在源代码加入注解
@Component @Data public class Student { //使用外部属性文件中的数据,@Value("${key名}") @Value("${myname}") private String name; private Integer age; }
2.3.2.3、自动注入的 @Autowired
@Autowired 属性 required
boolean类型的属性,默认为true
true 表示若引用类型属性赋值失败,终止程序执行,并报错
false 表示若引用类型属性赋值失败,程序正常执行,引用类型属性值为 null
@Component
@Data
public class Student {
//使用外部属性文件中的数据,@Value("${key名}")
@Value("${myname}")
private String name;
private Integer age;
/**
* 引用类型自动注入
* @Autowired:Spring框架提供,给引用类型属性赋值的,支持 byName、byType。默认是byType
* 属性: required ,boolean类型的属性,默认为true
* true:spring在启动时,创建容器对象,会检查引用类型属性是否赋值成功
* 若赋值失败,终止程序执行,并报错
* false:若引用类型属性赋值失败,程序正常执行,引用类型属性值为 null
* 位置: 1) 在属性定义的上面,无需 set 方法,推荐
* 2) 在set方法的上面
*/
@Autowired(required = false)
@Qualifier("aaaa")
private School school;
}
测试:
@Test
public void test1(){
ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) appc.getBean("student");
System.out.println(student);
}
输出结果:
Student(name=小草莓, age=null, school=null)
使用 byType 自动注入
注意:
当一个接口有多个实现类的时候,使用 @Autowired 进行类型自动注入就会报错
自定义类:
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component(value = "myschool")
public class School {
@Value(value = "家里蹲")
private String schoolName;
}
使用自动注入,引用类型属性
import com.domain02.School;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
public class Student {
@Value("${myname}")
private String name;
private Integer age;
/**
* 引用类型自动注入
* @Autowired:Spring框架提供,给引用类型属性赋值的,自动注入
* 支持 byName、byType。默认是byType
* 位置: 1) 在属性定义的上面,无需 set 方法,推荐
* 2) 在set方法的上面
*/
//默认使用byType
@Autowired
private School school;
}
测试:
@Test
public void test1(){
ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) appc.getBean("student");
System.out.println(student);
}
结果:
Student(name=小草莓, age=null, school=School(schoolName=家里蹲))
使用 byName 自动注入 @Qualifier
自定义类:
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component(value = "myschool")
public class School {
@Value(value = "家里蹲")
private String schoolName;
}
使用自动注入,引用类型属性
import com.domain02.School;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
public class Student {
@Value("${myname}")
private String name;
private Integer age;
/**
* byName自动注入
* 1) @Autowired:给引用类型属性赋值
* 2) @Qualifier(value = "bean标签的id"):从容器中找到指定名称的对象,
* 把对象赋值给引用类型属性
*/
//使用byName
@Autowired
@Qualifier("myschool")
private School school;
}
2.3.2.4、JDK注解 @Resource 自动注入
注意: JDK高于1.8的就没有这个注解,需要加入依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
默认先使用 byName 赋值,如果赋值失败,再使用 byType
@Component
@Data
public class Student {
@Value(value = "小草莓")
private String name;
private Integer age;
/**
* @Resource:来自JDK,给引用类型属性赋值的,自动注入,支持 byName、byType。默认是 byName
* 位置: 1)在属性定义的上面,无需 set 方法,推荐
* 2) 在set方法的上面
*
* 注意:JDK高于1.8的就没有这个注解,需要加入依赖 javax.annotation-api 1.3.2
*
*/
//默认使用 byName 自动注入
//先使用 byName 赋值,如果赋值失败,再使用 byType
@Resource
private School school;
}
如果只想使用 byName 赋值,使用注解属性 name="bean的id"
@Resource(name="myschool")
private School school;
2.3.2.5、IOC 总结
IOC :管理对象的,把对象放到容器中,创建、赋值、管理依赖关系
通过管理对象,实现解耦合,IOC 解决处理业务逻辑对象之间的耦合关系,也就是 servlet 与 dao 之间的解耦合
Spring 作为容器适合管理什么对象?
- service 对象、dao 对象
- 工具类对象
不适合交给 Spring 的对象?
- 实体类
- servlet、listener、filter 等web中的对象,这些是Tomcat创建和管理的
3、AOP 面向切面编程
在编写好程序之后,又有需求对编写好的代码上进行功能的增加
在源代码中、业务方法中增加功能,可能导致的问题:
- 源代码可能改动的比较多
- 重复代码多
- 代码难于维护
现在想要实现在不更改原来编写的源代码的情况下,增加新的功能
AOP 能完成代理的功能,在保证核心的源代码不动的情况下,增加其他功能,并调用核心的源代码
public class ServiceProxy implements UserService {
//真正的目标
UserService userService = new UserServiceImpl();
@Override
public void addUser() {
System.out.println("日志功能:记录方法执行时间" + new Date());
userService.addUser();
System.out.println("事务功能:业务方法之后,提交事务");
}
}
3.1、AOP概念
3.1.1、什么是 AOP ?
- AOP(Aspect Orient Programming):面向切面编
- Aspect:表示切面,给业务方法增加的功能,叫做切面。切面一般都是非业务功能,而且切面功能一般都是可以复用的。例如,日志功能、事务功能、权限检查、参数检查、统计信息等
- Orient:面向
- Programming:编程
怎么理解面向切面编程?
- 就是以切面为核心设计开发你的应用
- 设计项目时,找出切面的功能
- 安排切面的执行时间,执行的位置
AOP 的作用:
- 让切面功能复用
- 让开发人员专注业务逻辑,提高开发效率
- 实现业务功能和其他非业务功能解耦合
- 给存在的业务方法增加功能,不用修改原来的代码
3.1.2、AOP 中的术语:
- Aspect:切面。给业务方法增加的功能
- JoinPoint:连接点。连接切面的业务方法,在这嘎业务方法执行时,会同时执行切面的功能
- PointCut:切入点。是一个或多个连接点集合,表示这些方法执行时,都能增加切面的功能,表示切面执行的位置
- target:目标对象。给哪个对象增加切面的功能,这个对象就是目标对象
- Advice:通知(增强)。表示切面的执行时间,在目标方法之前执行切面,还是目标方法之后执行切面
AOP 中重要三个要素:Aspect、PointCut、Advice。
理解:在 Advice 的时间,在 PointCut 的位置,执行 Aspect
AOP 是一个动态的思想,在程序运行期间,创建代理(ServiceProxy),使用代理执行法时,增加切面的功能,这个代理对象是存在内存中的
什么时候用 AOP ?
- 当你要给某些非法增加相同的一些功能时,源代码不能改
- 给业务方法增加非业务功能,也可用 AOP
AOP 技术思想的实现
使用框架实现 AOP,实现 AOP 的框架有很多,有名的是以下两个:
- Spring:spring框架实现 AOP 思想中的部分功能,比较繁琐
- Aspectj:独立的框架,专门作用于 AOP,属于 Eclipse
3.2、使用AspectJ框架实现 AOP
AspectJ 框架可以使用注解和xml配置文件两种方式实现AOP
3.2.1、通知分类
AspectJ 表示切面执行时间,用的通知(Advice),这个通知可以使用注解表示
下面是表示切面的 5 个执行时间,这些注解叫做通知注解
1. @Before :前置通知
2. @AfterReturning :后置通知
3. @Around : 环绕通知
4. @AfterThrowing :异常通知
5. @After :最终通知
3.2.2、PointCut 位置(切入点表达式)
PointCut 用来表示切面执行的位置,使用 AspectJ 中切入点表达式来表示
切入点表达式: execution(权限类型 返回值类型 包名类名方法名(参数类型和参数个数) 抛出异常类型)
举例:execution(public void com.proxy.MyProxy.doSome(String,Integer))
- 权限类型、包名类名、抛出异常类型 :三个是可选部分,可有可无
- 返回值类型、方法名(参数类型和参数个数) :必须存在
通配符:
*
: 0至多个任意字符..
: 用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径,此时后须跟*+
: 用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类
切入点表达式例子:
execution(puiblic * *(..))
指定切入点为:任意公共方法execution( * set*(..))
指定切入点为:任意以set开头方法名的方法execution( * com.service.*.*(..))
指定切入点为:在com.service包内的任意类的任意方法execution( * com.service..*.*(..))
指定切入点为:在com.service包及其子包的任意类的任意方法execution( * *..service.*.*(..))
指定所有包下的 service 子包的任意类的任意方法
3.2.3、前置通知@Before
实现步骤:
1. 新建maven项目,加入依赖
spring-context、spring-aspects、junit
2. 创建业务接口和实现类
3. 创建一个普通类,作为切面类
1. 在类的上面加入@Aspect
2. 在类中定义方法,方法表示切面的功能
在方法的上面加入 Aspect 框架中的通知注解,如@Before(value="切入点表达式")
4. 创建Spring配置文件
1. 声明目标对象
2. 声明切面类对象
3. 声明自动代理生成器
5. 创建测试类,测试目标方法执行时,增加切面的功能
前置通知方法的定义:
- 方法是 public
- 方法是没有返回值的,void
- 方法名称自定义
- 方法可以有参数,如果有是JoinPoint;也可以没有参数
示例:
com.service.impl.SomeServiceImpl 实现类:
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name, Integer age) {
System.out.println("业务方法doSome()");
}
}
com.handle.MyAspect 切面类:
/**
* @Aspect:切面类的注解
* 位置:在普通类的上面
* 作用:表示当前类是切面类(表示切面功能的类)
*/
@Aspect
public class MyAspect {
//定义方法
/*
前置通知方法的定义:
1)方法是 public
2)返回值为 void
3)方法名称自定义
4)方法可以有参数,如果有是JoinPoint;也可以没有参数
*/
/**
* @Before:前置通知
* 属性:value ,值为 切入点表达式,表示切面的执行位置,在执行此方法时,会同时执行切面的功能
* 位置:在方法上面
* 特点:
* 1)执行时间:在目标方法之前执行
* 2)不会影响目标方法的执行
* 3)不会修改目标方法的执行结果
*/
@Before(value = "execution(public void com.service.impl.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
System.out.println("前置通知,切面的功能。在目标方法之前先执行");
}
}
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: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="someServiceImpl" class="com.service.impl.SomeServiceImpl"/>
<!--声明切面类对象-->
<bean id="myAspect" class="com.handle.MyAspect"/>
<!--声明自动代理生成器:目的是创建目标对象的代理(ServiceProxy)
调用AspectJ框架中的功能,寻找Spring容器中所有目标对象,将每个目标对象加入切面类中的功能,生成代理
这个代理对象是修改的内存中的目标对象,这个目标对象就是代理对象(ServiceProxy)
-->
<aop:aspectj-autoproxy/>
</beans>
测试:
@Test
public void test02(){
ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
//注意,对于使用AOP这里一定是接口的类型,而不是实现类的类型
SomeService service = (SomeService) appc.getBean("someServiceImpl");
System.out.println(service.getClass().getName()); //com.sun.proxy.$Proxy8
service.doSome("",null);
}
使用参数 JoinPoint :
当想要在通知方法里面使用到目标类的信息时,可以使用参数 JoinPoint
使用参数的前置通知方法:
/**
* @Aspect:切面类的注解
* 位置:在普通类的上面
* 作用:表示当前类是切面类(表示切面功能的类)
*/
@Aspect
public class MyAspect {
//定义方法
/*
前置通知方法的定义:
1)方法是 public
2)返回值为 void
3)方法名称自定义
4)方法可以有参数,如果有是JoinPoint;也可以没有参数
*/
/**
* 切面类中的通知方法可以有参数 JoinPoint
* JoinPoint :表示正在执行的业务方法,相当于反射中 Method
* 使用要求:必须是参数列表的第一个
* 作用:获取方法执行时的信息,例如方法名称、方法的参数集合
*/
@Before(value = "execution(* *..SomeServiceImpl.do*(..))")
public void myBefore(JoinPoint jp){
//获取方法的定义
System.out.println("前置通知==获取方法声明" + jp.getSignature());
System.out.println("前置通知==获取方法名" + jp.getSignature().getName());
//获取方法执行时参数
Object[] args = jp.getArgs(); //数组中存放的是方法的所有参数
for (Object o : args) {
System.out.println("前置通知==获取方法的参数" + o);
}
/*
获取到方法执行的信息有什么用呢?
下面举例:
*/
String methodName = jp.getSignature().getName();
if("doSome".equals(methodName)){
//切面的代码
System.out.println("前置通知==输出日志功能");
}else if ("doOther".equals(methodName)){
System.out.println("前置通知==记录时间功能");
}
}
}
测试结果:
3.2.4、后置通知@AfterReturning
后置通知方法的执行是在目标方法之后执行
语法:@AfterReturning(value=“切入点表达式” , returning=“通知方法的形参名”)
后置通知方法的定义:
- 方法是 public
- 方法是没有返回值的,void
- 方法名称自定义
- 方法有参数,推荐使用 Object
Spring配置文件未做改变
SomeServiceImpl 实现类:
public class SomeServiceImpl implements SomeService {
@Override
public String doSome(String name, Integer age) {
System.out.println("业务方法doSome()");
return name+age;
}
}
MyAspect 切面类:
@Aspect
public class MyAspect {
/**
* @AfterReturning:后置通知,属性间用逗号隔开
* 属性: value 切入点表达式
* returning 自定义的变量,表示目标方法的返回值
* 自定义变量名必须和通知方法的形参名一致
* 位置:在方法的上面
* 特点:
* 1)在目标方法之后执行
* 2)能获取到目标方法的执行结果
* 3)不会影响目标方法的执行
* 方法参数:
* Object obj :表示目标方法的返回值,使用obj接受doSome方法的调用结果
* Object obj = doSome();
*/
@AfterReturning(value = "execution(* *..SomeServiceImpl.doSome(..))",returning = "obj")
public void myAfterReturning(Object obj){
System.out.println("后置通知==在目标方法之后执行,能拿到执行结果:" + obj);
}
}
测试:
@Test
public void test03(){
ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
//注意,对于使用AOP这里一定是接口的类型,而不是实现类的类型
SomeService service = (SomeService) appc.getBean("someServiceImpl");
System.out.println(service.getClass().getName()); //com.sun.proxy.$Proxy8
String result = service.doSome("xcm",18);
System.out.println(result); //xcm18
}
测试结果:
后置通知的方法参数可以拿到目标方法的返回值结果,那么拿到结果有什么用呢?
@AfterReturning(value = "execution(* *..SomeServiceImpl.doSome(..))",returning = "obj")
public void myAfterReturning(Object obj){
System.out.println("后置通知==在目标方法之后执行,能拿到执行结果:" + obj);
/*
参数 Object obj 有什么用?
1)根据返回值的不同执行不同的增强功能
2)更改方法返回值结果
*/
if ("xcm".equals(obj)){
System.out.println("根据返回值的不同执行不同的增强功能");
}
if (obj != null){
obj = "更改后的方法返回值结果";
}
}
思考:
- doSome 方法返回值是String、Integer、Long等基本类型时
在后置通知中,修改返回值,是不会影响目标方法的最后调用结果的 - doSome 返回的结果是对象类型,例如 Student
在后置通知中,修改这个Student对象的属性值,会不会影响最后调用结果?
3.2.5、环绕通知@Around
环绕通知方法的定义:
-
方法是 public
-
方法是必须是有返回值,推荐使用 Object 类型
-
方法名称自定义
-
方法必须有 ProceedingJoinPoint 参数
-
不使用环绕通知的 ProceedingJoinPoint 参数时:使用环绕通知,执行目标方法的时候实际上调用的是切面类中的通知方法
示例:
-
切面类中的环绕通知:
/** * @Aspect:切面类的注解 * 位置:在普通类的上面 * 作用:表示当前类是切面类(表示切面功能的类) */ @Aspect public class MyAspect { /** * 环绕通知方法的定义: * 1)方法是public,方法名称自定义 * 2)方法是必须是有返回值,推荐使用 Object 类型 * 3)方法必须有 ProceedingJoinPoint 参数 * @Around:环绕通知 * 属性: value 切入点表达式 * 位置: 在方法定义的上面 * * 返回值: Object ,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值) * 参数: ProceedingJoinPoint ,相当于反射中 Method * 作用:执行目标方法,等于 Method.invoke() * 特点: * 1)在目标方法执行的前、后都能有增强功能 * 2)控制目标方法是否执行 * 3)修改目标方法的执行结果 */ @Around(value = "execution(* *..SomeServiceImpl.doSome(..))") public Object myAround(ProceedingJoinPoint pjp){ System.out.println("环绕通知==执行环绕通知方法"); return "HelloAround"; } }
-
测试:
@Test public void test04(){ ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml"); //注意,对于使用AOP这里一定是接口的类型,而不是实现类的类型 SomeService service = (SomeService) appc.getBean("someServiceImpl"); System.out.println(service.getClass().getName()); //com.sun.proxy.$Proxy8 String result = service.doSome("xcm",18); //环绕通知==执行环绕通知方法 System.out.println(result); //HelloAround }
-
测试结果:输出的返回值也是通知方法的返回值
-
-
使用 ProceedingJoinPoint 参数
-
在目标方法执行的前、后都能有增强功能;控制目标方法是否执行
@Aspect public class MyAspect { @Around(value = "execution(* *..SomeServiceImpl.doSome(..))") public Object myAround(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知==在目标方法执行之前"); Object methodResult = null; //执行目标方法,相当于反射的method.invoke() methodResult = pjp.proceed(); //返回的结果methodResult就是目标方法的返回值 System.out.println("环绕通知==在目标方法执行之后"); return "HelloAround"; //可以直接返回目标方法的返回结果,不修改 //return methodResult; } }
-
修改目标方法的执行结果
@Aspect public class MyAspect { @Around(value = "execution(* *..SomeServiceImpl.doSome(..))") public Object myAround(ProceedingJoinPoint pjp) throws Throwable { Object methodResult = null; methodResult = pjp.proceed(); //返回的结果就是目标方法的返回值 //修改目标方法的返回值 if (methodResult != null){ methodResult = "在环绕通知中更改方法返回值"; } return methodResult; } }
-
可以获取目标执行方法的参数、方法名
@Aspect public class MyAspect { @Around(value = "execution(* *..SomeServiceImpl.doSome(..))") public Object myAround(ProceedingJoinPoint pjp) throws Throwable { Object methodResult = null; //ProceedingJoinPoint 类继承了 JoinPoint 类,所以也可以得到目标方法的参数、方法名 String objName = ""; Object[] args = pjp.getArgs(); for (Object o : args) { if (o != null){ //此时前提是doSome方法的参数仅有一个 String name objName = (String) o; } } //判断参数内容 if ("xcm".equals(objName)){ //执行目标方法,相当于反射的method.invoke() methodResult = pjp.proceed(); //返回的结果就是目标方法的返回值 } //可以直接返回目标方法的返回结果 return methodResult; } }
-
3.2.6、异常通知@AfterThrowing
语法:@AfterThrowing (value=“切入点表达式” , throwing=“通知方法的形参名”)
异常通知方法的定义:
- 方法是 public
- 方法是没有返回值的,void
- 方法名称自定义
- 方法有参数,参数是 Exception
Spring配置文件未做改变
@Aspect
public class MyAspect {
/**
* @AfterThrowing:异常通知
* 属性:value 切入点表达式
* throwing 自定义变量,表示目标方法抛出的异常
* 变量名必须和通知方法的形参名一致
* 位置:在方法的上面
* 特点:
* 1)在目标方法抛出异常后执行,没有异常不执行
* 2)能获取到目标方法的异常信息
* 3)不是异常处理程序,可以得到发生异常的通知,可以发送邮件、短信通知开发人员
* 看做是目标方法的监控程序
*/
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSome(..))", throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知,在目标方法抛出异常的时候执行。异常原因:" + ex.getMessage());
/*
异常发生可以做:
1、记录异常的时间、位置等信息
2、发送邮件、短信,通知开发人员
*/
}
}
3.2.7、最终通知@After
最终通知方法的定义:
- 方法是 public
- 方法是没有返回值的,void
- 方法名称自定义
- 方法没有参数
Spring配置文件未做改变
@Aspect
public class MyAspect {
/**
* @After:最终通知
* 属性: value ,切入点表达式
* 位置:在方法的上面
* 特点:
* 1)在目标方法之后执行
* 2)总是会被执行
* 3)可以用来作程序的收尾工作,例如清除临时数据、变量,清理内存
*/
@After(value = "execution(* *..SomeServiceImpl.doSome(..))")
public void myAfter(){
System.out.println("最终通知,总是会被执行的");
}
}
3.2.8、@Pointcut 定义切入点
当使用较多相同的 execution 切入点表达式时,可使用 @Pointcut 注解,用于定义 execution 切入点表达式
注解 @Pointcut 放在一个方法上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点
@Aspect
public class MyAspect {
/**
* @Pointcut:定义和管理切入点,不是通知注解
* 属性:value 切入点表达式
* 位置:在一个自定义方法上面,mpt()方法名看做是切入点表达式execution(* *..SomeServiceImpl.doSome(..))的别名
* 其他的通知注解中,可以使用方法名,就表示使用这个切入点表达式
* 自定义方法一般定义为 private ,防止其他地方调用
*/
@Pointcut(value = "execution(* *..SomeServiceImpl.doSome(..))")
private void mpt(){
//方法里面无需代码
}
//这里 mpt() 就相当于 execution(* *..SomeServiceImpl.doSome(..))
@After(value = "mpt()")
public void myAfter(){
System.out.println("最终通知,总是会被执行的");
}
}
3.3、总结
AOP 是一种动态的技术思想,目的是实现业务功能和非业务功能的耦合。业务功能是独立的模块,其他功能也是独立的模块
例如 事务功能、日志功能等,让这些事务、日志功能是可以被复用的
当目标方法需要一些功能时,可以在不修改、不能修改源代码的情况下,使用 AOP 技术在程序执行期间,生产代理对象,通过代理执行业务方法,同时增加功能
3.4、练习
使用 AOP 做方法的参数检查
要求:
- 当 addNumber 方法的参数不为null、参数大于0 的时候,才可以执行 addNumber() 方法计算
- 如果任意一个参数是 null 、或者小于 0,则调用 addNumber 方法返回结果是 -1
Spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: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="numberServiceImpl" class="com.service.impl.NumberServiceImpl"/>
<bean id="myAspect" class="com.aspect.MyAspect"/>
<aop:aspectj-autoproxy/>
</beans>
切面类
package com.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class MyAspect {
@Around(value = "execution(* *..NumberServiceImpl.addNumber(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知");
Object methodResult = null;
Object[] args = pjp.getArgs();
for (Object arg : args) {
if (arg == null || (Integer)arg <= 0){
methodResult = -1;
break;
}else {
methodResult = pjp.proceed();
}
}
return methodResult;
}
}
实现类
public class NumberServiceImpl implements NumberService {
@Override
public Integer addNumber(Integer n1, Integer n2, Integer n3) {
return n1+n2+n3;
}
}
测试类
import com.service.NumberService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test1(){
ApplicationContext appc = new ClassPathXmlApplicationContext("applicationcontext.xml");
NumberService numberService = (NumberService) appc.getBean("numberServiceImpl");
Integer integer = numberService.addNumber(23, 23, 23);
System.out.println(integer);
}
}
4、Spring 集成 MyBatis
4.1、集成思路
Spring 能集成很多的框架,是Spring的一个有事功能,通过集成功能,让开发人员使用其他框架更方便,集成使用的是 Spring IOC 核心技术
使用 MyBatis,需要创建 MyBatis 框架中的某些对象,使用这些对象,就能使用 MyBatis 提供的功能了
分析:MyBatis 执行 SQL 语句,要使用哪些对象?
-
需要有 dao 接口的代理对象,例如 StudentDao 接口,需要它的代理对象
使用 SqlSession.getMapper(StudenDao.class),得到 dao 代理对象
-
需要有 SqlSessionFactory ,创建 SqlSessionFactory 对象,才能使用 openSession() 得到 SqlSession 对象
-
数据源 DataSource 对象,使用一个更强大、功能更多的连接池对象代替 MyBatis 自己的 PooledDataSource
4.2、集成步骤
实现步骤:
-
使用 MySQL 库,使用学生表
-
创建 maven 项目
-
加入依赖
spring依赖、mybatis依赖、mysql驱动、Junit依赖、mybatis-spring依赖(用来在spring中创建mybatis对象)、sprig有关事务的依赖
-
创建实体类 Student
-
创建 Dao 接口和 mapper 文件写 sql 语句
-
MyBatis 主配置文件
-
新建 service 接口和其实现类
-
创建spring的配置文件
- 声明数据源DataSource,使用功能阿里的Druid连接池
- 声明 SQLSessionFactoryBean类,在这个类内部创建SQLSessionFactory对象
- 声明MapperScannerConfiguration类,在内部创建dao代理对象,创建的对象都放在spring容器中
- 声明service对象,将上面 3 中的 dao代理对象 赋值给service属性
-
测试dao访问数据库
4.2.1、pom.xm文件
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<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>
<!--mybatis和spring的集成依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.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>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
4.2.2、Dao 接口和 mapper 文件
dao接口:com.afei.dao.StudentDao
public interface StudentDao {
int insertStudent(Student student);
List<Student> selectStudents();
}
Mapper文件:com.afei.dao.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.afei.dao.StudentDao">
<select id="selectStudents" resultType="com.afei.domain.Student">
SELECT * FROM `student`
</select>
<insert id="insertStudent" >
insert into `student`(name,age) values(#{name},#{age})
</insert>
</mapper>
4.2.3、MyBatis 主配置文件
主配置文件:resources/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>
<!--设置日志-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--别名-->
<typeAliases>
<package name="com.afei.domain"/>
</typeAliases>
<!--指定其他 mapper 文件的位置-->
<mappers>
<mapper resource="com/afei/dao/StudentDao.xml"/>
<!--使用要求:
1)Mapper文件和dao接口在同一目录
2)Mapper文件和dao接口名称一致
-->
<!-- <package name="com.afei.dao"/> -->
</mappers>
</configuration>
4.2.4、service实现类
注意要有 set 方法
@Setter
public class StudentServiceImpl implements StudentService {
private StudentDao studentDao;
@Override
public int addStudent(Student student) {
return studentDao.insertStudent(student);
}
@Override
public List<Student> queryStudents() {
return studentDao.selectStudents();
}
}
4.2.5、Spring 配置文件★★★★★★
- 声明数据源DataSource,使用功能阿里的Druid连接池
- 声明 SQLSessionFactoryBean类,在这个类内部创建SQLSessionFactory对象
- 声明MapperScannerConfiguration类,在内部创建dao代理对象,创建的对象都放在spring容器中
- 声明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">
<!--加载外部的属性配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 声明数据源 DataSource -->
<bean id="myDataSources" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--Druid会根据URL自动识别驱动类名,就不需要配置属性driverClassName-->
</bean>
<!--声明SqlSessionFactoryBean
在该类内部创建 SqlSessionFactory,这个bean创建的对象的数据类型是 SqlSessionFactory 类型
底层SqlSessionFactoryBean还是使用SqlSessionFactoryBuilder创建SqlSessionFactory对象
-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--指定数据源-->
<property name="dataSource" ref="myDataSources"/>
<!--指定mybatis主配置文件
Resource可以直接使用 value属性赋值,value指定主配置文件的路径
-->
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--声明 MapperScannerConfigurer
作用:循环basePackage所表示的包,将包中的每个接口都找到,然后调用 sqlSession.getMapper(StudentDao.class)
对找到的每个接口都创建出 dao 代理对象,dao 代理对象放在容器中,对象名即为首字母小写的接口类名
SqlSession sqlSession = 容器对象.getBean("factory").openSession();
遍历basePackage所表示的包 : 接口 对象 = sqlSession.getMapper(接口);
SpringMap.put(首字母小写的接口类名, 对象);
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定 SqlSessionFactory对象的名称-->
<property name="sqlSessionFactoryBeanName" value="factory"/>
<!--指定基本包:dao接口所在的包名-->
<property name="basePackage" value="com.afei.dao"/>
</bean>
<!--声明service-->
<bean id="studentService" class="com.afei.service.impl.StudentServiceImpl">
<!--这里能直接使用ref="studentDao",也是因为在上面MapperScannerConfigurer中,
生成了dao代理对象,对象名即为首字母小写的接口类名
-->
<property name="studentDao" ref="studentDao"/>
</bean>
</beans>
5、Spring事务
5.1、事务概念
什么事事务?事务是一些 SQL 序列的集合,是多条SQL,作为一个整体执行
mysql执行
beginTransaction 开启事务
insert into student() values...
select * from student where id = 1005
update school set name=xxx where id = 1005
endTransaction 结束事务
什么情况下需要使用事务?
一个操作需要多条(2条或以上的SQL)SQL语句一起完成,操作才能成功
事务:加在业务类的方法上面(public方法上面),表示业务方法执行时,需要事务的支持
public class StudentServiceImpl implements StudentService {
private StudentDao studentDao;
private SchoolDao schoolDao;
@Override
public int addStudent() {
studentDao.insertStudent();
schoolDao.insertSchool();
}
}
Spring统一管理事务概念
不同的数据库访问技术,处理事务的代码是不同的,Spring统一管理事务,把不同的数据库访问技术的事务处理统一起来
使用 Spring 的事务管理器,管理不同数据库访问技术的事务处理,开发人员只需要掌握 Spring 的事务处理一个方案,就可以实现使用不同数据库访问技术的事务管理
管理事务面向的是 Spring,有 Spring管理事务,做事务提交,事务回滚
例如:使用 jdbc 访问数据库,事务处理如下
public void updateAccount(){
Connection conn = ...;
conn.setAutoCommit(false);
stat.insert();
stat.update();
stat.commit();
conn.setAutoCommit(true);
}
例如:mybatis 执行数据库,事务处理如下
public void updateAccount(){
SqlSession sqlSession = SqlSession.openSession(false);
try{
sqlSession.insert("insert into student...");
sqlSession.update("update school...");
sqlSession.commit();
}catch(Exception e){
sqlSession.rollback();
}
}
5.2、事务管理器接口
Spring 框架使用事务管理器对象,管理所有的事务
事务管理器接口:PlatformTransactionManager
作用:定义了事务的操作,主要是 commit(),rollback()
事务管理器接口有很多实现类:一种数据库的访问技术对应着有一个实现类,由实现类具体完成事务的提交,回滚
- jdbc或者mybatis访问数据库有自己的事务管理器实现类:
DataSourceTransactionManager
- hibernate框架:
HibernateTransactionManager
事务管理器工作方式:
事务提交和事务回滚的时机:
什么时候提交、回滚?
方法中抛出了运行时异常【事务回滚】
方法正常执行、抛出受查异常【事务提交】
异常分类:
- Error:严重错误。【回滚事务】
- Exception:异常类
- 运行时异常:RuntimeException 及其子类都是运行时异常【回滚事务】,例如 NullPointException、NumberFormatException、ArithmeticException、IndexOutOfBoundException
- 受查异常(编译时异常):必须处理的异常,否则编译不通过【提交事务】,例如 IOException、SQLException、FileNotFoundException
事务底层实现:
事务底层使用的是 AOP 的环绕通知,可以在目标方法的前后都增强功能,不需要修改目标原代码
spring给业务方法执行时,增加上事务的切面功能
@Around(value = "execution(* 所有业务类中的方法")
public Object myAround(ProceedingJoinPoint pjp){
try{
PlatformTransactionManager.begeinTransaction(); //使用功能Spring事务管理器,开启事务
pjd.proceed(); //执行目标方法 doSome()
PlatformTransactionManager.commit(); //业务方法正常执行,提交事务
}catch(Exception e){
PlatformTransactionManager.rollback(); //回滚事务
}
}
5.3、事务定义接口
事务定义接口 TransactionDefinition ,其中定义了事务描述相关的三类常量:
1)事务隔离级别、2)事务传播行为、3)事务默认超时时限
给业务方法说明事务属性,和 ACID 不一样
5.3.1、事务隔离级别
隔离级别:控制事务之间影响的程度
5 个常量,定义 4 个隔离级别
- DEFAULT :采用 DB 默认的事务隔离级别,如mysql默认REPEATABLE_READ 、oracle默认READ_COMMITTED
- READ_UNCOMMITTED:读未提交,未解决任何并发问题
- READ_COMMITTED:读已提交,解决脏读问题,存在不可重复读、幻读问题
- REPEATABLE_READ:可重复读,解决脏读、不可重复读问题,存在幻读问题
- SERIALIZABLE:串行化,解决所有并发问题
5.3.2、超时时限
超时时间,以秒为单位,是增数值,默认为 -1
表示一个业务方法最长的执行时间,到达时间没有执行完毕,便回滚事务
5.3.3、传播行为
有 7 个值,表示业务方法在调用时,事务在方法之间的,传递和使用。使用传播行为,标识方法有无事务
-
PROPAGATION_REQUIRED
spring默认传播行为,方法在调用的时候,如果存在事务就使用当前事务;如果没有事务,就新建事务,方法在新事务中执行
-
PROPAGATION_REQUIRES_NEW
方法有事务可以正常执行,没有事务也可以正常执行 (业务查询操作)
-
PROPAGATION_SUPPORTS
方法需要一个新事务。如果调用方法时,存在事务,则原来的事务暂停,直到新事务执行完毕;
如果方法调用时没有事务,则新建事务,在新事务中执行代码
以上三个需要掌握
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
5.4、程序举例(环境搭建)
实现步骤:
-
使用 MySQL 库,使用sale表、goods表
-
创建 maven 项目
-
加入依赖
spring依赖、mybatis依赖、mysql驱动、Junit依赖、mybatis-spring依赖(用来在spring中创建mybatis对象)、sprig有关事务的依赖
-
创建实体类 Sale、Goods
-
创建 Dao 接口和 mapper 文件
SaleDao、GoodSale,两个 mapper 文件
-
MyBatis 主配置文件
-
新建 service 接口及其实现类,实现 buy 的方法
-
创建spring的配置文件
- 声明数据源DataSource,使用功能阿里的Druid连接池
- 声明 SQLSessionFactoryBean类,在这个类内部创建SQLSessionFactory对象
- 声明MapperScannerConfiguration类,在内部创建dao代理对象,创建的对象都放在spring容器中
- 声明service对象,将上面 3 中的 dao代理对象 赋值给service属性
-
测试
5.4.1、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.afei</groupId>
<artifactId>springjdbc</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<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>
<!--mybatis和spring的集成依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.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>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
5.4.2、实体类
@Data
public class Goods {
private Integer id;
private String name;
private Integer amount;
private Float price;
}
@Data
public class Sale {
private Integer id;
private Integer gid;
private Integer num;
}
5.4.3、dao接口与mapper
GoodsDao
public interface GoodsDao {
Goods selectById(Integer id);
//goods表示本次购买的商品id和购买数量amount
//更新商品库存
int updateGoods(Goods goods);
}
<?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.dao.GoodsDao">
<select id="selectById" resultType="com.domain.Goods">
select * from goods where id = #{id}
</select>
<update id="updateGoods" >
update goods set amount = amount-#{amount} where id = #{id}
</update>
</mapper>
SaleDao
public interface SaleDao {
int insertSale(Sale sale);
}
<?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.dao.SaleDao">
<insert id="insertSale" >
insert into sale(gid,num) values(#{gid},#{num})
</insert>
</mapper>
5.4.4、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>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--别名-->
<typeAliases>
<package name="com.domain"/>
</typeAliases>
<!--指定其他 mapper 文件的位置-->
<mappers>
<mapper resource="com/dao/GoodsDao.xml"/>
<mapper resource="com/dao/SaleDao.xml"/>
<!--使用要求:
1)Mapper文件和dao接口在同一目录
2)Mapper文件和dao接口名称一致
-->
<!-- <package name="com.dao"/> -->
</mappers>
</configuration>
5.4.5、service接口的实现类
@Setter
public class BuyGoodsServiceImpl implements BuyGoodsService {
private GoodsDao goodsDao;
private SaleDao saleDao;
@Override
public void buy(Integer goodsId, Integer num) {
System.out.println("===buy方法的开始===");
//生成销售记录
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNum(num);
int rows = saleDao.insertSale(sale);
//查询商品
Goods goods = goodsDao.selectById(goodsId);
if (goods == null){
throw new NullPointerException(goodsId + "商品不存在");
}else if (goods.getAmount() < num){
throw new NotEnoughException(goodsId +"库存不足");
}
//更新库存
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(num);
goodsDao.updateGoods(buyGoods);
System.out.println("===buy方法的完成===");
}
}
5.4.6、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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/atguigu"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>
</bean>
<!--声明SqlSessionFactoryBean-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--声明MapperScannerConfiguration,内部dao代理类-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.dao"/>
<property name="sqlSessionFactoryBeanName" value="factory"/>
</bean>
<!--创建service对象-->
<bean id="buyGoodsServiceImpl" class="com.service.impl.BuyGoodsServiceImpl">
<property name="goodsDao" ref="goodsDao"/>
<property name="saleDao" ref="saleDao"/>
</bean>
</beans>
5.5、注解方式@Transactional
@Transactional 注解,使用注解的属性控制事务(隔离级别,传播行为,超时)
注解的属性:
- propagation:设置事务传播属性,使用 Propagation 类的枚举值,默认为 Propagation.REQUIRED
- isolation:设置事务隔离级别,使用 Isolation 类的枚举值,默认为 Isolation.DEFAULT
- readOnly:是boolean类型,表示数据库操作是不是只读,默认是 false
- timeout:设置本操作与数据库连接的超时时间,单位为秒,int 类型,默认 -1
- rollbackFor:表示回滚的异常类列表,值是一个数组,每个值是异常类型的 class
- rollbackForClassName:表示回滚的异常类列表,值是异常类名称,是 String 类型的值
- noRollbackFor:表示不需要回滚的异常类列表,是 class 类型
- noRollbackForClassName:表示不需要回滚的异常类列表的名称,是 String 类型
注解的位置:
- 在业务方法上面,public 类型
- 在类的上面
注解的使用步骤:
-
在 spring 的配置文件,声明事务的内容
声明事务管理器,说明使用哪个事务管理器对象
声明使用注解管理事务,开启注解驱动
-
在类的源代码中,加入 @Transactional
修改 spring 配置文件:
注意事务驱动使用哪一个
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务驱动:
告诉框架使用注解管理事务
transaction-manager:指定事务管理器的 id
数据源标签bean可以有多个,事务管理器标签bean可以有多个,在此指定一个
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
添加 @TRansactional 注解:
注解使用的特点:
- 一定在 public 方法上面
- 适用于中小型项目,使用方便
package com.service.impl;
import com.dao.GoodsDao;
import com.dao.SaleDao;
import com.domain.Goods;
import com.domain.Sale;
import com.exception.NotEnoughException;
import com.service.BuyGoodsService;
import lombok.Setter;
import org.springframework.transaction.annotation.Transactional;
@Setter
public class BuyGoodsServiceImpl implements BuyGoodsService {
private GoodsDao goodsDao;
private SaleDao saleDao;
/**
* @Transactional 注解加在 public 方法的上面,表示方法有事务功能
* 1、第一种方式:
* @Transactional(
* propagation = Propagation.REQUIRED,
* isolation = Isolation.DEFAULT,
* readOnly = false,
* timeout = 20,
* rollbackFor = {NullPointerException.class , NotEnoughException.class}
* )
* 2、第二种方式:
* @Transactional(
* propagation = Propagation.REQUIRED,
* isolation = Isolation.DEFAULT,
* readOnly = false,
* timeout = 20
* )
* rollbackFor的使用:
* 1)框架首先检查方法抛出的异常是不是rollbackFor的数组中,若是,一定会滚
* 2)如果方法抛出的异常不在rollbackFor数组中,框架会继续检查是不是 RuntimeException
* 如果第 RuntimeException 异常,继续回滚
* 3、第三种方式:
* @Transactional
* 此时注解里面的属性都是使用默认值,默认 Propagation.REQUIRED,默认运行时异常回滚
*/
@Transactional
@Override
public void buy(Integer goodsId, Integer num) {
System.out.println("===buy方法的开始===");
//生成销售记录
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNum(num);
int rows = saleDao.insertSale(sale);
//查询商品
Goods goods = goodsDao.selectById(goodsId);
if (goods == null){
throw new NullPointerException(goodsId + "商品不存在");
}else if (goods.getAmount() < num){
throw new NotEnoughException(goodsId +"库存不足");
}
//更新库存
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(num);
goodsDao.updateGoods(buyGoods);
System.out.println("===buy方法的完成===");
}
}
5.6、xml配置文件方式
使用 AspectJ 的 AOP ,在spring配置文件中声明事务控制
使用步骤:
-
pom.xml 文件中加入 spring-aspects 依赖
<!-- spring-aspects --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
在 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:tx="http://www.springframework.org/schema/tx"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--声明数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/book"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>
</bean>
<!--声明SqlSessionFactoryBean-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--声明MapperScannerConfiguration,内部dao代理类-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.dao"/>
<property name="sqlSessionFactoryBeanName" value="factory"/>
</bean>
<!--创建service对象-->
<bean id="buyGoodsServiceImpl" class="com.service.impl.AddStudentServiceImpl">
<property name="dao" ref="studentDao"/>
</bean>
<!--1、声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2、声明业务方法的事务属性(隔离级别、传播行为、超时)
id:给业务方法配置事务段代码名称,唯一值
transaction-manager:事务管理器的id
-->
<tx:advice id="serviceAdvice" transaction-manager="transactionManager">
<!--给具体的业务方法增加事务的说明-->
<tx:attributes>
<!--
给具体的业务方法,说明它需要的事务属性
name:业务方法名称,配置 name 的值:
1)业务方法名称
2)带有部分通配符*的方法名称,如 addStudent、addSchool、addTeacher方法,使用add*
3)使用 *
propagation:指定传播行为
isolation:隔离级别
read-only:是否只读,默认是false
timeout:超时时间
rollback-for:指定回滚的异常类型,使用异常类的全限定名称
-->
<tx:method name="buy" rollback-for="java.lang.NullPointerException , com.exception.NotEnoughException"/>
<!-- 在业务方法有命名规则后,可以对一些方法批量使用事务 -->
<tx:method name="add*" propagation="REQUIRES_NEW"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="modify*" propagation="REQUIRED"/>
<!-- 以上方法以外的 -->
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 3、声明切入点表达式:
上面一步是给具体的业务方法,这一步就是表示哪些包中的类、类中的方法参与事务
-->
<aop:config>
<!-- 声明切入点表达式
expression:切入点表达式,表示哪些类和类中的方法要参与事务
下面的式子表示所有包内有 service 包的所有子包内的所有类的所有方法
id:切入点表达式的名称,唯一性
-->
<aop:pointcut id="servicepointcut" expression="execution(* *..service..*.*(..))"/>
<!-- 关联切入点表达式和事务通知 -->
<aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicepointcut"/>
</aop:config>
</beans>
声明式事务优缺点:
- 理解难、配置复杂
- 代码和事务配置分开,控制事务源代码不用修改
- 能快速了解项目全部事务,适合大型项目
6、Spring 与 Web
6.1、环境配置
完成学生注册功能,步骤:
- 新建maven,修改 pom.xml
- 创建实体类 Student
- dao接口和mapper文件
- 创建 mybatis 主配置文件
- service接口和实现类
- servlet,接收请求参数,调用service对象
- 创建 jsp ,提交请求参数
- 创建jsp,作为视图,显示请求的处理结果
- 创建 spring 配置文件
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.afei</groupId>
<artifactId>demo02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<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>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- jsp -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
</dependency>
<!-- 监听器依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- xml配置文件方式配置事务的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
<scope>compile</scope>
</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>
<!--mybatis和spring的集成依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.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>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<!-- 插件 Tomcat7 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>8080</port> <!-- 端口 -->
<path>/</path> <!-- 上下路径 -->
<uriEncoding>UTF-8</uriEncoding> <!-- 针对 GET 方式乱码处理 -->
</configuration>
</plugin>
</plugins>
</build>
</project>
实体类 Student
@Data
public class Student {
private Integer stuId;
private String stuName;
private Integer stuAge;
}
dao接口和mapper文件
package com.dao;
import com.domain.Student;
import org.apache.ibatis.annotations.Param;
public interface StudentDao {
int insertStudent(Student student);
Student selectById(@Param("studentid")Integer id);
}
<?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="dao接口的全限定名称com.dao.StudentDao">
<!-- 参数是自定义对象时,#{属性名称} -->
<insert id="insertStudent">
insert into student(name,age) values(#{stuName},#{stuAge})
</insert>
<!-- 定义列和属性的对应关系 -->
<resultMap id="studentMap" type="com.domain.Student">
<id column="id" property="stuId"/>
<result column="name" property="stuName"/>
<result column="age" property="stuAge"/>
</resultMap>
<!-- 参数是基本类型时,#{@Param注解的值} -->
<select id="selectById" resultMap="studentMap">
select * from studnet where id = #{studentid}
</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>
<!--设置sql日志 打印在控制台-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--配置别名-->
<typeAliases>
<!--此包下的实体类名===别名-->
<package name="com.domain.Student"/>
</typeAliases>
<!--管理映射源文件-->
<mappers>
<!--加载 dao 包中所有 mapper 文件-->
<package name="com.dao"/>
</mappers>
</configuration>
service接口和实现类
package com.service.impl;
import com.dao.StudentDao;
import com.domain.Student;
import com.service.StudentService;
import lombok.Setter;
@Setter
public class StudentServiceImpl implements StudentService {
private StudentDao dao;
@Override
public int addStudent(Student student) {
return dao.insertStudent(student);
}
@Override
public Student queryById(Integer id) {
return dao.selectById(id);
}
}
servlet 接收请求参数
package com.controller;
import com.domain.Student;
import com.service.StudentService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AddStudentServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = request.getParameter("name");
String age = request.getParameter("age");
//调用service
ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService service = (StudentService) appc.getBean("studentServiceImpl");
Student student = new Student();
student.setStuName(name);
student.setStuAge(Integer.valueOf(age));
service.addStudent(student);
//给用户显示处理结果
request.getRequestDispatcher("/show.jsp").forward(request,response);
}
}
6.2、现在容器对象的问题
问题:
- 创建容器对象次数多,没调用一次 service ,就要创建一次容器对象
- 在多个 servlet 中,分别创建容器对象
当创建容器对象的时候,spring 配置文件中的对象都会一同被创建
现在需要一个什么样的容器对象?
- 容器对象只有一个,创建一次即可
- 容器对象应该在整个项目中共有,多个 servlet 都能使用同一个容器对象
解决问题:
- 使用监听器 ServletContextListener (内含两个方法:初始时执行的,销毁时执行的)
- 在监听器中创建好的容器对象,应该放在 web 应用中的 ServletContext 作用域中
6.3、ContextLoaderListener 监听器
ContextLoaderListener 是一个监听器对象,是 ServletContextListener 的实现类,是spring框架提供的
使用这个监听器作用:
- 创建容器对象,一次
- 把容器对象放到 ServletContext 作用域
步骤:
-
加入 spring-web 依赖
<!-- spring-web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
web.xml 声明监听器
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <!--声明监听器 1、默认监听器:创建容器对象是,读取配置文件:/WEB_INF/applicationContext.xml 2、自定义日期使用配置文件的路径:使用 context-param 标签 --> <!--自定义spring容器使用的配置文件路径 context-param 叫做上下文路径,给监听器提供参数的 --> <context-param> <!-- contextConfigLocation:固定名称,表示自定义spring配置文件路径 --> <param-name>contextConfigLocation</param-name> <!-- 自定义配置文件路径 --> <param-value>classpath:applicationContext.xml ,classpath:myspring.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>AddStudentServlet</servlet-name> <servlet-class>com.controller.AddStudentServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AddStudentServlet</servlet-name> <url-pattern>/add</url-pattern> </servlet-mapping> </web-app>
6.4、监听器源代码
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
//监听器的初始方法
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
}
private WebApplicationContext context;
public WebApplicationContext initWebApplicationContext(ServletContext servletContext){
try {
if (this.context == null) {
//创建spring的容器对象
this.context = this.createWebApplicationContext(servletContext);
}
//把容器对象放入 ServletContext 作用域
/*
key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
value = 容器对象
*/
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
this.context
);
} catch (Error | RuntimeException var8) {}
}
//WebApplicationContext是web项目中使用的容器对象
public interface WebApplicationContext extends ApplicationContext {
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
}
6.5、使用ContextLoaderListener监听器
自己手动编写来获取spring容器对象
//使用监听器已经创建好了的容器对象,从 ServletContext 作用域获取容器
WebApplicationContext wappc = null;
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
//获取 ServletContext 对象
ServletContext sc = getServletContext(); // servlet中的方法获取 ServletContext 对象
//ServletContext sc = request.getServletContext(); 请求对象来获取 ServletContext 对象
Object attribute = sc.getAttribute(key);
if(attribute != null){
wappc = (WebApplicationContext) attribute;
}
StudentService service = (StudentService) wappc.getBean("studentServiceImpl");
使用 spring 提供的工具方法,获取容器对象
//使用 spring 提供的工具方法,获取容器对象
WebApplicationContext wappc = WebApplicationContextUtils
.getRequiredWebApplicationContext(getServletContext());