0%

确定编码,建立文件

首先确定我们的编码都是UTF-8

image-20220519151709577

建立文件i18n(internationalization,表示18个字母,i开始,n结束)

image-20220519151957745

设置配置文件

image-20220519152305416

下载插件,进行可视化配置

image-20220519152944226

我们自己添加一个属性之后,会出现如图页面

image-20220519153107460

在这里设置翻译的语句等等,每当我们设置一个,需要重新新建一个变量

image-20220519153404436

我们全部配置完毕之后,查看login.properties

image-20220519153647754

配置文件配置

找到application.yml

image-20220519153903087

更改前端页面

image-20220519154631731

前端页面自动检测到我们的配置文件,打开网页,会发现我们的页面的显示的根据我们的设置的原文,更改为相应的文本,但是此时仅仅 实现了展示原文,并没有实现切换

接下来我们需要加上前端的请求,我们写入两个按钮button,点击之后分别发送请求

1
th:href="@{/index(language='zh_CN')}中文"

获得请求

我们接受到按钮的请求,更改返回的语言

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
package com.zss.rbacproject.config;

import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

public class MyLocalResolver implements LocaleResolver {
//放到bean里面
@Bean
public LocaleResolver localeResolver(){
return new MyLocalResolver();
}

@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取语言参数请求
String language=request.getParameter("language");
//获得默认的编码
Locale locale=Locale.getDefault();
//如果携带了国际化参数
if (language!=null||language!=""){
String []split=language.split("_");
//新建立国家地区
locale=new Locale(split[0],split[1]);

}
return locale;
}

@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

}
}

@RequestMapping注解

使用方法

从注解名称上我们可以看到,@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系。

SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的控制器方法来处理这个请求。

如果在多个controller中对同一个地址进行响应了,那么服务器会进行报错

注解位置

image-20220423200039239

标明了该注解的使用位置可以在类名和方法上

@RequestMapping标识一个类:设置映射请求的请求路径的初始信息

@RequestMapping标识一个方法:设置映射请求请求路径的具体信息

也就是说如果在类名上进行了注解,也在方法名上进行了注解,那么响应的路径就是(类注解地址+方法注解地址)

1
2
3
4
5
6
7
8
9
@Controller
@RequestMapping("/test")
public class RequestMappingController {
//此时请求映射所映射的请求的请求路径为:/test/testRequestMapping
@RequestMapping("/testRequestMapping")
public String testRequestMapping(){
return "success";
}
}

属性

value

1
@RequestMapping( value = {"/","/hi"})

两个请求的地址都可以被这个方法检测并且运行,其他的属性都可以不进行设置,但是value必须进行设置

method属性

通过请求的方法(get与post)进行响应,但是如果我们不设置请求的方法限制,那么我们的方法get请求与post请求都不会拒绝

比如我们知道网页中默认的都是get请求,那么控制器中的方法接受也是以默认get方法吗???

我们会发现网页还是会跳转成功,但是一旦我们给他加上的方法限制,那么请求必须符合才可以通过

1
method = RequestMethod.GET
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>主页面</title>
</head>
<body>
你好世界
<form th:action="@{/target}" method="post">
<input type="submit" value="以post方式提交表单">
</form>
</body>
</html>
1
2
3
4
@RequestMapping("/target")
public String toTarget(){
return "target";
}

image-20220423203158051

除了上述的请求方式之外,还可以在改变注解的方式,代替method方法

处理get请求的映射–>@GetMapping

处理post请求的映射–>@PostMapping

处理put请求的映射–>@PutMapping

处理delete请求的映射–>@DeleteMapping

但是注意,如果我们使用put或者delete方式进行表单提交,那么浏览器默认使用get方式进行请求,那么在响应的方法中如果定义了只能接受put方式,那么会发生错误

params

我们设置的请求的参数必须同时满足,我们才可以进行使用

“param”:要求请求映射所匹配的请求必须携带param请求参数

“!param”:要求请求映射所匹配的请求必须不能携带param请求参数

“param=value”:要求请求映射所匹配的请求必须携带param请求参数且param=value

“param!=value”:要求请求映射所匹配的请求必须携带param请求参数但是param!=value

1
2
3
4
5
<form th:action="@{/target}" method="post">
<input type="submit" value="以post方式提交表单">
<input type="text" name="username">
<input type="text" name="password">
</form>
1
2
3
4
5
6
7
@RequestMapping(value = "/target",
method = RequestMethod.POST,
params = {"username","password!=123456"}
)
public String toTarget(){
return "target";
}

如果我输入password=123456

image-20220423205229674

headers

@RequestMapping注解的headers属性通过请求的请求头信息匹配请求映射,即与我们的http协议中的请求头对应,我们可以设置请求头必须携带那一部分东西

1
2
3
4
5
@RequestMapping(value = "/target",
method = RequestMethod.POST,
params = {"username","password!=123456"},
headers = {"Host=localhost:8080"}
)

协议中必须携带host为8080的主机地址

SpringMVC支持ant风格的路径

?:表示任意的单个字符—->a?a/target,可任意访问a1a/target,alatarget等等路径,但是不能代替?,/等单个字符

*:表示任意的0个或多个字符

**:表示任意的一层或多层目录

注意:在使用**时,只能使用/**/xxx的方式,

SpringMVC支持路径中的占位符(重点)

