Java IO流

JavaWeb学习
1.初识web前端
Web标准
Web标准也称为网页标准,由一系列的标准组成,大部分由W3C(World Wide Web Consortium,万维网联盟)组织制定。
- 三个组成部分:
- HTML:负责网页的结构(页面元素和内容)
- CSS:负责页面的表现(页面元素的外观,位置等页面样式,如:颜色、大小等)
- JavaScript:负责页面的行为(页面元素的交互,如:鼠标点击、滑动等) ^f1dcc5
1.1 HTML
HTML(HyperText Markup Language):超文本标记语言。
- 超文本:超越了文本的限制,比普通文本更强大。除了文字信息,还可以定义图片、音频、视频等内容。
- 标记语言:由标签构成的语言。
- HTML标签都是预定义好的。例如:使用<a>展示超链接,使用<img>展示图片、<video>展示视频等。
- HTML代码直接在浏览器中运行,HTML标签由浏览器解析。
HTML结构标签:
1 |
|
语言特点:
- HTML标签不区分大小写
- HTML标签属性值单双引号都可以
- HTML语法松散
- 在HTML中无论输入多少个空格,只会显示一个,可以使用空格占位符:
1.1.1 标题标签
- 标签:<h1>…<h1> (h1 -> h6 重要程度依次降低)
- 示例:
1
2
3<h1>标题1</h1>
<h2>标题2</h2>
<h3>标题3</h3>
1.1.2 水平线标签
- 标签:<hr>
1.1.3 换行标签
- 标签:<br>
1.1.4 段落标签
- 标签:<p>…<p>
1.1.5 文本加粗标签
- 标签:<b>…</b> 或者 <strong>…</strong>
1.1.6 图片标签
- 标签:<img src=”图片路径” width=”宽度” height=”高度” alt=”替代文本”>
src
:指定图像的路径url(绝对路径/相对路径)width
:指定图像的宽度(像素/相对于父元素的百分比)height
:指定图像的高度(像素/相对于父元素的百分比)alt
:指定图像的替代文本
1.1.7 视频标签
- 标签:<video src=”视频路径” controls width=”宽度” height=”高度”>
src
:指定视频文件的路径url(绝对路径/相对路径)controls
:指定是否显示播放控件width
:指定视频的宽度(像素/相对于父元素的百分比)height
:指定视频的高度(像素/相对于父元素的百分比)
1.1.8 音频标签
- 标签:<audio src=”音频路径” controls>
src
:指定音频文件的路径url(绝对路径/相对路径)controls
:指定是否显示播放控件
1.1.9 超链接标签
- 标签:<a href=”链接地址” target=”…”>链接文本</a>
href
:指定链接地址url(绝对路径/相对路径)target
:指定链接打开方式(_self:默认值,当前窗口打开;_blank:新窗口打开)
1.1.10 布局标签
实际开发网页中,会大量频繁的使用div和span这两个没有语义的布局标签。
- 标签:<div>…</div> 或者 <span>…</span>
- 特点:
div标签
:- 一行只显示一个(独占一行)
- 宽度默认是父元素的宽度,高度默认由内容撑开
- 可以设置宽高(width、height)
span标签
:- 一行可以显示多个(不独占一行)
- 宽度和高度默认由内容撑开
- 可以设置宽高(width、height)
1.1.11 表格标签
- 标签:
- <table>…</table>:定义表格
- <tr>…</tr>:定义表格中的行,一个<tr>表示一行
- <th>…</th>:表示表头单元格,具有加粗居中效果
- <td>…</td>:表示普通单元格
- 场景:在网页中以表格(行、列)形式整齐展示数据,如:班级表。
1.1.12 表单标签
- 标签:<form action=”提交地址” method=”提交方式”>
action
:指定表单提交的地址url(绝对路径/相对路径)method
:指定表单提交的方式(get/post)get
:表单数据拼接在url后面,大小有限制。eg:?username=javapost
:表单数据放在请求体中,大小无限制。eg:username=java
- 表单项:
<input type="…">
:定义表单项,通过type属性控制输入形式<select>…</select>
:定义下拉列表,<option>定义列表项<textarea>…</textarea>
:定义多行文本输入框注意:表单项必须有name属性才可以提交。
1.2 CSS
CSS(Cascading Style Sheets):层叠样式表,用于控制页面的样式(表现)。
1.2.1 盒子模型
- 盒子:页面中的所有元素(标签),都可以看做是一个盒子,由盒子将页面中的元素包含在一个矩形区域内,通过盒子的视角更方便的进行页面布局
- 盒子模型:盒子由内容区域(content)、内边距区域(padding)、边框区域(border)、外边距区域(margin)组成。
- 属性:
width
:设置盒子的宽度height
:设置盒子的高度border
:设置盒子的边框属性,如:border: 1px solid red/#000/……;padding
:设置盒子的内边距margin
:设置盒子的外边距注意:如果只需要设置某一个方位的边框、内边距、外边距,可以在属性名后加上 -位置,如:border-top、padding-left、margin-right。
1.2.2 CSS引入方式
- 行内样式:写在标签的style属性中(不推荐)
1
<p style="color: red;">…</p>
- 内嵌模式:写在style标签中(可以写在页面任何位置,但通常约定写在head标签中)
1
2
3
4
5
6
7<style>
h1 {
<--!属性名:属性值;-->
xxx: xxx;
xxx: xxx;
}
</style> - 外联模式:写在外部一个单独的.css文件中(需要通过 link 标签在网页中引入)
1
<link rel="stylesheet" href="css/style.css">
1.2.3 CSS选择器
- 元素选择器:标签名 {…}
- id选择器:#id属性值 {…}
- 类选择器:.class属性值 {…}
优先级:id选择器 > 类选择器 > 元素选择器
1.2.4 CSS属性
颜色和背景
color
:设置文本颜色。background-color
:设置背景颜色。background-image
:设置背景图片。background-repeat
:设置背景图片的重复方式。background-size
:设置背景图片的大小。
字体和文本
font-family
:设置字体系列。font-size
:设置字体大小(记得加px)font-weight
:设置字体粗细。font-style
:设置字体样式(例如:italic)。text-transform
:设置文本的大小写(uppercase, lowercase, capitalize)。text-decoration
:设置文本的装饰(underline, overline, line-through, none)。letter-spacing
:设置字母间距。word-spacing
:设置单词间距。
边距和填充
margin
:设置外边距。padding
:设置内边距。
边框
border
:设置边框。border-width
:设置边框宽度。border-style
:设置边框样式(solid, dashed, dotted)。border-color
:设置边框颜色。border-radius
:设置圆角边框。
布局
display
:设置元素的显示类型(block, inline, flex, grid, none)。position
:设置元素的定位类型(static, relative, absolute, fixed, sticky)。top
,right
,bottom
,left
:设置定位偏移。z-index
:设置元素的堆叠顺序。overflow
:设置溢出内容的处理方式(visible, hidden, scroll, auto)。
尺寸
width
:设置元素的宽度。height
:设置元素的高度。max-width
:设置元素的最大宽度。max-height
:设置元素的最大高度。min-width
:设置元素的最小宽度。min-height
:设置元素的最小高度。
浮动和清除
float
:设置元素的浮动(left, right, none)。clear
:设置浮动的清除(left, right, both, none)。
表格
border-collapse
:设置是否合并表格边框。border-spacing
:设置表格单元之间的边框间距。caption-side
:设置表格标题的位置(top, bottom)。empty-cells
:设置是否显示空单元格(show, hide)。
其它
line-height
:设置行高text-indent
:设置首行的缩进text-align
:设置元素中文本的水平对齐方式(left/center/right/justify)
1.3 JavaScript
- JavaScript(简称:JS)是一门跨平台、面向对象的脚本语言。是用来控制网页行为的语言,它能使网页可交互。
- JavaScript和Java是完全不同的语言,不论是概念还是设计,但是基础语法类似。
- JavaScript在1995年由Brendan Eich发明,并于1997年成为ECMA标准。
- ECMAScript 6(ES6)是最新的JavaScript版本。
ECMA:ECMA国际(前身为欧洲计算机制造商协会),制定了标准化的脚本程序设计语言 ECMAScript,这种语言得到广泛应用。而JavaScript是遵守ECMAScript标准的脚本语言。
1.3.1 JS引入方式
- 内部脚本:将JS代码定义在HTML页面中
- JavaScript代码必须位于
<script>……</script>
标签之间 - 在HTML文档中,可以在任意地方,放置任意数量的<script>标签
- 一般会把脚本置于<body>标签中,这样,脚本会在页面加载后执行,改善显示速度。
1
2
3<script>
alert("Hello JavaScript!");
</script> - JavaScript代码必须位于
- 外部脚本:将JS代码定义在外部单独的JS文件中,然后引入到HTML页面中
- 外部JS文件中,只包含JS代码,不包含<script>标签,也不包含HTML代码
- <script>标签不能自闭合
1
<script src="js/demo.js"></script>
1
2// demo.js文件
alert("Hello JavaScript!");
1.3.2 基础语法
1.书写语法:
- 区分大小写:JavaScript区分大小写,变量名、函数名、关键字等必须使用正确的大小写。
- 每行结尾的分号可有可无
- 注释:
- 单行注释:// 注释内容
- 多行注释:/* 注释内容 */
- 大括号表示代码块:2.输出语句:
1
2
3
4// 判断
if (condition) {
alter(count);
} - 使用
window.alert()
函数输出警告框【window可以省略】 - 使用
document.write()
函数输出到页面 - 使用
console.log()
函数输出到浏览器控制台
1 | window.alert("Hello JavaScript!"); // 浏览器弹出警告框 |
3.变量:
- JavaScript中用
var
关键字(variable的缩写)声明变量。- var 声明的变量在其所在的函数或全局范围内有效,具有函数作用域,而不是块级作用域。这意味着即使 var 声明在块(如 if 或 for 循环)内部,变量仍然在函数范围内或全局范围内有效。
- var声明的变量可以被重复声明,这可能导致意外的行为,因此在现代JavaScript中通常不推荐使用。
1
2
3
4
5{
var a = 10;
var a = "A"; // 允许重复声明
}
console.log(a); // 输出 "A" - JavaScript是一门弱类型语言,变量可以存放不同类型的值。
1
2var a = 10;
a = "张三";// 变量 `a` 的类型从数字变成了字符串 - 变量名需要遵循如下规则:
- 组成字符可以是任何字母、下划线、$符号
- 不能以数字开头
- 建议使用驼峰命名
注意:
- ECMAScript 6(ES6)中,新增了
let
关键字来定义变量。它的作用类似于var,但是所声明的变量具有块级作用域,只在let关键字所在的代码块中有效,且不能重复声明。
1
2
3
4 {
let b = 10;
// let b = 20; // 会抛出错误,因为 `b` 已经在同一块级作用域中声明过
}
- ECMAScript 6(ES6)中,新增了
const
关键字,用来声明一个只读的常量,常量的值一旦声明就不能改变。【但是,const 并不意味着所引用的对象是不可变的。如果 const 引用的是一个对象(包括数组),对象的内容是可以改变的】
1
2
3
4
5 const c = 10;
// c = 20; // 会抛出错误,因为 `c` 是常量
const obj = { name: "张三" };
obj.name = "李四"; // 允许改变对象内部属性的值
4.数据类型:
JavaScript中的数据类型分为:原始类型和引用类型。
- 原始类型:
number
:表示数字类型,可以是整数、浮点数和 NaN(Not a Number)。string
:表示字符串类型,单引号和双引号都可以。boolean
:表示布尔类型,只能是 true 或 false。null
:表示空值,表示一个空对象。undefined
:表示未定义的值,表示一个未初始化的变量。
- 引用类型:
object
:用于存储键值对的集合。对象的键(属性)可以是字符串或符号,值可以是任何类型的数据,包括其它对象。array
:用于存储有序列表的集合。数组的元素可以是任何类型的数据,包括其它数组。可以通过索引(从0开始)访问数组的元素。function
:表示函数类型。函数在JavaScript中是”一等公民”,意味着函数可以作为变量的值,作为参数传递给其他函数,也可以作为返回值。- ……
使用
typeof
关键字可以获取变量的类型。
1
2 var a = 10;
alter(typeof a);
5.运算符:
- 算术运算符:+,-,*,/,%,++,–
- 赋值运算符:=,+=,-=,*=,/=,%=
- 比较运算符:,!=,
</mark>=
,!==
,>,<,>=,<= - 逻辑运算符:&&,||,!
- 三元运算符:条件表达式? true_value: false_value,
<mark>
与</mark>=
的区别:
==
:比较两个值是否相等,忽略类型。【会进行类型转换】===
:比较两个值是否相等,同时比较类型。【不会进行类型转换】
1
2
3
4 var a = 10;
alter(a == "10"); // true
alter(a === "10"); // false
alter(a == 10); // true类型转换:
- 字符串类型转为数字:
- 将字符串面值转为数字。如果字面值不是数字,则返回NaN。
- 其它类型转为boolean:
- Number:0和NAN为false,其它为true。
- String:空字符串为false,其它为true。
- Null和Undefined为false。
parseInt():将字符串转为整数的函数。它的语法如下:
1 | parseInt(string, radix) |
string: 你想要解析的字符串。这个字符串的开头可以包含空格,但在遇到第一个无法转换为数字的字符时,
parseInt
会停止解析并返回已经解析的部分。字符串中的非数字字符会导致解析停止。radix: 可选参数,表示进制数。它的值可以在 2 到 36 之间。如果不指定
radix
,parseInt
会根据字符串的内容自动确定进制:- 如果字符串以 “0x” 或 “0X” 开头,
parseInt
会将其视为 16 进制。 - 如果字符串以 “0” 开头,
parseInt
会将其视为 8 进制(在老版本的 JavaScript 中,现代浏览器已经不再这么处理)。 - 否则,
parseInt
会将其视为 10 进制。
- 如果字符串以 “0x” 或 “0X” 开头,
1 | parseInt("42"); // 返回 42 |
注意事项
- 如果
string
的第一个字符无法转换为数字,那么parseInt
将返回NaN
。NaN
(Not a Number) 是 JavaScript 中表示一个非数字的特殊值。- 你可以使用
isNaN()
函数来检查parseInt
的返回值是否是NaN
。
6.流程控制语句:
- if…else:用于条件判断。
- switch:用于多分支判断。
- for:用于循环。
- while:用于循环。
- do…while:用于循环。
1.3.3 JS函数
- 介绍:函数(方法)是被设计为执行特定任务的代码块。
- 定义:JavaScript 函数通过
function
关键字来定义。语法为:1
2
3
4
5
6
7
8// 方式一:
function functionName(parameter1, parameter2, ...) {
// 要执行的代码
}
// 方式二:
var functionName = function(parameter1, parameter2, ...) {
// 要执行的代码
} - 注意:
- 形式参数不需要类型。因为JavaScript 是弱类型语言,参数类型由调用者决定。
- 函数可以返回值。如果函数没有返回值 或者 return 后没有跟随任何值,则返回
undefined
。 - 返回值也不需要定义类型,可以在函数内部直接使用return返回值即可
- 调用:函数名称(实际参数列表)
1 | // 方式一: |
注意事项:
JS中,函数调用可以传递任意个数的参数。但只会截取需要的前几个
1.3.4 JS对象
1.Array
- 介绍:JavaScript 数组是一种特殊的对象,用于存储多个值。
- 定义:
1
2
3
4
5
6// 方式一:
var 变量名 = new Array(元素列表);
var arr = new Array(1, 2, 3);
// 方式二:
var 变量名 = [元素列表];
var arr = [1, 2, 3]; - 赋值/访问:
1
2
3arr[索引] = 值;
arr[10] = "hello";
alter(arr[10]);// 警告框输出hello
注意事项:
JavaScript 中的数组相当于 Java 中的集合,数组的长度是可变的,而JavaScript是弱类型,所以可以存储任意的类型的数据。
1
2
3
4
5
6
7
8
9
10 var arr = [1, 2, 3];
arr[10] = "hello";
console.log(arr[9]); // undefined
console.log(arr[10]); // hello
arr[9] = "A";
arr[8] = true;
console.log(arr);
- 属性:
length
:表示数组中元素的数量。你可以读取这个属性来获取数组的长度,也可以设置它来改变数组的长度。- 设置
length
:如果设置length
为比当前长度小的值,数组将被截断。如果设置为更大的值,数组会用undefined
填充新的位置。
- 设置
- 方法:
toString()
:将数组转换为字符串,元素之间用逗号分隔。join(separator)
:将数组转换为字符串,元素之间用指定的separator
分隔。如果不提供separator
,则使用逗号作为默认分隔符。forEach(callback)
:对数组中的每个元素执行一次提供的函数callback
。callback
接受三个参数:当前元素值、当前元素的索引、整个数组。forEach
不会改变原数组。push(element1, ..., elementN)
:将一个或多个新元素添加到数组的末尾,并返回数组的新长度。splice(start, deleteCount, item1, ..., itemN)
:用于在数组中添加或删除元素。start
是开始位置的索引,deleteCount
指定要删除的元素数量,item1, ..., itemN
是要添加的新元素。这个方法会直接修改原数组,并返回被删除的元素。
代码示例:
1 | // 创建一个数组 |
箭头函数(ES6):是用来简化函数定义语法的,具体形式为:(……) => {…},如果需要给箭头函数起名字:var func = (……) => {…}
2.String
介绍:JavaScript 字符串是一种特殊类型的对象,用于存储和操作文本。
定义:
1
2
3
4
5
6// 方式一:
var 变量名 = new String("……");
var str = new String("Hello, World!");
// 方式二:
var 变量名 = "……";
var str = "Hello, World!";属性:
length
:返回字符串的长度,表示字符串中字符的个数。
方法:
charAt(index)
:返回指定位置的字符。index
从 0 开始计算。indexOf(substring)
:返回子字符串在字符串中第一次出现的位置,如果没有找到,则返回-1
。trim()
:去除字符串两边的空格(包括换行符和制表符)。substring(startIndex, endIndex)
:提取字符串中从startIndex
到endIndex
之间的字符,endIndex
处的字符不包括在内。【包左不包右】
代码示例:
1 | // length 属性 |
3.自定义对象
- 介绍:JavaScript 中的对象是一种复杂的数据类型,可以包含多个属性和方法。
- 定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14/* var 对象名 = {
属性名1: 值1,
属性名2: 值2,
属性名3: 值3,
函数名称:function(参数列表) {} ,可以省略":function"
}; */
var person = {
name: "John",
age: 30,
gender: "Male",
eat: function() { eat(){
alert("Eating..."); == alert("Eating...");
} }
}; - 调用:
1
2
3
4/* 对象名.属性名;
对象名.方法名(); */
console.log(person.name);
person.eat();
4.JSON
- 介绍:
- JSON是通过JavaScript对象标记法书写的文本。
- JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,主要用于在网络传输数据。
1
2
3
4
5{
name: "John",
age: 30,
gender: "Male",
} - 定义:值的数据类型为:
1
2var 变量名 = '{"属性名1":"值1","属性名2":"值2","属性名3":"值3"}';
var person = '{"name":"John","age":30,"gender":"Male","addr":["北京","上海","广州"]}';- 数字(整数或浮点数)
- 字符串(在双引号中)
- 逻辑值(true 或 false)
- 数组(在方括号中)
- 对象(在花括号中)
- null
- 方法:
JSON.parse(json)
:将 JSON 字符串转换为 JavaScript 对象。JSON.stringify(obj)
:将 JavaScript 对象转换为 JSON 字符串。
1
2var jsObject = JSON.parse(person);
var jsonStr = JSON.stringify(jsObject);
5.BOM
- 介绍:BOM(Browser Object Model)是浏览器对象模型的缩写,它提供了与浏览器窗口交互的接口。JavaScript 将浏览器的各个组成部分封装为对象。
- 组成:
Window
:浏览器窗口对象Navigator
:浏览器信息对象Screen
:屏幕信息对象History
:历史记录对象Location
:地址栏对象
1 | // 获取 |
1 | // location |
6.DOM
- HTML中的Element对象可以通过
Document
对象获取,而Document
对象可以通过window
对象获取。 Document
对象中提供了以下获取Element元素对象的函数:getElementById(id)
:根据元素的id属性值获取元素对象,返回单个Element对象。getElementsByTagName(tagName)
:根据元素的标签名获取元素对象,返回一个Element对象的数组。getElementsByName(name)
:根据元素的name属性值获取元素对象,返回一个Element对象的数组。getElementsByClassName(className)
:根据元素的class属性值获取元素对象,返回一个Element对象的数组。
1
2
3
4
5
6
7
8// 根据元素的id属性值
var h1 = document.getElementById('h1');
// 根据元素的标签名
var divs = document.getElementsByTagName('div');
// 根据元素的name属性值
var hobbys = document.getElementsByName('hobby');
// 根据元素的class属性值
var clss = document.getElementsByClassName('cls');
7.事件监听
- 介绍:JavaScript 事件是发生在 HTML 元素上的交互行为,如鼠标点击、键盘按键等。
- 事件监听:JavaScript可以在事件被侦测到时执行代码。
- 事件绑定:
- 方式一:通过HTML标签中的事件属性进行绑定
1
2
3
4
5
6
7<input type="button" value="按钮" onclick="on()">
<script>
function on() {
alert("按钮被点击了");
}
</script>- 方式二:通过DOM元素属性绑定
1
2
3
4
5
6
7<input type="button" value="按钮" id="btn">
<script>
document.getElementById('btn').onclick = function() {
alert("按钮被点击了");
}
</script>
1.4 Vue
- Vue是一套前端框架,,免除原生JavaScript中的DOM操作,简化书写。
- 基于MVVM(Model-View-ViewModel)思想,实现数据的双向绑定,将编程的关注点放在数据上。
- 官网:https://cn.vuejs.org/
框架:是一个半成品软件,是一套可重用的、通用的、软件基础代码模型。基于框架进行开发,更加快捷,高效。
1.4.1 快速入门
- 新建HTML文件,引入Vue.js文件:
1
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
- 在JS代码区域,创建Vue核心对象,定义数据类型
1
2
3
4
5
6
7
8<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script> - 编写视图
1
2
3
4<div id="app">
<input type="text" v-model="message">
<p>{{ message }}</p>
</div>插值表达式:
- 形式:
{{ 表达式 }}
- 作用:将数据绑定到视图中
- 内容:变量、三元运算符、函数调用 或 算术运算
- 形式:
1.4.2 常用指令
- 指令:HTML标签上带有
v-
前缀的特殊属性,不同指令具有不同含义。 - 常用指令:
指令 | 用途 | 语法 |
---|---|---|
v-bind | 动态地绑定一个或多个特性(attributes)到一个元素上,如设置href、css样式等 | v-bind:attribute="expression" (简写:: ) |
v-model | 创建一个双向数据绑定,通常用于表单元素 | v-model="dataProperty" |
v-if | 条件渲染,根据表达式的真假条件有选择地渲染元素 | v-if="condition" |
v-else-if | 条件渲染,用于在 v-if 之外添加条件 | v-else-if="condition" |
v-else | 条件渲染,当所有 v-if 和 v-else-if 的条件都不满足时渲染元素 | v-else |
v-show | 基于条件展示元素,元素总是被渲染 ,只是简单地控制元素的显示和隐藏(display: none ) | v-show="condition" |
v-for | 基于一个数组或对象的迭代渲染一个列表。对于数组,可以获取每个元素的值和下标;对于对象,可以获取每个键值对的键和值。 | v-for="item in items" |
v-on | 监听DOM事件,并在事件发生时执行指定的JavaScript表达式或方法 | v-on:event="handler" (简写:@ ) |
v-cloak | 避免Vue实例接管模板前闪烁的内容(通常配合CSS使用) | 无表达式 |
v-pre | 跳过这个元素和它的所有子元素的编译过程,以加快初次渲染速度 | 无表达式 |
v-once | 只渲染元素和组件一次,之后就不会再更新 | 无表达式 |
v-html | 输出原始的HTML,不进行HTML转义(小心XSS攻击) | v-html="htmlContent" |
代码示例
1 | <!-- v-bind 示例 --> |
注意事项:
- 通过
v-bind
或者v-model
绑定的变量,必须在数据模型中声明
1.4.3 生命周期
- 生命周期:指一个对象从创建到销毁的整个过程。
- 八个阶段:每触发一个生命周期事件,会自动执行一个生命周期方法(钩子)。
- mounted:挂载完成,Vue初始化成功,HTML页面渲染成功。(发送请求到服务端,加载数据)
1.5 Ajax
概念:
Ajax
是Asynchronous JavaScript and XML
的缩写,表示异步的 JavaScript 和 XML。它是一种用于在不重新加载整个网页的情况下,进行数据交换并更新网页部分内容的技术。Ajax 通常利用XMLHttpRequest
对象来发送和接收数据,但它并不仅限于 XML,现代应用中也常用 JSON 格式进行数据交换。作用:
- 数据交换:通过 Ajax 可以与服务器进行数据交互,发送请求并接收响应数据,而不需要刷新整个页面。
- 异步交互:Ajax 允许网页在后台与服务器进行数据交换,从而在不刷新整个页面的情况下动态更新页面的部分内容。常见的应用场景包括:搜索联想、表单验证(如用户名是否可用)、自动加载更多内容等。
进一步说明
- 虽然
Ajax
中的XML
表示可使用 XML 进行数据交换,但现代开发中更多使用 JSON 作为数据交换格式,因为 JSON 更轻量且更易于 JavaScript 解析。- Ajax 的核心是
XMLHttpRequest
对象,但在现代开发中,fetch
API 也经常用来实现类似的异步数据交互功能。
异步VS同步:
Ajax请求流程:
XMLHttpRequest对象属性:
1.6 Axios
概念:Axios对原生的Ajax进行了封装,简化书写,快速开发。
请求方式别名:
axios.get(url [,config])
axios.delete(url [,config])
axios.post(url [,data[,config]])
axios.put(url [,data[,config]])
入门示例:
- 1.引入Axios的js文件
1
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
- 2.使用Axios发送请求,并获取响应结果
1
2
3
4
5
6
7axios.get("https://api.example.com/data").then((result) => {
console.log(result.data);
})// 处理响应结果
axios.post("https://api.example.com/data","id=1").then((result) => {
console.log(result.data);
})// 处理响应结果
1.7 Vue项目
1.8 Element组件
- 概念:是饿了么团队开发的,一套为开发者、设计师和产品经理准备的基于Vue2.0的桌面端组件库。
- 组件:组成网页的部件,例如 超链接、按钮、图片、表格、表单、分页条等。
- 官网:https://element.eleme.cn/
快速入门:
- 安装ElementUI组件库(在当前工程的目录下),在命令行执行指令:
1
npm install element-ui@2.15.6
- 引入ElementUI组件库
1
2
3
4
5// main.js文件中引入
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI); - 访问官网,复制组件代码并调整。
2.Maven
2.1 介绍
- 概念:Apache Maven 是一个项目管理和构建工具,用于管理项目的构建、报告和文档。它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建。
- 作用:
- 方便的依赖管理
- 统一的项目结构
- 标准的项目构建流程
- 官网:https://maven.apache.org/
2.2 Maven坐标
- 概念:
- Maven中的坐标是资源的唯一标识,通过该坐标可以唯一定位资源位置。
- 使用坐标来定义项目或者引入项目中需要的依赖。
- 主要组成:
groupId
:定义当前Maven项目隶属组织名称(通常是域名反写,例如:ink.lusy)artifactId
:定义当前Maven项目名称(通常是模块名,例如:order-service、goods-service)version
:定义当前Maven项目版本号(例如:1.0.0)
1 | <groupId>ink.lusy</groupId> |
2.3 依赖配置
- 依赖:指当前项目运行所需要的jar包,一个项目中可以引入多个依赖。
- 配置:
- 在pom.xml文件中编写
<dependencies>
标签 - 在
<dependencies>
标签中使用<dependency>
引入坐标 - 定义坐标的
groupId
、artifactId
、version
- 点击刷新按钮,引入最新加入的坐标
- 在pom.xml文件中编写
注意事项:
- 如果引入的依赖,在本地仓库不存在,将会连接远程仓库/中央仓库,然后下载依赖。
- 如果不知道依赖的坐标信息,可以到https://mvnrepository.com/中搜索。
2.4 依赖传递
- 依赖具有传递性
- 直接依赖:在当前项目中通过依赖配置建立的依赖关系
- 间接依赖:被依赖的资源如果依赖其它资源,当前项目间接依赖了被依赖的资源
- 依赖冲突:当依赖传递时,如果存在多个依赖,且依赖的版本号不同,则存在依赖冲突。
当直接引用或者间接引用出现了相同的jar包! 这时呢,一个项目就会出现相同的重复jar包,这就算作冲突!依赖冲突避免出现重复依赖,并且终止依赖传递!
maven自动解决依赖冲突问题能力,会按照自己的原则,进行重复依赖选择。同时也提供了手动解决的冲突的方式,不过不推荐!
解决依赖冲突(如何选择重复依赖)方式:- 短路优先原则(第一原则)
A—>B—>C—>D—>E—>X(version 0.0.1)
A—>F—>X(version 0.0.2)
则A依赖于X(version 0.0.2)。 - 依赖路径长度相同情况下,则“先声明优先”(第二原则)
A—>E—>X(version 0.0.1)
A—>F—>X(version 0.0.2)
在<depencies>\……中,先声明的,路径相同,会优先选择!
- 短路优先原则(第一原则)
小测试:
1 | 前提: |
- 排除依赖:当依赖传递时,如果存在依赖冲突,可以通过
<exclusions>
标签,排除依赖。 - 依赖范围:
2.5 生命周期
Maven的生命周期就是为了对所有的maven项目构建过程进行抽象和统一。
Maven中有3套相互独立的生命周期:
- clean:清理项目
- default:核心工作,包含编译、测试、打包、部署等操作
- site:生成报告、发布站点等。
执行指定生命周期的两种方式:
2.6 项目结构
Maven 是一个强大的构建工具,它提供一种标准化的项目结构,可以帮助开发者更容易地管理项目的依赖、构建、测试和发布等任务。以下是 Maven Web 程序的文件结构及每个文件的作用:
1 | |-- pom.xml # Maven 项目管理文件 |
- pom.xml:Maven 项目管理文件,用于描述项目的依赖和构建配置等信息。
- src/main/java:存放项目的 Java 源代码。
- src/main/resources:存放项目的资源文件,如配置文件、静态资源等。
- src/main/webapp/WEB-INF:存放 Web 应用的配置文件。
- src/main/webapp/index.html:Web 应用的入口页面。
- src/test/java:存放项目的测试代码。
- src/test/resources:存放测试相关的资源文件,如测试配置文件等。
3.HTTP
3.1 概念
HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种用于传输超文本信息的协议。规定了浏览器和服务器之间数据传输的规则。
3.2特点
- 基于TCP协议:面向连接,安全。
- 基于请求-响应模型:一次请求对应一次响应。
- 无状态:对于事务处理没有记忆能力。每次请求-响应都独立的,没有保存信息。
- 缺点:多次请求间不能共享数据。
- 优点:速度快
3.2 请求数据格式
3.3响应数据格式
常见的响应状态码
状态码 | 英文描述 | 解释 |
---|---|---|
200 | OK | 客户端请求成功。即处理成功,这是我们最想看到的状态码 |
302 | Found | 指示所请求的资源已移动到由Location 响应头给定的 URL,浏览器会自动重新访问到这个页面 |
304 | Not Modified | 告诉客户端,你请求的资源至上次取得后,服务器并未改。你直接用你本地缓存吧。隐式重定向 |
400 | Bad Request | 客户端请求有语法错误,不能被服务器所理解 |
403 | Forbidden | 服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源 |
404 | Not Found | 请求资源不存在。一般是URL输入有误,或者网站资源被删除了 |
405 | Method Not Allowed | 请求方法有误,比如本应用GET请求方式的资源,用了POST |
428 | Precondition Required | 服务器要求有条件的请求,告诉客户端需要访问该资源,必须携带特定的请求头 |
429 | Too Many Requests | 用户在给定时间内发送了太多请求(“限速”),配合Retry-After (多长时间后可以请求)响应头一起使用 |
431 | Request Header Fields Too Large | 请求头太大。服务器不愿意处理请求,因为它的头字段太大。请求可以在减少请求头域的大小后重新提交。 |
500 | Internal Server Error | 服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧 |
503 | Service Unavailable | 服务器尚未准备好处理请求。服务器刚启动,还未初始化好 |
3.4 接收请求头数据
可以使用@RequestHeader
注解将请求标头绑定到控制器中的方法参数。
请考虑以下带有标头的请求:
1 | Host localhost:8080 |
下面的示例获取 Accept-Encoding
和 Keep-Alive
标头的值:
1 |
|
4.Tomcat
4.1 概念
- Tomcat是Apache软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/JSP和少量JavaEE规范。
- Tomcat也称为Web容器、Servlet容器。Servlet程序需要tomcat才能运行。
- https://tomcat.apache.org/
JavaEE:Java企业级应用环境,指Java企业级开发的技术规范总和。包含13项技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF
4.2 基本使用
- 配置Tomcat端口号 (conf/server.xml)
1 | <Connector port="8080" protocol="HTTP/1.1" |
HTTP协议默认端口号为80,如果将Tomcat端口号改为80,则将来访问Tomcat时,将不用输入端口号。
- Tomcat部署项目:
将项目放置到webapps目录下,即部署完成
基于SpringBoot开发的web应用程序,内置了Tomcat服务器,当启动类运行时,会自动启动内嵌的Tomcat服务器,无需手动配置。
5.请求响应
请求响应:
- 请求(HttpServletRequest):获取请求数据
- 响应(HttpServletResponse):设置响应数据
BS架构:Browser-Server架构,浏览器向服务器发送请求,服务器处理请求,返回响应,浏览器接收响应,解析响应数据。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。(维护方便 ,体验一般)
CS架构:Client-Server架构,客户端向服务器发送请求,服务器处理请求,返回响应,客户端接收响应,解析响应数据。客户端需要浏览器和应用程序,应用程序的逻辑和数据都存储在客户端。(维护难,体验好)
5.1 请求
5.1.1 简单参数
- 原始方式:
在原始的web程序中,获取请求参数,需要通过HttpServletRequest
对象手动获取。通过
HttpServletRequest
对象获取请求参数,需要通过getParameter()
方法获取,且获取到的请求参数都是字符串类型,需要手动转换为其他类型。 - SpringBoot方式
- 参数名与形参变量名相同,定义形参即可接收参数,且会自动进行类型转换。【可以不传递,不会报错】
- 如果方法形参名称与请求参数名称不匹配,可以使用
@RequestParam
注解,指定请求参数的名称。【默认情况下必须传递,否则会报错】注意事项:
@RequestParam
中的required
属性默认值为true,代表该请求参数必须传递,如果不传递将会报错。如果该参数是可选的,可以将required
属性设置为false。@RequestParam
中有三个属性:name
、required
、defaultValue
,分别代表请求参数的名称、是否必须传递、默认值。
- 参数名与形参变量名相同,定义形参即可接收参数,且会自动进行类型转换。【可以不传递,不会报错】
5.1.2 实体参数
- 简单实体参数:
请求参数名与形参对象属性名相同,定义POJO即可 - 复杂实体参数:
请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数。
注意事项:
可以在实体类中直接给定默认值,如果请求参数没有传递,则使用默认值。
5.1.3 数组集合参数
- 数组参数:
请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收数组参数。 - 集合参数:
请求参数名与形参集合名称相同且请求参数为多个,@RequestParam
绑定参数关系
数组:请求参数名与形参中数组变量名相同,可以直接使用数组封装
集合:请求参数名与形参中集合变量名相同,可以使用@RequestParam
绑定参数关系
5.1.4 日期参数
- 日期参数:
使用@DateTimeFormat
注解,指定日期格式。
5.1.5 JSON参数
- JSON参数:
JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数,需要使用@RequestBody
注解标识。
5.1.6 路径参数
- 路径参数:
通过请求URL直接传递参数,使用{……}来标识该路径参数,需要使用@PathVariable
注解获取路径参数。
5.1.7 小结
5.2 响应
5.2.1 响应数据
@ResponseBody
:
- 类型:方法注解、类注解
- 位置:Controller方法上/类上
- 作用:将方法返回值直接响应,如果返回值是 实体对象/集合,将会转换为JSON格式响应。
- 说明:
@RestController
==@Controller
+@ResponseBody
如果不添加
@ResponseBody
注解,Spring会认为返回值是一个视图名称。
5.2.2 统一响应结果
6.分层解耦
6.1 三层架构
controller
:控制层,接收前端发送的请求,对请求进行处理,并响应数据service
:业务逻辑层,处理具体的业务逻辑,调用DAO层进行数据访问dao
:数据访问层(持久层),负责数据访问操作,包括数据的增删改查
6.2 分层解耦
- 内聚:软件中各个功能模块内部的功能联系。
- 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
- 软件设计原则:高内聚低耦合。
6.3 IOC & DI 入门
6.3.1 Bean的声明
要把某个对象交给IOC容器管理,需要先在对应的类上加上如下注解之一:
注解 | 说明 | 位置 |
---|---|---|
@Component | 声明Bean的基础注解 | 不属于以下三类时,使用此注解 |
@Controller | @Component的衍生注解 | 标注在控制器类上 |
@Service | @Component的衍生注解 | 标注在业务类上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上 (由于与mybatis整合,用的少) |
注意事项:
- 声明Bean的时候,可以通过value属性指定Bean的名称,如果没有指定,默认为类名首字母小写。
- 使用以上四个注解都可以声明Bean,但在SpringBoot集成web开发中,声明控制器Bean只能使用
@Controller
注解。
6.3.2 Bean组件扫描
- 前面声明Bean的四大注解,要想生效,还需要被组件扫描
@ComponentScan
扫描。 @ComponentScan
注解虽然没有显示配置,但是实际上已经包含在了启动类声明注解SpringBootApplication
中。- 默认扫描的范围是启动类所在包及其子包。
6.3.3 Bean的依赖注入
- 依赖注入的注解
@Autowired
:默认按照类型自动装配。- 如果同类型的Bean存在多个:
@Primary
:指定Bean的优先级。@Autowired + @Qualifier("Bean的名称")
:@Resource(name = "Bean的名称")
- @Resource与@Autowired区别:
- @Autowired是Spring框架提供的注解,而@Resource是JDK提供的注解
- @Autowired默认是按照类型注入,而@Resource默认是按照名称注入
7.Lombok
- Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashCode、toString等方法,并可以自动生成日志变量,简化Java开发、提高效率。
注意事项:
Lombok会在编译时,自动生成对应的Java代码。我们使用Lombok时,还需要安装一个lombok插件(idea自带)。
8.MyBatis
- MyBatis是一款优秀的持久层(dao)框架,用于简化JDBC的开发。
- MyBatis本是Apache的一个开源项目iBatis,2010年这个项目由Apache迁移到了Google Code,并且改名为MyBatis。2013年11月迁移到Github。
- 官网:https://mybatis.org
8.1 快速入门
8.2 JDBC介绍
- JDBC:
Java DataBase Connectivity,就是使用Java语言操作关系型数据库的一套API。 - 本质:
- sun公司官方定义的一套操作所有关系型数据库的规范,即接口。
- 各个数据库厂商去实现这套接口,提供数据库驱动jar包。
- 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
8.3 数据库连接池
- 概念:
- 数据库连接池是一个容器,负责分配,管理数据库连接(Connection)
- 它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
- 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
- 优势:
- 资源重用
- 提升系统响应速度
- 避免数据库连接遗漏
- 标准接口:
DataSource
- 官方(Sun)提供的数据库连接池接口,由第三方组织实现此类接口。
- 功能:获取连接
Connection getConnection() throws SQLException;
- 常见产品:
C3P0、DBCP、Druid、Hikari(springboot默认)- Durid(德鲁伊):是阿里巴巴开源的一个数据库连接池实现,它具有监控功能、SQL防火墙、SQL优化功能等。
- 切换Druid数据库连接池:
- 官方地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
- 添加依赖(pom.xml):
1
2
3
4
5
6<!--在pom.xml中添加依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
8.4 基础操作
8.4.1 参数占位符
#{……}
:- 在执行SQL时,会将
#{……}
替换为 ? ,生成预编译SQL,会自动设置参数值。 - 使用时机:参数传递,都使用
#{……}
。 - 不能直接在引号内使用,因为如果将 #{……} 放在引号内,MyBatis 会将其处理为一个普通的字符串值,而不会进行参数替换。
- 在执行SQL时,会将
${……}
:- 拼接SQL,直接将参数拼接在SQL语句中,存在SQL注入问题。
- 使用时机:如果对表名、列表进行动态设置时使用。
总结:
- 动态值 使用 #{key},动态的列名、容器名、关键字等使用 ${……}。
- ?只能替代值的位置,不能替代标签、列名、表名、关键字等。emp_id = ? ,不能写 ?= ?
1
2
3>//注解方式传入参数!!
>
>User findByColumn(; String column, String value)
8.4.2 预编译SQL
优势:
- 性能更高
- 更安全(防止SQL注入)
8.4.3 日志输出
可以在application.yml中,打开mybatis的日志,并指定输出到控制台。
1 | mybatis: |
8.4.4 删除
- SQL语句:
1
delete from emp where id = 17;
- 接口方法:
1
2
public void delete(Integer id);
注意事项:
如果mapper接口方法形参只有一个普通类型的参数,#{……}里面的属性名可以随便写,如:#{id}、#{value},但最好和参数名一致。
8.4.5 添加
无需主键返回
- SQL语句:
1
insert into emp(username, gender, email, dept_id) values ('宋江',1,1056985080@qq.com,1);
- 接口方法:
1
2
3
4
5
6
7
8
9
10
11
public void insert(Emp emp);
// emp对象
public class Emp {
private Integer id;
private String username;
private Short gender;
private String email;
private Integer deptId;
}
主键返回
- 描述:
在数据添加成功后,需要获取插入数据库数据的主键值。(eg:添加套餐数据后,还需要维护套餐菜品关系表数据) - 实现:
在接口方法前添加@Options(keyProperty = "id", useGeneratedKeys = true)
注解 => 会自动将生成的主键值,赋值给emp对象的id属性。1
2
3
8.4.6 更新
- SQL语句(根据ID更新员工信息):
1
update emp set username = '李逵', gender = 1, email = '1056985080@qq.com', dept_id = 1 where id = 17;
- 接口方法:
1
2
public void update(Emp emp);
8.4.7 查询
根据ID查询
- SQL语句:
1
select * from emp where id = 17;
- 接口方法:模糊查询
1
2
public Emp getById(Integer id); - SQL语句:
1
select * from emp where username like '%张%' and gender = 1 order by id desc;
- 接口方法:
1
2
3
4
5
6
7
public List<Emp> getByNameAndGender(String name, Short gender);
// 性能低、不安全,存在SQL注入问题
public List<Emp> getByNameAndGender(String name, Short gender);
// 性能高、安全,不会存在SQL注入问题
concat()
函数:连接字符串,eg:concat(‘%’,#{name},’%’),解决#{……}
不能直接在引号中使用的问题。
8.4.8 参数名说明
8.4.9 数据封装
- 实体类属性名 和 数据库表查询返回的字段名一致,mybatis会自动封装。
- 如果实体类属性名 和 数据库表查询返回的字段名不一致,不能自动封装。
解决方式:
起别名:在SQL语句中,对不一样的列名器别名,别名和实体类中的属性名一致。(
as
可以省略)1
2
public Emp getById(Integer id);手动结果映射:
- 通过
@Results
和@Result
注解,手动映射。
1
2
3
4
5
6
7
public Emp getById(Integer id);- 通过
<resultMap>
标签定义对应关系,再在后面的SQL语句中引用这个对应关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!-- 专门声明一个resultMap设定column到property之间的对应关系 -->
<resultMap id="selectEmployeeByRMResultMap" type="com.atguigu.mybatis.entity.Employee">
<!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
<!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
<id column="emp_id" property="empId"/>
<!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
<result column="emp_name" property="empName"/>
<result column="emp_salary" property="empSalary"/>
</resultMap>
<!-- Employee selectEmployeeByRM(Integer empId); -->
<select id="selectEmployeeByRM" resultMap="selectEmployeeByRMResultMap">
select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}
</select>- 通过
开启驼峰命名:如果字段名与属性名符合驼峰命名法则,mybatis会自动通过驼峰命名规则映射。
主要用于单层级的字段映射。它不能自动处理更复杂的场景,例如多层级的嵌套对象或深层次的属性名转换。1
2
3
4
5
6
7# 在application.yml中,开启驼峰命名自动映射开关。即从数据库字段名a_column映射到实体类属性名aCloumn。
mybatis:
configuration:
# 配置mybatis的日志, 指定输出到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启mybatis的驼峰命名自动映射开关 a_column ------> aCloumn
map-underscore-to-camel-case: true开启自动映射:
autoMappingBehavior
是 MyBatis 中的一个全局配置项,用于控制 MyBatis 如何自动映射查询结果中的列到 Java 实体类的属性上。根据配置的不同,autoMappingBehavior
可以影响映射的自动化程度,从而减少或增加手动配置的需求。它有以下三个配置选项:1
2
3
4
5
6mybatis:
configuration:
# 配置MyBatis的日志, 指定输出到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 设置MyBatis的自动映射行为
auto-mapping-behavior: NONE # 可选值:NONE, PARTIAL, FULLNONE
:MyBatis 不会自动映射任何列到实体类属性。所有的映射关系必须手动配置,通常通过@Result
注解或<resultMap>
标签来指定。这种模式适合需要对映射关系进行严格控制的场景,以避免意外的映射错误。PARTIAL
(默认值):MyBatis 会自动映射查询结果中的列到实体类属性,但只限于那些没有在ResultMap
中明确定义过的列。如果在ResultMap
中已经定义了某列的映射关系,则 MyBatis 不会再次自动映射该列。PARTIAL
是大多数情况下的推荐设置,能够在保持映射控制的同时,自动处理简单的映射任务。FULL
:MyBatis 会尝试自动映射所有查询结果中的列到实体类属性,无论这些列是否在ResultMap
中定义。FULL
设置适合简单对象的映射,能够减少手动配置的工作量。但在复杂映射场景中,可能导致意外的映射错误,因此需要谨慎使用。- 应用场景与注意事项:
autoMappingBehavior
的设置直接影响 MyBatis 的自动映射能力。设置为FULL
时,MyBatis 可以帮助自动处理简单的嵌套对象映射,但对于复杂的嵌套结构,手动映射依然是必不可少的。即使设置为FULL
或PARTIAL
,在复杂或关键场景下,建议依然手动配置映射,以确保映射的准确性和可控性。此外,在处理大数据量或复杂对象映射时,FULL
的自动映射可能增加系统负担,因此在性能敏感的应用中应谨慎选择。
8.4.10 多表映射
一对一映射:
在一对一关联中,一个实体对象包含另一个实体对象,通常通过外键关系实现。使用@One
注解、**@Result
注解、<association>
**标签均可以帮助 MyBatis 自动处理这种关系的查询和映射。示例:
假设我们有两个表:user
和address
,它们之间存在一对一关系。user
表:id
(用户ID)name
(用户名)address_id
(外键,指向address
表的 ID)
address
表:id
(地址ID)street
(街道)city
(城市)
实体类
1
2
3
4
5
6
7
8
9
10
11
12
13public class User {
private Integer id;
private String name;
private Address address; // 一对一关系
// Getters and setters
}
public class Address {
private Integer id;
private String street;
private String city;
// Getters and setters
}
注解方式 (
@One
注解)
在UserMapper
中,使用@One
注解来配置一对一关联。在这种方式中,@One
注解用于指定如何从主查询的结果集中加载一个单一的关联对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14public interface UserMapper {
User findById(Integer id);
}@Result(column = "address_id", property = "address", one = @One(select = "com.example.mapper.AddressMapper.findById"))
:column
:数据库字段名。property
:实体类中的属性名。one
:指定一个方法来查询Address
对象,select
属性指向了AddressMapper
中的方法findById
。
1
2
3
4
5public interface AddressMapper {
Address findById(Integer id);
}注解方式 (
@Result
注解)
在UserMapper
中,使用@Result
注解来直接映射数据库字段到实体类的属性。如果关联对象的属性在主查询中被直接包含,则可以通过@Result
注解来映射。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public interface UserMapper {
User findById(Integer id);
}@Result(column = "address_id", property = "address.id")
:自动将address_id
映射到Address
对象的id
属性。@Result(column = "street", property = "address.street")
:将street
映射到Address
对象的street
属性。
XML 配置方式 (
<association>
标签)
在UserMapper.xml
中,通过<association>
标签指定如何查询和映射关联对象。1
2
3
4
5
6
7
8
9
10
11<select id="findById" resultType="com.example.pojo.User">
SELECT u.id, u.name, a.id AS address_id, a.street, a.city
FROM user u
LEFT JOIN address a ON u.address_id = a.id
WHERE u.id = #{id}
<association property="address" javaType="com.example.pojo.Address">
<id column="address_id" property="id"/>
<result column="street" property="street"/>
<result column="city" property="city"/>
</association>
</select><association>
标签:用于指定如何加载和映射关联对象。property
:在主对象中用来表示关联对象的属性名。javaType
:被关联的实体类的全类名。<id>
:映射主键字段。<result>
:映射其他字段。
总结
@One
注解:用于在注解方式中配置一对一关系,通过指定select
属性来查询关联对象。@Result
注解:直接映射查询结果到实体对象的属性,包括嵌套对象的属性。<association>
标签:在 XML 配置中定义一对一关系,通过指定property
和javaType
来配置如何映射关联对象。
一对多映射:
假设我们有两个表:user
和orders
,其中一个用户 (user
) 可以有多个订单 (orders
)。我们将展示如何在User
对象中使用@Many
注解以及在 XML 配置中使用<collection>
标签来处理一对多关系。实体类
1
2
3
4
5
6
7
8
9
10
11
12
13public class User {
private Integer id;
private String name;
private List<Order> orders; // 一对多关系
// Getters and setters
}
public class Order {
private Integer id;
private String product;
private Integer userId; // 外键
// Getters and setters
}
使用
@Many
注解
在这种方式中,我们在UserMapper
接口中使用@Many
注解来定义一对多关系。
Mapper 接口1
2
3
4
5
6
7
8
9
10
11
12
13public interface UserMapper {
User findById(Integer id);
}OrderMapper 接口
1
2
3
4public interface OrderMapper {
List<Order> findByUserId(Integer userId);
}@Many
注解:在UserMapper
中,@Result
注解的many
属性指定了如何加载orders
属性。select
属性指向OrderMapper
中的findByUserId
方法,该方法返回
与userId
相关的所有订单。
使用 XML 配置中的
<collection>
标签
在这种方式中,我们使用 XML 配置文件来定义一对多关系。UserMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13<mapper namespace="com.example.mapper.UserMapper">
<select id="findById" resultType="com.example.pojo.User">
SELECT u.id, u.name, o.id AS order_id, o.product
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
<!-- 使用 `<collection>` 标签定义一对多关系 -->
<collection property="orders" ofType="com.example.pojo.Order">
<id column="order_id" property="id"/>
<result column="product" property="product"/>
</collection>
</select>
</mapper><collection>
标签:用于定义User
对象中的orders
集合属性。property
属性指定集合属性的名称,ofType
属性指定集合中元素的类型。在查询结果中,MyBatis 会自动 将结果集中的多个订单映射到User
对象的orders
属性中。
总结
@Many
注解:在UserMapper
中通过@Many
注解指定如何查询与User
相关的多个Order
对象。这种方式简洁明了,并且适用于注解方式的配置。- XML 配置中的
<collection>
标签:在UserMapper.xml
中使用<collection>
标签来定义一对多关系。通过这种方式,你可以在 XML 文件中清晰地配置如何将多个结果映射到User
对象的集合属性中。这种方式适用于 XML 配置文件,并允许你在查询中定义更复杂的映射逻辑。
三表查询:
在 MyBatis 中处理三表关联时,通常涉及到多对多或更复杂的多级关联关系。我们可以通过多对一和一对多的组合来实现三表关联。下面我将展示一个处理三表关联的完整示例,包括使用注解和 XML 配置两种方式。示例
假设我们有以下三张表:user
表:存储用户信息。id
(用户ID)name
(用户名)
orders
表:存储订单信息,关联到user
表。id
(订单ID)product
(产品名)user_id
(外键,指向user
表的 ID)
order_items
表:存储订单项信息,关联到orders
表。id
(订单项ID)order_id
(外键,指向orders
表的 ID)item_name
(订单项名)
实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class User {
private Integer id;
private String name;
private List<Order> orders; // 一对多关系:用户 -> 订单
// Getters and setters
}
public class Order {
private Integer id;
private String product;
private List<OrderItem> orderItems; // 一对多关系:订单 -> 订单项
// Getters and setters
}
public class OrderItem {
private Integer id;
private String itemName;
// Getters and setters
}目标
我们要查询User
对象,并且包含用户的订单 (Order
),以及每个订单下的订单项 (OrderItem
)。
使用注解的方式:
UserMapper 接口1
2
3
4
5
6
7
8
9
10
11
12
13
14public interface UserMapper {
User findById(Integer id);
}OrderMapper 接口
1
2
3
4
5
6
7
8
9
10
11public interface OrderMapper {
List<Order> findByUserId(Integer userId);
}OrderItemMapper 接口
1
2
3
4
5public interface OrderItemMapper {
List<OrderItem> findByOrderId(Integer orderId);
}在这个配置中:
UserMapper
:负责查询User
信息及其关联的Order
列表。@Many
注解指向OrderMapper
中的findByUserId
方法,该方法进一步加载每个订单的详细信息。OrderMapper
:负责查询Order
信息及其关联的OrderItem
列表。@Many
注解指向OrderItemMapper
中的findByOrderId
方法。OrderItemMapper
:负责查询OrderItem
列表。
使用 XML 配置的方式:
UserMapper.xml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<mapper namespace="com.example.mapper.UserMapper">
<select id="findById" resultType="com.example.pojo.User">
SELECT u.id, u.name
FROM user u
WHERE u.id = #{id}
<collection property="orders" ofType="com.example.pojo.Order">
SELECT o.id AS order_id, o.product
FROM orders o
WHERE o.user_id = #{id}
<collection property="orderItems" ofType="com.example.pojo.OrderItem">
SELECT oi.id AS item_id, oi.item_name
FROM order_items oi
WHERE oi.order_id = #{order_id}
</collection>
</collection>
</select>
</mapper><collection>
标签:用于定义一对多关系,首先映射User
与Order
之间的关系,然后在Order
与OrderItem
之间再次使用<collection>
标签。- 多级映射:在
UserMapper.xml
中,我们嵌套使用<collection>
标签来处理多级关联。
总结
- 注解方式:
@Many
注解在三表关联中非常有效,可以简化代码,特别是在分布式项目中,但在复杂查询中可能不如 XML 配置灵活。- XML 配置方式:
<collection>
标签在处理复杂关联时更直观,尤其是当查询需要多级嵌套或复杂的结果映射时。
8.5 XML映射文件
- 规范:
- XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放在同一个包下(同包同名)。
- XML映射文件的namespace属性与Mapper接口全限定名一致。
- XML映射文件中的SQL语句的id属性与Mapper接口方法名一致,并保持返回类型一致。
使用Mybatis的注解,主要是来完成一些简单的增删改查功能,如果需要实现复杂的SQL功能,建议使用XML来配置映射语句。
8.5.1 if标签
- <if>:
- 用于判断条件是否成立,如果条件为true,则拼接SQL。
- 形式:<if test=”条件”>……</if>。
1
<if test="gender != null">……</if>
8.5.2 where标签
- <where>:
- where元素只会在子元素有内容的情况下才能插入where子句,而且会自动去除子句开头的and或or
- 形式:<where>……</where>。
1
2
3
4
5
6
7
8
9
10
11
12
13
14<select id="findUsers" resultType="User">
select * from user
<where>
<if test="name != null and name != ''">
name = #{name}
</if>
<if test="age != null">
and age = #{age}
</if>
<if test="gender != null and gender != ''">
and gender = #{gender}
</if>
</where>
</select>
8.5.3 set标签
- <set>:
- 动态地在行首插入set关键字,并会删除掉额外的逗号。(用在update语句中)
- 形式:<set>……</set>。
8.5.4 foreach标签
- SQL语句:
1
delete from emp where id in (1,2,3,4,5);
- 接口方法:
1
2// 批量删除
public void deleteByIds(List<Integer> ids); - XML映射文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<delete id="deleteByIds">
<!-- 从 emp 表中删除满足条件的记录 -->
delete from emp where id in
<!--
使用 <foreach> 标签遍历传入的集合 ids
collection="ids" 表示传入的集合参数名称是 ids
item="id" 表示遍历集合中的每个元素,并将当前元素赋值给 id
open="(" 表示在遍历元素之前,SQL 语句中添加一个左括号 (
separator="," 表示在每个元素之间,用逗号 , 作为分隔符
close=")" 表示在遍历元素之后,SQL 语句中添加一个右括号 )
原SQL:delete from emp where id in (1,2,3,4,5)
-->
<foreach collection="ids" item="id" open="(" separator="," close=")">
<!--
将当前遍历到的 id 以 #{id} 的形式插入到 SQL 语句中
#{id} 是 MyBatis 的占位符,用于安全地传递参数,防止 SQL 注入
-->
#{id}
</foreach>
</delete> - 属性:
collection
:集合属性名。item
:集合遍历出来的元素/项的名称。open
:遍历集合元素前的前缀。【在遍历元素之前需要往SQL语句中添加什么内容】separator
:遍历集合元素间的分隔符。【在遍历元素之间需要往SQL语句中添加什么内容】close
:遍历集合元素后的后缀。【在遍历元素之后需要往SQL语句中添加什么内容】
8.5.5 sql片段
- <sql>:定义可重用的SQL片段。
- <include>:通过属性refid,指定包含的sql片段的id。
8.5.6 trim标签
使用trim标签控制条件部分两端是否包含某些字符
- prefix属性:指定要动态添加的前缀
- suffix属性:指定要动态添加的后缀
- prefixOverrides属性:指定要动态去掉的前缀,使用”|”分隔有可能的多个值
- suffixOverrides属性:指定要动态去掉的后缀,使用”|”分隔有可能的多个值
1 | <!-- List<Employee> selectEmployeeByConditionByTrim(Employee employee) --> |
8.5.7 choose/when/otherwise标签
在多个分支条件中,仅执行一个。
- 从上到下依次执行条件判断
- 遇到的第一个满足条件的分支会被采纳
- 被采纳分支后面的分支都将不被考虑
- 如果所有的when分支都不满足,那么就执行otherwise分支
1 | <!-- List<Employee> selectEmployeeByConditionByChoose(Employee employee) --> |
9. 项目搭建
9.1 环境搭建
9.2 开发规范
注意事项:
- REST是风格,是约定方式,约定不是硬性规定,可以打破。
- 描述模块的功能通常是复数, 也就是加s的格式来描述,表示此类资源,而非单个资源。如:uers、emps、books……
9.3 @RequestMapping
- 类级别的
RequestMapping
定义了所有该类中处理方法的共同URL前缀。 - 方法级别的
RequestMapping
则在类级别的URL基础上进一步细分,指定具体的处理路径(eg:GET、POST)。可以使用更具体的GetMapping、PostMapping等注解替代。
如果不指定,默认情况下任何请求方式都可以访问。
1 | // 使用方法级别的@RequestMapping注解 |
注意事项:
@RequestMapping
不用必须使用 / 开头@RequestMapping
可以同时指定多个URL路径,用数组表示,如:@RequestMapping(value = {"/hello", "/hi"})
@RequestMapping
可以同时指定多个请求方式,用数组表示,如:@RequestMapping(value = "/hello", method = {RequestMethod.GET, RequestMethod.POST})
@RequestMapping
支持模糊匹配,* 表示任意一层字符串,**表示任意层字符串。
1
2 /hello/* -> /hello/world /hello/world123 [不可以是 /hello/world/123]
/hello/** -> /hello /hello/world /hello/world/123
- 方法前必须加上
@RequestMapping
注解,就算类上已经添加了@RequestMapping
注解,否则该方法将不会被识别成外部请求。
1
2
3
4
5
6
7
8
public class HelloController {
// 不能省略, url: /hello
public String hello() {
return "hello";
}
}
9.4 PageHelper分页插件
- 导入依赖:
1
2
3
4
5<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency> - 使用方法:
1
2
3PageHelper.startPage(pageNum, pageSize);
List<Emp> list = empMapper.list();
Page<Emp> page = (Page<Emp>)list;
9.5 文件上传
- 简介:
- 文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其它用户浏览或下载的过程。
- 文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。
- 前端页面:
- 表单项 type=”file”
- 表单提交方式 post
- 表单的enctype属性:multipart/form-data
- 服务端接收文件:
MultipartFile
接口- String getOriginalFilename():获取原始文件名
- void transferTo(File dest):将文件保存到指定位置
- long getSize():获取文件大小,单位:字节
- byte[] getBytes():获取文件内容的字节数组
- InputStream getInputStream():获取接收到的文件内容的输入流
9.5.1 本地仓库
1 |
|
在SpringBoot中,文件上传,默认单个文件允许最大大小为 1M,如果需要上传大文件,可以进行如下配置:
1
2
3
4
5
6
7 # 在application.yml中配置
servlet:
multipart:
# 配置单个文件最大上传大小
max-file-size: 10MB
# 配置单个请求最大上传大小(一次请求可以上传多个文件)
max-request-size: 100MB
9.5.2 阿里云OSS
- 引入阿里云OSS相关依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency> - 引入阿里云OSS上传文件工具类(由官方的示例代码改造而来):
- 上传图片接口开发:
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
public class UploadController {
/* 本地存储
@PostMapping("/upload")
public Result upload(MultipartFile image) throws IOException {
// 获取原始文件名
String originalFilename = image.getOriginalFilename();
// 构建新的文件名
String newFileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
// 将文件保存到本地
image.transferTo(new File("E:\\Edge下载(定期删除)\\day11-SpringBootWeb案例\\" + newFileName));
return Result.success();
}*/
private AliOSSUtils aliOSSUtils;
public Result upload( MultipartFile image)throws IOException {
log.info("文件上传,文件名:{}",image.getOriginalFilename());
String url = aliOSSUtils.upload(image);
log.info("文件上传完成,文件访问的url:{}",url);
return Result.success(url);
}
}
9.6 配置文件
- yml文件基本语法:
- 大小写敏感
- 数值前边必须有空格,作为分隔符
- 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能使用空格(idea中会自动将Tab键转换为空格)
- 缩进的空格数目不重要,只要相同层级的元素左对齐即可
- # 表示注解,从这个字符开始到行尾,都是注释,不会被解析
9.7 外部配置注入
在Spring框架中,@Value
和@ConfigurationProperties
注解用于将外部配置的值注入到Spring管理的Bean中。它们的使用场景和功能有所不同,下面分别讲解它们的作用和使用方法。
@Value
注解@Value
注解用于将配置文件中的单个属性值注入到Spring Bean的字段或方法中。它通常用于注入简单的属性值,比如字符串、数值等。- 基本语法:
1
2
private String propertyValue;其中
${property.key}
表示从配置文件(如application.properties
或application.yml
)中读取指定键的值。- 默认值:
可以在@Value
注解中指定一个默认值,当配置文件中找不到相应的键时,使用默认值:
1
2
private String propertyValue;- 复杂表达式:
@Value
也可以使用Spring表达式语言(SpEL)来处理复杂的值:
1
2
private double randomValue;- 使用示例
假设在application.properties
文件中有以下配置:
1
2app.name=MyApp
app.version=1.0.0可以在Bean中使用
@Value
注解注入这些值:1
2
3
4
5
6
7
8
9
10
11
public class AppConfig {
private String appName;
private String appVersion;
// getters and setters
}@ConfigurationProperties
注解@ConfigurationProperties
用于将一组相关的配置属性绑定到一个Java类的属性上。它可以将配置文件中的属性自动映射到类的字段中,尤其适用于需要处理一组复杂或嵌套配置的场景。- 基本语法:
定义一个类来对应配置文件中的一组属性:
1
2
3
4
5
6
7
8
public class AppProperties {
private String name;
private String version;
// getters and setters
}prefix = "app"
表示这个类将映射application.properties
或application.yml
中以app
开头的属性。- 启用
@ConfigurationProperties
:
通常需要使用@EnableConfigurationProperties
注解或@Component
来让Spring管理这个配置类:
1
2
3
4
5
public class AppConfig {
// 其他配置
}或者在配置类上直接使用
@Component
:1
2
3
4
5// 如果已经在配置类中使用了@EnableConfigurationProperties,则不需要再使用@Component
public class AppProperties {
// fields, getters, setters
}- 使用示例
假设在application.properties
文件中有以下配置:
1
2
3
4app.name=MyApp
app.version=1.0.0
app.details.description=This is a demo application.
app.details.author=John Doe可以定义一个类来映射这些配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AppProperties {
private String name;
private String version;
private Details details = new Details();
public static class Details {
private String description;
private String author;
// getters and setters
}
// getters and setters
}这样,
AppProperties
类中的字段会自动映射到配置文件中的相应属性值。- 基本语法:
主要区别
- 粒度:
@Value
用于注入单个属性值,而@ConfigurationProperties
更适合注入一组相关的属性。- 类型安全:
@ConfigurationProperties
支持类型安全的配置绑定,并且可以通过构建复杂对象来映射嵌套属性,而@Value
只能注入简单的属性值。- 可维护性:
@ConfigurationProperties
有助于将配置集中管理,并且对属性进行结构化处理,这对大型项目的配置管理更为友好。- @Value的局限性:
@Value
只能注入单个属性值,不能注入集合、数组等。
选择建议- 当需要注入单个或少量的配置值时,使用
@Value
。- 当需要注入大量相关配置或希望将配置分组时,使用
@ConfigurationProperties
。
在Spring框架中,@PropertySource
和@ConfigurationProperties
都涉及配置属性的管理,但它们的作用和使用场景有所不同,不能简单地认为它们是类似的。@PropertySource
的作用
- 用途:
@PropertySource
主要用于加载自定义的属性文件(如.properties
文件)到Spring的Environment
中。通过这个注解,Spring能够识别和使用这些属性文件中的键值对。 - 典型场景:
当你的配置文件不是默认的
application.properties
或application.yml
,而是自定义的文件时,需要用@PropertySource
来加载这个文件。例如,如果你有一个名为
custom.properties
的文件,且希望Spring加载其中的配置项,那么可以使用@PropertySource
注解:1
2
3
4
5
public class AppConfig {
// 配置类内容
}
- 配合
@Value
使用:@PropertySource
通常与@Value
注解配合使用,从加载的属性文件中读取具体的属性值并注入到Bean的字段中。
@ConfigurationProperties
的作用
- 用途:
@ConfigurationProperties
用于将一组相关的配置属性绑定到一个Java类的属性上。它可以将配置文件中的属性自动映射到类的字段中,尤其适用于需要处理一组复杂或嵌套配置的场景。 - 典型场景:
- 当你有一组相关的配置项,比如与应用程序、数据库、消息队列等相关的多个配置项时,可以使用
@ConfigurationProperties
将这些配置项整合到一个专门的配置类中。 - 例如,假设有以下配置:可以使用
1
2
3
4app.name=MyApp
app.version=1.0.0
app.details.description=This is a description
app.details.author=John Doe@ConfigurationProperties
将这些属性映射到类中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AppProperties {
private String name;
private String version;
private Details details = new Details();
public static class Details {
private String description;
private String author;
// Getters and Setters
}
// Getters and Setters
}@PropertySource
vs.@ConfigurationProperties
@PropertySource
:负责告诉Spring从哪里加载配置文件,并与@Value
结合使用,允许单独注入具体的配置值。@PropertySource
只加载属性文件,不负责属性的绑定和注入。@ConfigurationProperties
:负责将一组相关的配置属性映射到一个Java类中,通常用于处理结构化的、较复杂的配置。@ConfigurationProperties
不仅加载属性,还绑定它们到类的字段上,使配置的管理更加方便和结构化。
总结
- 相似点:两者都与配置属性的处理有关,但
@PropertySource
更侧重于属性文件的加载,而@ConfigurationProperties
侧重于属性值的绑定和管理。 - 使用场景不同:
@PropertySource
用于从自定义的文件加载属性,而@ConfigurationProperties
则用于将这些属性整合到Java类中进行统一管理。 ^223021
- 当你有一组相关的配置项,比如与应用程序、数据库、消息队列等相关的多个配置项时,可以使用
9.8 登录校验
9.8.1 会话技术
- 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
- 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求间共享数据。
- 会话跟踪方案:
- 客户端会话跟踪技术:Cookie
- 服务器会话跟踪技术:Session
- 令牌技术:Token
Cookie:
什么是Cookie?
Cookie是由服务器生成并发送到客户端(浏览器)的小型文本文件。客户端会将这些Cookie存储在本地,并在每次访问相同服务器时将它们包含在HTTP请求中,发送回服务器。Cookie的工作原理
生成和存储:当用户第一次访问网站时,服务器可以生成一个或多个Cookie,并通过HTTP响应头将它们发送到客户端。客户端浏览器接收到这些Cookie后,会根据服务器指示(如
Expires
或Max-Age
属性)将Cookie存储在本地。发送Cookie:在用户的后续请求中,浏览器会自动将与该域名匹配的所有Cookie附加到HTTP请求中,发送给服务器。服务器可以读取这些Cookie的值,以便识别用户的身份或保持会话状态。
Cookie属性:
Name=Value
:Cookie的名称和值,浏览器会在后续请求中发送这些信息。Domain
:指定Cookie属于哪个域名,浏览器只会在访问该域名时发送此Cookie。Path
:指定Cookie适用的路径,只有访问此路径或子路径时,Cookie才会被发送。Expires/Max-Age
:设置Cookie的过期时间,决定Cookie在客户端存储的时长。HttpOnly
:防止客户端脚本访问Cookie,增加安全性。Secure
:指示Cookie只能通过HTTPS发送,防止Cookie在传输过程中被截获。SameSite
:防止跨站请求伪造攻击(CSRF)。
- Cookie的应用场景
- 会话管理:保存用户登录状态,跟踪用户在网站上的活动。
- 个性化设置:保存用户偏好,如主题、语言选择等。
- 跟踪和分析:用于广告跟踪、用户行为分析等。
- Cookie的优缺点
优点:
- 易于使用:可以在客户端和服务器之间简单地传递信息。
- 跨请求保持状态:允许在多个请求之间保持状态(如用户登录状态)。
缺点:
- 安全性问题:存储在客户端,容易被篡改或窃取。
- 数据量有限:通常一个Cookie的大小限制为4KB,且总数有限。
- 性能影响:每次请求都会带上Cookie,可能影响带宽和性能。
- 不能跨域。
跨域问题只是针对浏览器的,后端不同服务器之间的数据传输不会存在跨域问题
- 样例
1 |
|
Session:
什么是Session?
Session是服务器端的一种会话机制,用于跟踪和保存用户的状态和数据。与Cookie不同,Session数据存储在服务器端,客户端只保留一个标识符(通常是Session ID)。Session的工作原理
生成Session:当用户访问服务器时,服务器会创建一个Session,并生成一个唯一的Session ID。这个Session ID会通过Cookie或URL参数传递给客户端。
存储和检索:服务器将与该Session相关的数据存储在服务器端(如内存、数据库或文件系统)。当客户端再次发送请求时,会带上Session ID,服务器根据这个ID检索相应的Session数据。
Session的生命周期:
- 创建:Session在用户首次访问时创建,并分配一个唯一的Session ID。
- 过期:Session有一定的有效期(可配置),当用户长时间不活动,Session会自动过期和销毁。
- 销毁:用户主动登出或Session过期后,Session数据会从服务器端删除。
- Session的应用场景
- 用户认证:保存用户登录信息,确保用户在会话期间不需要重复登录。
- 购物车功能:保存用户的购物车内容,在整个会话期间保持一致。
- 用户跟踪:跟踪用户在网站上的操作,并根据这些操作做出响应。
- Session的优缺点
优点:
- 安全性较高:由于Session数据存储在服务器端,不容易被用户篡改或窃取。
- 支持复杂数据:可以在Session中存储复杂的对象和数据结构。
- 保持状态:适合需要在多个请求之间保持复杂状态的应用。
缺点:
- 占用服务器资源:每个用户的Session数据都存储在服务器上,用户数量多时可能导致服务器资源消耗过大。
- 扩展性问题:在分布式系统中,Session管理较为复杂,需要采取措施(如Session共享、Session粘性)来保证用户会话的一致性。
样例
1 |
|
JWT:
JWT(JSON Web Token)是一种常用的认证机制,它允许在网络应用中安全地在客户端和服务器之间传递信息。与Cookie和Session相比,JWT有着独特的工作方式和应用场景。
- 什么是JWT?
JWT是一种开放标准(RFC 7519),用于在各方之间作为JSON对象安全传输信息。信息可以被验证和信任,因为它是数字签名的。JWT通常用于认证和授权。 - JWT的工作原理
生成JWT:当用户成功登录后,服务器会生成一个JWT。这个Token由三部分组成:Header(头部)、Payload(负载)和Signature(签名)。
- Header:指定Token的类型(通常是JWT)和签名算法(如HMAC SHA256或RSA)。例如:{“alg”:”HS256”,”type”:”JWT”}
- Payload:包含声明(Claims),即需要传输的用户信息、默认信息或其他数据。这些数据是公开的,任何人都可以解码查看。例如:{“id”:1,”username”:”Tom”}
- Signature:由Header和Payload通过指定的算法加密生成,用于验证Token的真实性,确保安全性。
生成的JWT通常类似这样:
测试样例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 在pom.xml中添加依赖:
<!--JWT配置-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version> <!-- 请检查最新版本号 -->
</dependency>
// 测试样例
public void genjwt(){
Map<String, Object> claims = new HashMap<>();
claims.put("id", "1");
claims.put("username","Tom");
String jwt = Jwts.builder()
.setClaims(claims) // 自定义内容(载荷)
.signWith(SignatureAlgorithm.HS256, "lusy")// 签名算法
.setExpiration(new Date(System.currentTimeMillis() + 12 * 3600 * 1000)) // 有效期 单位 ms
.compact();
System.out.println(jwt);
}传递JWT:JWT生成后,服务器会将其返回给客户端。客户端通常将JWT存储在
Local Storage
、Session Storage
或Cookie中。验证JWT:在后续请求中,客户端会将JWT包含在请求头(通常是
Authorization: Bearer <token>
)中发送给服务器。服务器接收到JWT后,会使用相同的签名算法和密钥验证Token的真实性,并从Payload中提取用户信息。1
2
3
4
5
6
7
8
public void parseJwt(){
Claims claims = Jwts.parser()
.setSigningKey("lusy")// 指定签名秘钥
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJleHAiOjE3MjM4NDk2MzIsInVzZXJuYW1lIjoiVG9tIn0.KrQ-8Y4ISZds9DZ_OHaXvrrLM-4By84ngwV8vz9A6bE")// 解析令牌
.getBody();
System.out.println(claims);
}注意事项:
- JWT校验时使用的签名秘钥,必须和生成的JWT令牌时使用的秘钥是配套的。
- 如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法。
- JWT的应用场景
用户认证:在用户登录成功后,服务器生成JWT并返回给客户端。客户端在后续请求中携带这个JWT,以证明自己的身份。
授权:服务器可以在JWT的Payload中包含用户的权限信息(如角色、权限范围),以决定用户能访问哪些资源。
信息交换:JWT可以安全地传输信息,因为它是经过签名的,接收方可以验证信息的来源和完整性。
- JWT的优缺点
优点:
- 无状态:JWT是无状态的,不需要在服务器端存储会话信息,这使得它非常适合分布式系统。
- 跨域支持:JWT不依赖于Cookie,因此可以在不同域名之间轻松传递。
- 可扩展性:JWT的Payload部分可以包含自定义的声明,灵活性高。
缺点:
- 安全性风险:JWT的Payload部分是可见的,尽管它是签名的,但不能加密。如果存储敏感信息,可能会有安全隐患。
- Token大小:JWT通常比传统的Session ID大,因为它包含了更多信息,这可能增加网络传输的开销。
- Token管理:一旦JWT被签发,服务器无法主动撤销Token的权限,除非采用额外的策略(如Token黑名单)。
会话跟踪方案对比
9.8.2 过滤器Filter
概述:
- 概念:Filter 过滤器,是JavaWeb三大组件(Servlet,Filter,Listener)之一。
- 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
- 过滤器一般完成一些通用的操作,比如:登录校验,统一编码处理、敏感字符处理等。
快速入门:
- 创建一个Filter类,实现Filter接口,重写接口中的方法。
- 配置Filter:Filter类加上
@WebFilter
注解,配置拦截资源的路径,引导类上加@ServletComponentScan
注解开启Servlet组件支持。
拦截路径:
Filter可以根据需求,配置不同的拦截资源路径:拦截路径 urlPatterns值 含义 拦截具体路径 /login
只有访问 /login
路径时,才会被拦截目录拦截 /emps/*
访问 /emps/
下的所有资源,都会被拦截拦截所有 /*
访问所有资源,都会被拦截 1
2
3
4
public class MyFilter implements Filter {
}执行流程:
过滤器链:
注意:
存在多个过滤器时,过滤器的执行顺序是按照过滤器配置的顺序执行的。但如果没有明确指定执行顺序(例如通过@Order注解),那么容器通常会根据类名的字典顺序(字母顺序)来决定过滤器的执行顺序。
9.8.3 拦截器(Interceptor)
概述:
- 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。
- 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
快速入门:
WebMvcConfigurer: [[WebMvcConfigurer#^mk-20240915210900|WebMvcConfigurer接口的讲解]]拦截器可以根据需求,配置不同的拦截路径:
执行流程:
多个拦截器执行顺序:
- preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
- postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
- afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。
9.8.4 异常处理
9.9 参数校验
在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。
校验概述
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并过标准的验证接口对Bean进行验证。
注解 | 规则 |
---|---|
@Null | 标注值必须为 null |
@NotNull | 标注值不可为 null |
@AssertTrue | 标注值必须为 true |
@AssertFalse | 标注值必须为 false |
@Min(value) | 标注值必须大于或等于 value |
@Max(value) | 标注值必须小于或等于 value |
@DecimalMin(value) | 标注值必须大于或等于 value |
@DecimalMax(value) | 标注值必须小于或等于 value |
@Size(max,min) | 标注值大小必须在 max 和 min 限定的范围内 |
@Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内 |
@Past | 标注值只能用于日期型,且必须是过去的日期 |
@Future | 标注值只能用于日期型,且必须是将来的日期 |
@Pattern(value) | 标注值必须符合指定的正则表达式 |
JSR 303 只是一套标准,需要提供其实现才可以使用。Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
注解 | 规则 |
---|---|
@Email | 标注值必须是格式正确的 Email 地址 |
@Length | 标注值字符串大小必须在指定的范围内 |
@NotEmpty | 标注值字符串不能是空字符串 |
@Range | 标注值必须在指定的范围内 |
Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在SpringMVC 中,可直接通过注动 @EnableWebMvc
的方式进行数据校验。Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在Spring容器定义一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean中。Spring本身并没有提供JSR 303的实现,所以必须将JSR 303的实现者的jar包放到类路径下。
配置 @EnableWebMvc
后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的作。
操作演示
- 导入依赖
1 | <!-- 校验注解 --> |
- 应用校验注解
1 | import jakarta.validation.constraints.Email; |
- handler标记和绑定错误收集
1 |
|
易混总结
@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同。
@NotNull (包装类型不为null)
@NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解。
@NotEmpty (集合类型长度大于0)
@NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败。
@NotBlank (字符串,不为null,切不为” “字符串)
@NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验。
总之,这三种注解都是用于校验字段值是否为空的注解,但是其校验规则和用法有所不同。在进行数据校验时,需要根据具体情况选择合适的注解进行校验。
10. 事务管理
10.1 事务回顾
- 概念:事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败
- 操作:
- 开启事务(一组操作开始前,开启事务):
start transaction / begin
; - 提交事务(这组操作全部成功后,提交事务):
commit
; - 回滚事务(中间任何一个操作出现异常,回滚事务):
rollback
;
- 开启事务(一组操作开始前,开启事务):
10.2 Spring事务管理
- 注解:
@Transactional
- 位置:业务(Service)层的方法上、类上、接口上
- 作用:将当前方法交给Spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
- 属性:
rollbackFor
回滚:默认情况下,只有出现RuntimeException
才会回滚异常,rollbackFor
属性用于控制出现何种异常类型,回滚事务noRollbackFor
不回滚:指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!如果
rollbackFor
和noRollbackFor
同时指定同一类型,Spring的行为会出现冲突和不确定性timeout
超时时间:当事务运行时间超过了指定的时间限制,事务将被强制回滚,并抛出一个 TransactionTimedOutException 异常。默认值为-1,表示永不超时propagation
传播行为:指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制
属性值 | 含义 |
---|---|
REQUIRED | 【默认值】需要事务,有则加入,无则创建新事务 |
REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
SUPPORTS | 支持事务,有则加入,无则在无事务状态中运行 |
NOT_SUPPORTED | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 |
MANDATORY | 必须有事务,否则抛出异常 |
NEVER | 必须没有事务,否则抛出异常 |
REQUIRED
:大部分情况下都是用该传播行为即可。REQUIRES_NEW
:当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。
isolation
隔离级别:设置事务的隔离级别,mysql默认是repeatable read!
- 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
- 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
- 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
- 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
开启spring事务管理日志:
在配置文件中添加1
2
3>logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
注意事项:
如果在非SpringBoot框架中想使用事务功能,需要在配置类添加@EnableTransactionManagement
注解,编写一个Spring事务管理器配置类,并在pom.xml中添加引入spring-tx依赖。@EnableTransactionManagement
是Spring中的一个注解,用于启用Spring的注解驱动的事务管理功能。它通常与 @Configuration 注解一起使用,配置类中声明它表示希望在应用程序中使用基于注解的事务管理。1
2
3
4
5
6<!-- 声明式事务依赖-->
><dependency>
<groupId>org. springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>6.0.6</version>
></dependency>
11.AOP
11.1 概述
- AOP:Aspect Oriented Programming(面向切面编程,面向方法编程),其实就是面对特定方法编程。
- 场景:
案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时- 记录操作日志
- 权限控制
- 事务管理
- 优势:
- 代码无侵入
- 减少重复代码
- 提高开发效率
- 维护方便
- 实现:
动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理Bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。
11.2 快速入门
统计各个业务层方法执行耗时
- 导入依赖:在pom.xml中添加AOP依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency> - 编写AOP依赖:针对特定的方法根据业务需要进行编程
1
2
3
4
5
6
7
8
9
10
11
12
public class TimeAspect {
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // 调用原始方法运行
long end = System.currentTimeMillis();
log.info(pjp.getSignature() + "方法执行耗时:{}ms" + (end - start));
return result;
}
}注意事项:
如果不是在springboot框架中使用AOP,还需要在配置类中添加@EnableAspectJAutoProxy
注解开启AOP功能
11.3 AOP核心概念
11.4 通知类型
@Around
:环绕通知,此注解标注的通知方法在目标方法前、后都被执行@Before
:前置通知,此注解标注的通知方法在目标方法前被执行@After
:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行@AfterReturning
:返回后通知,此注解标注的通知方法在目标方法正常返回后被执行,有异常不会执行@AfterThrowing
:异常后通知,此注解标注的通知方法在目标方法抛出异常后被执行,正常返回不会执行注意事项:
@Around
环绕通知需要自己调用ProceedingJoinPoint.proceed()
方法来让原始方法执行,其它通知则不需要考虑目标方法执行@Around
环绕通知的返回值,必须指定为Object类型,来接收原始方法的返回值@AfterReturning
和@AfterThrowing
通知,如果需要指定返回值来接收原始方法的返回值或异常对象,可以通过各自的属性值returning
和throwing
来指定接收返回值或异常对象的变量名。
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// @AfterReturning注解标记返回通知方法
// 在返回通知中获取目标方法返回值分两步:
// 第一步:在@AfterReturning注解中通过returning属性设置一个名称
// 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[AOP返回通知] "+methodName+"方法成功结束了,返回值是:" + targetMethodReturnValue);
}
// @AfterThrowing注解标记异常通知方法
// 在异常通知中获取目标方法抛出的异常分两步:
// 第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称
// 第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们
public void printLogAfterCoreException(JoinPoint joinPoint, Throwable targetMethodException) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[AOP异常通知] "+methodName+"方法抛异常了,异常类型是:" + targetMethodException.getClass().getName ());
}
11.5 @Pointcut注解
该注解的作用是将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。
11.6 通知顺序
11.7 切入表达式
- 概念:描述切入点方法的一种表达式
- 作用:主要用来决定项目中的哪些方法需要加入通知
- 常见形式:
execution(……)
:根据方法的签名来匹配annotation(……)
:根据注解匹配
11.7.1 execution
execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:execution([访问修饰符] 返回值 [包名.类名.]方法名(方法参数) [throws 异常])
- 其中方法参数填写的是参数类型的全类名,简单类型可以直接指定
- 其中可省略的部分:
- 访问修饰符:可以省略,表示任意访问修饰符(比如:public、protected)
- 包名.类名:可以省略,表示任意包名、类名
- throws 异常:可以省略,表示任意异常(注意是方法上声明抛出的异常,不是实际抛出的异常)
1 |
|
- 可以使用通配符描述切入点
*
:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
1
execution(* com.*.service.impl.*.*(*))
..
:多个独立的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数,在通配包时,不能作为开头。如果要指定全部包,可以用*..
1
2
3
4
5execution(* com.itheima..DeptService.*(..))
execution(* com.example.service..*.*(..))
execution(* ..example.service..*.*(..)) // 错误的
// 查询全部包下,无参数的方法
execution(* *..*.*(..)) //第三个 *:匹配类名中的最后一级,防止出现连续的 ...
注意事项:
根据业务需要,可以使用 且(&&) 、或(||)、非(!)来组合比较复杂的切入点表达式。
- 书写建议
- 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是find开头,更新类方法都是update开头
- 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
- 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用.. ,使用 * 匹配单个包。
11.7.2 annotation
@annotation
注解的切点表达式用于匹配被指定注解标记的方法。例如,如果你有一个自定义注解@MyLog
,你可以使用@annotation
来指定所有带有@MyLog
注解的方法作为切点。
- 示例:
假设你有一个自定义注解@MyLog
:你希望在所有标记了1
2
3
4
public MyLog {
}@MyLog
的地方记录方法执行时间,可以定义一个切面如下:使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LoggingAspect {
public void logExecutionTime(JoinPoint joinPoint) {
// 在方法执行前记录开始时间
System.out.println("Method " + joinPoint.getSignature().getName() + " is about to execute");
}
public void afterExecution(JoinPoint joinPoint) {
// 在方法执行后记录结束时间
System.out.println("Method " + joinPoint.getSignature().getName() + " has executed");
}
}@MyLog
注解:1
2
3
4
5
6
7
8
9
public class MyService {
public void performTask() {
// 业务逻辑
System.out.println("Task is being performed");
}
}
11.8 连接点
- 在Spring中用JoinPoint抽象了连接点,用它可以获取方法执行时的相关信息,如目标类名,方法名,方法参数等。
- 对于
@Around
通知,获取连接点信息只能使用ProceedingJoinPoint
- 对于其它四种通知,获取连接点信息只能使用
JoinPoint
,它是ProceedingJoinPoint
的父类
- 对于
12.配置
- SpringBoot除了支持配置文件属性配置,还支持Java系统属性和命令行参数的方式进行属性配置。
- 优先级(高 -> 低)
- 命令行参数 (–xxx=xx)
- java系统属性(-Dxxx=xxx)
- application.properties
- application.yml
- application.yml(忽略)
13.Bean的管理
13.1 获取Bean
- 默认情况下,Spring项目启动时,会把Bean都创建好放在IOC容器中,如果想要主动获取这些Bean,可以通过如下方式:
- 根据name获取Bean:
Object getBean(String name)
- 根据类型获取Bean:
<T> T getBean(Class<T> requireType)
- 根据name获取Bean(带类型转换):
<T> T getBean(String name, Class<T> requireType)
注意事项:
上述所说的【Spring项目启动时,会把其中的Bean都创建好】还会受到作用域及延迟初始化影响,这里主要针对于默认的单例非延迟加载的Bean而言。
- 根据name获取Bean:
13.2 Bean作用域
- Spring支持五种作用域,后三种在web环境下才生效:
- 可以通过
@Scope
注解来进行配置作用域1
2
3
4
5
6
public class DeptController{
}注意事项:
- 默认singleton的bean,在容器启动时被创建,可以使用
@Lazy
注解来延迟初始化(延迟到第一次使用时) - prototype的bean,每一次使用该bean的时候都会创建一个新的实例
- 实际开发当中,绝大部分的Bean是单例的,也就是说大部分Bean不需要配置Scope属性
- 默认singleton的bean,在容器启动时被创建,可以使用
13.3 第三方Bean
@Bean
- 如果要管理的Bean对象来自第三方(不是自定义的),是无法用
Component
及衍生注解声明Bean的,就需要用到@Bean
注解。 - 若要管理的第三方Bean对象,建议对这些Bean进行分类配置,可以通过
@Configuration
注解来声明一个配置类。注意事项:
- @Bean修饰的方法将当前方法的返回值对象交给IOC容器管理,成为IOC容器的Bean
- 通过@Bean注解的name或value属性可以声明Bean的名称,如果不指定,默认Bean的名称就是方法名。
- 周期方法指定:
- 原有注解方案:@PostConstruct、@PreDestroy 注解指定
- bean属性指定:initMethod、destroyMethod 指定
- 如果第三方Bean需要依赖其它Bean对象,直接在@Bean定义的方法中设置形参即可,容器会根据类型自动装配。如果有多个类型匹配,可以使用形参名等同于对应的Beanid标识即可。
- 如果要管理的Bean对象来自第三方(不是自定义的),是无法用
@Component及衍生注解与@Bean注解使用场景?
- 项目中自定义的,使用@Component及其衍生注解
- 项目中引入第三方的,使用@Bean注解
14.自动配置原理
14.1 @ComponentScan 组件扫描
14.2 @Import导入
@Import注解将配置类、普通类、第三方的组件注册到 IOC 容器,组件在容器中的名字是全类名
14.3 源码跟踪
@SpringBootApplication
该注解标识在SpringBoot工程引导类上,是SpringBoot中最重要的注解。该注解由三个部分组成:@SpringBootConfiguration
:该注解与@Configuration
注解作用相同,用来声明当前也是一个配置类。@ComponentScan
:组件扫描,默认扫描当前引导类所在包及其子包。@EnableAutoConfiguration
:SpringBoot实现自动化配置的核心注解。
@Conditional
- 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的Bean对象到Spring IOC容器中。
- 位置:方法、类
- @Conditional本身是一个父注解,派生出大量的子注解:
@ConditionalOnClass
:判断环境中是否有对应字节码文件,才注册Bean到IOC容器@ConditionalOnMissingBean
:判断环境中没有对应的Bean(类型或名称),才注册Bean到IOC容器@ConditionalOnProperty
:判断配置文件中有对应属性和值,才注册Bean到IOC容器
1 |
|
14.4 自定义starter
15.Maven高级
15.1 分模块设计
- 什么是分模块设计?
- 将项目按照功能拆分成若干个子模块
- 为什么要分模块设计?
- 方便项目的管理维护、拓展、也方便模块间的相互调用,资源共享
- 注意事项
- 分模块设计需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分
15.2 继承
- 概念:继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承
- 作用:简化依赖配置、统一管理依赖
- 实现:
<parent>……</parent>
- 具体步骤:
- 总结:
打包方式:
- jar:普通模块打包,springboot项目基本都是jar包(内嵌tomcat运行)
- war:普通web程序打包,需要部署在外部的tomcat服务器中运行
- pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理
15.3 版本锁定
- 在maven中,可以在父工程的pom文件中通过
<dependencyManagement>
来统一管理依赖版本。 <dependencyManagement>
是用来管理依赖版本的,它用于声明依赖的版本,但这些依赖不会自动导入到项目中,还需要再<dependencies>中引入。
注意事项:
- 子工程引入依赖时,无需指定 <version>版本号,父工程统一管理。变更依赖版本,只需在父工程中统一变更。
<dependencyManagement>与<dependencies>的区别:
- <dependencies>是直接依赖,在父工程配置了依赖,子工程会直接继承下来。
- <dependencyManagement>是统一管理依赖版本,不会直接依赖,还需要在子工程中引入所需依赖(无需指定版本)
- 也可以通过
<properties>
自定义/引用属性。
15.4 聚合
- 概念:将多个模块组织成一个整体,同时进行项目的构建。
- 聚合工程:一个不具有业务功能的”空”工程(有且仅有一个pom文件)
- 作用:快速构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)
- 实例:聚合使得在父项目中执行
mvn install
等命令时,所有子模块都会被一起构建。
继承与聚合:
- 作用
- 聚合用于快速构建项目
- 继承用于简化依赖配置、统一管理依赖
- 相同点:
- 聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
- 聚合与继承均属于设计型模块,并无实际的模块内容
- 不同点:
- 聚合是在聚合工程中配置关系,聚合可以感知到参与聚合的模块有哪些
- 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己
15.5 私服
- 概念: 私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部的资源共享与资源同步问题
- 资源上传与下载
16 Mybatis 的 Mapper 文件什么时候需要使用 @Param
@Param
注解的主要作用是 给 MyBatis 传递的参数起别名,从而在 mapper.xml
里可以正确引用参数。是否需要 @Param
取决于你的方法参数类型和 MyBatis 解析参数的方式。
✅ 需要 @Param
的情况
1️⃣ 方法参数有多个
如果 Mapper
方法有 两个或以上参数,MyBatis 默认不会自动识别参数名,需要 @Param
指定别名,否则 mapper.xml
无法正确引用:
1 | List<DeptVo> findDeptByNameAndState( String deptName, |
1 | <select id="findDeptByNameAndState" resultMap="BaseResultMap"> |
❌ 不加 @Param
可能会报错:
1 | Parameter 'deptName' not found. Available parameters are [arg0, arg1, param1, param2] |
因为 MyBatis 默认只给参数分配 param1, param2
这样的名字,XML 里 #{deptName}
找不到对应的参数。
2️⃣ [ ]
、List
或 Collection
作为参数
如果方法参数是 List 或 Collection,MyBatis 不会自动赋予参数名,需要用 @Param
指定别名:
1 | List<DeptVo> findDeptInDeptNos(; List<String> deptNos) |
1 | <select id="findDeptInDeptNos" resultMap="BaseResultMap"> |
❌ 不加 @Param("deptNos")
可能会报错:
1 | There is no getter for property named 'deptNos' in class 'java.util.ArrayList' |
因为 MyBatis 认为 List
参数没有名字,而 XML 里 collection="deptNos"
需要一个明确的参数名。
3️⃣ 需要在 mapper.xml
里使用参数别名
如果你想在 SQL 里更清楚地引用参数,比如:
1 | DeptVo findDeptInfo(; DeptDto dept) |
1 | <select id="findDeptInfo" resultMap="BaseResultMap"> |
如果不加 @Param("dept")
,在 XML 里就不能用 #{dept.xxx}
来访问 DeptDto
里的属性。
❌ 不需要 @Param
的情况
1️⃣ 只有一个基本类型参数
如果方法只有一个参数,且是 基本数据类型或 String,MyBatis 会自动识别:
1 | DeptVo findDeptByNo(String deptNo); |
1 | <select id="findDeptByNo" resultMap="BaseResultMap"> |
✅ 不需要 @Param
,MyBatis 会自动匹配 #{deptNo}
。
2️⃣ 只有一个 JavaBean 或 DTO 作为参数
如果方法只有一个对象参数(JavaBean 或 DTO),MyBatis 会自动匹配对象的字段:
1 | DeptVo findDeptByDto(DeptDto deptDto); |
1 | <select id="findDeptByDto" resultMap="BaseResultMap"> |
✅ MyBatis 会自动解析 DeptDto
里的字段,不需要 @Param
。
3️⃣ 只有一个 List
或 Collection
参数,并且 XML 里用 collection="list"
MyBatis 默认把 List
绑定到 list
,所以如果 XML 里使用 collection="list"
,也可以不用 @Param
:
1 | List<DeptVo> findDeptInDeptNos(List<String> deptNos); |
1 | <foreach collection="list" item="deptNo" open="(" separator="," close=")"> |
✅ 可以不加 @Param
,因为 MyBatis 默认会把 List
绑定到 list
这个名字。
🚀 总结:什么时候需要 @Param
?
情况 | 需要 @Param | 说明 |
---|---|---|
多个参数(基本类型或 String) | ✅ 需要 | MyBatis 默认不会自动匹配参数名 |
只有一个基本类型参数 | ❌ 不需要 | MyBatis 会自动识别 |
只有一个 JavaBean 或 DTO 参数 | ❌ 不需要 | MyBatis 会自动匹配字段 |
List 或 Collection 参数 | ✅ 需要(除非用 list 作为默认名) | MyBatis 默认不给 List 取名 |
想给参数起别名 | ✅ 需要 | 方便 XML 里使用更清晰的参数名 |
📌 推荐的做法
- 单个基本类型或 JavaBean 作为参数时,不用
@Param
,MyBatis 能自动解析。 - 有多个参数时,一定要用
@Param
,否则 MyBatis 只能用param1, param2
访问参数。 List
作为参数时,最好加@Param("xxx")
,否则只能用collection="list"
访问。
如果你不确定是否需要 @Param
,加上它总是安全的,但如果只有一个简单参数,可以省略。 🚀
- Title: Java IO流
- Author: Lu
- Created at : 2024-06-01 08:46:53
- Updated at : 2025-03-11 12:44:58
- Link: https://lusy.ink/2024/06/01/JavaWeb/
- License: This work is licensed under CC BY-NC-SA 4.0.