Basic Web Development -- Java Web
Java Web
Web 网站的开发模式
Web 网站的工作流程
Web 开发课程安排
包括:
-
前端 Web 开发
- HTML、CSS、JavaScript
- Vue、ElementUI、Nginx
- Ajax、Axios
-
后端 Web 开发
-
Maven
-
SpringBoot Web 基础篇
-
MySQL
-
SpringBoot Mybatis
-
SpringBoot Web 开发篇
-
SpringBoot Web 进阶篇
-
Web 前端开发
Web 标准
Web 标准也称为网页标准,由一系列的标准组成,大部分由 W3C 负责指定。
三个组成部分:
- HTML:负责网页的结构(页面元素和内容)
- CSS:负责网页的表现(页面元素的外观、位置等页面样式,如颜色、大小等)
- JavaScript:负责网页的行为(交互效果)
HTML & CSS
JavaScript & Vue
什么是 JavaScript
-
JavaScript 是一门跨平台、面向对象的脚本(不需要编译)语言。是用来控制网页行为的,它能使网页可交互。
-
JavaScript 和 Java 是完全不同的语言,不论是概念还是设计。但是基础语法类似。
-
JavaScript 在 1995 年有 Brendan Eich 发明,并于 1997 年成为 ECMA 标准。
ECMA 国际(前身为欧洲计算机制造商协会),制定了标准化的脚本程序设计语言 ECMAScript,这种语言得到广泛应用。而 JavaScript 是遵守 ECMAScript标准的。
-
ECMAScript 6 是最新的 JavaScript 版本 (发布于 2015 年)。
JavaScript 引入方式
- 内部脚本:将 JavaScript 代码定义在 HTML页面中
- JavaScript 代码必须位于
<script></script>标签之间 - 在 HTML 文档中,可以在任意地方,放置任意数量的
<script> - 一般会把脚本置于
<body>元素的底部,可以改善页面显示速度
- JavaScript 代码必须位于
- 外部脚本:将 JavaScript 代码定义在外部 js 文件中,然后引入到 HTML 页面中
- 外部 js 文件中,只包含 js 代码,不包含
<script>标签 <script>标签不能自闭合
- 外部 js 文件中,只包含 js 代码,不包含
JavaScript 基础语法
书写语法
- 区分大小写
- 每行结尾的分号可有可无
- 注释:
- 单行注释: //
- 多行注释:/* */
- 大括号表示代码块
- 输出语句:
- window.alert(): 浏览器弹出警告框
- document.write():输出到 HTML 文档,在浏览器页面中展示
- console.log():输出到浏览器控制台
变量
-
JavaScript 中用
var关键字来声明变量。 -
JavaScript 是一门弱类型语言,变量可以存放不同类型的值。
-
变量名需要遵循如下规则:
- 变量名可以是任何字母、数字、下划线或美元符号
- 数字不能开头
- 建议使用驼峰命名法
-
var关键字定义的变量的特点:- 定义出来的变量属于全局变量
- 相同变量可以重复定义
- ECMAScript 6 新增了
let关键字来定义变量。它的用法类似于var,但是所声明的变量,只在let关键字所在的代码块内有效(局部变量),且不允许重复声明。 - ECMAScript 6 新增了
const关键字,用来声明一个只读的常量。一旦声明,常量的值就不能改变。
数据类型、运算符、流程控制语句
数据类型:JavaScript 中分为:原始类型 和 引用类型
-
原始类型有:
- number:数字(整数、小鼠、NaN)
- string:字符串,单双引号皆可
- boolean:布尔类型,取值为
ture和false - null:对象为空
- undefined:当声明的变量未初始化时,该变量的默认类型 是
undefined
使用
typeof运算符可以获取数据类型 -
运算符:
- 算数运算符:+、-、*、/、%、++、–
- 赋值运算符:=、+=、-=、*=、/=、%=
- 比较运算符:>、<、>=、<=、!=、==、
=== - 逻辑运算符:&&、||、!
- 三元运算符:条件表达式 ? true_value : false_value
== 与 ===:
- == 在比较时会先进行类型转换,再判断是否相等
- === 在比较时不会进行类型转换,如果类型不同直接返回 false
类型转换:
- 字符串类型转为数字类型:
- 将字符串字面值转为数字。如果字面值不是数字,则转为 NaN
- 使用 parseInt() 函数转换,该函数从字符串头开始匹配数字,直到遇到非数字为止
- 举例:parseInt(12) -> 12, parseInt(12A45) -> 12, parseInt(A45) -> NaN
- 其他类型转为布尔类型:
- Number:0 和 NaN 为 false,其他均为 true
- String:空字符串为 false,其他均为 true
- Null 和 undefined:均为 false
-
流程控制语句:
- if…else if…else
- switch
- for
- while
- do … while
- 语法与 Java 一致
JavaScript 函数
-
介绍:函数(方法)是被设计为执行特定任务的代码块
-
定义:JavaScript 函数通过
function关键字进行定义,语法为:function funcName(arg1, arg2, ...) {
// function body
}或:
var funcName = function(arg1, arg2, ...) {
// funciton body
} -
注意:
- 形式参数不需要类型。因为 JavaScript 是弱类型语言
- 返回值也不需要定义类型,在函数内部直接使用
return返回即可
-
调用:函数名称(实参列表)
-
注意事项:JavaScript 中,函数调用可以传递任意个数的参数(但函数使用哪些参数会按照顺序来决定)
JavaScript 对象
基础对象
Array
-
JavaScript中 Array 对象用于定义数组。
-
定义:
var name = new Array(element list);
或:
var name = [element list];
-
访问:
name[index]; -
特点:
-
长度可变:
var arr = [1, 2, 3, 4];
arr[10] = 50; // 允许的操作
console.log(arr[10]); // 50
console.log(arr[9]); // undefined
console.log(arr[8]); // undefined -
类型可变(任意):
arr[5] = 'hello'; // 允许的操作
console.log(arr[5]); // 'hello'
-
-
属性:
- length:设置或返回数组中元素的数量
-
方法:
-
forEach():遍历数组中的每个有值的元素,并调用一次传入的函数:
arr.forEach(function(e) {
console.log(e); // 遍历数组每个有值的元素,并对其执行一次函数的操作
})或用箭头函数简化函数定义:
arr.forEach((e) => {
console.log(e);
}) -
push():将新元素添加到数组的末尾,并返回新的长度
-
splice():从数组中删除元素
arr.splice(2, 2); // 从arr的索引为2的位置开始删2个元素
-
String
- 创建方式:
- 方式1:
var name = new String("..."); - 方式2:
var name = "...";
- 方式1:
- 属性:length:获取字符串的长度
- 方法:
- charAt():返回在指定位置的字符
- indexOf():检索某个子字符串第一次出现的位置
- trim():去除字符串两边的空格,返回新字符串
- substring():提取字符串中两个指定索引之间(含头不含尾)的字符子串
JSON
-
JavaScript 自定义对象
-
定义格式:
var name = {
attr1: value1,
attr2: value2,
...
attrn: valuen,
funcName: function(arg list){},
funcName(){} // 定义函数的另一种语法
}; -
调用格式:
- name.attr;
- name.funcName(arg list);
-
-
JSON 介绍
-
概念:JSON 全称为 JavaScript Object Notation,JavaScript对象标记法
-
JSON 是通过 JavaScript 对象标记法书写的文本
-
JSON 格式的数据:
-
key 都要用双引号引起来,其他的与 JavaScript对象的格式相同
-
由于 JSON语法简单,层次结构鲜明,现在多用于数据载体,在网络中进行数据传输
-
-
JSON 基础语法
-
定义:
var name = '{"key1": value1, "key2": value2}';-
示例:
var userStr = '{"name": "Jerry", "age": 18, "addr": ["北京", "上海", "西安"]}'; -
value 的数据类型可以为:
- 数字(整数或浮点数)
- 字符串(在双引号中)
- 逻辑值(true 或 false)
- 数组(在方括号中)
- 对象(在花括号中)
- null
-
JSON 字符串转为 JS 对象
var jsObject = JSON.parse(userStr); -
JS 对象转为 JSON 字符串
var jsonStr = JSON.stringify(jsObject);
-
-
浏览器对象模型 - BOM
- 概念:Browser Object Model 浏览器对象模型,允许 JavaScript 与浏览器对话,JavaScript 将浏览器的各个组成部分封装为对象。
- 组成:
- Window:浏览器窗口对象
- Navigator:浏览器对象
- Screen:屏幕对象
- History:历史记录对象
- Location:地址栏对象
- Window
- 介绍:浏览器窗口对象
- 获取:直接使用 window,其中
window.可省略 - 属性
- history:对 History 对象的只读引用。
- location:对于窗口或框架的 Location 对象
- navigator:对 Navigator 对象的只读引用
- 方法
- alert():显示带有一段消息和一个确认按钮的警告框
- confirm():显示带有一段消息以及确认按钮和取消按钮的对话框,有返回值,点击确定返回 true,点击取消返回 false
- setInterval():按照指定的周期(毫秒为单位)来调用函数或计算表达式
- setTimeout():在指定的毫秒数后调用函数或计算表达式
- Location
- 介绍:地址栏对象
- 获取:使用
window.location获取,其中window.可以省略 - 属性:
- href:设置或返回完整的 URL
文档对象模型 - DOM
-
概念:Document Object Model,文档对象模型
-
将标记语言的各个组成部分封装为对应的对象:
- Document:整个文档对象
- Element:元素对象
- Attribute:属性对象
- Text:文本对象
- Comment:注释对象
-
JavaScipt 通过 DOM,能够对HTML进行操作:
- 改变 HTML 元素的内容
- 改变 HTML 元素的样式
- 对 HTML DOM 事件作出反应
- 添加和删除 HTML 元素
-
DOM 是 W3C 的标准,定义了访问 HTML 和 XML 文档的标准,分为三个不同部分:
- Core DOM - 所有文档类型的标准模型‘
- Document:整个文档对象
- Element:元素对象
- Attribute:属性对象
- Text:文本对象
- Comment:注释对象
- XML DOM- XML 文档的标准模型
- HTML DOM - HTML 文档的标准模型
- Image:
<img> - Button:
<input type='button'>
- Image:
- Core DOM - 所有文档类型的标准模型‘
-
获取指定 HTML 元素的对象
-
HTML 中的 Element 对象可以通过 Document 对象获取,而 Document 对象是通过 window 对象获取的
-
Document 对象中提供了以下获取 Element 元素对象的函数:
-
根据
id属性值获取,返回单个 Element 对象var h1 = document.getElementById('h1'); -
根据标签名称获取,返回 Element 对象数组
var divs = document.getElementsByTagName('div'); -
根据
name属性值获取,返回 Element 对象数组var hobbies = document.getElementsByName('hobby'); -
根据
class属性值获取,返回 Element 对象数组var clss = document.getElementsByClassName('cls');
-
-
-
改变 Element 元素
- 查阅文档,根据 Element 元素所拥有的属性和方法来改变 Element 元素
JavaScript 事件监听
- 事件: HTML 事件是发生在 HTML 元素上的 “事情”。比如:
- 按钮被点击
- 鼠标移动到元素上
- 按下键盘按键
- 事件监听:JavaScript 可以在事件被侦测到时 执行代码
事件绑定
-
方式一:通过 HTML 标签中的事件属性进行绑定
<input type="button" onclick="on()" value="button1">
<script>
function on() {
alert("I'm clicked!");
}
</script> -
方式二:通过 DOM 元素属性绑定
<input type="button" id="btn" value="button2">
<script>
document.getElementById('btn').onclick = function() {
alert("I'm clicked!");
}
</script>
常见事件
| 事件名 | 说明 |
|---|---|
| onclick | 鼠标单击事件 |
| onblur | 元素失去焦点 |
| onfocus | 元素获得焦点 |
| onload | 某个页面或图像被完成加载 |
| onsubmit | 当表单提交时触发该事件 |
| onkeydown | 某个键盘的键被按下 |
| onmouseover | 鼠标被移到某元素之上 |
| onmouseout | 鼠标从某元素上移开 |
Vue
什么是Vue?
-
Vue 是一套前端框架,免除原生 JavaScript 中的 DOM 操作,简化书写
-
基于 MVVM (Model-View-ViewModel) 思想,实现数据的双向绑定,将编程的关注点放在数据上
-
框架:是一个半成品软件,是一套可重用的、通用的软件基础代码模型。基于框架进行开发,更加快捷、更加高效。
-
MVVM模型
Vue快速入门
-
新建 HTML 页面,引入 Vue.js 文件
<script src = "js/vue.js">
-
在 JS 代码区域,创建 Vue 核心对象,定义数据模型
<script>
new Vue({
el: "#app",
data: {
message: "Hello Vue!"
}
})
</script> -
编写视图
<div id="app">
<input type="text" v-model="message">
{{message}}
</div>插值表达式:
- 形式:
- 内容可以是:
- 变量
- 三元运算符
- 函数调用
- 算术运算
Vue 常见指令
-
指令:指 HTML 标签上带有
v-前缀的特殊属性,不同指令具有不同含义。 -
常用指令:
指令 作用 v-bind 为 HTML 标签绑定属性值,如设置 href,css 样式等 v-model 在表单元素上创建双向数据绑定 v-on 为 HTML 标签绑定事件 v-if 条件性地渲染某元素,判定为 true 是渲染,否则不渲染 v-else-if 同上 v-else 同上 v-show 根据条件展示某元素,区别在于切换的是 display 属性的值 v-for 列表渲染,遍历容器的元素或者对象的属性 -
v-bind:
常规用法:
<a v-bind:href="url">xxx</a>简化用法:
<a :href="url">xxx</a> -
v-model:
<input type="text" v-model="url"><script>
new Vue({
el: "#app",
data: {
url:"https://www.google.com"
}
})
</script>
注意事项:通过 v-bind 或者 v-model 绑定的变量,必须在数据模型中声明。
-
v-on:
常规用法:
<input type="text" value="button" v-on:click="handle()">简化写法:
<input type="text" value="button" @click="handle()"><script>
new Vue({
el: "#app",
data: {
// ...
},
methods: {
handle: function() {
alert("I'm clicked!");
}
}
})
</script> -
v-if、v-else-if、v-else:
年龄{{age}},经判定为:
<span v-if="age <= 35">年轻人</span>
<span v-else-if="age > 35 && age < 60">中年人</span>
<span v-else>老年人</span> -
v-show:
年龄{{age}},经判定为:
<span v-show:"age <= 35">年轻人</span> -
v-for:
<div v-for="addr in addrs">
{{addr}}
</div><div v-for="(addr, index) in addrs">
{{index + 1}} : {{addr}}
</div>data: {
...
addrs: ['北京', '上海', '广州', '深圳', '成都', '杭州']
},
-
Vue 生命周期
-
生命周期:指一个对象从创建到销毁的整个过程。
-
Vue 生命周期的八个阶段:每触发一个生命周期事件,会自动执行一个生命周期方法(钩子)
状态 阶段周期 beforeCreate 创建前 created 创建后 beforeMount 挂载前 mounted 挂载完成 beforeUpdated 更新前 updated 更新后 beforeDestory 销毁前 destoryed 销毁后
Ajax & Axios & Element
Ajax
-
概念:Asynchronous JavaScript And XML,异步的 JavaScript 和 XML
-
作用:
-
数据交换:通过 Ajax 可以给服务器发送请求,并获取服务器相应的数据
-
异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用的校验等等。
-
-
同步与异步
- 同步:服务器处理请求时客户端需要等待,直到服务器返回数据后才能进行后面操作。
- 异步:服务器处理请求时客户端可以进行其他操作,不需要等待服务器端。
-
原生 Ajax(繁琐,已弃用)
-
创建
XMLHttpRequest对象:用于和服务器交换数据 -
向服务器发送请求
-
获取服务器响应数据
<body>
<input type="button" value="获取数据" onclick="getData()">
<div id= "div1"></div>
</body>
<script>
function getData() {
// 1.创建XMLHttpRequest
var xmlHttpRequest = new XMLHttpRequest();
// 2.发送异步请求
xmlHttpRequest.open('GET', http://yapi.smart-xwork.cn/mock/169327./emp/list);
xmlHttpRequest.send(); // 发送请求
// 3.获取服务器响应数据
xmlHttpRequest.onreadystatechange = function() {
if (xmlHttpRequest.readyState == 4 && xmlHttpRequest.status == 200) {
document.getElementById('div1').innerHTML = xmlHttpRequest.responseText;
}
}
}
</script>
-
Axios(基于原生 Ajax 封装,简化书写,快速开发)
-
Axios 使用步骤:
-
引入 Axios 的 js 文件:
<script src="js/axios-0.18.0.js"></script> -
使用 Axios 发送请求,并获取响应结果
axios({
method:"get",
url:"http://..."
}).then((result) => {
console.log(result.data);
});axios({
method:"post",
url:"http://...",
data:"id=1"
}).then((result) => {
console.log(result.data);
});then 方法中需要传入一个函数参数,称为成功回调函数,在成功获得服务器数据时调用,数据被封装为一个对象赋值给 result(回调函数参数)。
-
请求方式简化(推荐):
-
axios.get(url [, config])
-
axios.delete(url [, config])
-
axios.post(url [, data [, config]])
-
axios.put(url [,data [,config]])
-
使用方式示例:
axios.get("http://...").then((result) => {
console.log(result.data);
});axios.post("http://...", "id=1").then((result) => {
console.log(result.data);
});
-
-
前后端分离开发
介绍
早期开发模式:前后端混合开发
- 前端人员开发好一个静态的 HTML 页面,剩下的 CSS JS AXIOS JAVA 数据库等都交给主力开发人员来开发,前端代码和后端代码都放在一个工程文件中。
- 问题:
- 沟通成本高
- 分工不明确
- 项目不便于管理
- 项目不便于维护扩展
当前主流开发模式:前后端分离开发
前端团队负责前端工程,后端团队负责后端工程,前端与后端交互依靠异步请求,保证两者能够顺利交互的开发规范定义在接口文档中。
接口文档由产品经理通过分析页面原型和需求来定义和书写。
开发流程:
需求分析 -》 接口定义 -》 前后端并行开发 -》 测试 -》 前后端联调测试
YApi
- 介绍:YApi 是高效、易用、功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务
- 官网:[http://yapi.smart-xwork.cn/](YApi 官网)
- 功能:
- API 接口管理
- Mock 服务:模拟真实接口,生成接口的模拟测试数据,用于前端工程测试
- 使用步骤:
- 添加项目
- 添加分类
- 添加接口
- 完善接口信息
- (可选)添加 mock 期望
前端工程化
前端工程化开发:在企业级的前端项目开发中,把前端开发所需的工具、技术、流程、经验等进行规范化,标准化(借助 Vue 提供的脚手架工具)。
- 模块化:将 JS 和 CSS 制作成一个个可以复用的模块
- 组件化:将 UI 结构、CSS 样式以及 JS 的行为封装成一个个的组件
- 规范化:提供一套标准的规范的目录结构、编码规范和接口规范,所有开发人员都遵循这一套统一的规范进行开发
- 自动化:项目的构建、部署和测试自动化进行
环境准备
vue-cli
- 介绍:vue-cli 是 Vue 官方提供的一个脚手架,用于快速生成一个 Vue 的项目模板
- vue-cli 提供了如下功能:
- 统一的目录结构
- 本地调试
- 热部署
- 单元测试
- 集成打包上线
- 依赖环境:NodeJS
Vue 项目简介
-
目录结构:
基于 Vue 脚手架创建出来的工程,有标准的目录结构,如下:
- node_modules:存放整个项目的依赖包
- public:存放项目的静态文件
- src:存放项目的源代码
- package.json:存放模块基本信息,项目开发所需要模块,版本信息
- vue.config.js:保存 vue 配置的文件,如:代理、端口的配置等
src目录下的结构如下:
- assets:静态资源
- components:可重用的组件
- router:路由配置
- views:视图组件(页面)
- App.vue:入口页面(根组件)
- main.js:入口 js 文件
Vue 项目开发流程
Vue 组件库 Element
什么是 Element
- Element:是饿了么团队研发的,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库
- 组件:组成网页的部件,例如 超链接、按钮、图片、表格、表单、分页条等等
- 官网:https://element.eleme.cn/#/zh-CN
快速入门
-
安装 ElementUI 组件库(在当前工程的目录下),在命令行执行指令:
npm install element-ui@2.15.3 -
引入 ElementUI 组件库
// 在 main.js 文件中引入
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI); -
访问官网,复制组件代码,调整
常见组件
表格
- Table 表格:用于展示多条结构类似的数据,可对数据进行排序、筛选、对比或其他自定义操作。
分页
- Pagination 分页:当数据量过多时,使用分页分解数据。
对话框
- Dialog 对话框:在保留当前页面状态的情况下,告知用户并承载相关操作。
表单
- Form 表单:由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据。
案例
完成如下页面:
要求:根据页面原型完成员工管理页面开发,并通过 Axios 完成数据异步加载。
步骤:
- 创建页面,完成页面的整体布局规划
- 布局中各个部分的组件实现
- 列表数据的异步加载,并渲染展示
Vue 路由
- 前端路由:URL 中 #号 与 组件 之间的对应关系
Vue Router
-
介绍:Vue Router 是 Vue 的官方路由
-
组成:
VueRouter:路由器类,根据路由请求在路由视图中动态渲染选中的组件。其中维护了一份路由表,记录了路由路径和组件的对应关系。<router-link>:请求链接组件,浏览器会解析成<a><router-view>:动态视图组件,用来渲染展示与路由路径对应的组件
打包部署
打包
方式:在项目目录下执行指令:npm run build,之后会在项目目录下生成一个 dist 文件夹,为打包好的文件。
部署
Nginx
-
介绍:Nginx 是一款轻量级的 Web 服务器/反向代理服务器及电子邮件 (IMAP/PO3)代理服务器。其特点是占用内存少,并发能力强,在各大型互联网公司都有非常广泛的使用
-
Nginx 目录结构如下:
其中:
- conf:配置文件目录
- html:静态资源文件目录
- logs:日志文件目录
- temp:临时文件目录
-
步骤:将打包好的
dist目录下的文件,复制到 nginx 安装目录的 html 目录下即可注意事项:Nginx 默认占用 80 端口号,如果 80 端口号被占用,可以在
/conf/nginx.conf中修改端口号附在 Windows 控制台查看占用端口程序的代码:
netstat -ano | findStr port_no
Web 后端开发
Maven
什么是 Maven?
-
Maven 是 Apache 旗下的一个开源项目,是一款用于管理和构建 Java 项目的工具
Apache 软件基金会,成立于 1999年 7 月,是目前世界上最大的最受欢迎的开源软件基金会,也是一个专门为支持开源项目而生的非盈利组织
开源项目一览:[https://www.apache.org/index.html#project-list](Apache 开源项目一览)
Maven 的作用
-
依赖管理:方便快捷地管理项目依赖的资源包(jar包),避免版本冲突问题
-
统一项目结构:提供标准、统一的项目结构
- main:实际项目资源
- java:java 源代码目录
- resources:配置文件目录
- test:测试项目资源
- pom.xml:项目配置文件
- main:实际项目资源
-
项目构建:标准跨平台(Linux、Window、MacOS)的自动化项目构建方式
Maven 概述
介绍
-
Apache Maven 是一个项目管理和构建工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建
-
作用:
- 方便的依赖管理
- 统一的目录结构
- 标准的项目构建流程
-
官网:[http://maven.apache.org/](Maven 官网)
-
Maven 基本模型如下:

- 仓库:用于存储资源,管理各种 jar 包
- 本地仓库:自己计算机上的一个目录
- 中央仓库:由 Maven 团队维护的全球唯一的仓库。仓库地址:[https://repo1.maven.org/maven2/](Maven 中央仓库)
- 远程仓库(私服):一般为公司团队搭建的私有仓库。
- 查找顺序:先查找本地仓库,如果没有,再从远程仓库(如果有)下载,还未找到再从中央仓库下载至本地仓库。
- 仓库:用于存储资源,管理各种 jar 包
安装
安装步骤:
-
解压下载好的 zip 包
目录结构如下:
- bin:存放的是可执行文件,负责执行一些项目周期相关的指令(如
mvn) - config:存放 Maven 的配置文件
- lib:存放 Maven 本身所依赖的 jar 包资源(因为 Maven 基于 Java 开发)
- bin:存放的是可执行文件,负责执行一些项目周期相关的指令(如
-
配置本地仓库:修改
conf/settings.xml中的<localRepository>为一个指定目录,如:<localRepository>E:\develop\apache-maven-3.6.1\mvn_repo</localRepository>
-
配置阿里云私服:修改
conf/settings.xml中的<mirrors>标签,为其添加如下子标签:<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<mirrorOf>central</mirrorOf>
</mirror> -
配置环境变量:
MAVEN_HOME配置为 Maven 的解压目录,并将其bin目录加入PATH环境变量
IDEA 集成 Maven
-
配置 Maven 环境
配置当前工程:
- IDEA 中选择 File --> Settings --> Build, Execution, Deployment --> Build Tools --> Maven
- 设置 IDEA 使用本地安装的 Maven,并修改配置文件及本地仓库路径
配置 Maven 环境(全局):在创建项目页 Customize 栏中的 All Settings 处配置,步骤同上
-
创建 Maven 项目
-
创建模块,Build system选择 Maven
-
Advanced Settings 里填写模块名称,坐标信息,点击 Create,创建完成
Maven 坐标:
- 什么是坐标?
- Maven 中的坐标是资源的唯一标识,通过该坐标可以唯一定位资源位置
- 使用坐标来定义项目或引入项目中需要的依赖
- 坐标的主要组成
- groupId:定义当前 Maven 项目隶属组织名称(通常是域名反写,例如:com.yl)
- artifactId:定义当前 Maven 项目名称(通常是模块名称,例如 order-service、goods-service)
- version:定义当前项目版本号
- 什么是坐标?
-
编写 HelloWorld,并运行
-
-
导入 Maven 项目
- 方式一:先将待导入项目文件夹复制一份在当前工程目录下,然后选择 IDEA 右侧 Maven 面板,点击 + 号,选中该项目的 pom.xml 文件,双击即可
- 方式二:可以在当前工程的 Project Structure 页面 Module 栏中中点击 + 号,选择 Import Module,选择导入项目的 pom.xml 文件即可
依赖管理
依赖配置
-
依赖:值当前项目运行所需要的 jar 包,一个项目中可以引入多个依赖
-
配置:
- 在 pom.xml 文件中编写
<dependencies>标签 - 在
<dependencies>标签中,使用<dependency>引入坐标 - 定义坐标的 groupId, artifaceId, version
- 点击刷新按钮,引入最新加入的坐标
注意事项:
- 如果引入的依赖,在本地仓库不存在,将会连接远程仓库/中央仓库,然后下载依赖
- 如果不知道依赖的坐标信息,可以到 [https://mvnrepository.com/ ](Maven 坐标搜索)中搜索
- 在 pom.xml 文件中编写
依赖传递
-
依赖具有传递性
- 直接依赖:在当前项目中通过依赖配置建立的依赖关系
- 间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源
- 可以在项目的 pom.xml 文件中右键选择 Diagrams,选择 Show Diagram 就可以看到项目的依赖情况
-
排除依赖:指主动断开依赖的资源,被排除的资源无需指定版本
- 图示:

- 图示:
依赖范围
依赖的 jar 包,默认情况下,可以在任何地方使用。可以通过 <scope>...</scope> 设置其作用范围
作用范围:
-
主程序范围有效(main 文件夹范围内)
-
测试程序范围有效(test 文件夹范围内)
-
是否参与打包运行(package 指令范围内)
scope 值 主程序 测试程序 打包(运行) 范例 complie(默认) Y Y Y log4j test - Y - junit provided Y Y - servlet-api runtime - Y Y jdbc 驱动
生命周期
Maven 的生命周期就是为了对所有的 Maven 项目构建过程进行抽象和统一
Maven 中有 3 套相互独立的生命周期:
- clean:清理工作
- default:核心工作,如:编译、测试、打包、安装、部署等
- site:生成报告、发布站点等
每套生命周期包含一些阶段(phase),阶段是有顺序的,后面的阶段依赖于前面的阶段
五个重点关注阶段:
-
clean:一处上一次构建生成的文件
-
compile:编译项目源代码
-
test:使用合适的单元测试框架运行测试(junit)
-
package:将编译后的文件打包,如:jar、war等
-
install:安装项目到本地仓库
注意事项:在同一套生命周期中,当运行后面的阶段时,前面的阶段都会运行。
如何执行生命周期:
- 在 IDEA 中,右侧的 maven 工具栏,选中对应的生命周期,双击执行
- 在命令行中,通过命令执行:
mvn phase_name
SpringBoot 基础篇
Spring
- 官网:[spring.io](Spring 官网)
- Spring 发展到今天已经形成了一种开发生态圈,Spring 提供了若干个子项目,每个项目用于完成特定的功能
SpringBoot
SpringBoot 可以帮助我们非常快速地构建应用程序、简化开发、提高效率,是为了解决使用底层 Spring 框架构建项目太复杂繁琐而生。
SpringBoot Web 入门
需求:使用 SpringBoot 开发一个 Web 应用,浏览器发起请求 /hello 后,给浏览器返回字符串 “Hello World~”
步骤:
- 创建 SpringBoot 工程,并勾选 Web 开发相关依赖
- 定义 HelloController 类,添加方法 hello,并添加注解
- 运行测试
HTTP 协议
概述
- 概念:Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器与服务器之间数据传输的规则
- 特点:
- 基于 TCP 协议:面向连接,安全
- 基于请求 - 响应模型:一次请求对应一次响应
- HTTP 协议是无状态的协议:对于事务处理没有记忆能力。每次请求 - 响应都是独立的
- 缺点:多次请求间不能共享数据
- 优点:速度快
请求协议
请求数据格式分为三个部分:
-
请求行:请求数据的第一行,包括请求方式,资源路径和协议
-
请求头:第二行开始,格式为
key: value常见的请求头及其含义:
请求头 含义 Host 请求的主机名 User-agent 浏览器版本 Accept 表示浏览器能接收的资源类型,如 text/*,image/* 或者 */* 表示所有 Accept-Language 表示浏览器偏好的语言,服务器可以据此返回不同语言的网页 Accept-Encoding 表示浏览器可以支持的压缩类型,例如 gzip、deflate等 Content-Type 请求主体的数据类型 Content-Length 请求主体的大小(单位:字节) -
请求体:POST 请求特有,与请求行和请求头间隔一个空行,存放请求参数
GET:请求参数在请求行中,没有请求体。GET 请求大小有限制
POST:请求参数在请求体中,POST请求大小没有限制
响应协议
响应数据格式也分为三个部分:
-
响应行:响应数据的第一行,包括协议、状态码和描述
状态码分类:
状态码 描述 1xx 响应中:临时状态码,表示请求已经接收,告诉客户端应该继续请求或者如果它已经完成则忽略它 2xx **成功:**表示请求已经被成功接收,处理已完成 3xx **重定向:**重定向到其他地方;让客户端再发起一次请求以完成整个处理 4xx **客户端错误:**处理发生错误,责任在客户端。如:请求了不存在的资源、客户端未被授权、禁止访问等 5xx **服务器错误:**处理发生错误,责任在服务端。如:程序抛出异常等 -
响应头:第二行开始,格式key: value
常见的响应头及其含义:
响应头 含义 Content-Type 表示该响应内容的类型,例如 text/html, application/json Content-Length 表示该响应内容的长度(字节数) Content-Encoding 表示该响应压缩算法,例如 gzip Cache-Control 指示客户端应如何缓存,例如 max-age=300 表示可以最多缓存 300 秒 Set-Cookie 告诉浏览器为当前页面所在的域设置 cookie -
响应体:最后一部分,与响应行和响应头间隔一个空行,存放响应数据
协议解析
浏览器内置了解析 http 协议格式数据的程序,以下主要研究服务端如何解析 请求协议和返回 响应协议
- 最原始的方法是在 Java 代码中用 socket 拿到网络的输入输出流,通过输入流按照数据格式一行一行读取请求行,请求头和请求体,通过逻辑判断来决定响应数据,最后通过输出流将 响应数据 输出回请求方(按照数据格式)
- 由于原始的方法太过繁琐,代码量过大,又因为 http 协议是统一固定的标准,所以有很多已被其他组织写好的软件程序对 http 协议进行了封装,使得程序员不必直接对 http 协议进行操作,这些软件程序称为 Web 服务器,以下将会介绍最为流行的 Web 服务器 —— Tomcat
Web 服务器 - Tomcat
Web 服务器
Web 服务器是一个软件程序,对 http 协议的操作进行了封装,简化了 Web 程序开发,使得程序员不必直接对协议进行操作,让 Web 开发更加便捷。主要功能是 ”提供网上信息浏览服务“
简介
- 概念:Tomcat 是 Apache 软件基金会的一个核心项目,是一个开源免费的轻量级 Web 服务器,支持 Servlet/JSP等少量 JavaEE 规范
- JavaEE:Java Enterprise Edition,Java 企业版。指 Java 企业级开发的技术规范总和。包含 13 项技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF
- Tomcat 也被称为 Web 容器、Servlet 容器。Servlet 程序需要依赖于 Tomcat 才能运行
- 官网:[https://tomcat.apache.org/](Tomcat 官网)
基本使用
-
下载:官网下载
-
安装:将压缩包解压到相应目录即为安装完成
-
解压后目录如下:
其中:
- bin:存放可执行文件
- conf:存放配置文件
- 其中的 server.xml 文件中可以修改 Tomcat 占用的端口号
- lib:存放 Tomcat 依赖的 jar 包
- logs:存放日志文件
- temp:存放临时文件
- webapps:应用发布目录
- work:工作目录
-
-
卸载:直接删除目录即可
-
启动:双击:``bin\startup.bat`
-
关闭:
- 直接关掉运行窗口:强制关闭
- 双击
bin\shutdown.bat:正常关闭 - Ctrl + C:正常关闭
注意事项:HTTP 协议默认端口号为 80,如果将 Tomcat 端口号改为 80,则将来访问 Tomcat 时,不需要输入端口号,直接输入
localhost或 IP
- 部署应用:将打包好的 web 应用目录放在
webapps目录下即可
入门程序解析
- 起步依赖:
- spring-boot-starter-web:包含了 web 应用开发所需要的常见依赖
- spring-boot-starter-test:包含了单元测试所需要的常见依赖
- 官方提供的 starter:[https://docs.spring.io/spring-boot/docs/2.7.4/reference/htmlsingle/#using.build-system.starters](Spring 官方提供的起步依赖一览)
- 内嵌 Tomcat 服务器
- 基于 SpringBoot 开发的 Web 应用程序,内置了 Tomcat 服务器,当启动类运行时,回自动启动内嵌的 Tomcat 服务器。
请求响应
SpringBoot 中的请求响应结构图示:
-
请求对象(HttpServletRequest):获取请求数据
-
响应对象(HttpServletResponse):设置响应数据
-
BS 架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端
-
CS 架构:Client/Server,客户端/服务器架构模式
请求
主要讲述各类请求参数的结构和封装
Postman
-
Postman 是一款功能强大的网页调试与发送网页 HTTP 请求的应用
-
作用:常用于进行接口测试
简单参数
-
原始方式:在原始的 Web 程序中,获取请求参数,需要通过 HttpServletRequest 对象手动获取
public String simpleParam(HttpServletRequest request) {
String name = request.getParameter("name");
String ageStr = request.getParameter("age");
int age = Integer.parseInt(ageStr);
System.out.println(name + ": " + age);
return "OK";
} -
SpringBoot 方式
-
简单参数:参数名与形参变量名相同,定义形参即可接收参数(GET 和 POST请求均可用,只要参数名与形参变量名相同即可),会自动进行类型转换,于是上述代码精简为:
public String simpleParam(String name, Integer age) {
System.out.println(name + ": " + age);
return "OK";
} -
如果方法的形参名称与请求参数名称不匹配,可以使用
@RequestParam完成映射
public String simpleParam( String username, Integer age) {
System.out.println(username + ": " + age);
return "OK";
}注意事项:
@RequestParam中的required属性默认为true,代表该请求参数必须传递,如果不传递将报错。如果该参数是可选的,可以将required属性设置为false
-
实体参数
-
简单实体对象:请求参数名与形参对象属性名相同,定义 POJO 接收即可
public String simplePojo(User user) {
System.out.println(user);
return "OK";
}
public class User {
private String name;
private Integer age;
}注意事项:
要确保实体类有合适的getter和setter方法。Spring 使用这些方法来绑定请求参数到对象属性上。
-
复杂实体对象:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套 POJO 属性参数,如:
public String complexPojo(User user) {
System.out.println(user);
return "OK";
}
public class User {
private String name;
private Integer age;
private Address address;
}
public class Address {
private String province;
private String city;
}则对应的请求参数顺序为:
name,age,address.province,address.city.
数组集合参数
-
应用场景:用于处理前端复选框组件传递的参数(一般为数组或集合)
-
数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数
public String arrayParam(String[] hobby) {
System.out.println(Arrays.toString(hobby));
return "OK";
} -
集合参数:请求参数名与形参集合名称相同且请求参数为多个,
@RequestParam绑定参数关系(因为默认多个值是封装到数组里的)
public String listParam( List<String> hobby) {
System.out.println(hobby);
return "OK";
}
日期参数
-
日期参数:使用
@DateTimeFormat注解完成日期参数格式转换,请求参数名与形参名称相同
public String dateParam( LocalDateTime updateTime) {
System.out.println(updateTime);
return "OK";
}
Json 参数
-
JSON参数:JSON 参数键名与形参对象属性名相同,定义 POJO 类型形参即可接收参数,需要使用
@RequestBody标识
public String jsonParam( Client client) {
System.out.println(client);
return "OK";
}{
"name": "Tom",
"age": 12,
"address": {
"province": "shanghai",
"city": "shanghai"
}
}
路径参数
-
路径参数:通过请求 URL 直接传递参数,使用 {…} 来表示该路径参数,需要使用
@PathVariable获取路径参数,路径参数名需要与方法形参名相同
public String pathParam( Integer num) {
System.out.println(num);
return "OK";
}
响应
@ResponseBody
- 类型:方法注解、类注解
- 位置:Controller 方法上/类上
- 作用:将方法返回值直接响应,如果返回值类型是 实体对象/集合,将会转换为 JSON 格式响应
- 说明:@RestController = @Controller + @ResponseBody
统一响应结果
图示:
public class Result { |
温馨提示:SpringBoot 项目的静态资源(html、css、js等前端资源)默认存放目录为:classpath:/static、classpath:/public、classpath:/resources
分层解耦
三层架构(基于单一职责原则)
-
Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
-
Service:业务逻辑层。处理具体的业务逻辑
-
Dao:数据访问层(Data Access Object)(持久层)。负责数据访问操作,包括数据的增删改查。
分层解耦
- 内聚:软件中各个功能模块内部的功能联系
- 耦合:衡量软件中各个层/模块之间的依赖、关联的程度
- 软件设计原则:高内聚低耦合
- 控制反转:Inversion Of Control,简称 IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转
- 依赖注入:Dependency Injection,简称 DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入
- Bean 对象:IOC 容器中创建、管理的对象,称之为 Bean
- 分层解耦就是基于 IOC 和 DI 来实现的
IOC & DI 入门
步骤:
-
将需要解耦的实现类,交给 IOC 容器来管理
在类上加入注解:
@Component -
在耦合的代码部分,进行依赖注入
在代码部分上方,加上注解
@Autowired -
运行测试
IOC详解
-
Bean 的声明
要把某个对象交给 IOC 容器管理,需要在对应的类上加上如下注解之一:
注解 说明 位置 @Component 声明 bean 的基础注解 不属于以下三类时,用此注解,例如:工具类 @Controller @Component 的衍生注解 标注在控制器类上 @Service @Component 的衍生注解 标注在业务类上 @Repository @Component 的衍生注解 标注在数据访问类上(由于与mabatis整合,用的少) 注意事项:
- 声明 bean 的时候,可以给每个 bean 取名字,具体做法是在以上四个注解后加上
(value = "beanName") - 使用以上四个注解都可以声明 bean,但是在 SpringBoot 集成 Web 开发中,声明控制器 bean 只能用 @Controller
- 声明 bean 的时候,可以给每个 bean 取名字,具体做法是在以上四个注解后加上
-
Bean 组件扫描
- 前面声明 bean 的四大注解要想生效,还需要被组件扫描注解
@ComponentScan扫描 @ComponentScan注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描范围是启动类所在包及其子包
- 前面声明 bean 的四大注解要想生效,还需要被组件扫描注解
DI详解
- @Autowired 注解,默认是按照类型进行,如果存在多个相同类型的 bean,则会报错,程序无法启动,可通过以下几种方案来解决:
- @Primary:在想要生效的 bean 上加上该注解即可
- @Qualifier:在
@Autowired下加上该注解并在该注解后加上("beanName")即可选择你想要生效的 bean - @Resource:不用
@Autowired而改用@Resource(name = "beanName")来指定你想要生效的 bean
MySQL
数据库设计
MySQL概述
登录语法:
mysql -uusername -ppassword [-hip] [-pport]
DDL
基于页面原型和需求文档来设计表结构
数据库操作
DML
DQL
多表设计
- 一对多
- 多对多
- 一对一
- 根据页面原型和需求文档分析多张表之间的关系,以及每张表中有哪些字段。
数据库优化
索引
介绍
- 索引(index)是帮助数据库高效获取数据的数据结构
- 优缺点:
- 优点:
- 提高数据查询效率,降低数据库 IO 成本
- 通过索引列对数据进行排序,降低数据排序成本,降低 CPU 消耗
- 缺点:
- 索引会占用存储空间
- 索引大大提高了查询效率,同时却降低了插入、更新和删除的效率
- 优点:
结构
MySQL 数据库支持的索引结构有很多,如:
- Hash 索引
- B+Tree 索引
- Full-Text 索引
我们平常所说的索引,如果没有特别指明,都是指默认的 B+Tree 结构组织的索引。
-
B+Tree(多路平衡搜索树)
- 每一个节点,可以存储多个 key(有 n 个 key,就有 n 个指针)
- 所有的数据都存储在叶子节点,非叶子节点仅用于索引数据
- 叶子节点形成了一条双向链表,便于数据的排序及区间范围查询
语法
-
创建索引
CREATE [UNIQUE] INDEX 索引名 ON 表名(字段名, ...);
-
查看索引
SHOW INDEX FROM 表名;
-
删除索引
DROP INDEX 索引名 ON 表名;
主键字段,在建表时,会自动创建主键索引
添加唯一约束时,数据库实际上会添加唯一索引
SpringBoot Mybatis
什么是 MyBatis?
- MyBatis 是一款优秀的持久层框架,用于简化 JDBC 的开发
- MyBatis 本是 Apache 的一个开源项目 iBatis,2010 年这个项目由 Apache 迁移到了 google code,并且改名为 MyBatis。2013 年 11 月 迁移到 Github
- 官网:[https://mybatis.org/mybatis-3/zh/index.html](MyBatis 官网)
MyBatis 入门
快速入门
步骤:
- 准备工作(创建 SpringBoot 工程、数据库表 user、实体类 User)
- 引入 MyBatis 的相关依赖,配置 MyBatis(数据库连接信息)
- 编写 SQL 语句(注解/XML)
配置 SQL 提示
- 产生原因:IDEA 和数据库没有建立连接,不识别表信息
- 解决方式:在 IDEA 中配置 MySQL 连接
JDBC 介绍
-
JDBC:Java DataBase Connectivity,就是使用 JAVA 语言操作关系型数据库的一套 API(规范,只有接口,无实现类)
JAVA 程序操作数据库流程图示
-
本质:
- sun 公司官方定义的一套操作所有关系型数据库的规范,即接口
- 各个数据库厂商去实现这套接口,提供数据库驱动jar包
- 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
-
原始 JDBC 操作数据库程序
问题:
- 注册驱动的驱动包名和获取连接的 url、用户名和密码变动频繁,原始程序是硬编码的方式,在更改后需要重新编译打包部署,维护成本高
- 解析封装返回数据的代码量大,操作繁琐
- 频繁地进行数据库资源的连接和释放,造成资源浪费和性能降低
-
MyBatis 实现的优点
- 驱动报名和获取连接的信息写在 application.properties 文件中,不在 JAVA 程序中,变更无需重新编译程序
- 解析封装返回数据由框架代为执行,不需要手动编写代码
- 数据库连接对象都放在数据库连接池之中,使用时取出,用完后退还,不会频繁地获取和释放连接对象,避免了资源的浪费
数据库连接池
介绍:
- 数据库连接池是一个容器,负责分配、管理数据库连接对象(Connection)
- 它允许应用程序重复使用一个现有的数据库连接对象,而不是再重新建立一个
- 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的客户端连接遗漏(有客户端获取不到连接)
标准接口:DataSource
-
官方(sun)提供的数据库连接池接口,由第三方组织实现此接口
-
功能:获取连接
-
核心方法:
Connection getConnection() throws SQLException; -
常见产品:
-
C3P0
-
DBCP
-
Druid(阿里巴巴开源的数据库连接池项目,是 JAVA 语言最好的数据库连接池之一)
-
依赖配置:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency> -
application.properties 内容照旧
-
官方文档:[https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter](Druid 官方文档)
-
-
Hikari(SpringBoot 默认)
-
lombok
-
lombok 是一个使用的 JAVA 类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString 等方法,并可以自动化生成日志变量,简化 JAVA 开发,提高效率
-
lombok 常用注解:
注解 作用 @Getter/@Setter 为所有的属性提供 getter/setter 方法 @ToString 会给类自动生成易于阅读的 toString 方法 @EqualsAndHashCode 根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法 @Data 提供了综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode) @NoArgsConstructor 为实体类生成无参的构造器方法 @AllArgsConstructor 为实体类生成除了 static 修饰的字段之外带有各参数的构造器方法 -
依赖引入:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
MyBatis 基础增删改查
准备
- 准备数据库表 emp
- 创建一个 SpringBoot 工程,选择引入对应的起步依赖(lombok, mybatis, mysql driver)
- application.properties 中引入数据库连接信息
- 准备对应的实体类 Emp(实体类属性采用驼峰命名)
- 准备 Mapper 接口 EmpMapper
删除
-
根据主键删除
-
SQL 语句:
DELETE FROM emp WHERE id = 17;
-
接口方法:
public interface EmpMapper {
public int delete(Integer id);
}注释:
#{}为 MyBatis 的变量占位符,可以拿到方法接收的参数@Delete注解拥有返回值:该 SQL 操作影响的行的数量,要拿到该返回值,只需将方法声明为 int 返回类型即可,方法执行后会返回该值。
注意事项:如果 mapper 接口方法形参只有一个普通类型的参数,#{…} 里面的属性名可以随便写,如 #{id}、#{value},但建议保持一致
日志输出:可以在 application.properties 中,打开 mybatis 的日志,并指定输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
预编译 SQL:
![]()
优势:
性能更高
![]()
更安全(防止SQL注入)
- SQL 注入是通过操作输入的数据来修改事先定义好的 SQL 语句,以达到执行代码对服务器进行攻击的方法。
参数占位符
- #{…}
- 执行 SQL 时,会将 #{…} 替换为 ?,生成预编译 SQL,会自动设置参数值
- 使用时机:参数传递,都是用 #{…}
- ${…}
- 拼接 SQL。直接将参数拼接在 SQL 语句中,存在 SQL 注入问题
- 使用时机:如果对表名、列表进行动态设置时使用
插入
接口方法:
|
使用实体类将传递参数封装起来,并在 #{}内写这个实体类的属性
主键返回
-
描述:在数据添加成功后,需要获取插入数据库数据的主键。如添加套餐数据时,还需要维护套餐菜品关系表数据
-
实现:
// 会自动将生成的主键值,赋值给emp对象的id属性
public void insert(Emp emp);
更新
接口方法:
|
查询
根据 ID 查询
接口方法:
|
数据封装
实体类属性名 和 数据库表查询返回的字段名一致,MyBatis 会自动封装
如果字段名不一致,则不能自动封装
解决方案:
给字段起别名,让别名与实体类属性名一致
public Emp getById(Integer id);通过 @Results, @Result 注解手动映射封装
public Emp getById(Integer id);开启 MyBatis 的驼峰命名自动映射开关,可以实现 a_column --------> aColumn。在 application.properties 中 输入
mybatis.configuration.map-underscore-to-camel-case=true
条件查询
接口方法:
|
XML 映射文件
规范
-
XML 映射文件的名称与 Mapper 接口名称一致,并且将 XML 映射文件和 Mapper 接口放置在相同包下(同包同名)
-
XML 映射文件的 namespace 属性与 Mapper 接口全限定名一致
-
XML 映射文件中 SQL 语句的 id 与 Mapper 接口中的方法名一致,并保持返回类型一致
-
文件结构:
<mapper namespace="com.yl.mapper.EmpMapper">
<!-- resultType 指单条记录所封装的类型-->
<select id="list" resultType="com.yl.pojo.Emp">
// SQL 语句
</select>
<delete>
</delete>
<update>
</update>
<insert>
</insert>
</mapper>
原则:使用 MyBatis 的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的 SQL 功能,建议使用 XML 来配置映射语句
MyBatis 动态 SQL
动态 SQL:随着用户的输入或外部条件的变化而变化的 SQL 语句,我们称为动态 SQL
<if>
-
用户判断条件是否成立。使用 test 属性进行条件判断,如果条件为 true,则拼接 SQL;否则不。
-
test 属性中的 and、or必须小写!!!
-
示例1:
<select id="list" resultType="com.yl.pojo.Emp">
SELECT *
FROM emp
<where>
<if test="name != null">
name LIKE concat('%', #{name}, '%')
</if>
<if test="gender != null">
AND gender = #{gender}
</if>
<if test="begin != null and end != null">
AND entrydate BETWEEN #{begin} AND #{end}
</if>
</where>
ORDER BY update_time DESC
</select><where>标签可以- 动态生成 WHERE 子句(有条件成立则生成,否则不生产)
- 自动去除掉条件前面多余的 and 和 or
-
示例2:
<update id = "update2">
UPDATE emp
<set>
<if test = "username != null">
username = #{username},
</if>
<if test = "name != null">
name = #{name},
</if>
<if test = "gender != null">
gender = #{gender}
</if>
</set>
WHERE id = #{id}
</update><set>标签可以- 动态地在行首插入 SET 关键字,机制同
<where> - 自动去除掉额外的逗号
- 动态地在行首插入 SET 关键字,机制同
<foreach>
要完成这个 SQL 语句的实现:
DELETE FROM emp WHERE id in(18, 19, 20); |
实现如下:
<delete id = "deleteByIds"> |
属性解释:
- collection:要遍历的集合
- item:遍历出的元素的名字(自定)
- separator:分隔符
- open:遍历开始前拼接的 SQL 片段
- close:遍历结束后拼接的 SQL 片段
<sql><include>
-
<sql>:用来定义可重用的 SQL 片段<sql id="yourName">
// sql
</sql> -
<include>:通过属性 refid,指定引用的 sql 片段// place to reuse the sql
<include refid="yourName" />
SpringBoot Web 开发篇
文件上传
简介
- 文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程
- 文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能
- 前端页面文件上传三要素:
- form 表单元素中:
method="post",enctype="multipart/form-data" - input 元素中:
type="file" - 图示:

- 使用 Spring 框架提供的类
MultipartFile来接收上传的文件
- form 表单元素中:
本地存储
在服务端,接收到上传上来的文件之后,将文件存储在本地服务器磁盘中。
|
在 SpringBoot 中,文件上传,默认单个文件允许最大大小为 1MB。如果需要上传大文件,可以进行如下配置
# 配置单个文件最大上传大小 |
本地存储的问题:
- 前端无法访问服务端保存的文件
- 服务器磁盘容量有限,扩容困难
- 服务器磁盘损坏后,造成的损失大
MultipartFile类的常用方法:
- String getOriginalFilename(); // 获取原始文件名
- void transferTo(File dest); // 将接收的文件转存到磁盘中
- long getSize(); // 获取文件的大小,单位:字节
- byte[] getBytes(); // 获取文件内容的字节数组
- InputStream getInputStream(); // 获取接收到的文件内容的输入流
阿里云 OSS
介绍
阿里云:阿里云是阿里巴巴集团旗下全球领先的云计算公司,也是国内最大的云服务提供商
云服务:通过互联网向外界提供的各种服务
阿里云对象存储OSS(Object Storage Service):是一款海量、安全、低成本、高可靠的云存储服务。使用 OSS,可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。
使用第三方服务的通用思路
-
准备工作:账号注册、实名认证、后台基本配置等
-
参照官方 SDK 编写入门程序
SDK:Software Development Kit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做 SDK
-
项目中集成该服务并使用
阿里云 OSS 使用步骤
-
注册阿里云(实名认证)
-
充值
-
开通对象存储服务(OSS)
-
创建 bucket
bucket 是阿里云 OSS 中的存储空间,是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间
-
获取 AccessKey(密钥)
-
参照官方 SDK 编写入门程序
-
项目集成 OSS
集成
步骤:
-
引入依赖:
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency> -
引入阿里云 OSS 上传文件工具类(由官方的示例代码改造而来)
-
上传图片接口开发
配置文件
参数配置化
问题:如果在一些类中定义所需要的参数(如OSS的密钥和bucket),会导致不变维护和管理
问题解决:
# OSS 参数配置 |
|
@Value(Spring提供) 注解通常用于外部配置的属性注入,具体用法为:@Value(“${配置文件中的key}”)
yml 配置文件
常见配置文件格式对比
- XML:臃肿
- properties:层级结构不清晰
- yml/yaml:简洁、数据为中心(推荐)
yml 基本语法
- 大小写敏感
- 数值前边必须有空格作为分隔符
- 使用缩进表示层级关系,缩进时,不允许使用 Tab 键,只能用空格(idea 会自动将 Tab 转换为空格)
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- # 表示注释,从这个字符一直到行尾,都会被解析器忽略
yml 数据格式
-
对象/Map集合
user:
name: zhangsan
age: 18
password: 123456 -
数组/List/Set集合:
hobby:
- java
- game
- sport
@ConfigurationProperties
想法:想要配置文件中的配置参数自动注入到相应对象的属性,而不需要每个属性都加上 @Value 注解
实现:
-
Spring 提供了方法以实现以上操作,步骤:
-
配置文件的 key 名称与实体类的属性名一致,并且提供对应的 getter/setter 方法
-
将该实体类交给 IOC 容器管理
-
配置文件的 key 名称前面的部分用 @ConfigurationProperties 注解来指定。如:
配置好的实体类示例如下:
public class AliOSSProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
} -
配置文件提示:
<dependency> |
@ConfigurationProperties 与 @Value
- 相同点:都是用来注入外部配置的属性的
- 不同点:
- @Value 注解只能一个一个地进行外部属性的注入
- @ConfigurationProperties 注解可以批量地将外部属性注入到 bean 对象的属性中
登录认证
登录功能
查询数据库中对应用户名和密码的用户,返回 Emp 对象,如果不为空,则登录成功;为空,则登录失败
登录校验
**问题:**在未登录的情况下,直接输入 url 就可以不登陆而访问部门管理、员工管理等功能
实现蓝图:
会话技术
-
会话:用户打开浏览器,访问 web 服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
-
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据
-
会话跟踪方案:
-
客户端会话跟踪技术:Cookie
- 图示:
-
优点:HTTP 协议中支持的技术
-
缺点:
-
移动端 APP 无法使用 Cookie
-
不安全,并且用户可以自己禁用 Cookie
-
Cookie 不能跨域
-
跨域区分三个维度:协议、IP/域名、端口,任何一个不同将导致跨域
-
Cookie 不能跨域,简单来说,就是浏览器目前访问的 ip + 端口的 Cookie 不能被其他 ip + 端口设置
-
// 设置Cookie
public Result cookie1(HttpServletResponse response) {
// 设置 Cookie(响应Cookie),cookie名为login_username,值为yl
response.addCookie(new Cookie("login_username", "yl"));
return Result.success();
}// 获取Cookie
public Result cookie2(HttpServletRequest request) {
// 获取所有的Cookie
Cookie[] cookies = request.getCookies();
for (Cookie cookie: cookies) {
// 输出name为login_username的cookie
if (cookie.getName().equals("login_username")) {
System.out.println("login_username" + cookie.getValue());
}
}
return Result.success();
}-
服务端会话跟踪技术:Session
-
与 Cookie 方案类似,Session 是基于 Cookie 实现的,区别在于存储方在服务端,而不是客户端浏览器
-
优点:
- 存储在服务端,安全
-
缺点:
- 服务器集群环境下无法直接使用 Session
- Cookie 的缺点
// 往 HttpSession 中存储数据
public Result session1(HttpSession session) {
// 往 Session 中存储数据
session.setAttribute("loginUser", "tom");
return Result.success();
}// 从 HttpSession 中获取数据
public Result session2(HttpServletRequest request) {
HttpSession session = request.getSession();
// 从 Session 中获取数据
Object loginUser = session.getAttribute("loginUser");
return Result.success(loginUser);
} -
-
令牌技术(主流方案)
-
优点:支持 PC 端、移动端
- 解决了集群环境下的认证问题
- 减轻了服务器端存储压力(不需要存 Session)
-
缺点:
- 需要自己实现
-
-
JWT 令牌
简介
-
全称:JSON Web Token(https://jwt.io/)
-
定义了一种简洁的、自包含的格式,用于在通信双方以 json 数据格式安全地传输信息。由于数字签名的存在,这些信息是可靠的。
-
组成:
-
第一部分:Header(头),记录令牌类型、签名算法等。例如
{"alg": "Hs256", "typ": "JWT"} -
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如
{"id": "1", "username": "Tom"} -
第三部分:Signature(签名),防止 Token 被篡改、确保安全性。将 Header、Payload 部分内容融入,并加入指定密钥,通过指定签名算法计算而来。
-
图示:
Base64:是一种基于 64 个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式
-
场景:登录认证
- 登录成功后,生成令牌
- 将令牌发放给前端和浏览器,浏览器在后续发送请求时都会携带 JWT 令牌到服务端以校验身份
- 服务端会在每次请求前先校验令牌,通过后,再处理请求
令牌生成和校验
-
引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency> -
生成 JWT 令牌
public void getJwt() {
HashMap<String, Object> claims = new HashMap<>();
claims.put("id", 1);
claims.put("name", "Tom");
String secretString = "yl"; // 原始签名密钥
byte[] secretBytes = Base64.getEncoder().encode(secretString.getBytes()); // 将原始签名密钥编码为 Base64编码以满足生成需要
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, secretBytes) // 设置签名算法和签名密钥
.setClaims(claims) // 自定义内容
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // 设置 JWT 令牌有限期为一小时
.compact();
System.out.println(jwt);
} -
校验并解析 JWT 令牌
public void parseJwt() {
Claims claims = Jwts.parser()
.setSigningKey(Base64.getEncoder().encode("yl".getBytes())) // 设置签名密钥,需与生成时一致
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwODkzNzU0Mn0.-_402mawcQ0HxFvxPMm8DfJC_XcK8btNmLwJYvwAn3w") // 传递 JWT 令牌,校验
.getBody(); // 获取令牌自定义内容
System.out.println(claims);
}
注意事项
- JWT 校验时所使用的签名密钥,必须和生成 JWT 令牌时使用的密钥是配套的
- 如果 JWT 令牌解析校验时报错,则说明 JWT 令牌被篡改 或 失效了,令牌非法
登录-生成令牌
- 引入 JWT 令牌操作工具类
- 登录完成后,调用工具类生成 JWT 令牌,并返回
过滤器 Filter
概述
- 概念:Filter 过滤器,是 JavaWeb 三大组件之一(Servlet、Filter、Listener)之一
- 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
- 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等
快速入门
步骤:
- 定义 Filter:定义一个类,实现 Filter 接口,并重写其所有方法(可以只重写 doFilter 方法)
- 配置 Filter:Filter 类加上 @WebFilter 注解,配置拦截资源的路径。引导类加上 @ServletComponentScan 开启 Servlet 组件支持
详解
-
执行流程
- 拦截到请求之后,会自动调用 doFilter 方法,在放行前,会执行放行代码之前的逻辑,放行时执行对应的访问操作,对应操作执行完毕后,还会返回来执行放行代码之后的逻辑。此为整个执行流程
-
拦截路径
-
Filter 可以根据需求,配置不同的拦截资源路径:
拦截路径 urlPatterns值 含义 拦截具体路径 /login 只有访问 /login 路径时,才会被拦截 目录拦截 /emps/* 访问 /emps 下的所有资源,都会被拦截 拦截所有 /* 访问所有资源,都会被拦截
-
-
过滤器链
- 介绍:一个 Web 应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链(类似栈)
- 图示:

- 顺序:注解配置的 Filter,顺序是按照过滤器类名(字符串)的自然排序
登录校验-Filter
流程:
拦截器 Interceptor
简介&快速入门
-
概述
- 概念:Interceptor 是一种动态拦截方法调用的机制,类似于过滤器。Spring 框架中提供的,用来动态拦截控制器方法的执行。
- 作用:拦截请求,在指定的方法调用后,根据业务需要执行预先设定的代码
-
步骤
-
定义拦截器,实现 HandlerInterceptor 接口,并重写其所有方法
public class LoginInterceptor implements HandlerInterceptor {
// 目标资源方法执行前执行,放行则返回 true,不放行则返回 false
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
return true;
}
// 目标资源方法执行后执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
// 视图渲染完毕后执行,最后执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
} -
注册拦截器
public class WebConfig implements WebMvcConfigurer {
private LoginInterceptor loginInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).addPathPatterns("/**");
}
}
-
详解
-
拦截路径
-
拦截器可以根据需求,配置不同的拦截路径:
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}addPathPatterns可以配置需要拦截哪些资源excludePathPatterns可以配置不需要拦截哪些资源拦截路径 含义 举例 /* 一级路径 只能匹配类别 /login,不能匹配 /depts/1 /** 任意级路径 任意匹配 /depts/* /depts 下的一级路径 可匹配 /depts/1,不能匹配 /depts/1/2 和 /depts /depts/** /depts 下的任意级路径 能匹配 /depts 和其下任何路径,不能匹配 /emps 和其下任何路径
-
-
执行流程
- 图示:

- 浏览器请求会先被 Tomcat 服务器中的过滤器拦截,过滤器放行的请求会转交给 Spring 中的 DispatcherServlet,然后被拦截器拦截,拦截器放行的请求才到达 controller 执行操作,然后再依次返回。
- 图示:
Filter 与 Interceptor 比较
- 接口规范不同:过滤器需要实现 Filter 接口,而拦截器需要实现 HandlerInterceptor 接口
- 拦截范围不同:过滤器 Filter 会拦截所有的资源,而 Interceptor 只会拦截 Spring 环境中的资源
登录校验-Interceptor
只需要将 Filter 中的代码照搬即可
异常处理
出现异常(如添加数据时数据库数据重复,添加失败),如何处理?
- **方案一:**在 Controller 的方法中进行 try…catch 处理(代码臃肿,不推荐)
- **方案二:**全局异常处理器(简单、优雅、推荐)
全局异常处理器图示:
|
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
事务管理
事务回顾
- 概念:事务 是一组操作的集合,它是一个不可分割的工作单位,这些操作 要么同时成功,要么同时失效
- 操作:
- 开启事务:
start transaction / begin; - 提交事务:
commit; - 回滚事务:
rollback;
- 开启事务:
Spring 事务管理
案例:解散部门:删除部门,同时删除该部门下的员工
注解 @Transactional
-
位置:业务(service)层的方法上(当前方法交给 Spring 事务管理)、类上(当前类所有方法交给 Spring 事务管理)、接口上(当前接口下的所有实现类的所有方法交给 Spring 事务管理)
-
作用:将当前方法交给 Spring 进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
-
开启 Spring 事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
事务进阶
@Transactional 注解有两个属性:
-
rollbackFor:默认情况下,只有出现 RuntimeException 才回滚异常。rollbackFor 属性用于控制出现何种异常类型时,回滚事务
上面这条语句使得事务在执行时出现任何异常时都回滚
-
propagation:控制事务的传播行为
事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个被调用的事务方法应该如何进行事务控制
常见事务传播行为:
属性值 含义 REQUIRED 【默认值】需要事务,有则加入(共用一个事务,任何一方抛异常都会导致两方的操作都无法完成),无则创建新事务 REQUIRED_NEW 需要新事务,无论有无,总是创建新事物(两方事务不会互相影响) SUPPORTS 支持事务,有则加入,无则在无事务状态中运行 NOT_SUPPORTED 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 MANDATORY 必须有事务,否则抛异常 NEVER 必须没事务,否则抛异常 … …
AOP
AOP 基础
AOP 概述
- AOP:Aspect Oriented Programming(面向切面编程),其实就是面向特定方法编程
- 实现:动态代理是面向切面编程最主流的实现。而 SpringAOP 是 Spring 框架的高级技术,目的是在管理 bean 对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程
AOP 快速入门
-
案例:统计各个业务层方法的执行耗时
-
步骤:
-
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency> -
编写 AOP 程序:针对于特定方法根据业务需要进行编程
public class TimeAspect {
// 前一个 * 表示返回值任意,后面的一串指定要将切面应用于哪些方法,(..)表示方法的参数任意
// 切入点表达式
public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 获取开始时间
long begin = System.currentTimeMillis();
// 调用原始方法运行,返回值 rtn 是原始方法的返回值
Object rtn = proceedingJoinPoint.proceed();
// 获取结束时间
long end = System.currentTimeMillis();
log.info(proceedingJoinPoint.getSignature() + "执行耗时:{}ms", end - begin);
return rtn;
}
}
-
-
场景:
- 记录操作日志
- 权限控制
- 事务管理(底层就是通过 AOP 来完成的)
- …
-
优势:
- 代码无侵入
- 减少重复代码
- 提高开发效率
- 维护方便
AOP 核心概念
- **连接点:JointPoint,**指可以被 AOP 控制的方法(暗含方法执行时的相关信息)
- **通知:Advice,**指那些被抽取出来的重复的逻辑,也就是共性功能(最终体现为一个方法)
- **切入点:PointCut,**匹配连接点的条件,通知仅会在切入点方法执行时被应用(用切入点表达式来描述)
- **切面:Aspect,**描述通知与切入点的对应关系(通知 + 切入点)
- **目标对象:Target,**通知所应用的对象
- 执行流程:
- AOP 会动态代理目标对象,生成增强之后的代理对象实例,并将此实例进行依赖注入
- 当执行到对应方法时,执行的是代理对象实例的增强方法,而不是原方法
AOP 进阶
通知类型
| 通知类型 | 功能 |
|---|---|
| @Around | 环绕通知,此注解标注的通知方法在目标方法前、后都被执行 |
| @Before | 前置通知,此注解标注的通知方法在目标方法前被执行 |
| @After | 后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行 |
| @AfterReturning | 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行 |
| @AfterThrowing | 异常后通知,此注解标注的通知方法发生异常后执行 |
注意事项:
- @Around 环绕通知需要自己调用
ProceedingJointPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行- @Around 环绕通知方法的返回值,必须指定为 Object,来接收原始方法的返回值并返回
避免重复,抽取切入点表达式,进行复用:
// 抽取切入点表达式 |
// 引用切入点表达式 |
通知顺序
当有多个切面类的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行
- 不同切面类中,默认按照切面类的类名字母排序:
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
- 用 @Order(数字) 加在切面类上来控制顺序
- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数字大的先执行
当一个切面类的中的多个通知方法匹配到了目标方法时,通知方法执行顺序:
- 未抛异常情况:环绕前置 -> @Before -> 目标方法执行 -> @AfterReturning -> @After -> 环绕返回 -> 环绕最终
- 抛异常情况:环绕前置 -> @Before -> 目标方法执行 -> @AfterThrowing -> @After -> 环绕异常 -> 环绕最终
切入点表达式
-
切入点表达式:描述切入点方法的一种表达式
-
作用:主要用来决定项目中的哪些方法需要加入通知
-
常见形式
-
execution(...):根据方法签名来匹配-
主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数类型全类名) throws 异常?)- 其中带 ? 的标识可以省略的部分
- 访问修饰符:可省略
- 包名.类名:可省略,但不建议
- throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
- 其中带 ? 的标识可以省略的部分
-
可以使用通配符描述切入点
- *:单个独立的任意符号,可以通配任意返回值、包名、类型、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
- …:多个连续的任意符号,可以通配任意层级的包、或任意类型、任意个数的参数
-
组合切入点表达式
-
可以使用 &&、||、! 来组合比较复杂的切入点表达式,如:
// 只有满足发送 或者 接收 这个切面都会切进去
-
-
书写建议:
- 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是 find 开头,更新类方法都是 update 开头
- 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
- 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名尽量不使用…,使用 * 匹配单个包
-
-
@Annotation(...):根据注解匹配-
@Annotation 切入点表达式,用于匹配标识有特定注解的方法,如
public void before() {
log.info("before...");
}
-
-
连接点
-
在 Spring 中用
JointPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等- 对于
@Around通知,获取连接点信息只能使用ProceedingJointPoint - 对于其他四种通知,获取连接点信息只能使用
JointPoint,它是ProceedingJointPoint的父类型
- 对于
-
方法 功能 getTarget().getClass().getName()获取目标对象的全类名 getSignature().getName()获取目标方法的方法名 getArgs()获取目标方法的参数 proceed()执行目标方法
AOP 案例
案例:将项目中 增删改 相关接口的操作日志记录到数据库表中
日志信息包括:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长
步骤:
- 准备:
- 引入 AOP 起步依赖
- 创建好数据库表结构,准备好日志实体类
- 编码
- 自定义注解 @Log
- 定义切面类,完成记录操作日志的逻辑
|
SpringBoot Web 进阶篇(原理篇)
配置优先级
-
SpringBoot 中支持三种格式的配置文件:
- .properties
- .yml
- .yaml
- 三种配置文件优先级:properties > yml > yaml
-
SpringBoot 除了支持配置文件属性配置,还支持 Java 系统属性 和 命令行参数 的方式进行属性配置
-
Java 系统属性
-Dserver.port=9000 -
命令行参数
--server.port:10010 -
优先级:命令行参数 > Java 系统属性
-
在打包后添加这两个参数的方法:
-
执行 maven 打包指令 package
-
执行 java 指令,运行 jar 包
java -Dserver.port=9000 -jar tlias-web-management-0.0.1-SNAPSHOT.jar --server.port=10010
-
-
-
总优先级(高 -> 低):
- 命令行参数
- Java 系统属性
- properties
- yml
- yaml
Bean 管理
获取 bean
-
默认情况下,Spring 项目启动时,会把 bean 都创建好放在 IOC 容器中,如果想要主动获取这些 bean,可以通过如下方式:
-
先获取 IOC 容器对象:
private ApplicationContext applicationContext; -
再调用下列方法来获取 bean
-
根据 name 获取 bean:
Object getBean(String name)
-
根据类型获取 bean
<T> T getBean(Class<T> requiredType)
-
根据 name 获取 bean(带类型转换)
<T> T getBean(String name, Class<T> requiredType)
-
上述【Spring 项目启动时,会把 bean 都创建好放在 IOC 容器中】还会收到作用域及延迟初始化影响,这里主要针对 默认的单例非延迟加载的 bean 而言
bean 作用域
-
Spring 支持五种作用域,后三种在 Web 环境才生效
作用域 说明 singleton IOC 容器内相同名称的 bean 只有一个实例(单例)(默认) prototype 每次使用该 bean 时会创建新的实例(非单例) request 每个请求范围内会创建新的实例(Web 环境中,了解) session 每个会话范围内会创建新的实例(Web 环境中,了解) application 每个应用范围内会创建新的实例(Web 环境中,了解) -
可以通过 @Scope 注解来配置作用域,如
-
如果想要在第一次使用 bean 时才创建实例,可以在 bean 上添加注解
@Lazy
注意事项:
- 默认 singleton 的 bean,在容器启动时被创建,可以使用 @Lazy 注解来延迟初始化
- prototype 的 bean,每一次使用该 bean 的时候都会创建一个新的实例
- 实际开发当中,绝大部分的 bean 是单例的,也就是说绝大部分的 bean 不需要配置 Scope 属性
第三方 bean
-
第三方的类无法通过直接在类上加注解的形式来声明为 bean,如果要将第三方的类交给 IOC 容器管理成为项目中的 bean,就需要用到 @Bean 注解,如
// 不建议在启动类中声明第三方bean
public class SpringBootWebConfig2Application {
// Spring会自动执行该方法,并将方法返回值交给IOC容器管理,成为IOC容器的bean对象
public SAXReader saxReader() {
return new SAXReader();
}
}// 建议单独开一个类来集中声明第三方bean
public class CommonConfig {
// 可以通过@Bean的name或value属性来指定bean的名称,默认为方法名
public SAXReader saxReader() {
return new SAXReader();
}
}
注意事项:
- 通过 @Bean 注解的 name 或 value 属性可以设置 bean 的名称,默认名称为方法名
- 如果第三方 bean 需要依赖其他 bean 对象,直接在 bean 定义方法中设置形参即可,IOC 容器会根据类型自动装配
SpringBoot 原理
起步依赖
为什么 SpringBoot 项目只需要引入一个起步依赖就可以使用诸多功能了?
原理&解答:Maven 的依赖传递
自动配置
什么是自动配置
- SpringBoot 的自动配置就是当 Spring 容器启动后,一些配置类、bean 对象就自动存入到了 IOC 容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作
自动配置原理
@SpringBootApplication
-
该注解标识在 SpringBoot 工程引导类上,是 SpringBoot 中最最最重要的注解。该注解由三个部分组成
-
@SpringBootcConfiguration:该注解与 @Configuration 注解作用相同,用来声明当前类也是一个配置类
-
@ComponentScan:组件扫描,默认扫描当前引导类所在包及其子包
-
@EnableAutoConfiguration:SpringBoot 实现自动化配置的核心注解
-
该注解中封装了 @Import 注解,此注解制定了一个 InportSelector 接口的实现类,该实现类实现了 InportSelector 接口的方法 selectImports,该方法返回一个 String类型的数组,数组封装了要导入到 IOC 容器的全部的配置类的全类名
-
selectImports 方法中加载了文件:
META/INF/spring/org.springframework.boot.autoconfiguration.AutoConfiguration.imports -
这个文件中就包含了所有要导入到 IOC 容器的配置类的全类名
-
导入到 IOC 容器的配置类中配置了该项目的所有 bean,所以导入该配置类就可以导入所有的 bean
-
-
@Conditional
- 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的 bean 对象到 IOC 容器中
- 位置:方法、类
- @Conditional 本身是一个父注解,派生出大量的子注解,如:
- @ConditionalOnClass:判断环境中是否有对应字节码文件,有才注册 bean 到 IOC 容器
- @ConditionalOnMissingBean:判断环境中是否没有对应的 bean(类型或名称),没有才注册 bean 到 IOC 容器
- @ConditionalOnProperty:判断配置文件中是否有对应的属性和值,有才注册 bean 到 IOC 容器
自定义 starter
需求
- 需求:自定义 aliyun-oss-spring-boot-starter,完成阿里云 OSS 操作工具类 AliyunOssUtils 的自动配置
- 目标:引入起步依赖之后,要想使用阿里云OSS,直接注入 AliyunOssUtils 直接使用即可
步骤
- 创建
aliyun-oss-spring-boot-starter模块 - 创建
aliyun-oss-spring-boot-autoconfigure模块,在starter中引入该模块 - 在
aliyun-oss-spring-boot-autoconfigure模块中自定义自动配置功能,并定义自动配置文件META-INF/spring/xxx.imports
Maven 高级
分模块设计与开发
当开发中大型项目时,如果把所有的代码都写在一个模块中,则代码会难以维护且不便复用,为了解决这些问题,Maven 中引入了分模块设计与开发的思想
将项目按照功能拆分成若干个子模块,方便项目的管理维护、扩展,也方便模块间的相互调用、资源共享
现在,我们来对 Tlias 案例进行模块拆分
- 创建 maven 模块 tlias-pojo,存放实体类
- 创建 maven 模块 tlias-utils,存放相关工具类
注意:分模块开发需要先针对模块功能进行设计,再进行编码,不会先将工程开发完毕,然后进行拆分
几点注意事项:
- 创建模块选择
New Module,然后选择 Maven 作为构建工具 - 在高级设置中填写自己的
GroupId - 多个模块最好放在同一工程文件夹中
- 复制相应的代码到新模块中,并把需要的依赖配置在
pom.xml文件中 - 最后在主模块的
pom.xml文件中引入这些分模块即可
继承与聚合
继承
继承关系
- 概念:继承描述的是两个工程间的关系,与 Java 中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承,即可以把通用的依赖写在父工程中,然后子工程直接继承
- 作用:简化依赖配置、统一管理依赖
- 实现:在子工程的
pom.xml文件中用<parent>...</parent>标签来指定父工程的坐标
现在,我们下面就创建一个父工程,把我们目前三个模块中所有的共同依赖放到父工程中,有以下几个步骤:
-
创建 maven 模块 tlias-parent,该工程为父工程,设置打包方式为 pom(默认为 jar)
maven 中的三种打包方式:
- jar:普通模块打包,springboot 项目基本都是 jar 包(内嵌 tomcat 运行)
- war:普通 web 程序打包,需要部署在外部的 tomcat 服务器中运行
- pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理
设置方法如下:
<groupId>org.example</groupId>
<artifactId>tlias-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>由于 maven 项目只能有一个父工程,而我们的
tlias-web-management项目已经继承了 springboot 项目的父工程,所以为了实现我们的需求,我们将继承关系更改为如下(采用级联继承):
-
在子工程的
pom.xml文件中,配置继承关系,如<parent>
<groupId>com.yl</groupId>
<artifactId>tlias-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--在该标签中指定父工程pom文件的相对路径-->
<relativePath>../tlias-parent/pom.xml</relativePath>
</parent> -
在父工程中配置各个子工程共有的依赖
若父工程和子工程都配置了同一个依赖的不同版本,以子工程的为准
版本锁定
版本锁定用于统一管理项目中各个模块的各个依赖的版本,如我们想控制各个子工程的 jwt 依赖的版本为 0.9.1,则可以这样做:
-
在父工程的 pom 文件中通过
<dependencyManagement>标签来统一管理版本<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
</dependencyManagement> -
在子工程中引入该依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
此时,子工程引入依赖就不需要再指定版本号,版本号由父工程统一管理。要变更依赖版本,只需在父工程中统一变更。
此时,各个依赖的版本号还是零散地分布在 <dependencyManagement>,为了解决这一问题,我们可以使用 自定义属性/引用属性,如
<properties> |
然后在版本控制和引入依赖时使用自定义属性:
<dependencies> |
<dependencies> |
聚合
分模块开发完毕后,如果要对主模块打包,需要把主模块依赖的全部分模块和它的父模块都安装到 maven 本地仓库中(使用 maven 声明周期的 install 命令),十分繁琐且枯燥,因此,为了解决这个问题,就引入了 maven 的聚合功能。
-
聚合:将多个模块组织成一个整体,同时进行项目的构建,可以实现一键打包,一键安装等操作
-
要实现聚合功能,需要提供一个聚合工程——一个不具有业务功能的 ”空“ 工程,有且仅有一个 pom 文件,一般由 父工程 来充当聚合工程的角色
-
作用:快速构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)
-
实现:只要在聚合工程的 pom 文件中用
modules标签设置当前聚合工程所包含的子模块的名称即可,如:<modules>
<module>tlias-pojo</module>
<module>tlias-utils</module>
<module>tlias-web-management</module>
</modules>maven 会自动根据模块间的依赖关系来设置构建顺序,与聚合工程中模块的配置书写位置无关
配置聚合工程后,只需要对聚合工程实行 maven 声明周期指令就可以对其下的所有子工程实行相同的 maven 指令
小结:继承与聚合的异同
私服
介绍
私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部资源共享与资源同步问题。
依赖查找顺序:
- 本地仓库
- 私服
- 中央仓库
资源上传与下载
项目版本:
- RELEASE(发行版本):功能趋于稳定,当前停止更新,可用于发行的版本,存储在私服的 RELEASE 仓库中。
- SNAPSHOT(快照版本):功能不稳定,尚处于开发中的版本,即快照版本,存储在私服的 SNAPSHOT 仓库中。
步骤:
-
设置私服的访问用户名/密码(
settings.xml中的servers中配置),如:<server>
<id>maven-releases</id>
<username>admin</username>
<password>admin</password>
</server>
<server>
<id>maven-snapshot</id>
<username>admin</username>
<password>admin</password>
</server> -
在 IDEA 的 maven 工程的 pom 文件中配置上传(发布)地址,如:
<distributionManagement>
<!--配置releases仓库的地址-->
<repository>
<!--id需要与server中配置的id一致,用于对应用户名和密码-->
<id>maven-releases</id>
<url>http://192.168.150.101:8081/repository/maven-releases</url>
</repository>
<!--配置snapshots仓库的地址-->
<snapshotRepository>
<id>maven-snapshots</id>
<url>http://192.168.150.101:8081/repository/maven-snapshots</url>
</snapshotRepository>
</distributionManagement> -
设置从私服下载依赖的对应仓库组的地址(
settings.xml中的mirrors、profiles中配置),如:<!--如果配置过阿里云的地址,需要删除掉,mirror标签只能有一对-->
<mirror>
<id>maven-public</id>
<url>http://192.168.150.101:8081/repository/maven-public/</url>
<mirrorOf>*</mirrorOf>
</mirror><profile>
<id>allow-snapshots</id>
<!--开启可以下载快照版本-->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<!--指定下载的仓库组位置-->
<repositories>
<repository>
<!--指定id,url以及允许下载releases仓库和snapshots仓库-->
<id>maven-public</id>
<url>http://192.168.150.101:8081/repository/maven-public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</profile>
配置好后,执行 maven 生命周期的 deploy 指令就可以将工程上传到私服
如果工程的版本号末尾带
SNAPSHOT,则会上传到快照仓库,其他的一律上传到发布仓库