1
<a th:href="@{/test/1}">hehie</a>
1
2
3
4
5
6
7
//接受test/(参数)的地址
@RequestMapping("/test/{id}")
//将id标记为Integer id的对应信息
public String test(@PathVariable("id")Integer id){
System.out.println(id);
return "target";
}

HttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文

注意请求体只有在post请求里才拥有,而get请求直接写在了url中

请求报文注解

RequestBody

@RequestBody可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值

1
2
3
4
5
<form th:action="@{/test}" method="post">
<input type="text" name="name" value="name">
<input type="text" name="password" value="password">
<input type="submit" value="提交">
</form>
1
2
3
4
5
@RequestMapping("/test")
public String test(@RequestBody String body){
System.out.println(body);
return "target";
}

ResponseBody

普通响应方法

1
<a th:href="@{/test}">servletapi跳转</a>
1
2
3
4
@RequestMapping("/test")
public void test(HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.getWriter().print("你好");
}

mvc方法响应

1
2
3
4
5
6
@RequestMapping("/test")
@ResponseBody
public String test() {
//加上注解之后,返回的东西不再是页面,而是响应体
return "hi";
}

响应对象

但是如果我们响应对象的话,直接返回一个对象,浏览器对这种情况是不能处理的,这时候需要将数据转换为json格式

导入json包

1
2
3
4
5
6
<!--json-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2.2</version>
</dependency>

配置Java信息

1
2
3
4
5
@RequestMapping("/test")
@ResponseBody
public User test() {
return new User("zss","2001","男",20,"200@qq.com");
}

开启驱动

1
2
<!--开启注解驱动,要这两个标签同时使用-->
<mvc:annotation-driven/>

如果加载失败,注意是否将json包出现在工件中,单单maven导入可能还是会出现问题,包会自动转换为json的字符串

image-20220426102506460

RequestEntity

1
2
3
4
5
6
7
@RequestMapping("/test")
public String test(RequestEntity<String> entity){
System.out.println(entity);
System.out.println(entity.getBody());
//除此之外还有很多方法
return "target";
}

image-20220426092937684

ResponseEntity

SpringMVC处理ajax

我们要设置点击链接而不进行跳转,只是在本页进行局部的显示

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
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>主页面</title>
<script language="JavaScript" th:src="@{/static/js/vue.js}"></script>
<script language="JavaScript" th:src="@{/static/js/axios.js}"></script>
</head>
<body>
<div id="app">
<a @click="testAjax" th:href="@{/test}">test</a>
</div>
<script type="text/javascript">
new Vue({
//获取对象
el:"#app",
//定义方法
methods:{
testAjax:function (event){
//阻止跳转
event.preventDefault();
//以axios的方式进行处理
axios({
method: "post",
//目标的url
url:event.target.href,
params:{
username:"zss",
password:"123"
}
}).then(function (response){
//成功之后所做出的事情
alert(response.data)
});
}
}
})
</script>
</body>
</html>
1
2
3
4
5
6
@RequestMapping("/test")
@ResponseBody
public String test(String username,String password) {
System.out.println(username+password);
return username;
}

@RestController注解

@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解

文件上传与下载

文件下载

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>主页面</title>

</head>
<body>
<a th:href="@{/testDown}">下载</a>
</body>
</html>
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
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/夜空.png");
//创建输入流
InputStream is = new FileInputStream(realPath);

//创建字节数组,is.available()表示创建的字节流的大小
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=天空.png");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象,传入数组,响应头,与状态描述
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}

文件上传

上传不能使用get功能,只能使用post请求

添加依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>

文件解析器进行解析,并封装为MultipartFile对象

1
2
<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象,并且分配一个id-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

设置响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RequestMapping("/textup")
public String textUp(MultipartFile photo, HttpSession session) throws IOException {
String fileName=photo.getOriginalFilename();
//获得服务器的路径
ServletContext servletContext=session.getServletContext();
String path=servletContext.getRealPath("/static/img");
//设置上传路径
//首先判断路劲存在与否
File file=new File(path);
if(!file.exists()){
file.mkdir();
}
/*File.separator表示分隔符*/
String finalPath=path+File.separator+fileName;
photo.transferTo(new File(finalPath));
return "target";
}

最终上传到out目录下,但是演示的到了target目录下,总感觉哪里配置错误了,插眼!!!

文件重名的问题

如果文件重名

1
2
3
4
//获得文件后缀,包括点
String suffixName=fileName.substring(fileName.lastIndexOf("."));
String uuid= UUID.randomUUID().toString();
fileName=uuid+suffixName;

简介

IOC(Inversion of controlller),控制反转,是一个思想,概念。把对象的创建,赋值,管理工作都交给代码之外的容器实现,也就是对象的创建是由其他外部资源完成的。

控制:创建对象,对象的属性赋值,对象之间的关系管理

正转:由开发人员在代码中,使用new 构造方法创建对象,开发人员主动管理对象

反转:把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现,由容器代替开发人员管理对象,创建对象。

容器可以是一个服务器软件,一个框架。

IOC可以降低程序之间的耦合,DI(依赖注入)是ioc的技术实现,我们只需要在程序中使用对象名称就可以,至于如何渎职,查找都由内部容器实现。

举例

