Java-Spring 框架
Java-Spring 框架
了解什么是Spring
-
spring
官网 -
点击如下图查看具体内容:
-
优点
-
轻量
1
Spring 框架使用的 jar 都比较小,一般在 1M 一下或者几百 KB,Spring 核心功能的所需的 jar 总共在 3M 左右
-
针对接口编程
1
Spring 提供了 ioc 控制反转,
由容器管理对象, 对象的依赖关系, 原来在程序代码中的对象创建方式,现在由容器来完成, 对象之间的依赖解耦合。 -
AOP
编程支持 1
2通过 Spring 提供的 AOP 功能, 方便进行面向切面的编程, 许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付
在 Spring 中,开发人员可以从繁琐的事物管理代码中解脱出来,通过声明方式灵活的进行事物的管理,提高开发效率和质量 -
方便各种优秀框架的集成
1
Spring 不排斥各种优秀的开源框架,
相反 Spring 可以降低各种框架的使用难度,Spring 提供了对各种优秀框架 (如: Struts,Hibernate,MyNatis) 等的直接支持, 简化框架的使用,Spring 像插线板一样, 其他框架是插头, 可以容易的组合到一起,需要使用那个框架,就把这个插头放入插线板中,不需要可以轻易的移除。
-
-
Spring
体系结构 体系结构
使用Spring
框架
Spring 核心功能
-
第一个核心功能:
IOC
-
控制反转,
是一个理论, 思想 -
描述: 把对象的创建,赋值,管理工作交给代码之外的容器来实现,
也就是对象的创建是有其他外部资源完成。 -
控制: 创建对象,对象的属性赋值,
对象之间的关系管理 -
反转:
把原来的开发人员管理, 创建对象的权限转移给容器之外的代码实现, 由容器代替开发人员管理对象, 创建对想, 给属性赋值 -
正转:
由开发人员在代码中, 使用 new
构造方法创建对象, 开发人员主动管理对象 1
2
3public static void main(String[] args){
Student student = new Student(); 在代码中创建对象 => 正转
} -
容器: 是一个服务器软件,
一个框架 ( spring
) -
为什么要使用
IOC
:目的就是减少对代码的改动,也能实现不同的·功能,实现解耦合 -
IOC
的技术实现: - DI(Dependency Injection):
依赖注入,只需要在程序中提供要使用的对象名称就可以, 至于对象如何在容器中·创建,赋值,查找都由容器内部实现
- DI(Dependency Injection):
-
IOC 控制反转
第一个 Spring 程序
-
创建新项目-
maven
项目 创建 下一步
-
Spring
目录结构 spring tree
-
数据测试
测试
-
创建配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<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="" class="" />
</beans>
<!--
作用: 告诉 spring 创建对象
声明 bean 就是告诉 spring 要创建某个类的对象
id: 对象的自定义名称,唯一值,spring 通过这个名称找到对象
class: 类的全限定名称(不能是接口,因为 spring 是反射机制创建对象,必须使用类)
spring的配置文件:
1. beans: 是根标签,spring把 java 对象成为 bean
2. spring-beans.xsd 是约束文件,和 mybatis 指定, dtd 是一样的
spring 就实现 => UserInfo userInfo = new UserInfo();
spring 是吧创建好的对象放入到 map 中,spring 框架有一个 map 存放对象的.
springMap.put(id的值, 对象);
一个 bean 标签声明一个对象
-->
-
特别的注意点
| 注意 |
| :———————————————————-: |
| |
-
使用
spring
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34package org.coderitl.impl;
import org.coderitl.UserInfo;
/**
* @author coder-itl
*/
public class UserInfoImpl implements UserInfo {
public void doSome() {
System.out.println("do some is using...");
}
}
=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>
public void testSpring() {
/**
* 使用 Spring 容器创建的对象
*/
/* 1. 指定 spring 配置文件的名称 */
String config = "Beans.xml";
/* 2. 创建表示 spring 容器的对象, ApplicationContext
* ApplicationContext 就是表示 Spring 容器 通过容器获取对象
* ClassPathXmlApplicationContext: 表示从 类 路径中加载 spring 的配置文件
* */
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
/* 从容器中获取某个对象,你要调用的对象的方法 参数: 配置文件中的 id 值,转换成接口类型 */
UserInfo userInfo = (UserInfo) applicationContext.getBean("UserInfo");
/* 使用 spring 创建好的对象 */
userInfo.doSome(); // do some is using...
} -
spring
创建对象的时机 1
2
3
4
5/*
spring默认创建对象的时间: 在创建 spring 的容器时, 会创建配置文件中的所有对象
spring创建对象: 默认调用的是无参构造方法
*/
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config); -
获取
spring
信息 Bean
1
2
3
4
5
6
7
8
9
10/* 获取容器中的对象名称 */
String names[] = ac.getBeanDefinitionNames();
for (String name : names) {
System.out.println("容器中的所有对象名称: " + name);
}
/*输出:
容器中的所有对象名称: UserInfo
容器中的所有对象名称: UserInfo2
*/ -
获取一个非自定义的类对象
1
2
3
4
5
6
7
8
9
10
11
12
13Beans.xml:
<!-- spring 能创建一个非定义类的对象吗? 创建一个存在的某个类的对象 -->
<bean id="myDate" class="java.util.Date"/>
public void testGetDate() {
String config = "Beans.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
/* 使用 getBean() */
Date date = (Date) ac.getBean("myDate");
System.out.println("时间为: " + date);
}
Bean 的基本配置
类别 | 描述 |
---|---|
名称 | bean |
类型 | 标签 |
所属 | bean
|
功能 | 定义spring
|
列表属性 | id :bean
id
id
bean
id
class :bean
bean
|
范例 | <bean id="bookDao" class="com.example.springheima.dao.impl.BookDaoImpl"/> |
-
bean
配置别名 类别 描述 名称 name
类型 属性 所属 bean
标签 功能 定义 bean
的别名, 使用 逗号、分号、空格
分隔范例 <bean id="bookDao" name="dao bookDaoImpl" class="com.example.springheima.dao.impl.BookDaoImpl"/>
数据源管理
-
数据源管理
1
2
3
4
5
6
7
8
9
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>-
输出测试
数据源管理测试
-
-
c3p0
数据源 -
依赖
1
2
3
4
5
6
7
8
9
10
11<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency> -
数据源管理
1
2
3
4
5
6<bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean> -
测试输出
c3p0
-
加载 Properties
-
使用
1
2
3
4
5
6
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd-
分析
配置加载 properties
-
基于 XML
的 DI
注入分类
set 注入
-
配置文件对属性赋值
1
2
3
4
5
6
7注意: 一个 preperty 只能给一个属性赋值
<!-- 给属性复赋值 -->
<bean id="student" class="org.coderitl.impl.StudentImpl">
<property name="name" value="张三"/> <!-- => setName("张三") -->
<property name="age" value="18"/> <!-- => setAge("18") -->
<property name="score" value="88"/> <!-- => setScore("88") -->
</bean>
-
测试设置值后的输出
1
2
3
4
5
6
7
8
9
10
11
12注意: 私有属性必须提供对应的 setXXX()
public void testSetValue() {
String config = "Beans.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
/* 从容器中获取 student 对象 验证: set 方法 => */
StudentImpl student = (StudentImpl) ac.getBean("student");
/* StudentImpl{name='张三', age=18, score=88.0} */
System.out.println(student);
}
-
验证
set
注入 set
注入
-
引用类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package org.coderitl.other;
public class School {
private String schooleName;
private String schooleAddres;
public void setSchooleName(String schooleName) {
this.schooleName = schooleName;
}
public void setSchooleAddres(String schooleAddres) {
this.schooleAddres = schooleAddres;
}
public String toString() {
return "School{" +
"schooleName='" + schooleName + '\'' +
", schooleAddres='" + schooleAddres + '\'' +
'}';
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package org.coderitl.other;
public class StudentInfo {
private int setDates;
/**
* 创建一个引用类型
*/
private School school;
public void setSetDates(int setDates) {
this.setDates = setDates;
}
public void setSchool(School school) {
this.school = school;
}
public String toString() {
return "StudentInfo{" +
"setDates=" + setDates +
", school=" + school +
'}';
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package org.coderitl;
import org.coderitl.other.School;
import org.coderitl.other.StudentInfo;
import org.junit.Test;
public class TestStudentInfo {
public void testStudentInfo() {
/* 普通对象赋值 */
School school = new School();
school.setSchooleName("泾川利于");
school.setSchooleAddres("西外");
StudentInfo studentInfo = new StudentInfo();
/* 引用类型的使用 */
studentInfo.setSetDates(2021);
studentInfo.setSchool(school);
/* StudentInfo{setDates=2021, school=School{schooleName='泾川利于', schooleAddres='西外'}} */
System.out.println(studentInfo);
}
}引用类型配置文件 -
构造注入
构造注入:
spring
调用类的有参构造, 在创建对象的同时,在构造方法中给属性赋值, 构造注入使用 <constructor-arg>
表示构造方法一个参数 <constructor-arg>
标签属性: name
: 表示构造方法的形参名index
: 表示构造方法的参数的位置,参数从左往右位置是0,1,2..
的顺序 value
: 构造方法的形参类型是简单类型的,使用 value
ref
: 构造方法的形参类型是引用类型的,使用 ref
-
普通类
1
2
3
4
5
6
7
8
9
10// 提供有参构造
public Student(String stuName, Integer stuAge, School school) {
this.stuName = stuName;
this.stuAge = stuAge;
this.school = school;
}
// set..
// toString...
-
配置文件
1
2
3
4
5
6
7
8
9
10
11// name 属性实现构造注入 | index | 还可以省略 index,name (推荐 name 可读性高)
<bean id="constructorset" class="org.example.day02.Student">
<constructor-arg name="stuName | index(0)" value="coder-itl"/>
<constructor-arg name="stuAge" value="18"/>
<!-- 引用类型赋值 -->
<constructor-arg name="school" ref="day02School"/>
</bean>
<bean id="day02School" class="org.example.day02.School">
<property name="schoolName" value="育才中学"/>
</bean> -
测试
1
student = Student{stuName='coder-itl', stuAge=18, school=School{schoolName='育才中学'}}
引用类型属性自动注入
-
byName
方式自动注入 -
按名称注入
byName
普通方式实现引用类型赋值 通过 byName
实现引用类型赋值 -
-
byType
方式自动注入 -
按类型注入
byType
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/* 实现一: java
类中引用数据类型的数据类型和 bean 的 class 的值是一样的 */
<bean id="myStudent" class="org.coderitl.school.Student" autowire="byType">
<!-- student 普通属性赋值操作 -->
<property name="stuName" value="byType"/>
<property name="stuAge" value="18"/>
<property name="stuSex" value="男"/>
</bean>
<!-- 修改 id 区分 byName -->
<bean id="mySchool" class="org.coderitl.school.School">
<property name="schoolName" value="byType中学"/>
<property name="schoolSetUpDate" value="2021-10-02"/>
</bean>
/* 实现二: java类中引用数据类型的数据类型和 bean 的 class 的值是父子类关系 */
1. 创建引用类型的子类
2. 子类继承引用类型的类
3. 配置文件进行属性赋值
/* 实现三: java类中引用数据类型的数据类型和 bean 的 class 的值是接口与实现类的关系 */
... -
集合注入
-
注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<bean id="bookDao" class="com.example.springheima.dao.impl.BookDaoImpl">
<property name="array">
<!-- 数组 -->
<array>
<value>100</value>
<value>300</value>
<value>200</value>
<value>500</value>
</array>
</property>
<property name="list">
<list>
<value>1</value>
<value>23</value>
<value>11</value>
<value>45</value>
</list>
</property>
<property name="map">
<map>
<entry key="username" value="coder-itl"></entry>
</map>
</property>
<property name="set">
<set>
<value>12</value>
<value>11</value>
</set>
</property>
<property name="properties">
<props>
<prop key="url">localhost</prop>
</props>
</property>
</bean> -
输出
集合注入输出
为什么使用多配置文件
- 每个文件的大小比一个文件要小的多,
效率高 - 避免多人竞争带来的冲突
- 按功能模块,
一个模块一个配置文件 - 按类的功能,数据库相关的配置一个文件配置文件,
做事物的功能一个配置文件, 做 service 功能的一个配置文件等
-
多配置文件使用
多配置文件使用
-
文件目录
1
2
3
4
5
6
7
8
9
10
11配置文件路径: resource/multipleconfiguration
<!--
包含关系的配置文件中,可以使用通配符 (*: 表示任意字符) 主配置文件不能被包含
否则会陷入死循环,配置文件需要放置在同一目录下, 注意命名规范, 以便于书写通配符
在测试用例中,依然只读取主配置 (Eg: mergeconfig.xml) 文件
-->
<import resource="classpath:multipleconfiguration/spring-*.xml" />
基于注解的DI
- 使用注解的步骤:
- 加入
maven
的依赖:Spring-context
,在加入Spring-context
的同时, 间接加入 Spring-aop
的依赖,使用注解必须使用Spring-aop
依赖 - 在类中加入
Spring
的注解(多个不同功能的注解) - 在
Spring
的配置文件中,加入一个组件扫描器的标签, 说明注解在你的项目中的位置
- 加入
- 基于注解的
DI
:通过注解完成java
对象的创建, 属性赋值
1 |
/* |
-
@Component(value="bean id")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33package org.example.day02.anno;
import org.springframework.stereotype.Component;
/**
* @Component(value = "myStudent"): 创建对象的,等同于 bean 的功能
* 属性: value 就是对象的名称,也就是 bean 的 id 值
* value 的值是唯一的,创建的对象在整个 spring 容器中就一个
* 位置: 在类的上面
*/
public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
} -
组件扫描器
组件扫描器 -
组件扫描器的使用
1
2
3
4
5
6
7
8
9
10
11
12
13组件扫描器生成方式:
IDEA中输入:<component-scan 即可出现对应提示
<!-- 指定多个包的三种方式 -->
<context:component-scan base-package="com.coderitl.component1"/>
<context:component-scan base-package="com.coderitl.component2"/>
<context:component-scan base-package="com.coderitl.component1;com.coderitl.component2;"/>
<!-- 第三种方式: 指定父包 -->
<context:component-scan base-package="com.coderitl"/> -
单元测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package org.example.setdi.day02.anno;
import org.example.day02.anno.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestComponent {
public void testComponent() {
// 注意读取配置文件路径
String config = "day02/anno/anno.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// getBean("使用注解的value 的值")
Student student = (Student) ac.getBean("myStudent");
System.out.println("student = " + student);
}
} -
注解的几种写法
1
2
3
4
5
6// 第一种
// 第二种=> 推荐使用方式
// 第三种(bean 的 id 就等于该类小写名称) => 默认是类的小写
1 |
: 简单类型的属性赋值 |
-
属性值为什么是
String
属性值类型 -
引起错误的原因
错误总结
-
语法
1
2
3
4
5
6
7
8
9
10
11/**
* 添加引用类型
*
* @Autowired: Spring 框架提供的注解,实现引用类型的赋值
* Spring 中通过注解给引用类型赋值,使用的是自动注入原理, 支持 byName,byType
* @Autowired: 默认使用的是 byName 自动注入
*
* 位置:
* 1. 在属性定义的上面 无需 set 方法 => 推荐使用
* 2. 在 set 方法的上面
*/ -
被引用类型依然使用注解赋值
注解
-
语法
1
2
3
4
5
6
7
8/* 添加注解 @Qualifier 和 @Autowired 没有先后顺序 */
private School school;
属性: required 是一个 boolean 类型的,默认 true 程序报错, 并终止执行 => 推荐 : 表示引用类型赋值失败, 程序正常执行, 引用类型的结果为: null : 引用类型如果赋值失败, -
使用流程
ByName
的使用流程
-
Resource
1
2
3
4
5
6
7/*
Resource 为 JDK 提供:
Spring 框架提供了对这个注解的使用,可以使用它给饮用类型赋值,使用的也是自动注入原理, 支持 baName,byType
默认 byName
位置: 1. 在属性定义的上面,无需 set 方法 => 推荐使用
*/ -
JDK
版本引起 ( @Resource
)不导包的解决方案
1 |
|
1 |
|
-
配置文件
-
ByName
ByName
-
ByType
byName
输出 修改 name
名称 byType
-
-
注解分析
-
byType
注解 byType
使用分析 -
byName
注解 byName
使用分析
-
Autowired
- 属性注解,
方法注解 ( set
),方法 声明当前属性自动装配, 默认为 byType
@Autowired(required=false)
通过 required
属性设置当前自动装配是否为必须 (默认必须, 如果没有找到类型与属性类型匹配的 bean
则抛出异常 spring
提供的注解
- 属性注解,
@Resource
- 属性注解,
也用于声明属性自动装配 - 默认装配方式为
byName
,如果根据byName
没有找到对应的 bean
,则继续根据byType
寻找对应的 bean
,根据byType
如果依然没有找到 Bean
或者找不到一个类型匹配的 bean
则抛出异常
- 属性注解,
-
DI
DI
IOC 常用注解
-
@Component
- 类注解,
声明此类被 spring
容器进行管理, 相当于 bean
标签的作用 @Component(value="stu")
,value
属性用于指定当前 bean
的 id
,value
属性也可以省略, 如果省略当前类的 id
默认为 类名首字母改小写
- 类注解,
-
@Scope
- 类注解,
用于声明当前类单例模式还是非单例模式, 相当于 bean
标签的 scope
属性 @Scope(value="prototype")
表示声明当前类为非单例模式, 默认单例模式
- 类注解,
-
@Lazy
- 类注解,
声明一个单例模式的 Bean
是否为 懒汉模式
@Lazy(true)
表示声明为懒汉模式
- 类注解,
-
@PostConstruct
-
方法注解
,声明一个方法为当前类的初始化方法 (在构造器之后执行), 相当于 bean
标签的 init-method
属性 1
2
3
4
5
6// 初始化方法
public void init() {
System.out.println("init...");
}
-
-
@PreDestroy
-
方法注解
,声明一个方法为当前类的销毁方法 (在对象从容器中释放之前执行), 相当于 bean
标签的 destory-method
属性 1
2
3
4
5
6// 销毁方法
public void destroy() {
System.out.println("destroy....");
}
-
动态代理
动态代理是指: 程序在整个运行过程中根本就不存在的目标类的代理类,JVM
-
普通功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package org.example.day03.impl;
import org.example.day03.SomeService;
import org.example.day03.utils.DynamicProxyUtils;
public class SomeServiceImpl implements SomeService {
public void doSome() {
System.out.println("doSome...");
}
public void doOther() {
System.out.println("doOther...");
}
} -
功能增加
1
2
3
4
5
6
7
8
9
10
11
12
13
14package org.example.day03.utils;
import java.util.Date;
public class DynamicProxyUtils {
public static void doLog() {
System.out.println("doLog ...");
}
public static void getTime() {
System.out.println("当前系统时间是: " + new Date());
}
} -
myInvocationHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33package org.example.day03.handler;
import org.example.day03.SomeService;
import org.example.day03.utils.DynamicProxyUtils;
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;
}
public void setTarget(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通过代理对象执行方法时,会调用这个 invoke
Object res = null;
// 增加的功能
DynamicProxyUtils.doLog();
// 执行目标类的方法,通过 Method 类实现
res = method.invoke(target, args); // => SomeServiceImpl.doLog() | SomeServiceImpl.doSome()
DynamicProxyUtils.doLog();
// 目标方法的执行结果
return res;
}
} -
创建代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package org.example.day03;
import org.example.day03.handler.MyInvocationHandler;
import org.example.day03.impl.SomeServiceImpl;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class TestDynamicProxy {
/**
* 测试动态代理
*/
public void testDynamicProxy() {
// JDK 的 Proxy 实现动态代理
// 1. 创建目标对象
SomeService target = new SomeServiceImpl();
// 2. 创建 InvocationHandler
InvocationHandler handler = new MyInvocationHandler(target);
// 3. 使用 Proxy 创建代理
SomeService proxy = (SomeService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), handler);
// 4. 通过代理执行方法,会调用 handler 中的 invoke()
proxy.doSome();
}
}
AOP 面向切面编程
基本概念
-
动态代理
-
jdk
实现方式:
jdk
动态代理, 使用 jdk
中的 Proxy
,Method
,InvocaitonHandler
创建代理对象 jdk
动态代理 要求目标类必须实现接口
-
cglib
第三方的工具库,
创建代理对象, 原理是继承, 通过继承目标类,创建子类, 子类就是代理对象,要求目标类不能是 finaly
,方法也不能是finaly
的
-
- 在目标类源代码不改变的情况下,
增加功能 - 减少代码的重复
- 专注业务逻辑代码
- 解耦合,
让你的业务功能和日志. 事物非业务功能分离
AOP
: 面向切面编程,jdk
,cglib
AOP
AOP(Aspect Orient Programming)
面向切面编程 Aspect
: 切面,给你的目标类增加的功能,就是切面.像上面的日志,事物都是切面。
·
- 需要分析项目功能时,
找出切面 - 合理的安排切面的执行时间,(在目标方法前还是目标方法后)
- 合理的安排切面执行的位置,
在哪个类,那个方法增加增强功能
AOP
AOP
Spring
Spring
:Spring
在内部实现了 AOP
的规范. 能做 AOP
的工作 Spring
主要在事物处理时使用 AOP
aspectJ
- 一个开源的专门做
AOP
的框架, Spring
框架中集成了 aspectJ
框架, 通过 Spring
就能使用 aspectJ
的功能。 aspectJ
框架实现 AOP
的两种方式 - 使用
XML
的配置文件 配置全局事务
- 使用注解,
aspectJ
有 5
个注解
- 使用
- 一个开源的专门做
-
术语
术语解释 -
关键三要素
切面三要素
AspectJ 的使用
-
切面的执行时间,
这个执行之间在规范中叫做 advice
(通知,增强),在 aspectJ
框架中使用注解表示的, 也可以使用 XML
配置文件中的标签 1
2
3
4
5
-
详细信息
-
简单实用
1
2
3
4
5
6execution(访问权限 方法返回值 方法声明
(参数) 异常类型 ) => 方法返回值 方法声明 (参数) 必须有的
切入点表达式要匹配的对象就是目标方法的方法名,所以 execution 表达式中明显就是方法的签名
注意: 表达式中必不可少的部分内容,各部分使用空格分开. 在其中可以使用以下符号
符号 意义 *
0 至多个任意字符 ..
用在方法参数中, 表示任意多个参数, 用在包名称后, 表示当前包及其子包的路径 +
用在类名后, 表示当前类及其子类用在接口后, 表示当前接口及其实现类 -
使用举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 指定切入点为: 任意公共方法
execution(public * *(...))
// 指定切入点为: 任何以 coder 开始的方法
execution(* com.coder*(...))
// 指定切入点为: 定义在 service 包里的任意类的任意方法
execution(* com.coderitl.service.*.*(...))
// 指定切入点为: 定义在 service 包或者子包里的任意类的任意方法, ".."出现在类名中时,后面必须跟 "*",表示包下所有类
execution(* *..service.*.*(..))
// 指定所有包下的 service 子包下所有类(接口) 中所有方法为切入点
execution(* *..service.*.*(..))
-
新建
maven
项目 -
加入依赖
-
spring
依赖 1
2
3
4
5<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency> -
aspectj
依赖 1
2
3
4
5
6<!-- aspects 框架依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency> -
junit
单元测试 1
2
3
4
5
6<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
-
-
创建目标类:
接口和它的实现类
->要做的是给类中方法增加功能 -
创建切面类:
普通类
- 在类的上面加
@Aspect
- 在类中定义方法,
方法就是切面要 执行
的功能代码
- 在方法的上面加入
aspect
中的通知注解, 例如:@Before - 有需要指定切入点表达式
execution()
- 在方法的上面加入
- 在类的上面加
-
创建
spring
的配置文件: 声明对象, 把对象交个容器统一管理, 声明对象你可以使用 注解
或者xml
配置文件 -
声明目标对象
-
声明切面类对象
-
声明
aspectj
框架中的自动代理生成器标签,自动代理生成器: 用来完成代理对象的自动创建功能的 -
创建测试类,
从 spring
容器中获取目标对象 (实际就是代理对象), 通过代理执行方法, 实现 aop
的功能增强
-
创建
maven
项目 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<!-- 添加依赖 -->
<dependencies>
<!-- 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- spring 依赖 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- aspects 框架依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies> -
创建普通接口并实现该接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 普通类的接口
package org.example;
public interface SomeService {
public void doSome(String name, int age);
}
// 接口实现
package org.example.impl;
import org.example.SomeService;
import org.springframework.stereotype.Component;
/**
* 目标类
*/
// <bean id="mySomeServiceImpl" class="org.example.impl.SomeServiceImpl">
public class SomeServiceImpl implements SomeService {
public void doSome(String name, int age) {
System.out.println("doSome is used....");
}
} -
实现切面
(用于增强业务功能) 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43// 切面类
package org.example;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* MyAspectJ => 增强功能
*
* @Aspect: 是 aspectj 框架中的注解
* 作用: 表示当前类是切面类
* 切面类: 是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置: 在类定义的上面
*/
// <bean id="myAspectj" class=" org.example.MyAspectJ">
public class MyAspectJ {
/**
* 定义方法,方法是实现切面功能的
* 方法的定义要求:
* 1. 公共方法 public
* 2. 方法没有返回值
* 3. 方法名称自定义
* 4. 方法可以有参数,也可以没有参数
* 如果有参数,参数不是自定义的, 有几个参数类型可以使用
*/
/**
* @Before: 前置通知注解
* 属性: value,是切入点表达式, 表示切面的功能执行的位置
* 位置: 在方法上面
* 特点:
* 1. 在目标方法之前先执行
* 2. 不会改变目标方法的执行结果
* 3. 不会影响目标方法的执行
*/
public void doLog() {
// 切面的功能代码
System.out.println("日志信息.....");
}
} -
配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 声明组件扫描器 -->
<context:component-scan base-package="org.example"/>
<!--
声明自动代理生成器: 使用 aspectj 框架内部的功能,创建目标对象的代理对象
创建代理对象是在内存中实现的,修改目标对象的内存中的结构
构建为代理对象,所以目标对象就是被修改后的代理对象
aspectj-autoproxy: 会把 spring 容器中的所有目标对象,一次性都生成代理对象
-->
<aop:aspectj-autoproxy/>
</beans> -
测试功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package org.example;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Unit test for simple AspectJ.
*/
public class TestAspectJBefore {
/**
* 测试 aspectJ 的 Before(前置通知)
*/
public void testAspectjBefore() {
// 获取配置文件路径
String config = "aspectj_before/aspectj_before.xml";
// 创建容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 生成代理对象
SomeService proxy = (SomeService) ac.getBean("mySomeServiceImpl");
// 通过代理对象执行方法,实现目标方法执行时, 增强了功能
proxy.doSome("coder-itl", 18);
// 目标类有接口是 jdk 动态代理
// com.sun.proxy.$Proxy14
System.out.println(proxy.getClass().getName());
}
} -
测试输出
AspectJ Before
-
切面表达式
1
2
3
4
5
6
7
8
9
10// 最全面写法
// 省略权限修饰符
// 执行所有 do 开头的方法
-
依赖
-
接口定义
1
2
3
4
5
6
7
8
9package org.coderitl;
/**
* coderitl package: 后置通知所有测试用例
*/
public interface SomeService {
public Student doSome(String name, int age);
} -
接口实现类
(目标类) 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package org.coderitl.impl;
import org.coderitl.SomeService;
import org.coderitl.Student;
import org.springframework.stereotype.Component;
public class SomeServiceImpl implements SomeService {
public Student doSome(String name, int age) {
Student student = new Student(name, age);
System.out.println("SomeServiceImpl dome: " + student);
// SomeServiceImpl dome: Student{name='coder-itl', age=18}
return student;
}
} -
后置通知
(增强功能代码块) 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50package org.coderitl;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
public class MyAfterRuturning {
/**
* 后置通知定义方法,方法的是实现切面功能的
* 方法的定义要求:
* 1. 公共方法 public
* 2. 方法没有返回值
* 3. 方法名称自定义
* 4. 方法有参数,推荐是 Object,参数名自定义
*
* @param result 后置通知返回值
*/
// execution => 目标方法的表达式
/**
* @AfterReturning: 后置通知
* 属性:
* 1. value: 切入点表达式
* 2. returning 自定义的变量,表示目标方法的返回值的,自定义变量名必须和通知方法的形参名一样 (Object result <= returning = "result" )
* @AfterReturning(value = "execution(* *..SomeServiceImpl.*(..))", returning = "result")
* JoinPoint 要i 使用必须在参数的第一位
*/
public void myAfterRuturning(Object result) {
// 增强内容代码块
System.out.println("后置通知执行时student 值: " + result);
// 修改是不会影响原先内容信息的
if (result != null) {
// 修改地址指向
// result = new Student("address", 80);
// System.out.println("修改 result 地址指向: " + result);
// 真正的修改
Student student = (Student) result;
System.out.println("后置通知中修改前: " + student);
student.setName("fixName");
student.setAge(19);
System.out.println("后置通知中修改后: " + student);
}
}
} -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package org.coderitl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAfterReturning {
public void testAfterReturning() {
String config = "aspectj_afterreturning/aspectj_afterreturning.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService) ac.getBean("SomeServiceImplAfterRuturning");
// 注意参数
System.out.println("测试方法中实参(也是最终结果): " + proxy.doSome("coder-itl", 18));
}
} -
疑问:
spring 后置通知修改返回值会影响最终返回值吗?
会
(上述中附有实现)
-
输出
结果输出 执行流程分析
-
依赖
-
接口
-
实现类
-
环绕通知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68package org.around;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Date;
public class AroundAspect {
/**
* 环绕通知方法的定义格式
* 1. public
* 2. 必须有一个返回值,推荐使用 Object
* 3. 方法名称自定义
* 4. 方法有参数,固定的参数 P
*/
/**
* @Around: 环绕通知
* 属性: value 切入点表达式
* 位置: 在方法的定义上
* 特点:
* 1. 他是功能最强的通知
* 2. 在目标方法的前和后都能增强功能
* 3. 控制目标方法是否被调用执行
* 4. 修改原来的目标方法的执行结果, 影响最后的调用结果
* 环绕通知,等同于 jdk 动态代理的 InvocationHandler 接口
* <p>
* 参数: ProceedingJoinPoint 等同于 动态代理的(Invoceke 的)Method
* 作用: 执行目标的方法
* 返回值: 就是目标方法的执行结果,可以被修改
*/
public Object MyAroundAspect(ProceedingJoinPoint point) throws Throwable {
// public interface ProceedingJoinPoint extends JoinPoint { ... } ,所以 ProceedingJoinPoint== JoinPoint 也就可以使用它的方法
// 实现环绕通知
Object result = null;
System.out.println("环绕通知,在目标方法之前执行: " + new Date());
/* 目标方法调用(在目标方法前后加入功能) */
result = point.proceed(); /* 等同于执行动态代理的 method.invoke(); Object result=doSome(); */
System.out.println("环绕通知,在目标方法之后, 提交事务");
/********************** 测试: 控制调用、修改值 **********************************/
// 获取实参
String name = "";
Object[] args = point.getArgs();
if (args != null && args.length > 1) {
Object arg = args[0];
name = (String) arg;
}
// 实现是否调用目标方法
if ("coderitl".equals(name)) {
// result = point.proceed();
} else {
// ...
}
// 修改返回值
if (result != null) {
result = "coder-itl";
}
/********************************************************/
// 返回目标方法的执行结果
return result;
}
} -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package org.around;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAspectAround {
public void testAround() {
String config = "aspectj_around/aspectj_around.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService) ac.getBean("AroundSomeServiceImpl");
// 执行了切面的MyAroundAspect()
String result = proxy.doSome("coder-itl", 18);
System.out.println("(最终结果)result: " + result);
}
} -
输出
环绕通知
-
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* @Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的, 可以复用 可以使用 @Pointcut
* 属性: value 切入点表达式
* 位置: 在自定义的方法上面
* 特点:
* 当使用 @Pointcut 定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名
* 其他的通知中,value 属性就可以使用这个方法的名称,代替切入点表达式了
*/
private void myexcution() {
// 无需代码
}
public Object MyAroundAspect(ProceedingJoinPoint point) throws Throwable { ... }
目标类没有接口,cglib
spring
cglib
目标类如果有接口,cglib
,则只需要修改配置文件<aop:aspectj-autoproxy proxy-target-class="true">
-
@Before
参数 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* 指定通知方法中的参数: JoinPoint(Aspect的其他注解同理)
* JoinPoint: 业务方法,要加入切面功能的业务方法
* 作用是: 可以在通知方法中获取方法执行时的信息,例如方法名称, 方法的实参
* 如果你的切面功能中需要用到方法的信息,就加入 JoinPoint
* 这个 JoinPoint 参数的值是由框架赋予,必须是第一个位置的参数
*/
// 参数
public void doTransform(JoinPoint joinPoint) {
System.out.println("******************************************");
System.out.println("事务控制......");
// 获取完整定义
System.out.println("完整定义: "+joinPoint.getSignature());
// 获取方法的声明类型
System.out.println("方法的声明类型: "+joinPoint.getSignature().getDeclaringType());
// 获取方法的名称
System.out.println("名称: "+joinPoint.getSignature().getName());
// 获取方法的实参
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
System.out.println("参数名称: "+arg);
}
}
spring
-整合mybatis
spring-mybatis
集成用的技术是 IOC
- 为什么是
IOC
,- 能把
spring
和 mybatis
集成在一起, 像一个框架一样, 是因为 ioc
能创建对象, 可以把 mybatis
框架中的对象交给 spring
统一创建, 开发人员从 spring
中获取对象, 开发人员就不用同时面对两个或多个框架了, 就面对一个 spring
- 能把
- 为什么是
-
新建
maven
项目 -
添加
maven
依赖 -
spring
依赖 -
mybatis
依赖 -
mysql
驱动 -
spring
的事务依赖 -
mybats
和 spring
集成的依赖, mybatis
官方提供的, 用来在 spring
项目中创建 mybatis
的 SqlSessionFactory,dao
对象的 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88<dependencies>
<!-- 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- spring 依赖 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- aspects 框架依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- mybatis 依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!-- 事务相关 -->
<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 和 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.34</version>
</dependency>
<!-- 阿里公司数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
<resources>
<resource>
<!-- 所在目录 -->
<directory>src/main/java</directory>
<includes>
<!-- 包括目录下的 下的 properties,xml 过滤 -->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<!-- resource 下的 properties,xml 过滤 -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
-
-
创建实体类
-
创建
dao
接口和 mapper
文件 -
创建
mybatis
主配置文件 -
创建
Service
接口和实现类, 属性是 dao
-
创建
spring
的配置文件, 声明 mybatis
的对象交给 spring
创建 - 数据源
SqlSessionfactory
Dao
对象 - 声明自定义的
Service
-
创建测试类,
获取 Service
对象, 通过 Service
调用 dao
完成数据库访问
1 |
create database springdb CHARACTER SET utf8 COLLATE utf8_general_ci; |
-
整合配置文件
( spring
)的 xml 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<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:mapper/jdbc.properties"/>
<!-- 配置数据源 https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE -->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- set 注入 给 DruidDataSource 提供连接数据库信息 -->
<property name="url"
value="${jdbc.url}}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<!-- mybatis 主配置文件的位置
configLocation 属性是 Resource 类型,作用: 读取配置文件
它的赋值,使用 value, 指定文件的路径, 使用 classpath: 表示文件的位置
-->
<property name="configLocation" value="classpath:mapper/mybatis-config.xml"/>
</bean>
<!--
创建 dao 对象,使用 SqlSession 的 getMapper(studentDao.class)
MapperScannerConfigurer: 在内部调用 getMapper() 生成每个 dao 接口的代理对象
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
MapperScannerConfigurer 会扫描这个包中的所有接口,把每个接口都执行一次 getMapper() 方法, 得到每个接口的 dao 对象
创建好的 dao 对象放入到 spring 容器中 dao 对象的默认名称是 接口名首字母小写
-->
<property name="basePackage" value="org.springmybatis.dao"/>
</bean>
<bean id="studentService" class="org.springmybatis.service.impl.StudentServiceImpl">
<property name="studentDao" ref="studentDao"/>
</bean>
</beans> -
获取信息
1
2
3
4
5
6
7
8
9
10// 测试 spring 集成 mybatis 的使用
public void testSpringMybatis() {
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
String[] names = ac.getBeanDefinitionNames();
for (String name : names) {
System.out.println("name + ac.getBean(name) = " + name + ac.getBean(name));
}
} -
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 通过 spring 获取对象
public void testSpringMybatisInsertStudents() {
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 对象名为 dao 接口的首字母小写
// StudentDao dao = sqlSession.getMapper(StudentDao.class);
// List<Student> studentList = dao.selectStudents();
StudentDao studentDao = (StudentDao) ac.getBean("studentDao");
Student student = new Student(1009, "spring-mybatis", "spring@qq.com", 1);
int insertResult = studentDao.insertStudent(student);
if (insertResult > 0) {
System.out.println("数据添加成功!");
} else {
System.out.println("数据添加失败!");
}
} -
使用
service
对象 1
2
3
4
<bean id="studentService" class="org.springmybatis.service.impl.StudentServiceImpl">
<property name="studentDao" ref="studentDao"/>
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void testSpringMybatisService() {
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// studentService => 来自于 applicationContext.xml 对service 定义
StudentService studentService = (StudentService) ac.getBean("studentService");
Student student = new Student(1010, "spring-mybatis", "spring@qq.com", 1);
int insertResult = studentService.addStudent(student);
if (insertResult > 0) {
System.out.println("数据添加成功!");
} else {
System.out.println("数据添加失败!");
}
}
-
什么是事务
事务是一组
sql
语句的集合, 集合中有多条 sql
语句, 可能是 insert、update、delete
我们希望这些多条 sql
语句都能成功或者都失败, 这些 sql
语句的执行是一致的,作为一个整体执行 -
在什么时候想到使用事务
当操作涉及到多个表时,
或者是多个 sql
语句的 insert、update、delete
需要保证这些语句都是成功才能完成功能时或者都失败, 保证操作是符合要求的 在
java
代码中写程序,控制事务, 此时事务应该放在 service
类的业务方法上, 因为业务方法会调用多个 dao
方法, 执行多个 sql
语句 -
通常使用
JDBC
访问数据库, 还是 mybatis
访问数据据库怎么处理事务 1
2jdbc
访问数据库, 处理事务: Connection conn; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务: SqlSession.commit(); SqlSession.rollback(); -
3
问题中处理事务方式, 有什么不足 - 不同的数据库访问技术,
处理事务的对象, 方法不同,需要了解不同数据库访问技术使用事务的原理 - 需要掌握多种数据库中事务的处理逻辑,
什么时候提交事务, 什么时候回滚事务 - 就是多种数据库的访问技术,
有不同的事务处理机制,对象,方法
- 不同的数据库访问技术,
-
怎么解决不足
spring
提供一种处理事务的统一模型, 能使用统一步骤, 方式完成多种不同数据库访问技术的事务处理 -
处理事务,
需要怎么做, 做什么 spring
处理事务的模型, 使用的步骤都是固定的, 把事务使用的信息提供给 spring
就可以了 - 事务内部提交,
回滚事务, 使用的事务管理器对象, 代替你完成 commit,rollback
- 接口:
platformTransactionManager
定义了事务重要方法 commit,rollback
- 实现类:
spring
把每一种数据库访问技术对应的事务处理类都创建好了 mybatis
访问数据库 spring
创建好的 DataSourceTransactionManager
Hibernate
访问数据库 spring
创建好的 HibernateTransactionManager
- 怎么使用?怎么告诉
spring
?- 只需要告诉
spring
,你是用那种数据库的访问技术 <bean id="自定义" class="...DataSourceTransactionManager">
- 只需要告诉
-
数据准备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20use springdb;
create table sale
(
id int auto_increment not null primary key,
gid int,
nums int
) default character set utf8;
create table goods
(
id int not null primary key,
name varchar(100),
amount int comment '库存',
price float
) default character set utf8;
insert into goods values (1001,'笔记本电脑',100,5000);
insert into goods values (1002,'手机',100,3000);
-
小项目使用,
注解方案 spring
框架自己用 aop
实现给业务方法增加事务的功能, 使用 @Transaction
注解增加事务 @Transaction
注解是 spring
框架自己的注解, 放在 public
方法的上面, 表示当前方法具有事务,可以给注解的属性赋值, 表示具体的隔离级别, 传播行为, 异常信息等
-
使用
@Transaction
的步骤 -
需要声明事务管理器对象
1
2
3
4
5
6
7
8
9
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
annotation-driven: http://www.springframework.org/schema/tx(必须是)
-->
<tx:annotation-driven transaction-manager="transactionManager"/> -
开启事务注解驱动,
告诉 spring
框架, 我们要使用注解的方式管理事务 spring
使用 aop
机制, 创建 @Transaction
所在类的代理对象, 给方法增加事务的功能 - 给
spring
业务方法加入事务 - 在你的业务方法执行之前,
先开启事务, 在业务方法之后提交或回滚事务, 使用 aop
环绕通知
- 在你的业务方法执行之前,
-
在你的方法上面加入
@Transaction
-
-
大项目配置事务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44<!-- 大项目事务配置流程 -->
<!-- 1. 声明事务管理器对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
id: 自定义名称 表示 <tx:advice> 和 </tx:advice> 之间的配置内容
transaction-manager: 事务管理器对象的 id
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<tx:attributes>
name: 方法名称:
1. 完整的方法名称,不带有包和类
2. 方法可以使用通配符, * 表示任意字符
propagation: 传播行为 枚举值
isolation: 隔离级别
no-rollback-for: 你指定的异常类名,全限定类名 发生异常一定回滚
buy 是一个 public 的方法(需要使用到事务控制)
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
no-rollback-for="java.lang.NullPointerException,org.example.execption.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>
<aop:config>
id: 切入点表达式的名称 唯一值
expression: 切入点表达式,指定那些类要使用事务,aspectj 会创建代理对象
* *..service..*.*(..)): 返回值类型 *目录层级下的 /service/* 层级目录下 /*class/*(方法)(..) [* => 任意]
-->
<!-- 找到所有需要添加配置的方法 -->
<aop:pointcut id="servicePointcut" expression="execution(* *..service..*.*(..))"/>
<!-- 配置增强器: 关联 advice 和 pointcut -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePointcut"/>
</aop:config> -
分析
spring 与 web 项目
-
需求
Web
项目中容器对象只需要创建一次, 把容器对象放入到全局作用域 ServeletContext
中 -
实现方案
使用
监听器
,当全局作用域对象被创建时, 创建容器, 存入 ServletContext
-
监听器作用
- 创建容器对象,
执行 ApplicationContext ac = new ClasspathXmlApplicationContext("applicationContext.xml")
- 把容器对象放入到
ServletContext
,ServletContext.setAttribute(key,ac)
- 创建容器对象,
-
具体配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- 监听器依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- 2. web.xml 注册监听器
注册监听器: ContextLoaderListener监听器被创建对象后, 会读取 /WEB-INF/spring.xml 为什么要读取配置文件: 因为在监听器中要创建 ApplicationContext 对象, 需要加载配置文件
/WEB-INF/applicationContext.xml 就是监听器默认读取的 spring 配置文件
可以修改默认的文件位置,使用 context-param 重新指定文件的位置
-->
<context-param>
<!-- contextConfigLocation: 表示配置文件的路径 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextCleanupListener</listener-class>
</listener>