JavaWeb基础小知识
前端
JSON
JSON在前端
JSON字符串转JS对象:
JS对象 = JSON.parse(JSON对象);
JS对象转JSON字符串:
JSON对象 = JSON.stringify(JS对象);
BOM对象
浏览器对象模型
封装了以下对象:
Window:浏览器窗口对象
Navigator: 浏览器对象
Screen: 屏幕对象
History: 浏览器记录对象
Location: 地址栏对象
Window
获取
直接使用window调取即可,其中如window.alert(“aa”);中的window.是可以省略的。
属性
history
location
navigator
是对其他对象的引用
方法
alert():显示消息和确认信息
confirm():显示消息和确认与取消信息
setInterval():按照指定的周期调用函数或表达式
setTimeout():指定的时间后调用函数或表达式
DOM
文档对象模型
Core DOM 封装了以下对象:
Document:整个文档对象
Element:元素对象,每一个标签
Attribute: 属性对象,标签的属性
Text:文本对象,标签的文本
Comment:注释对象
除此之外还有XML DOM是用于XML文档的标准模型、HTML DOM是用于HTML文档的标准模型包含如Image、Button对象。
JS通过DOM对HTML进行操作:1、改变HTML元素的内容 2、改变元素的样式 3、对HTML DOM事件做出响应 4、添加删除HTML元素
获取元素对象
在DOM中元素对象是通过Document对象获取的,而Document对象是通过Window对象获取的
var h1 = document.getElementsById('h1');//ID
var h1 = document.getElementsByTagName('h1');//标签
var h1 = document.getElementsByName('h1');//名称
var h1 = document.getElementsByClassName('h1');//类名
事件
事件绑定:
通过HTML标签中的属性进行绑定。
<input type = "button" onclick="on()" value="按钮1">
<script>
function on(){
alert("被点了");
}
</script>
通过DOM元素进行绑定
<input type = "button" id="byd" value="按钮2">
<script>
document.getElementById('byd').onclick()=function(){
alert("被点了");
}
</script>
VUE
基于MVVM(Model-View-ViewModel)的数据双向绑定模型框架。
MVVM的核心思想是通过ViewModel将Model和View的数据变化双向绑定起来,无论哪一个发生了变化对应的另一个的数据将同步变化。
普通步骤:
1、新建HTML页面,引入Vue.js文件
<script src="js/vue.js"></script>
2、在js代码区域创建vue的核心对象,定义数据类型
<script>
new Vue({
//挂载点,vue的接管区域
el: "#app",
data:{
message:"Hello Vue!",
url:""
}
})
</script>
3、编写视图
<div id="app">
<input type="text" v-model="message">
{{ message }}
</div>
插值表达式
形式:{{ 表达式 }}
其内容可以是:变量、三元运算符、函数调用、算数运算
常用指令
v-bind :
为HTML标签绑定属性值,如动态绑定href、css等,绑定的数据必须在数据模型中声明
<a v-bind:href="url"></a>
//也可以简写成以下形式:
<a :href="url"></a>
v-model :
在表单元素上创建双向数据绑定,绑定的数据必须在数据模型中声明
<input type="text" v-model="url">
v-on :
为HTML标签绑定事件
<input type="button" value="按钮" v-on:click="handle()">
//也可以简写成以下形式:
<input type="button" value="按钮" @click="handle()">
同时必须在vue中声明对应的方法
<script>
new Vue({
el: "#app",
data:{
//...
},
methods:{
handle:function(){
alert('被点击了');
}
}
})
</script>
v-if :
v-else-if :
v-else :
条件渲染,判定为true时加载到DOM中
v-show :
影响对应元素的display属性,条件为true时才展示。
v-for :
列表渲染,遍历容器的元素或者对象的属性。
<div v-for ="addr in addrs">{{ addr }}</div>
<div v-for ="(addr,index) in addrs">{{ index+1 }}:{{addr}}</div>
生命周期
beforeCreate、created、beforeMout、mouted、beforeUpdate、updated、beforeDestroy、destroyed
创建过程主要进行data、method的初始化、绑定属性
挂载过程主要对el目标进行挂载,完成模板对真正DOM的渲染
更新过程主要重新构建虚拟dom与上一次的虚拟dom树利用diff算法进行对比之后重新渲染
示例:
<script>
new Vue({
el: "#app",
data:{
//...
},
mounted(){
console.log("Vue挂载完毕,请求获取数据")
},
methods:{
},
})
</script>
Ajax
作用:
1、数据交换:给服务器发送请求,并获取服务器响应的数据
2、异步交互:可以不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术。
原生Ajax(了解)
//1、创建XMLHttpRequest对象,即创建一个异步调用对象.
var xmlHttpRequest = new XMLHttpRequest();
//2、创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
//XMLHttpRequest.open(method,URL,flag,name,password);
xmlHttpRequest.open("get","http://www.",true);
//3、设置响应HTTP请求状态变化的函数.
//设置当XMLHttpRequest对象状态改变时调用的函数,注意函数名后面不要添加小括号
xmlHttpRequest.onreadystatechange = function(){
//判断XMLHttpRequest对象的readyState属性值是否为4,如果为4表示异步调用完成
if(xmlHttpRequest.readyState==4){
if(xmlHttpRequest.status == 200 || xmlHttpRequest.status == 0){//设置获取数据的语句
document.write(xmlHttpRequest.responseText);//将返回结果以字符串形式输出
//docunment.write(xmlHttpRequest.responseXML);//或者将返回结果以XML形式输出
}
}
}
//4、发送HTTP请求.
XMLHttpRequest.send(data);
//5、获取异步调用返回的数据.
//即在其他地方调用上述函数
//6、使用JavaScript和DOM实现局部刷新.
Axios
对原生Ajax进行封装,简化书写、快捷开发。
1、引入Axios的js文件
<script src="js/axios-0.18.0.js"><script>
2、使用Axios发送请求,并获取响应结果
axios({
method:"get",//请求方法
url:"http://www.",
}).then((result)=>{//成功响应的回调函数
console.log(result.data);
});
axios({
method:"post",
url:"http://www.",
data:"id=1",
}).then((result)=>{
console.log(result.data);
});
前端工程化
把前端开发使用的工具、技术、流程、经验进行标准化、流程化。
Mock
用于模拟后端返回的数据使得前端可以自己调试。常用的有YAPI
环境
Vue-cli用于Vue的项目模板快速生成,他提供了以下功能:
1、统一的目录结构 2、本地调试 3、热部署 4、单元测试 5、集成打包上线
依赖环境:NodeJS
Vue项目
目录结构
node_modules :项目的依赖
public :项目的静态文件
src :源代码
asset :静态资源
components :可重用的组件
router :路由配置
views :视图组件(页面)
App.vue :入口页面(根组件)
main.js :入口js文件
package.json :模块基本信息,项目开发需要的模块,版本信息
vue.config.js :保存vue配置的文件,如代理、端口的配置等
指令
npm run serve 运行
配置
在vue.config.js 文件中进行配置
配置端口号:
devServer:{
port: 7000,
}
工作流程解析
main.js是入口js文件
//导入组件
import Vue from 'vue'
import App from './App.vue'
import router from './router'
//设置 Vue 的全局配置项,设置为 false,以阻止在生产环境中显示生产提示。
Vue.config.productionTip = false
new Vue({
//传入配置对象
//实际上是router: router,是将Vue的router属性赋值为router因为一致所以可以省略
router,
//使用 render 函数渲染 App 组件即创建虚拟DOM
render: h => h(App),
}).$mount('#app')//挂载到id为app的元素上,此处的app为public下index.html文件中的元素
App.vue入口页面(根组件)
模板部分,由它生成HTML代码。定义原生HTML
<template>
此处的app和其他文件无关,用于本文件中的元素定位
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
控制模板中的数据来源和行为
<script>
import HelloWorld from './components/HelloWorld.vue'
当前组件导出成模块
export default {
name: 'App',
components: {
HelloWorld
},
data(){
return {
message: "Hello Vue",
},
},
methods:{
}
}
</script>
css样式
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Vue中使用axios
安装:
npm install axios
导入
import axios from ‘axios’;
在挂载完毕的钩子方法中加载信息
mounted(){
axios.get("http://..").then((result)=>{
this.tableData = result.data.data;
})
}
Vue路由
点击菜单栏改变URL,前端路由:URL中的hash(#号)与组件之间的对应关系。
组成:
VueRouter:路由器类,更具路由请求在路由视图中动态渲染选中的组件
<router-link>: 请求连接组件,浏览器会解析成<a>,即点击后会让视图组件跳转到对应的组件。
<router-view>: 同台视图组件,用来渲染展示与路由路径对应的组件。即页面的展示组件,跳转后的组件将会加载到这个组件上。
使用:
安装:npm install vue-router@版本号
导入: 在main.js中导入
import router from './router'
new Vue({
el: '#app',
router,
render: h => h(App)
})
定义路由:
在router下的index.js中定义相关路由信息。
{
path: '',
component: Layout,
redirect: 'index',
children: [
{
path: 'index',
component: () => import('@/views/index'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true }
}
]
},
在使用router后Vue项目的加载过程会发生变化,之前是通过main.js进入app.vue。使用之后将会进一步在app.vue中根据默认的 ’ '路由跳转对应的组件,这个组件需要你去配置。
打包部署
使用相关命令打包。
将打包好 的dist目录下的文件复制到nginx安装目录的html目录下。双击启动nginx即可。nginx的核心配置文件在config目录下的nginx.conf中。
Element UI
1、安装(在当前项目中)
npm install element-ui@2.15.3
2、引入组件库
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
3、访问官网,复制组件代码并调整
在官网相关组件的页面底端有相关属性、事件等的说明。
4、使用组件标签
需要查找文档学习的部分组件
container 布局容器
对话框
表单
Maven
概述
Maven是Apache旗下的一个开源项目,是一款用于管理和构建java项目的工具。
使用Maven能够做到:
- 依赖管理
- 统一项目结构
- 项目构建
Maven模型
- 项目对象模型 (Project Object Model)
- 依赖管理模型(Dependency)
- 构建生命周期/阶段(Build lifecycle & phases)
Maven仓库分为:
- 本地仓库:自己计算机上的一个目录(用来存储jar包)
- 中央仓库:由Maven团队维护的全球唯一的。仓库地址:https://repo1.maven.org/maven2/
- 远程仓库(私服):一般由公司团队搭建的私有仓库
生命周期
主要关注以下几个:
• clean:移除上一次构建生成的文件
• compile:编译项目源代码
• test:使用合适的单元测试框架运行测试(junit)
• package:将编译后的文件打包,如:jar、war等
• install:安装项目到本地仓库
Maven打包方式:
- jar:普通模块打包,springboot项目基本都是jar包(内嵌tomcat运行)
- war:普通web程序打包,需要部署在外部的tomcat服务器中运行
- pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理
分模块开发
所谓分模块设计,顾名思义指的就是我们在设计一个 Java 项目的时候,将一个 Java 项目拆分成多个模块进行开发。
为什么要分模块设计:方便项目的管理维护、扩展,也方便模块间的相互调用,资源共享
1、创建对应的各个模块
2、开发对应各个模块
3、模块间相互使用时,引入依赖
4、通过Maven安装模块到本地仓库(install 命令,最好是聚合实现)
依赖
依赖传递的时候可以加上optional选项,此时将隐藏本项目使用的该依赖,不具备依赖传递。(不给别人)
<groupId>com.itheima</groupId>
<artifactId>maven_e3_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
<!--可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源将不具有依界-->
<optional>true</optional>
可视化依赖:在pom.xml文件中右键选中Diagrams选择show dependencies
排除依赖:在对应的dependency中
<exclusions>
<exclusion>
<groupId></groupId>
<artifactId></artifactId>
</exclusion>
</exclusions>
依赖范围
使用scope标签设置作用范围
默认:compile(主程序、测试、运行)
test:测试
provided:主程序、测试
runtime:测试、运行
继承
概念 : 继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。
操作:
1、配置父工程,说明所有子模块通用的依赖,设置打包方式是pom包,对于springboot项目往往还需要继承spring-boot-starter-parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/>
</parent>
<groupId>com.itheima</groupId>
<artifactId>tlias-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
2、配置子模块继承自父模块
<!--配置当前工程继承自parent 工程-->
<parent>
<groupId>com.itheima</groupId>
<artifactId>maven_e1_parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../ maven_01_parent/pom.xml</relativePath>
</parent>
3.1 配置所有子模块共有的依赖
各个模块中都公共的这部分依赖,我们可以直接定义在父工程中,从而简化子工程的配置。
在父工程中配置各个工程共有的依赖(子工程会自动继承父工程的依赖)。
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
3.2 在父工程中配置子工程中可选的依赖,进行统一的依赖版本管理
在maven中,可以在父工程的pom文件中通过 <dependencyManagement>
来统一管理依赖版本。
<!--定义依赖管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencies>
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
</dependencyManagement>
4、在子工程中引用对应的依赖
子模块自动集成了父模块的dependency,也就是lombok自动就有了。
此时拥有的junit无需加上版本信息,并且并没有继承到可选的Jwt依赖。
如果此时对junit指定版本,将会以指定的版本为准
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
聚合
聚合:将多个模块组织成一个整体,同时进行项目构建的过程称为聚合
聚合工程 : 通常是一个不具有业务功能的“空”工程(有且仅有一个pom文件,通常是父工程)
作用︰使用聚合工程可以将多个工程编组,通过对聚合工程进行构建,实现对所包含的模块进行同步构建
当工程中某个模块发生更新(变更)时,必须保障工程中与已更新模块关联的模块同步更新,此时可以使用聚合工程来解决批量
模块同步构建的问题
操作:
1、设置管理的模块名称
<!--设置管理的模块名称-->
<modules>
<module>../ maven_e2_ssm</module>
<module>../ maven_e3_pojo</module>
<module>../maven_e4_dao</module>
</modules>
属性
在pom中定义属性
<!--定义属性-->
<properties>
<spring.version>5.2.10.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<mybatis-spring.version>1.3.0</mybatis-spring.version>
<jdbc.ur1>jdbc:mysql://127.8.0.1:3306/ssm_db</jdbc.ur1>
</properties>
在pom中引用属性
<version>${junit.version}</version>
配置文件加载属性
扩大Maven的控制范围,在build中说明资源配置路径
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
此时在resources文件夹下的文件就能使用${}调用pom中的属性了
打war包时无需web.xml
添加插件,使他在无web.xml时不报错
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<failonMissingwebxml>falsek/failonMissingwebxml>
</configuration>
</plugin>
多环境开发
1、在pom中配置不同环境的属性信息
<!--配置多环境-->
<profiles>
<!--开发环境-->
<profile>
<id>env_dep</id>
<properties>
<jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
</properties>
<!--没定是否为默认启动环境-->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!--生产环境-->
<profile>
<id>env_pro</id>
<properties>
<jdbc.ur1>jdbc :mysql://127.2.2.2:3306/ssm_db</jdbc.ur1>
</properties>
</profile>
<!--测试环境-->
<profile>
<id>env_test</id>
<properties>
<jdbc.ur1>jdbc :mysql://127.3.3.3:3306/ssm_db</jdbc.ur1>
</properties>
</profile>
2、在执行Maven时的命令时使用多环境
mvn 指令 -P 环境定义 id
范例:
mvn install -P pro_env
私服
团队内部开发时的相互依赖关系解决。
私服简介:私服是一台独立的服务器,用于解决团队内部的资源共享与资源同步问题。
使用私服的找依赖的过程:本地Maven仓库->私服->中央仓库。
Nexus:Sonatype公司的一款maven私服产品。
构建使用
启动:在对应文件夹下启动cmd 执行命令nexus.exe /run nexus
在浏览器打开 8081端口,其他如下
启动服务器(命令行启动)
nexus.exe /run nexus
访问服务器(默认端口:8081)
http: //localhost:8081
修改基础配置信息
安装路径下etc目录中nexus-default.properties文件保存有nexus基础配置信息,例如默认访问端口修改服务器运行配置信息
安装路径下bin目录中nexus .vmoptions文件保存有nexus服务器启动对应的配置信息,例如默认占用内存空间
私服仓库分类
仓库类别 | 英文名称 | 功能 | 关联操作 |
---|---|---|---|
宿主仓库 | hosted | 保存自主研发+第三方资源 | 上传 |
代理仓库 | proxy | 代理连接中央仓库 | 下载 |
仓库组 | group | 为仓库编组简化下载操作 | 下载 |
操作流程
上传时给出放到什么仓库中,下载时直接向仓库组调用。
配置私服访问
1、配置Maven文件夹下conf中的setting.xml文件
2、配置对私服的访问权限,id就是私服中仓库的名称所有用到的仓库都需要配置权限
<server>
<id>私服中的服务器id名称</id>
<username>repofser</username>
<password>repopwd</password>
</server>
<server>
<id>maven-releases</id>
<username>admin</username>
<password>admin</password>
</server>
<server>
<id>maven-snapshots</id>
<username>admin</username>
<password>admin</password>
</server>
3、创建私服中的仓库
4、将创建的仓库放入仓库组进行管理,找到类型为group的仓库
5、配置私服的访问路径;在自己maven安装目录下的conf/settings.xml中的mirrors、profiles中配置
<mirror>
<id>mirrorid</id>
<mirrorOf>LepositoryId</mirrorOf>
<url>http://my.repository.com/repo/path</ur1>
</mirror>
<profile>
<id>allow-snapshots</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<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>
Id代表仓库组的id即group的名称,mirrorOf表示什么样的来自私服,通常*指代全部,url则是将group中的url粘贴即可。
私服的上传下载
1、在pom中配置保存在私服中的具体位置
<!--配置当前工程保存在私服中的具体位置-->
<distributionManagement>
<repository>
<id>itheima-release</id>
<url>http://localhost:8081/repository/itheima-release/</url>
</repository>
<snapshotRepository>
<id>itheima-snapshot</id>
<url>http://localhost:8081/repository/itheima-snapshot/</url>
</snapshotRepository>
</distributionManagement>
2、执行Maven命令 deploy发布,此时私服也会下载用到的依赖
3、此时工程版本号为snapshot则会上传到snapshot类型的仓库,release则上传到release仓库
中央仓库调用阿里镜像
点击Maven-central,修改Proxy - Remote storage
SpringBoot
Spring发展到今天已经形成了一种开发生态圈,Spring提供了若干个子项目,每个项目用于完成特定的功能。而我们在项目开发时,一般会偏向于选择这一套spring家族的技术,来解决对应领域的问题,那我们称这一套技术为spring全家桶。
最基础、最核心的是 SpringFramework,但是他配置繁琐、入门难度大。
springboot呢,最大的特点有两个 :
- 简化配置
- 快速开发
Spring Boot 可以帮助我们非常快速的构建应用程序、简化开发、提高效率 。
Bean 管理
获取Bean
默认情况下,Spring项目启动时,会把bean都创建好放在IOC容器中,如果想要主动获取这些bean,可以通过如下方式:
- 先注入IOC容器对象
@Autowired
private ApplicationContext applicationcontext; //IOC容器对象
- 根据name获取bean(带类型转换):
Object getBean(String name)
DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
- 根据类型获取bean:
<T> T getBean (Class<T>requiredType)
DeptController bean2 = applicationContext.getBean(DeptController.class);
根据name获取bean(带类型转换):
<T> T getBean(String name,Class<T> requiredType)
DeptController bean3 = applicationContext.getBean("deptcontroller",DeptController.class);
Bean作用域与初始化
在Spring中支持五种作用域,后三种在web环境才生效:
作用域 | 说明 |
---|---|
singleton | 容器内同名称的bean只有一个实例(单例)(默认) |
prototype | 每次使用该bean时会创建新的实例(非单例) |
request | 每个请求范围内会创建新的实例(web环境中,了解) |
session | 每个会话范围内会创建新的实例(web环境中,了解) |
application | 每个应用范围内会创建新的实例(web环境中,了解) |
可以通过@Scope注解来进行配置作用域,配置在整个文件上面:
可以通过@Lazy注解来延迟初始化,延迟到第一次使用的时候进行初始化
@Scope("prototype") //bean作用域为非单例
@Lazy //延迟加载
@RestController
@RequestMapping("/depts")
public class DeptController {
@Autowired
private DeptService deptService;
public DeptController(){
System.out.println("DeptController constructor ....");
}
//省略其他代码...
}
第三方Bean配置
如果要管理的bean对象来自于第三方〈不是自定义的),是无法用@Component及衍生注解声明bean的,就需要用到@Bean注解。
在启动类或者配置类当中使用@Bean注解返回交给IOC容器。Bean容器的名称默认是方法名。
@Configuration
public class CommonConfi {
@Bean//将方法返回值交给IOC容器管理,成为IOC容器的bean对象
public SAXReader saxReader(){
return new SAXReader();
}
}
注意事项
通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。
如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。
起步依赖
当前项目继承自Spring-boot-starter-parent而Spring-boot-starter-parent继承自Spring-boot-dependencies
Spring-boot-dependencies 中配置了大量的依赖,并配置了对应的版本号,此时写对应依赖时不需要加版本,自动继承。
当前项目只需要配置各种起步依赖即可,起步依赖中继承了大量的依赖,通过依赖传递就能有大量依赖。
自动配置
SpringBoot的自动配置就是当spring容器启动后,一些配置类、bean对象就自动存入到了IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。
原理:
启动类能自动扫描本包,若要扫描到第三方依赖。
1、使用@ComponentScan({})组件扫描到本包和对应的包(不推荐)。
2、使用@Import导入使用@Import导入的类会被Spring加载到IOC容器中,导入形式主要有以下几种:
- 1)导入普通类:
@Import(TokenParser.class) //导入的类会被Spring加载到IOC容器中
- 2)导入配置类:该配置类当中的所有Bean对象都会导入到IOC容器当中。
@Configuration
public class HeaderConfig {
@Bean
public HeaderParser headerParser(){
return new HeaderParser();
}
@Bean
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}
@Import(HeaderConfig.class) //导入配置类
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
- 3)导入ImportSelector接口实现类:将哪些类交给IOC,给出全类名。@Import({MyImportSelector.class})其中要实现ImportSelector接口的
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//返回值字符串数组(数组中封装了全限定名称的类)
return new String[]{"com.example.HeaderConfig"};
}
}
-
4)使用第三方依赖提供的@Enable****注解(最推荐),在配置类当中添加该种注解,具体可以先引入相关包后查看报错提示信息,根据信息添加注解。
- 第三方依赖中提供的注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类 public @interface EnableHeaderConfig { }
- 在使用时只需在启动类上加上@EnableXxxxx注解即可
@EnableHeaderConfig //使用第三方依赖提供的Enable开头的注解 @SpringBootApplication public class SpringbootWebConfig2Application { public static void main(String[] args) { SpringApplication.run(SpringbootWebConfig2Application.class, args); } }
源码跟踪(启动类)
启动类封装了
1、配置类注解。
2、封装了使用importSelector的注解,它的参数是META-INF/spring.factories以及另一个文件(org.springframework…import)下的配置文件的全类名。值得注意的是不是所有都生效的,查看下节@Conditional
3、组件扫描注解。
@Conditional 条件声明Bean对象
作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring lOC容器中。
位置:方法、类
@Conditional本身是一个父注解,派生出大量的子注解:
- @ConditionalOnClass:判断环境中是否有对应字节码文件,才注册bean到IOC容器。
@ConditionalOnClass(name = "io.jsonwebtoken.Jwts")//环境中存在指定的这个类,才会将该bean加入Ioc容器中
- @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器
@ConditionalonMissingBean//不存代该类型的bean,才会将该bean加入IoC容器中--指定类型(value属性)或名称(name属性)
- @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。
@ConditionalOnProperty(name = "name",havingValue = "itheima")//配置文件中存在指定的属性与值,才会将bean加入Ioc容器中
!!(重要)案例:自定义starter
在实际开发中,经常会定义一些公共组件,提供给各个项目团队使用。而在SpringBoot的项目中,一般会将这些公共组件封装为SpringBoot 的starter。
需求:自定义aliyun-oss-spring-boot-starter,完成阿里云OSS操作工具类Aliyunossutils的自动配置。
目标:引入起步依赖引入之后,要想使用阿里云OSS,注入AliyunosSUtils直接使用即可。
步骤:
1、创建aliyun-oss-spring-boot-starter模块
2、创建aliyun-oss-spring-boot-autoconfigure模块,在starter中引入该模块
3、在aliyun-oss-spring-boot-autoconfigure模块中的定义自动配置功能,并定义自动配置文件META-INF/spring/xox.imports
具体操作细节很重要,详细见相关视频。
- 一、创建新模块,starter、autoconfigure
- 二、starter除了pom和.iml文件的其他文件都不需要。autoconfigure中无需启动类、测试类等,并在starter的pom文件中引入autoconfigure
- 三、将阿里云OSS操作工具类需要的依赖放入autoconfigure模块中。
- 四、将阿里云OSS操作工具类以及它的引用类、配置类放入autoconfigure模块。处理这些类以及相关报错信息:
- 1、对于需要引入依赖的引入
- 2、生成getter、setter方法,或引入
- 3、删除@component注解、删除原有的@Autowired注入
- 五、在autoconfigure中定义自动配置类,声明为配置类、加载配置文件、根据配置文件生成对象、将当前对象交给IOC容器管理。
@Configuration
@EnableConfigurationProperties(AliOSSProperties.class)//为什么使用这个注解可以参考在@configurationProperties注解报错的提示上的提示信息
public class AliOSSAutoConfiguration{
@Bean
public AliOSSUtils aliossutils(Ali0SSProperties aliosSProperties){
AlioSsutils aliossutils = new AliOSSUtils();
aliossUtils.setAliOSSProperties(aliOsSProperties);
return aliossUtils;
}
}
- 六、在resources下创建二级目录META-INF/spring。创建文件org.***.import(具体名称参考其他启动类的文件),并配置本模块下的自动配置类的全类名,比如com.aliyun.oss.Ali0SSAutoConfiguration
流程解析
在AutoConfiguration中定义了需要自动配置的Bean,在org.***.import这份固定的文件中配置了自动配置类的全类名,将来boot启动的时候会自动加载这个文件,根据文件内容将自动配置类加载到IOC容器当中。
使用
在其他项目中如果想要使用,则只需要在引入starter依赖后,直接注入工具类对象即可使用。值得注意的是使用本模块需要哪些配置信息需要查看**Properties类封装了哪些对象。
接收参数
原始方式
通过HttpServletRequest 对象获取
name = request.getParameter("name");
简单参数
参数名和形参变量名相同,自定义形参即可接收参数,类型会自动转化。
//http://.../simpleParam?name=Tom&age=10
@RequestMapping("/simpleParam")
public String simpleParam(String name,Integer age){
return "ok";
}
形参名称和请求参数名称不匹配,可以使用@RequestParam完成映射
//http://.../simpleParam?name=Tom&age=10
@RequestMapping("/simpleParam")
public String simpleParam(@RequestParam(name = "name") String userName, Integer age){
return "ok";
}
默认参数是必须传递的,若是可选参数那么应该添加注解@RequestParam(required = false)
实体参数
封装的实体对象必须有对应的参数同名的属性才能使用接收到对应的参数,同时实体类必须有对应的get、set方法才行
//http://.../simplePojo?name=Tom&age=10
@RequestMapping("/simplePojo")
public String simplePojo(User user){
return "ok";
}
public class User{
String name;
Integer age;
public String getName(){}
public void setName(){}
}
对于嵌套的复杂参数可以用字符’.'访问嵌套POJO属性参数,若有报错则注意生成对应的getter与setter以及tostring。
注意在嵌套的时候,子对象的名称也要和传入的属性名称对齐
//http://.../simplePojo?name=Tom&age=10&address.province=北京&address.city=朝阳
@RequestMapping("/simplePojo")
public String simplePojo(User user){
return "ok";
}
public class User{
String name;
Integer age;
Address address;
}
public class Address{
String province;
String city;
}
数组参数:
直接用数组变量名作为参数,写对应的各个元素的值即可
集合参数:
参数同数组,在编写业务类时注意以下格式,即添加@RequestParam。
//http://.../simplePojo?hobby=game&hobby=java
@RequestMapping("/listParam")
public String listParam(@RequestParam list<String> hobby){
sout(hobby);
return "ok";
}
日期参数
使用@DateTimeFormat注解完成日期参数格式转化
//http://.../dataTime?updateTime=2025-05-16 17:35:00
@RequestMapping("/dateTime")
public String dateTime(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDate updateTime){
sout(updateTime);
return "ok";
}
如果是定义在对象当中的日期可以使用@JsonFormat(pattern = “yyyy-MM-dd”)来指定格式
public class ClassVo {
int id;
String className;
String roomName;
@JsonFormat(pattern = "yyyy-MM-dd")
LocalDate startTime;
@JsonFormat(pattern = "yyyy-MM-dd")
LocalDate endTime;
Emp emp;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime updateTime;
}
Json 参数
注意Json的参数应该在请求体当中配置,添加注解@RequestBody说明是请求体中的参数.
JSON数据键名和形参对象属性名相同,定义POJO类型形参接收参数
@RequestMapping("/listParamForJson")
public String listParamForJson(@RequestBody User user){
sout(user);
return "ok";
}
public class User{
String name;
Integer age;
Address address;
}
public class Address{
String province;
String city;
}
路径参数
通过请求URL直接传递参数,使用{…}来标识该路径参数,需要使用@PathVariable获取路径参数
如果有多个路径参数:/class/1,2,3 @PathVariable
可以直接绑定一个逗号分隔的集合
@RequestMapping("/path/{id}/{name}")
public String pathParam(@PathVariable Integer id, @PathVariable String name){
sout(id,name);
return "ok";
}
响应数据
在Controller方法/类上加上@ResponseBody,他的主要作用是将方法返回值直接响应,如果返回值类型是实体对象/集合,将会转化为JSON格式。
如果使用REST风格那么只需要加上@RestController,他等于@Controller+@ResponseBody。
统一响应结果
正常的返回类型过多,为了方便前后端沟通,封装成统一的响应结果较好。一种封装如下:
public class Result{
private Integer code;
private String msg;
private Object data;
}
//封装sucess方法
//其他code对应的方法
单元测试
对于测试类加上@SpringBootTest注解声明为单元测试。
对于测试方法加上@Test注解
对于日志输出可以在文件前加上@Xsl4j注解,然后直接调用log如log.info(“输出信息”);
配置文件
常见配置文件
常见三种方式,配置文件生效循序:properties>yml>yaml
除了支持配置文件的配置方式以外,还支持另外两种常见的配置方式:
-
Java系统属性配置 (格式: -Dkey=value)
-Dserver.port=9000
-
命令行参数 (格式:–key=value)
--server.port=10010
这两者可以在 Run configuration中配置VM option和Program argument。
打包后 java -Dserver.port = 9000 -jar *.jar --server.port = 1010
生效顺序是 命令行参数>Java系统属性>配置文件
properties
1、application.properties 文件
直接写上server.port=80
2、新建文件application.yml
写上
Server:
Port: 80
3、新建文件application.yaml
写上
Server:
Port: 80
自动提示
将新建配置文件加入Spring管理
在项目的Facet中选择对应模块的Configuration Files并选中+号添加新的文件
Yaml格式
enterprise:
name: it
age: 14
sunbject:
-Java
-前端
-大数据
读取yaml:
1、使用@Value读取,用 . 分级,用 [] 读取数组
@Value("${enterprise.sunbject[0]}")
String sunbject0;
2、使用环境变量+getProperty
@Autowired
private Enviroment enviroment;
enviroment.getProperty("lesson");
3、常用;定义domain与配置文件管理,并在使用时采用自动装配
@Component
@ConfigurationProperties(prefix = "enterprise")
public class Enterprise{
//对应的配置项名
}
@Autowired
Enterprise ente;
entr.对应的配置项名;
若有警告,则加入依赖
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor </artifactId>
配置文件分类
- SpringBoot中4级配置文件
1级: file : config/application.yml【最高】
2级: file : application.yml
3级: classpath: config/application.yml
4级: classpath: application.yml【最低】
其中的classpath是类路径,在Springboot下就是resources文件夹
- 作用:
1级与2级留做系统打包后设置通用属性,1级常用于运维经理进行线上整体项目部署方案调控
3级与4级用于系统开发阶段设置通用属性,3级常用于项目经理进行整体项目属性调控
多环境开发
单文件
1.多环境开发需要设置若干种常用环境,例如开发、生产、测试环境
2.yaml格式中设置多环境使用—区分环境设置边界
#应用环境
spring:
profiles:
active: pro
---
#设置环境
#生产环境
spring:
profiles: pro
server:
port: 80
---
#开发环境
spring:
profiles: dev
server:
port: 81
---
#测试环境
spring:
profiles: test
server:
port: 84
3.每种环境的区别在于加载的配置属性不同
4.启用某种环境时需要指定启动时使用该环境
多文件
新建application-dev.yml文件、application-pro.yml文件等,在其中定义环境配置。
主启动配置文件application. yml
spring:
profiles:
active: dev
主配置文件中设置公共配置(全局)
环境分类配置文件中常用于设置冲突属性(局部)
properties版
除了书写内容的格式,其余一致。
多环境分组管理
在著启动配置中添加include
spring:
profiles:
active: dev
include: devDB,devMVC
此时三个都会生效,加载顺序是devDB,devMVC,dev。后加载的覆盖先加载的。
也可以使用如下group的方式,此时的加载顺序是dev,devDB,devMVC,(推荐)
spring:
profiles:
active: dev
group:
"dev": devDB,devMvq
"pro": proDB,proMVC
多环境开发控制
当Maven与boot的多环境冲突的时候按照Maven为准。
所以我们一般是在boot中读取Maven中的激活参数。同时想要使得在Maven中的修改生效要使用compile命令更新。
spring:
profiles:
active: @profile.active@
group:
"dev": devDB,devMvq
"pro": proDB,proMVC
分层解耦合
三层架构
在我们进行程序设计以及程序开发时,尽可能让每一个接口、类、方法的职责更单一些(单一职责原则)。
可以将代码分为三层:
- Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
- Service:业务逻辑层。处理具体的业务逻辑。
- Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。
控制反转 IOC:对象创建控制权由程序本身转移到外部(容器),被称为IOC容器或者Spring容器。
依赖注入 DI:容器为应用程序提供运行时所依赖的资源。
Bean对象: IOC容器中创建、管理的对象称为Bean
IOC & DI 入门
1、Service与Dao层的实现类,交给IOC容器管理:为相关对象加上@Component 注解,将当前类交给IOC容器成为bean
2、为Service与Controller注入运行时依赖的对象:为相关对象加上@Autowired 注解,自动装配相关对象
3、运行测试
注意事项
其中@Component 下有三个子注释:@Controller,@Service,@Repository
在使用自动装配时自动装配的对象应当是对应服务的接口,这样若实现类发生变化也无需修改这个文件。
IOC详解
Bean的声明
@Component 下有三个子注释:@Controller,@Service,@Repository,通常不属于以上三类时才使用@Component
@Controller标注于控制器类上
@Service标注于业务类上
@Repository标注于数据访问类上。
在声明bean的时候可以通过value属性指定bean的名字,若没有指定默认为首字母小写。
Bean组件扫描
想要使得IOC容器添加对应的Bean还应该使用@ComponentScan()注解扫描,在SpringBoot中虽然没有显示配置但是实际上包含在了启动类的注解@SpringBootApplication 中,默认范围是启动了所在的包及其子包。
如果不在这个范围内需要使用注解显示配置对应的包,配置时是重载的,所以不要忘记了启动类所在的包也要加上。但是这个方法不推荐。
DI详解
@Autowired注解默认按照类型装配,若有多个实现类会报错,即是单例的。
解决方案:
1、使用@primary,在多个实现类中选择需要使用的那个加上@primary。
2、使用@Qualifier(value=" "),在自动装配时配合 @Qualifier(value=“实现类的名称”)来指定实现类
3、使用@Resource(name= “”),在自动装配时使用@Resource(name= “实现类”)代替@Autowired来指定实现类。
注意:
@Resource不是Spring框架下的注解,而是JDK提供的注解。
@Autowired注解是按照类型装配的,而@Resource()是按照名称进行装配的
也就是对于MxService mxService;而言@Autowired关注的是MxService这个类;@Resource()关注的是mxService这个名称;
对于SpringBoot的Bean,在使用IOC的时候,添加@Component注解会默认创建一个名称为首字母小写的单例实例。
Mybatis
入门
持久层框架,简化开发。
在 java中编写,发送给数据库解析。
1、准备工作(创建SpringBoot工程、数据库表、实体类)
2、引入Mybatis相关依赖,配置Mybatis
3、编写sql语句(xml/注解)
建议字段名和属性名相同,可以自动实现对应关系。
@Mapper
public interface UserMapper{
@Select("select * from user")
public List<User> list();
}
上述文件示例中@Mapper在运行时会自动生成该接口的实现对象(代理对象),并将该对象交给IOC容器管理。
配置SQL提示
默认编写SQL语句是不识别的,可以选中语句后右键点击Show Context Actions -> Inject language or reference -> mysql
在IDEA中配置数据库连接识别表信息。
数据库连接池
数据库连接池是个容器,负责分配、管理数据库连接。他允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。释放空闲事件超过最大空闲时间的连接,避免没有释放连接引起的数据库连接漏洞。
优势:资源重用、提升系统响应速度、避免数据库连接遗漏
标准接口:DataSource
常见产品:c3p0、DBCP、Druid、Hikari(追光者);后两者常用,最后一个是SpringBoot的默认
切换Druid数据库连接池:引入相关依赖
<dependency>
<gropId>com.alibaba</gropId>
<aritifactId>druid-spring-boot-starter</aritifactId>
<version>1.2.8</version>
</dependency>
lombok
使用lombok类库简化开发,可以代替生成代码
@Data==@Getter+@Setter+@Tostring+@EqualsAndHashCode
@NoArgsConstructor无参构造器方法
@AllArgsConstuctor带有个参数的构造器方法
使用:
0、安装lombok插件
1、引入依赖
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
2、对应位置加上注解@Data以及其他需要的注解
3、在其他位置正常使用getter、setter方法。
基础操作
配置输出控制台
在配置中配置
mybatis.configration.log = org.apache.ibatis.logging.stdout.StdOutImpl
将会在log文件中输出预编译sql
删除
- 1、设计对应的Mapper接口,以及对应的sql语句。
@Delete("delete from emp where id = #{id}")
public void delete(Integer id);
值得注意的是一定要采用# {}将内容替换为?并生成预编译的sql。若是采用${}进行拼接sql可能会出现sql注入问题。
预编译的sql是呈现为"delete from emp where id = ?"再使用参数parameter当中的值替换?
拼接sql是直接将id拼接到内容当中执行的就是"delete from emp where id = 17"
预编译的性能更高,还能防止sql注入问题;
如下图,只需要编译一次"delete from emp where id = ?"之后传入不同的参数即可,同时还会再执行前检查参数是否合法
- 2、在使用它的service中自动装配对应的接口,通过该对象使用对应方法。
插入
@Insert("insert into emp(username,name) "+"value(#{username},#{name})")
public void insert(Emp emp)
其中值得注意的一点在于在sql语句中#{内容}的内容应当是封装对象Emp的对应的属性的名称。
主键返回
在数据添加成功后许需要获取插入数据库数据后的主键。如添加套餐数据时还需要维护套餐菜品的关系表数据。
在默认情况下是不会返回主键值的,特别是对于形如自增的主键的数据库而言,我们在程序中只能得到null。
解决方法:
使用@Options注解
@Options(keyProperty = "id",useGeneratedKeys = true)
以上的注解将会将自动生成的主键值赋值给emp对象的id属性。
更新
@Update("update emp set username = #{username},name = #{name}" +"where id = #{id}")
public int update(Emp emp);
查询
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);
@Select("select * from emp where name like '%${name}%' and age between #{begin} and #{end} order by update_time desc")
public list<Emp> getList(String name,LocalDate begin,LocalDate end);
值得注意的是在上述查询的多条件查询中,模糊查询使用的是$而不是#是因为#无法在引号中使用,因为不能生成预编译sql了,但是使用$可以拼接字符串,可以使用。但是可能出现sql注入,解决方法如下:
使用concat拼接字符串
@Select("select * from emp where name like concat("%",#{name},"%") and age between #{begin} and #{end} order by update_time desc")
数据封装
若实体类属性名和数据库表查询返回的字段名一致,会自动封装。
若不一致则有以下方案:
1、给字段起别名,让别名与实体类属性一致。
@Select("select dept_id deptId from emp where id = #{id}")
2、通过@Results注解手动映射封装。将不一致的进行封装。
@Results({
@Result(colum = "dept_id",property = "deptId"),@Result(colum = "",property = ""),@Result(colum = "",property = "")
})
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);
3、开启mybatis驼峰命名自动映射开关,这样如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射
配置配置文件如下:
mybatis.configuration.map-underscore-tocamel-case = true
XML映射文件
基本结构
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mx.mxboot.mapper.StudentMapper">
</mapper>
规范
项目 | 要求 |
---|---|
XML 文件名 | 与 Mapper 接口名保持一致 |
文件位置 | 与 Mapper 接口放在相同的包路径下(resources 目录下) |
namespace 属性 | 设置为对应 Mapper 接口的 全限定类名 |
sql语句的id 属性 | 与Mapper接口中的方法名一致 |
sql语句的resultType 属性 | 与Mapper接口中的返回类型一致 |
操作
resultType指的是单条记录所封装的类型,注意此处的id 和Mapper接口的方法名一致
<select id = "getList" resultType = "com.mx.pojo.Emp">
select * from emp where name like concat('%',#{name},'%') and age between #{begin} and #{end} order by update_time desc
</select>
参数
场景 | 标签属性 | 说明 |
---|---|---|
单参数 | parameterType | 常见类型如 int 、String 、Map |
多参数 | 推荐封装成 JavaBean 或 Map | |
返回单对象 | resultType="User" | |
返回集合 | List<User> 对应 resultType="User" |
动态SQL
随着用户的输入或外部条件的变化而变化的sql语句。
标签名 | 功能说明 | 使用注意事项与推荐写法 |
---|---|---|
<if> | 条件判断,是否拼接某段 SQL | - 表达式必须是布尔类型或结果为布尔值- 推荐:!= null and != '' |
<where> | 自动补全 WHERE,并自动处理多余 AND/OR | - 推荐搭配多个 <if> - 可避免首个 AND/OR 出错问题 |
<set> | 用于 UPDATE 中,自动处理逗号 | - 搭配 <if> 用于动态更新字段- 避免多余逗号导致 SQL 错误 |
<foreach> | 遍历集合,用于 IN 子句、批量操作 | - 必须设定 collection 、item 、open 、separator 、close 等属性- 常用于 list , array , map 类型 |
<choose> | 类似 if/else if/else 分支结构 | - 与 <when> <otherwise> 搭配使用,确保逻辑完整 |
<trim> | 手动控制前缀和后缀,比如去掉开头的 AND/OR | - 与 <where> / <set> 类似但更灵活- 可自定义 prefix , suffix , prefixOverrides 等 |
在使用上述标签的时候,大多数是只减不增的,对于可以处理多余的****的情况是不会自动添加这个内容的
MybatisX
IDEA的插件,可以快速定位sql语句和接口方法。
使用后在接口方法下按住alt+enter可以快速生成对应的xml语句。
登录验证
在服务器端接收到请求后,在服务器端检查是否登录了。
登录标记
在每次请求中都可以获取到登录之后的标记。
会话技术
会话:用户打开浏览器访问web的资源,会话建立,知道一方断开连接会话结束。一次会话中包含多次请求和响应。
会话跟踪:维护浏览器状态的方法,服务其识别多次请求来自于统一浏览器们一边统一次会话的多次请求间共享数据。
cookie
保存在本地,每次请求发送给服务端。在响应头中setcookie,在请求头中cookie。
在调试台的Application中可以查看到对应的cookie。
cookie不安全、不能跨域,且移动端无法使用。
response.addCookie(new Cookie("login_username" , "mx"));
Cookie[] cookies = request.getCookies();
session
服务器端创建的,浏览器携带保存了sessionId的cookie,服务器将会根据这个cookie存储的sessionId找到对应创建的session对象。
但是session只能存储在一个服务器上,对于多个服务器无法共享session。如果服务器集群并使用负载均衡服务器数据会出错。
令牌技术
为用户生成一个令牌,令牌可以存储在cookie或其他当中,服务器发送令牌给浏览器存储在本地。浏览器每次响应数据都要携带令牌,如果令牌检验成功则成功响应。
可以将共享的数据存储在令牌当中。
支持PC端和移动端,解决了集群环境下的认证问题,减轻了服务器的存储压力。
使用密码学校验令牌的完整性,来确定没有被修改过。
需要自己实现令牌。
JWT
是一种间接的自包含的格式,用于通信双发以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
组成:
Header:记录令牌类型、签名算法。
Payload:携带自定义信息、默认信息等
Signature:防止Token被篡改、确保安全性。
前两个部分是BASE64编码的数据,并没有加密。
JWT工具类
public class JwtUtils {
private static String signKey = "itheima";//签名密钥
private static Long expire = 43200000L; //有效时间
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims){
String jwt = Jwts.builder()
.addClaims(claims)//自定义信息(有效载荷)
.signWith(SignatureAlgorithm.HS256, signKey)//签名算法(头部)
.setExpiration(new Date(System.currentTimeMillis() + expire))//过期时间
.compact();
return jwt;
}
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt) throws Exception{
Claims claims = Jwts.parser()
.setSigningKey(signKey)//指定签名密钥
.parseClaimsJws(jwt)//指定令牌Token
.getBody();
return claims;
}
}
使用:
1、引入jwt依赖
<!-- JWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2、生成
JwtUtils.generateJwt(claims);
3、登录成功后使用JSON下发令牌
@RestController
@Slf4j
public class LoginController {
//依赖业务层对象
@Autowired
private EmpService empService;
@PostMapping("/login")
public Result login(@RequestBody Emp emp) {
//调用业务层:登录功能
Emp loginEmp = empService.login(emp);
//判断:登录用户是否存在
if(loginEmp !=null ){
//自定义信息
Map<String , Object> claims = new HashMap<>();
claims.put("id", loginEmp.getId());
claims.put("username",loginEmp.getUsername());
claims.put("name",loginEmp.getName());
//使用JWT工具类,生成身份令牌
String token = JwtUtils.generateJwt(claims);
return Result.success(token);
}
return Result.error("用户名或密码错误");
}
}
4、前端每次请求时在请求头中携带令牌
5、解析令牌
JwtUtils.parseJWT(jwt);
6-1、使用过滤器拦截请求,检测是否登录
@Slf4j
@WebFilter(urlPatterns = "/*") //拦截所有请求
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
//前置:强制转换为http协议的请求对象、响应对象 (转换原因:要使用子类中特有方法)
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1.获取请求url
String url = request.getRequestURL().toString();
log.info("请求路径:{}", url); //请求路径:http://localhost:8080/login
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
if(url.contains("/login")){
chain.doFilter(request, response);//放行请求
return;//结束当前方法的执行
}
//3.获取请求头中的令牌(token)
String token = request.getHeader("token");
log.info("从请求头中获取的令牌:{}",token);
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
if(!StringUtils.hasLength(token)){
log.info("Token不存在");
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return;
}
//5.解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(token);
}catch (Exception e){
log.info("令牌解析失败!");
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return;
}
//6.放行
chain.doFilter(request, response);
}
}
6-2使用拦截器拦截请求,检测是否登录
//自定义拦截器
@Component //当前拦截器对象由Spring创建和管理
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
//前置方式
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle .... ");
//1.获取请求url
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
//3.获取请求头中的令牌(token)
String token = request.getHeader("token");
log.info("从请求头中获取的令牌:{}",token);
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
if(!StringUtils.hasLength(token)){
log.info("Token不存在");
//创建响应结果对象
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
//设置响应头(告知浏览器:响应的数据类型为json、响应的数据编码表为utf-8)
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return false;//不放行
}
//5.解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(token);
}catch (Exception e){
log.info("令牌解析失败!");
//创建响应结果对象
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
//设置响应头
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return false;
}
//6.放行
return true;
}
注册配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginCheckInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
若令牌没有修改、过期则可以正常解析,否则报错。
统一拦截
在业务前设置拦截器拦截请求。
过滤器Filter
将对资源的请求拦截下来,实现一些特殊的功能。一般完成一些通用的操作比如:登录校验、统一编码处理、敏感字符处理。
定义Filter:定义一个类,实现Filter接口,重写所有方法
配置:@WebFilter(urlPatterns = “/*”)拦截哪些请求。同时在启动类上添加注解 @ServletComponentScan 开启Servlet支持。
@WebFilter(urlPatterns = "/*") //配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )
public class DemoFilter implements Filter {
@Override //初始化方法, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init 初始化方法执行了");
}
@Override //拦截到请求之后调用, 调用多次
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Demo 拦截到了请求...放行前逻辑");
//放行
chain.doFilter(request,response);
System.out.println("DemoFilter 放行后逻辑.....");
}
@Override //销毁方法, 只调用一次
public void destroy() {
System.out.println("destroy 销毁方法执行了");
}
}
执行流程
1、放行前逻辑
2、放行,正常执行操作
3、放行后逻辑
ilter可以根据需求,配置不同的拦截资源路径:
拦截路径 | urlPatterns值 | 含义 |
---|---|---|
拦截具体路径 | /login | 只有访问 /login 路径时,才会被拦截 |
目录拦截 | /emps/* | 访问/emps下的所有资源,都会被拦截 |
拦截所有 | /* | 访问所有资源,都会被拦截 |
过滤器链
在一个web中可以配置多个过滤器,多个过滤器形成了一个过滤器链。
即doFilter中的参数FilterChain 使用chain.doFilter做的是放行到下一个过滤器当中,若是最后一个过滤器,则正常访问资源。
过滤器链的执行顺序由类名决定
拦截器
动态拦截方法条用的机制类似于过滤器。
它是Spring框架提供的动态拦截控制器方法的执行。
它用于拦截请求,在指定的方法调用的前后根据业务需要执行预先设定的代码。
使用
1、定义拦截器实现HandlerInterceptor接口,并重写其所有方法。
//自定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
//目标资源方法执行前执行。 返回true:放行 返回false:不放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle .... ");
return true; //true表示放行
}
//目标资源方法执行后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ... ");
}
//视图渲染完毕后执行,最后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion .... ");
}
}
2、注册拦截器
注册配置拦截器:实现WebMvcConfigurer接口,并重写addInterceptors方法
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自定义的拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry
.addInterceptor(loginCheckInterceptor)
.addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
.excludePathPatterns("/login");//设置不拦截的请求路径
}
}
拦截路径
值得注意的是在拦截器中,通配符*的使用方法有所不同/*表示的是匹配下一级路径/**才是任意级路径
不仅可以指定要拦截哪些资源,还可以指定不拦截哪些资源,只需要调用excludePathPatterns("不拦截路径")
方法,指定哪些资源不需要拦截。
拦截路径 | 含义 | 举例 |
---|---|---|
/* | 一级路径 | 能匹配/depts,/emps,/login,不能匹配 /depts/1 |
/** | 任意级路径 | 能匹配/depts,/depts/1,/depts/1/2 |
/depts/* | /depts下的一级路径 | 能匹配/depts/1,不能匹配/depts/1/2,/depts |
/depts/** | /depts下的任意级路径 | 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1 |
执行顺序
先执行服务器容器,所以先执行Filter过滤器;再进入Spring识别Servlet容器,执行前端控制器DispatcherServlet;再执行拦截器Interceptor ;最后访问Controller当中的方法。之后依次返回执行。
拦截器链
通过配置类实现拦截器链的配置,默认按照添加的顺序依次执行,但是也可以指配优先级
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 TimingInterceptor(顺序1)
registry.addInterceptor(new TimingInterceptor())
.order(1) // 优先级最高(数值越小优先级越高)
.addPathPatterns("/**"); // 拦截所有路径
// 注册 AuthInterceptor(顺序2)
registry.addInterceptor(new AuthInterceptor())
.order(2) // 优先级次之
.addPathPatterns("/**")
.excludePathPatterns("/login"); // 排除登录接口
}
}
Filter于Interceptor对比
1接口规范不同
2拦截范围不同:拦截器会拦截所有的资源,而Interceptor仅仅拦截String环境中的资源。
异常处理
方案1:try catch
方案2:全局异常处理器
全局异常处理器
所有异常抛出到表现层处理,分类处理,使用AOP处理。使得在出现异常的时候也能保持返回给前端的数据的一致性,并使得在出现异常的时候服务器还能正常运行。
使用Spring中的@RestControllerAdvice注解表明这是异常处理器,@ExceptionHandler说明处理的异常种类。
@RestControllerAdvice
public class GlobalExceptionHandler {
//处理异常
@ExceptionHandler(Exception.class) //指定能够处理的异常类型
public Result ex(Exception e){
e.printStackTrace();//打印堆栈中的异常信息
//捕获到异常之后,响应一个标准的Result
return Result.error("对不起,操作失败,请联系管理员");
}
}
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
处理异常的方法返回值会转换为json后再响应给前端
事务管理
一组操作的集合,是不可分割的工作单位,要么同时成功,要么同时失败。
注解:@Transactional
位置:业务层的方法上、类上、接口上
作用:将当前方法交给Spring进行事务管理,方法执行前开启事务;执行完毕提交事务;出现异常回滚事务。
一般加在经常进行的增删改操作上。
可以查询网页了解如何开启事务管理日志
回滚
默认情况下,只有出现RuntimeException(运行时异常)才会回滚事务,可以对rollbackFor属性用于控制何种异常会进行事务的回滚。
@Transactional(rollbackFor = Exception.class)
事务的传播行为
什么是事务的传播行为呢?
- 就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
例如:两个事务方法,一个A方法,一个B方法。在这两个方法上都添加了@Transactional注解,就代表这两个方法都具有事务,而在A方法当中又去调用了B方法。
所谓事务的传播行为,指的就是在A方法运行的时候,首先会开启一个事务,在A方法当中又调用了B方法, B方法自身也具有事务,那么B方法在运行的时候,到底是加入到A方法的事务当中来,还是B方法在运行的时候新建一个事务?这个就涉及到了事务的传播行为。
可以配置propagation属性进行传播行为的设定。
属性值 | 含义 |
---|---|
REQUIRED | 【默认值】需要事务,有则加入,无则创建新事务 |
REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
NESTED | 需要事务,有则创建一个嵌套事务,无则创建新事务 |
SUPPORTS | 支持事务,有则加入,无则在无事务状态中运行 |
NOT_SUPPORTED | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 |
MANDATORY | 必须有事务,否则抛异常 |
NEVER | 必须没事务,否则抛异常 |
… |
对于这些事务传播行为,我们只需要关注以下三个就可以了:
- REQUIRED(默认值)
- REQUIRES_NEW
- NESTED
REQUIRED、REQUIRES_NEW和NESTED的区别体现在,对于新加入的事务:
-
REQUIRED是将两者合并为同一个事务,同进退
-
REQUIRES_NEW认为新的事务是独立个体,其执行结果不影响父结果
-
NESTED认为两者是父子关系,如果儿子回滚,向父亲抛出异常,如果父亲try/catch了对应的异常,那么就认为父亲无需回滚。
2:REQUIRED | 2:REQUIRES_NEW | 2:NESTED | |
---|---|---|---|
1:REQUIRED 1无异常+2异常 | [回滚,回滚] | [回滚,回滚] | [回滚,回滚] |
1:REQUIRED 1无异常+2异常,1捕获2异常 | [回滚,回滚] 异常1 | [提交,回滚] | [提交,回滚] |
1:REQUIRED 1异常+2无异常 | [回滚,回滚] | [回滚,提交] | [回滚,回滚] |
1:NO 1无异常+2异常 | [提交,回滚] | [提交,回滚] | [提交,回滚] |
案例
**需求:**解散部门时需要记录操作日志
由于解散部门是一个非常重要而且非常危险的操作,所以在业务当中要求每一次执行解散部门的操作都需要留下痕迹,就是要记录操作日志。而且还要求无论是执行成功了还是执行失败了,都需要留下痕迹。
步骤:
- 执行解散部门的业务:先删除部门,再删除部门下的员工(前面已实现)
- 记录解散部门的日志,到日志表(未实现)
@Transactional(rollbackFor = Exception.class)
public void delete(Integer id) throws Exception {
try {
//根据部门id删除部门信息
deptMapper.deleteById(id);
//模拟:异常
if(true){
throw new Exception("出现异常了~~~");
}
//删除部门下的所有员工信息
empMapper.deleteByDeptId(id);
}finally {
//不论是否有异常,最终都要执行的代码:记录日志
DeptLog deptLog = new DeptLog();
deptLog.setCreateTime(LocalDateTime.now());
deptLog.setDescription("执行了解散部门的操作,此时解散的是"+id+"号部门");
//调用其他业务类中的方法
deptLogService.insert(deptLog);
}
}
其中的deptLogService.insert其实也是一个事务
@Service
public class DeptLogServiceImpl implements DeptLogService {
@Autowired
private DeptLogMapper deptLogMapper;
@Transactional //事务传播行为:有事务就加入、没有事务就新建事务
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}
在这种情况下我们可以看到DeptLogServiceImpl使用的是默认的事务传播方法,在这里和调用者delete其实是共享一个事务的。也就是说,对于delete出现异常导致回滚的情况下,这里的insert会一起回滚,因为属于同一个事务。
实际上我们想要对于delete出现异常也要输出日志,所以应该是创建一个新的事务,那么就应该使用propagation = Propagation.REQUIRES_NEW
@Service
public class DeptLogServiceImpl implements DeptLogService {
@Autowired
private DeptLogMapper deptLogMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)//事务传播行为:不论是否有事务,都新建事务
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}
这里再运行时,运行到insert就会先挂起外部的事务,等到内部的事务提交(或回滚)之后,再继续运行外部事务。
AOP面向切面编程
基于动态代理,在不惊动原有代码的基础上做功能增强。
1、导入依赖
2、编写AOP程序:针对特定方法根据业务编程。
@Component
@Aspect //说明当前是AOP类
class TimeAspect{
@Around("execution(* com.mx.dao.*.*(..))")//切入点表达式,指定AOP加载在哪些方法上
private Object recordTime(Proceeding JoinPoint pJP){
//......
Object now= pJP.proceed);//调用原始方法运行
//......
return now;
}
}
核心概念
连接点JoinPoint:可以被AOP控制的方法,暗含方法执行的相关信息。
通知Advice:指那些重复的逻辑,也就是编写的共性功能
切入点PointCut:匹配连接点的条件,通知仅会在切入点方法执行时被应用。即指定哪些方法被影响到的条件。
切面Aspect:描述通知和切入点的对应关系(通知+切入点)
目标对象Target:通知所应用的对象。
可以认为,所有可以被AOP控的方法都是连接点。但是只有被通知通过切入点应用到的对象才是目标对象。
运行过程
一旦使用AOP运行的就不再是原有的对象了,而是原有对象的代理对象。
通知类型
1.@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
2.@Before:前置通知,此注解标注的通知方法在目标方法前被执行
3.@After︰后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
4.@AfterReturning :返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
5.@AfterThrowing :异常后通知,此注解标注的通知方法发生异常后执行
在使用通知时的注意事项:
- @Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
- @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的。
//环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before ...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
//原始方法在执行时:发生异常
//后续代码不在执行
log.info("around after ...");
return result;
}
通知顺序
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。
具体的执行顺序和类名有关,before字母排序靠前的先执行,after相反。
也可以使用 @Order(数字)加在切面类上来控制顺序。
目标方法前的通知方法:数字小的先执行目标方法后的通知方法:数字小的后执行
切入点表达式
切入点表达式:描述切入点方法的一种表达式
可以使用@Pointcut()定义表达式,之后再使用
@Pointcut ("execution (public void com.itheima.service.impl.DeptServiceImpl.delete (java.lang.Integer)")
void pt(){};
@Before("pt()")
public void before (){
log.info ("MyAspect6 ... before ... ");
}
作用:主要用来决定项目中的哪些方法需要加入通知常见形式:
- execution(…)︰根据方法的签名来匹配
- annotation(…)︰根据注解匹配
execution
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
其中带?
的表示可以省略的部分
-
访问修饰符:可省略(比如: public、protected)
-
包名.类名: 可省略
-
throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
示例:
@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
可以使用通配符描述切入点
-
*
:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分 -
..
:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
切入点表达式示例
-
省略方法的修饰符号
execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
-
使用
*
代替返回值类型execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
-
使用
*
代替包名(一层包使用一个*
)execution(* com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))
-
使用
..
省略包名execution(* com..DeptServiceImpl.delete(java.lang.Integer))
-
使用
*
代替类名execution(* com..*.delete(java.lang.Integer))
-
使用
*
代替方法名execution(* com..*.*(java.lang.Integer))
-
使用
*
代替参数execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))
-
使用
..
省略参数execution(* com..*.*(..))
注意事项:
-
根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。
execution(* com.itheima.service.DeptService.list(..)) || execution(* com.itheima.service.DeptService.delete(..))
@annotation
@annotation切入点表达式,用于匹配标识有特定注解的方法。
需要自定义注解,
@Retention (RetentionPolicy.RUNTIME)
@Target (ElementType.METHOD)
public @interface MyLog{}
@annotation( com.itheima.anno.MyLog)
@Before("@annotation (com.itheima.anno.MyLog)"")
public void before () {
log.info ("before ...." );
}
使用的时候只要在对应的方法上加上自定义的注解即可
连接点
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
- 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint
- 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型
joinPoint.getTarget().getclass( ).getName();//获取目标类名
joinPoint.getsignature();//获取目标方法签名
joinPoint.getsignature().getName();//获取目标方法名
joinPoint.getArgs();//获取目标方法运行参数
joinPoint.proceed();//执行原始方法,获取返回值(环绕通知)
案例:写入Log当前操作的信息
获取信息:
ID:先自动装配一个HttpServletRequest request,然后request.getHeader(“token”);获取JWT令牌,进一步解析后获得ID
@Autowired
HttpServletRequest request;
@Around("Cannotation (com.itheima.anno.MyLog)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
//操作人ID-当前情录员工ID
//获取请求头中的jwt令牌,解析令牌
String jwt = request.getHeader("token");
claims clains = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer)claims.get("id" );
LocalDateTime operateTime = LocalDateTime.now();
string className = joinPoint.getTarget().getclass( ).getName();
private string methodName = joinPoint.getsignature().getName();
private string methodParams = joinPoint.getArgs();
Object result = joinPoint.proceed();//调用原方法,并返回结构
private string returnvalue = JSONObject.toJSONString(result);
private Long costTime = end - begin;
OperateLog op = new OperateLog(...);
operateLogMapper.insert(op);//shu'j'k
log.info("AOP操作日志",op);
}
JavaWeb技术栈总结图
SpringMVC不是一个单独的框架,它是Spring框架的一部分,是Spring框架中的web开发模块,是用来简化原始的Servlet程序开发的。