在Javaweb中我们经常会正在注解中表示servlet类与映射地址之间的关系,其实这里就是将创建对象的权限交给了tomcat服务器。我们并没有在代码中实现new一个对象的实例出来,但是该类却是实实在在的运行了,当然监听器和过滤器也一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>AddServlet</servlet-name>
<servlet-class>com.zss.servlets.AddServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AddServlet</servlet-name>
<url-pattern>/add</url-pattern>
</servlet-mapping>
</web-app>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AddServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
//注意要设置编码格式,非常重要,post方式要进行设置
req.setCharacterEncoding("UTF-8");
String fName=req.getParameter("fName");
int price=Integer.parseInt(req.getParameter("price"));
int fCount=Integer.parseInt(req.getParameter("fCount"));
String remark=req.getParameter("remark");
Integer fid=Integer.parseInt(req.getParameter("fid"));


FruitDAOImpl fruitDAO=new FruitDAOImpl();
try {
fruitDAO.addFruit(new Fruit(fName,price,fCount,remark,fid));
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
}

}
}

演示

加入maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 1.Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.19</version>
</dependency>
<!--spring config-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.19</version>
</dependency>

</dependencies>

创建类

1
2
3
public interface testService {
public void doService();
}
1
2
3
4
5
6
public class testServiceImpl  implements testService {
@Override
public void doService() {
System.out.println("执行了service方法");
}
}
1
2
3
4
5
public static void main(String[] args) {
//这里使用了正转的方法,创建类交给我们来进行
testService test=new testServiceImpl();
test.doService();
}

创建xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--告诉spring创建对象,id对象自定义名称,class类的全限定名称-->
<bean id="test" class="com.zss.service.impl.testServiceImpl"/>
<!--而spring把创建的文件放入到map中了,实际上执行了springmap.put(id,对象),所以一个bean标签只能声明一个对象-->

</beans>
<!--spring文件的配置文件
beans是跟标签,spring把Java对象称为beans
spring-beans.xsd是约束文件-->

测试

类路径:即我们编译程序之后,在target/classes的路径下面

image-20220503153456871

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
//使用spring容器创建对象
//指定spring配置文件名称
String config="beans.xml";
//创建spring容器的对象,applicationcontext---代表spring容器
//ClassPathXmlApplicationContext表示从类路径中加载文件
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(config);
//从容器中根据id获得类的真实类型
testService test=(testService) applicationContext.getBean("test");
test.doService();
}

验证

那么这个spring框架是在那一步将对象创建出来的呢??

我们首先写入无参构造器,如果对象被创建,那么此时构造器会被调用

1
2
3
public testServiceImpl() {
System.out.println("对象被创建了");
}

在debug模式下进行,当我执行完毕ApplicationContext 时,发现无参构造器被调用了,此时对象已经被创建,而下一步只是在map中取出值而已,也就是说在创建spring容器时,所有对象就已经被创建了

image-20220503155311168

当然spring也可以创建一个非自定义对象,同时在创建对象时调用类的无参构造器。

spring获取对象的信息

1
2
3
4
5
6
7
8
//获取容器中定义对象的数量
int nums=applicationContext.getBeanDefinitionCount();
System.out.println(nums);
//获取容器中每一个定义对象的名称
String []names=applicationContext.getBeanDefinitionNames();
for (String name:names){
System.out.println(name);
}

image-20220503160008099

di实现的语法有两种:

  1. spring配置文件完成,使用标签和属性
  2. 使用spring注解

di语法分类:

  1. set注入,spring调用set方法,在set方法中可以实现属性的赋值
  2. 构造注入,调用构造方法,创建对象

创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.zss.pojo;

/**
* @author zss
*/
public class Student {
String name;
Integer age;

public void setName(String name) {
this.name = name;
}

public void setAge(Integer age) {
this.age = age;
}
}

set方法赋值

1
2
3
4
5
6
<!--spring规定Java对象的基本数据类型与String都是简单类型,下面的这种方法调用了set方法,哪怕你没有定义这个变量,但是写入的set方法,这样也会成功-->
而且不管这个类是不是我们自己定义的,比如系统的Date类,只要他有set方法,我们就可以赋值
<bean id="myStudent" class="com.zss.pojo.Student">
<property name="age" value="20"/>
<property name="name" value="里斯"/>
</bean>

测试

1
2
3
4
5
6
public static void main(String[] args) {
String config="applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(config);
Student student= (Student) applicationContext.getBean("myStudent");
System.out.println(student);
}

image-20220503162615359

小插曲:Junit

单元测试,一个工具类库,做测试的使用,单元:指定的是方法,一个类中有很多方法,一个方法被称为单元

使用单元测试:

加入依赖

1
2
3
4
5
6
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>

创建一个测试类

在src/test/Java下面,创建测试方法,没有返回值,方法名称自定义,方法没有参数,并且拥有@test注解,这样的方法可以单独执行

image-20220503163340953

1
2
3
4
5
6
7
8
9
10
11
public class AppTest 
{
@Test
public void test01() {
String config = "applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
Student student = (Student) applicationContext.getBean("myStudent");
System.out.println(student);
}

}

引用类型赋值

现在student拥有除了拥有以上的信息外,还拥有一个school,而且school拥有名称和地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.zss.pojo;

public class School {
String name;
String address;

public void setName(String name) {
this.name = name;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
<bean id="myStudent" class="com.zss.pojo.Student">
<property name="age" value="20"/>
<property name="name" value="里斯"/>
<property name="school" ref="school"/>
</bean>
<bean id="school" class="com.zss.pojo.School">
<property name="name" value="北京大中学"/>
<property name="address" value="北京"/>
</bean>

image-20220505084434943

构造方法赋值

1
2
3
4
5
<bean id="myStudent" class="com.zss.pojo.Student">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="20"/>
<constructor-arg name="school" ref="school"/>
</bean>
1
2
3
4
5
public Student(String name, Integer age, School school) {
this.name = name;
this.age = age;
this.school = school;
}

顺序赋值

1
2
3
4
5
<bean id="myStudent" class="com.zss.pojo.Student">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="20"/>
<constructor-arg index="2" ref="school"/>
</bean>

自动注入

前面的注入太麻烦,如果有多个值,就需要写很多行(此方法仅仅对引用类型有效

按照名称注入

Java的引用数据类型和spring的配置文件的id名称一致,数据类型一致可以自动赋值,此时student的school通过Name找到了id为school的类型,但是注意此时构造方法也要改变

1
2
3
4
5
6
7
8
<bean id="myStudent" class="com.zss.pojo.Student" autowire="byName">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="20"/>
</bean>
<bean id="school" class="com.zss.pojo.School">
<property name="name" value="北京大中学"/>
<property name="address" value="北京"/>
</bean>

按照类型注入

Java中引用数据类型与spring容器中的的class属性关系同源,这样的bean能够赋值给引用数据类型,这一次匹配的是class,注意如果我们class为school的子类,也可以完成赋值操作,当然接口与接口的实现类也拥有这种关系

1
2
3
4
5
6
7
8
<bean id="myStudent" class="com.zss.pojo.Student" autowire="byType">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="20"/>
</bean>
<bean id="myschool" class="com.zss.pojo.School">
<property name="name" value="北京大中学"/>
<property name="address" value="北京"/>
</bean>

多配置文件

  1. 每一个文件的大小比一个文件要小

  2. 避免多人竞争带来的冲突

    (一个模块配置一个文件或者按照类的功能(做事务的一个文件,做service一个配置文件))

    比如:

    spring-student

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="myStudent" class="com.zss.pojo.Student" autowire="byType">
    <constructor-arg index="0" value="张三"/>
    <constructor-arg index="1" value="20"/>
    </bean>
    </beans>

spring-school

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myschool" class="com.zss.pojo.School">
<property name="name" value="北京大中学"/>
<property name="address" value="北京"/>
</bean>
</beans>

spring-total

1
2
3
4
5
6
7
8
<?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">

<import resource="classpath:spring-student.xml"/>
<import resource="classpath:spring-school.xml"/>
</beans>

其实我们也可以使用通配符

,但是注意不同包含该文件,否则会一直循环读取

事务

简介

当我们的事务涉及到多个表,或者多个sql语句时,需要我们进行加入到一个事务中数据库事务 | 偷掉月亮 (moonshuo.cn)

在哪里添加事务???

我们仔细观察在我们的三层结构dao,service与controller层中,dao控制单个语句的执行,但是service是对象完成的操作的地方,比如添加学生,查询表格最终都是service集成了dao的一个或者多个sql,事务控制应该在service进行

两种控制事务的方式:

  1. jdbc:connection.commit(),connection.rollback()
  2. mybaits:sqlSession.commit,sqlSession.rollback

事务的处理方式有什么不足:

  1. 不同的数据库访问技术,处理事务的对象,方法不同
  2. 需要掌握数据库事务的处理逻辑,什么时间提交等等

解决方法:

​ spring提供了处理事务的统一模型 ,可以完成mybaits等等事务处理机制

(来自动力节点)

image-20220507171225464

事务内部提交,回滚事务,使用的事务管理器对象,代替你完成commit,rollback
事务管理器是一个接口和他的众多实现类。
接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback
实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
mybatis访问数据库—spring创建好的是DataSourceTransactionManager
hibernate访问数据库—-spring创建的是HibernateTransactionManager

方法

我们不需要进行代码编写,但是需要告诉spring使用哪一种技术

1.需要声明事务管理器对象

2.开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务。
spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。
spring给业务方法加入事务:
在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知

1
2
3
4
5
6
7
8
9
10
11
@Around("你要增加的事务功能的业务方法名称")
Object myAround(){
开启事务,spring给你开启
try{
buy(1001,10);
spring的事务管理器.commit();
}catch(Exception e){
spring的事务管理器.rollback();
}

}

3.在你的方法的上面加入@Trancational

其他

你的业务方法需要什么样的事务,说明需要事务的类型。
说明方法需要的事务:
1)事务的隔离级别:有4个值。
DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
➢ SERIALIZABLE:串行化。不存在并发问题。

  1. 事务的超时时间: 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
    单位是秒, 整数值, 默认是 -1.

3)事务的传播行为 : 控制业务方法是不是有事务的, 是什么样的事务的。
7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的。

1
2
3
4
5
6
7
8
9
10
11
//指定的方法必须在事务内执行,若当前存在事务,就加入到当前事务中,若当前没有事务,则创建一个新的事务,是spring默认的事务传播行为,比如方法1调用方法2,而方法1本身已经声明事务,而方法2被 PROPAGATION_REQUIRED修饰,那么方法2与方法1同属于一个事务,如果方法1不是事务,那么方法2会自动创建一个事务
PROPAGATION_REQUIRED
//有事务的时候,新建事务,将原来的挂起,没有事务,也创建事务
PROPAGATION_REQUIRES_NEW
//如果方法被设置成PROPAGATION_SUPPORTS,那么该方法拥有事务也可以,不拥有事务也可以执行,查询操作有无事务皆可
PROPAGATION_SUPPORTS
//几乎不会使用
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED

当我们的业务方法执行成功,没有异常抛出,spring在方法执行之后提交事务,调用commit方法,而当出现异常的时候,spring调用回滚方法

示例演示

代码

商品表

image-20220507194334479

销售记录表

image-20220507194403669

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BuyServiceImpl implements BuyService {
GoodsDAO goodsDAO;
SaleDAO saleDAO;
@Override
public void buyGood() {
//我首先购买了一部手机所以要获得手机这个对象
Goods goods=goodsDAO.getGood("手机");
//sale中加入这个
saleDAO.addSale(goods);
//减少数量
goodsDAO.reduceGoodNum(goods);
}

public void setGoodsDAO(GoodsDAO goodsDAO) {
this.goodsDAO = goodsDAO;
}

public void setSaleDAO(SaleDAO saleDAO) {
this.saleDAO = saleDAO;
}
}

我们正常测试会没有发生异常,但是加入我们人为的加入异常措施,我们会发现出现了问题

image-20220507204906248

解决

@Transactional

适合中小项目使用的, 注解方案。
spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务。@Transactional注解是spring框架自己注解,放在public方法的上面,表示当前方法具有事务。可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等

propageation:设置事务的传播属性

isolcation:设置事务的隔离级别

readOnly:设置该方法对数据库的操作是否只读

rollbackFor表示需要回滚的异常类

等等

使用步骤

  • 声明事务管理器
1
2
3
4
5
6
7
<!--启用事务管理器的事务处理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定对哪一个事务进行管理-->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--开启事务注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
  • 方法上面加注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,readOnly = false,rollbackFor = {

ArithmeticException.class})
//其实上面的赋值大都是默认的,而且默认运行异常就会回滚事务,所以只要使用@Transactional也可以,但是此注解只会对public起作用
public void buyGood() {
//我首先购买了一部手机所以要获得手机这个对象
Goods goods=goodsDAO.getGood("手机");
saleDAO.addSale(goods);
System.out.println(10/0);
goodsDAO.reduceGoodNum(goods);
}

public void setGoodsDAO(GoodsDAO goodsDAO) {
this.goodsDAO = goodsDAO;
}

public void setSaleDAO(SaleDAO saleDAO) {
this.saleDAO = saleDAO;
}
}

此时就不会发生以上的错误了

Aspectj

适合大型项目,拥有很多的类方法需要大量的配置,使得业务方法和事务配置完全分离

加入依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

声明事务管理器,方法需要的事务属性

1
2
3
4
5
6
7
8
9
10
11
12
<!--启用事务管理器的事务处理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定对哪一个事务进行管理-->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--声明业务方法事务属性,id表示这一段的配置内容-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--<tx:attributes>配置事务属性,name配置方法名称,当然也可以使用通配符,也可以在这里配置隔离级别等信息-->
<tx:attributes>
<tx:method name="buyGood"/>
</tx:attributes>
</tx:advice>

配置aop,指定那些类需要创建代理

1
2
3
4
5
6
7
<!--配置aop,表示那些类需要应用事务-->
<aop:config>
<!--execution(* *..service..*.*(..)),表示所有返回值 ,所有包和子包的service中的子包的所有类和所有方法-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!--配置增强器,,关联事务的配置属性-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>

这样程序的事务也完成了

加入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<?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>org.example</groupId>
<artifactId>Spring-MyBaits</artifactId>
<version>1.0-SNAPSHOT</version>

<name>Spring-MyBaits</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--spring config-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.19</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>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>

<!--阿里巴巴数据库连接池依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
</dependencies>
<build>
<!--使得不在resoures文件下的依赖也可以被编译-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
<include>**/*.html</include>
</includes>
</resource>
</resources>
</build>
</project>

创建实体类

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
package com.zss.pojo;

/**
* @author zss
*/
public class Student {
private Integer id;
private String name;
private String email;
private Integer age;

public Student() {
}

public void setId(Integer id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setEmail(String email) {
this.email = email;
}

public void setAge(Integer age) {
this.age = age;
}

public Integer getId() {
return id;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

public Integer getAge() {
return age;
}

public Student(Integer id, String name, String email, Integer age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}

配置xml文件

studentdao.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.zss.dao.StudentDAO">
<select id="getStudentList" resultType="com.zss.pojo.Student">
select * from student order by id;
</select>

<insert id="insertStudent">
insert into student values (#{id},#{name},#{email},#{age})
</insert>
</mapper>

主配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!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.zss.pojo"/>
</typeAliases>

<!--该包下面的所有包配置文件-->
<mappers>
<package name="com.zss.dao"/>
</mappers>
</configuration>

Service方法

1
2
3
4
5
public interface StudentService {
List<Student> getStudents();

int insertStudent();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StudentServiceImpl implements StudentService {

private StudentDAO studentDAO;

/**使用set注入进行赋值*/
public void setStudentDAO(StudentDAO studentDAO) {
this.studentDAO = studentDAO;
}

@Override
public List<Student> getStudents() {
return studentDAO.getStudentList();
}

@Override
public int insertStudent() {
return studentDAO.insertStudent();
}
}

spring配置文件

数据源

druid进入官网,查看配置,网络不可,插眼???

1
2
3
4
5
6
7
8
9
10
<!--声明数据源对象,使用阿里巴巴的druid连接池-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!--使用set注入提供配置信息,如果在properties文件中进行看配置,则需要使用${属性名称的方式},十jdbc了解到他的位置
<context:property-placeholder location="jdbc.properties"/>
-->
<property name="url" value="jdbc:mysql://localhost:3306/school"/>
<property name="username" value="root"/>
<property name="password" value="200101"/>
</bean>

mybaits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!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.zss.pojo"/>
</typeAliases>

<!--该包下面的所有包配置文件-->
<mappers>
<package name="com.zss.dao"/>
</mappers>
</configuration>

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
<?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="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!--使用set注入提供配置信息-->
<property name="url" value="jdbc:mysql://localhost:3306/school"/>
<property name="username" value="root"/>
<property name="password" value="200101"/>
</bean>
<!--声明sqlSessionFactoryBean类,创建sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--设置植入数据库的连接池-->
<property name="dataSource" ref="myDataSource"/>
<!--找到mybaits主配置文件的位置,如果要在主配置文件中加入其他文件的配置,需要使用classpath,赋值需要使用value-->
<property name="configLocation" value="classpath:mybaits.xml"/>
</bean>

<!--创建dao,MapperScannerConfigurer这个类在内部调用get-mapper生成每个接口的代理对象,同时会扫描包名,每一个接口执行getMapper的方法,得到dao对象放到容器中,而且我们可以发现这里的dao都是第一个首字母小写-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
为每一个类似studentdao.xml创建一个sqlSessionFactory
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.zss.dao"/>
</bean>

<!--声明service-->
<bean id="studentService" class="com.zss.service.impl.StudentServiceImpl">
<property name="studentDAO" ref="studentDAO"/>
</bean>
</beans>

我们尝试输出这些创建的对象,我们可以看到sqlsessionfactory已经被创建出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AppTest 
{
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(config);
String[] names=applicationContext.getBeanDefinitionNames();
for (String name:names){
System.out.println(name);
System.out.println(applicationContext.getBean(name));
}
}
}

image-20220507161304645

测试

1
2
3
4
5
6
7
8
9
10
11
12
//这些方法的提交是自动提交的
@Test
public void test02(){
String config="applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(config);
List<Student> list=new ArrayList<>();
StudentService studentService=(StudentService) applicationContext.getBean("studentService");
list=studentService.getStudents();
for (Student student:list){
System.out.println(student);
}
}

image-20220507163128231

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.19</version>
</dependency>

加入此注解,也会间接加入spring-aop依赖

注解-创建对象

@component

用于创建对象,等同于bean功能,value是bean的id值,value值是唯一的,创建的对象在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
package com.zss.pojo;

import org.springframework.stereotype.Component;

/**
* @author zss
*/
//我们也可以不指定对象名称,spring默认指定对象名称,第一个字母小写

@Component(value = "myStudent")
public class Student {
String name;
Integer age;
School school;

public Student(String name, Integer age) {
this.name = name;
this.age = age;
}

public Student() {
}

public void setName(String name) {
this.name = name;
}

public void setAge(Integer age) {
this.age = age;
}

public void setSchool(School school) {
this.school = school;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}

扫描组件

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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 http://www.springframework.org/schema/context/spring-context.xsd">

<!--声明组件扫描器-->
<context:component-scan base-package="com.zss.pojo"/>
</beans>

测试

1
2
3
4
5
6
7
8
@Test
public void test01() {
String config = "applicationContext.xml";
//通过无参构造方法进行设置
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
Student student = (Student) applicationContext.getBean("myStudent");
System.out.println(student);
}

image-20220505103302621

其他

  1. @Repository(用在持久层上面):放在dao的实现类上面,表示创建dao对象,dao对象访问数据库
  2. @Service(用在业务层上面,放在service实现类上面),创建service对象,service对象是做业务处理,可以有事务等功能
  3. @Controller,用在控制器上面,创建控制器对象,接受用户的参数,显示请求的处理结果

以上的三个和@Component一样的,都能创建对象,但是还有额外的功能

注解-赋值

@Value简单类型赋值

在属性定义上,不需要set方法,当然也可以放到set方法上进行赋值

1
2
3
4
@Value("张三")
String name;
@Value("15")
Integer age;

引用数据类型赋值

@Autowired

自动注入,默认使用bytype注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.zss.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("myschool")
public class School {
@Value("北京大学")
String name;
@Value("北京")
String address;

public void setName(String name) {
this.name = name;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
1
2
3
4
5
6
@Value("张三")
String name;
@Value("15")
Integer age;
@Autowired
School school;

image-20220505110723199

我们也可以设置为ByName的方式,需要加入@Qualifier进行指定@Qualifier(value=“bean的id”)

1
2
3
@Autowired
@Qualifier("myschool")
School school;

@Autowired拥有一个required的属性,默认为true,表示如果是没有这个属性值,则控制台报告错误,如果我们更改成为false,则表示如果没有这个值,则不会给该值赋值

@Resource

1
2
3
4
5
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>

这个注解来源于jdk,并不是来源于spring,默认byname,首先会使用byname,如果不符合,自动使用bytype方式

1
2
@Resource
School school;

如果我们一定要使用byname类型

1
2
@Resource(name = "myschool")
School school;

总结

xml文件和注解的方式,xml文件可以与源代码类独立分开,更改时比较简单,而注解的方法比较适合不经常更改的方式。

AOP简介

AOP的底层是基于动态代理实现的,而动态代理实现有以下两种方式

  1. jdk动态代理:使用jdk中的Proxy,InvocationHanderl以及反射进行处理(jdk动态代理要求目标类必须实现接口)
  2. Oglib动态代理:第三方的工具库,创建代理对象,原理是继承,通过继承目标类,创建子类,但是目标类与方法都不是final的

动态代理可以在目标类源代码不改变的原来方法下进行更改,减少代码的重复,专注代码的逻辑,解耦合(使事务与非事务进行分离)

AOP:面向切面编程,可以使用以上的两种代理方法,把动态代理的实现步骤,方式都定义好了,让开发人员使用一种统一的方式

切面(Aspect):给目标类增加的功能就是切面,切面一般都是非业务方法,可以独立使用的。

AOP的使用注意事项:

  1. 什么功能下使用切面
  2. 合理安排切面的执行时间(哪一个方法之前,哪一个方法之后)
  3. 合理的安全切面的执行位置,对哪一类,哪一个方法增加功能

术语

Aspect:切面,表示增强的功能

JoinPoint:连接点,链接业务方法和切面的位置

PointOut:连接点方法的集合

目标对象:哪一个类增加功能

Advice:通知,切面功能执行的时间

AOP的实现

spring内部实现了AOP,在进行事务处理的时候使用了aop,但是在项目的开发的时候不使用spring的aop,springaop太过于笨重。我们经常使用aspectJ,一个专门做aop的框架,当然spring框架也集成了aspectj

切入点表达式(aspectj)

execution(权限名 返回值 方法名(参数))

execution(public * * (…)):指定切入点为任意公共方法

execution(* set*(…)):任意一个set方法

image-20220507090311046

任意类中的任意方法,比如service包中的userservice的index方法

image-20220507090606468:在service包或者子包里面任意类的任意方法。..出现在类名的时候,后面必须跟*,表示包或者子包下面的所有类

image-20220507090859800:表示所有包service子包所有类(接口中所有方法都为切入点)

加入依赖项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjtools -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>

注解

@Aspect是框架注解,表示当前的类是切面类,在此类中定义方法,要求是公共方法,方法名可以自定义,但是如果含有参数,只有一些参数类型卡哇伊使用

@Before前置通知注解

1
2
3
4
5
6
7
8
@Aspect
public class MyIncation {
//在目标方法之前进行执行,value表示切入点位置,在saygood(方法前面执行),如果拥有参数的话,需要在参数的位置使用Interger,String等等类型,不需要参数名称
@Before(value = "execution( void com.zss.service.impl.TestServiceImpl.*(..))")
public void myBefore(){
System.out.println("我是前置方法");
}
}

@AfterReturning后置通知

属性value表示切入点表达式,returning自定义变量,表示目标方法的返回值,自定义变量名必须和通知方法的形参名一样,当然也可以不接受返回值

1
2
3
4
5
6
7
8
9
//res是目标方法的返回值,可以根据返回值做切面的处理,如果没有返回值则为空,Object res相当于首先执行了sayhi等方法,之后拿到返回值进行后续处理
@AfterReturning(value = "execution( void com.zss.service.impl.TestServiceImpl.*(..))",returning = "res")
public void myAfter(Object res){
System.out.println("我是后置方法");

if (res==null){
System.out.println("返回值为空");
}
}

@Around环绕通知

环绕通知必须含有一个返回值,此时发环绕通知首先调用时,不是执行测试中的方法,而是执行了这里定义的myAround方法

1
2
3
4
5
6
7
8
9
10
11
12
13
/*可以在方法执行前,执行后都能增强功能,可以控制目标方法是否被执行,修改原来目标方法的执行结果,影响最后的调用
* ProceedingJoinPoint相当于jdk动态代理的method,执行目标方法,获得返回值*/
@Around(value = "execution( * com.zss.service.impl.TestServiceImpl.*(..))")
public Object myAround(ProceedingJoinPoint point) throws Throwable {
//实现环绕通知
Object result;
System.out.println("环绕通知前");
//目标方法调用
result=point.proceed();
System.out.println("提交事务");

return result;
}

那么我们如何控制该方法是否执行那???

1
JoinPoint表示切入点
1
2
3
4
//我们可以对这个进行判断,再去决定是否调用原方法
if (point==null){
//目标方法调用
result=point.proceed();}

当然我们也可以根据这个方法改变我们目标方法的返回值,环绕通知经常用于做事务,在执行前开启事务,执行完毕后关闭事务

@AfterThrowing

在程序发生异常时,执行的代码

1
2
3
4
@AfterThrowing(value = "execution( * com.zss.service.impl.TestServiceImpl.*(..))",throwing = "ex")
public void myThrowing(Exception ex){
System.out.println("发生了异常,发送短信");
}

image-20220507110115922

@After

最终通知,总是会执行,可以用来清除资源,不管中间是否会发生异常

创建配置文件

1
2
3
4
5
6
7
8
9
<!--声明目标-->
<bean id="someService" class="com.zss.service.impl.TestServiceImpl"/>

<!--声明切面-->
<bean id="myAspect" class="com.zss.handle.MyIncation"/>

<!--声明自动代理生成器,使用aspectj框架内部的功能,创建目标对象的代理对象,创建代理对象
是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象,所以目标对象就是被修改之后的代理对象,会把容器中所有的符合条件对象一次性生成代理对象-->
<aop:aspectj-autoproxy/>

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AppTest 
{
@Test
public void test01() {
String config="applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
TestService proxy=(TestService) applicationContext.getBean("testService");
System.out.println(proxy.getClass().getName());
//通过代理的对象执行方法,实现目标方法执行时,增强功能
proxy.sayGood();
}
}

image-20220507095210535

可以看出,此时的对象已经不再是testservice对象了,而是jdk动态代理生成的。

@Pointcut注解

如果切入点表达式含有多个重复的切入点。可以 使用此注解

1
2
3
4
@Pointcut(value = "execution( * com.zss.service.impl.TestServiceImpl.*(..))")
public void mypt(){
//无需代码
}

当别的使用时,可以直接进行引用

1
2
3
4
@AfterThrowing(value = "mypt()",throwing = "ex")
public void myThrowing(Exception ex){
System.out.println("发生了异常,发送短信");
}

注意

此时我们使用接口,此时aop使用的是jdk的动态代理方式,如果我们去除接口,此时使用的是cglib的动态代理。当然如果我们使用接口,也仍旧可以使用cglib的动态代理方式,但是需要在xml文件进行配置

1
<aop:aspectj-autoproxy proxy-target-class="true"/>

ParameterType

写在mapper文件中的一个属性,表示dao接口方法的参数的数据类型

比如我要查询一个id为1的对象

1
2
Student student=dao.selectStudent(1);
System.out.println(student);
1
2
3
<select id="selectStudent" parameterType="java.lang.Integer" resultType="com.zss.pojo.Student">
select * from student where id=#{id}
</select>

ParameterType里面可以存储mybaits的别名,也可以存储全类型,

image-20220502111313219

但是注意ParameterType不是强制的,通过反射机制也可以获得的参数类型

简单数据类型

mybaits把Java的基本数据类型和String都叫做简单类型,在mapper文件中就是一个#{任意字符},也就是说不必与传入参数的名称必须相同

1
2
//查询单个student对象
Student selectStudent(Integer id);
1
2
3
<select id="selectStudent" parameterType="java.lang.Integer" resultType="com.zss.pojo.Student">
select * from student where id=#{studentid}
</select>

我们使用mybaits实际还是使用了jdbc进行操作,只不过是在mybaits内部进行操作,

  1. mybaits创建connection对象,preparedStatement
  2. 执行封装为resultType的对象,设置参数,并且返回该对象

多个参数传值方式

使用@Param

1
2
//查询单个student对象
Student selectStudent(@Param("myid") Integer id,@Param("myname")String name);
1
2
3
<select id="selectStudent"  resultType="com.zss.pojo.Student">
select * from student where id=#{myid} and name=#{myname}
</select>

使用参数对象

1
2
//查询单个student对象
Student selectStudent(Student student);
1
2
3
4
5
6
public static void main( String[] args ) throws IOException {
SqlSession sqlSession= MyBaitsUtils.getSqlSession();
StudentDAO dao=sqlSession.getMapper(StudentDAO.class);
Student student=dao.selectStudent(new Student(1003,"haha"));
System.out.println(student);
}
1
2
3
4
5
6
7
8
9
10
11
<!--多个参数,使用Java对象的属性值,作为参数的实际值
使用对象语法#{属性名,Javatype=类型名称,jdbctype=数据类型}
Javatype指Java中属性的数据类型
jdbctype在数据库中的数据类型
例如:#{myid,Javatype=java.lang.Integer,jdbctype=Integer}
简化方式:其中的Javatype与jdbctype的值我们可以通过反射获取,所以只需要传入属性即可

-->
<select id="selectStudent" resultType="com.zss.pojo.Student">
select * from student where id=#{id} and name=#{name}
</select>

使用按位置获取

但是这种方法缺陷明显,位置一旦调换就会出错

1
2
//查询单个student对象
Student selectStudent(Integer id,String name);
1
2
3
<select id="selectStudent"  resultType="com.zss.pojo.Student">
select * from student where id=#{arg0} and name=#{arg1}
</select>

当然我们也可以使用map获取值,但是这种方式传参太繁琐

#与$

#:占位符,告诉mybaits使用实际的参数值代替。并使用preparestatement执行sql语句,#{}代替sql语句的?,更加安全

而$字符串替换,告诉mybaits使用$字符串替换所在的位置,使用statement把sql语句和${}的内容链接起来。主要用在替换表名字,列名字,不同列排序等操作

那么我们说起$与#的区别,其实就是说preparestatement与statement的区别,那么

  1. #在sql语句中做到占位的,使用preparestatement执行sql,效率高
  2. #能够避免sql注入
  3. $不适用占位符,是字符串的链接方式,使用statement对象执行sql效率低
  4. $可以替换列名和表名

$的使用

首先我们需要确定数据是安全的,比如现在我需要进行排序,但是不清楚现在是按照姓名排序,还是按照email排序,所以现在的order by后面的参数不能明确

1
public List<Student> selectStudents(String col);
1
2
3
<select id="selectStudents" resultType="com.zss.pojo.Student">
select * from student order by ${col};
</select>
1
2
3
4
5
6
7
//在这里我们只需要更改这个参数即可,而xml文件的order by语句不用再去改变
public static void main( String[] args ) throws IOException {
SqlSession sqlSession= MyBaitsUtils.getSqlSession();
StudentDAO dao=sqlSession.getMapper(StudentDAO.class);
List<Student> students=dao.selectStudents("name");
System.out.println(students);
}