Vue3路由
1. 路由器是用来管理/调度各个路由的;
2. 对于一个应用来说,一般路由器router只需要一个,但是 路由 route 是有多个的。
3. 单页面Web应用 (SPA:Single Page web Application) 就必须要使用路由;
4. 每一个key-value就是一个路由;key是路径,value是对应的组件。
5. 路由的本质:一个路由表达了一组对应关系。
6. 路由器的本质:管理多组对应关系。
1. 创建路由器对象
router.js
// 导入Vue Router相关函数
import { createRouter, createWebHistory } from 'vue-router'// 导入组件
import HeBei from '../components/HeBei.vue'
import HeNan from '../components/HeNan.vue'// 创建路由器对象
const router = createRouter({// 使用HTML5历史模式history: createWebHistory(),// Hash历史模式history: createWebHashHistory(),// 路由规则配置routes: [{path: '/hebei', // 路由路径component: HeBei, // 对应的组件name: 'hebei' // 建议添加路由名称},{path: '/henan',component: HeNan,name: 'henan'}]
})export default router
main.js
import { createApp } from 'vue'
import App from './App.vue'import router from './router/router'const app = createApp(App)
app.use(router)app.mount('#app')
App.vue
<template><div><div> <h1>省份</h1><ul><!-- router-link在Vue3中的用法保持一致 --><li><router-link to="/hebei" active-class="selected">河北省</router-link></li><li><router-link to="/henan" active-class="selected">河南省</router-link></li></ul></div><!-- 路由视图占位符用法不变 --><router-view></router-view></div>
</template><script setup>// 组件引入方式(如需使用):
// import HeBei from './components/HeBei.vue'
// import HeNan from './components/HeNan.vue'
// 引入后会自动注册,无需在components选项中声明
</script>
1.1 注意事项
路由组件在进行切换的时候,切掉的组件会被销毁。
路由组件实例比普通组件实例多两个属性:$route和$router
$route:属于自己的路由对象。 描述当前页面的路由状态(路径、参数等)
$router:多组件共享的路由器对象。 操作路由(跳转、前进后退等)
1.2 多级路由
// 创建路由实例
const router = createRouter({routes: [{path: '/hebei',component: HeBei,// 子路由配置(保持不变)children: [{path: 'shijiazhuang', // 子路由path依然不需要以/开头component: ShiJiaZhuang},{path: 'handan',component: HanDan}]},{path: '/henan',component: HeNan}]
})export default router
1.3 路由query传参
1.3.1 key-value
<li><router-link to="/hebei/city?a1=长安区&a2=裕华区&a3=新华区" active-class="selected">石家庄</router-link></li>
<li><router-link to="/hebei/city?a1=邯山区&a2=复兴区&a3=丛台区" active-class="selected">邯郸</router-link></li>
1.3.2 字符串拼接传参
<li><router-link :to="`/hebei/city?a1=${sjz[0]}&a2=${sjz[1]}&a3=${sjz[2]}`"active-class="selected">石家庄</router-link></li>
<li><router-link :to="`/hebei/city?a1=${hd[0]}&a2=${hd[1]}&a3=${hd[2]}`"active-class="selected">邯郸</router-link></li>
1.3.3 对象形式传参
<!-- 采用query方式传参,这里还可以使用对象形式 -->
<li><router-link active-class="selected" :to="{path : '/hebei/city',query : {a1 : sjz[0],a2 : sjz[1],a3 : sjz[2],}}">石家庄</router-link>
</li><li><router-link active-class="selected" :to="{path : '/hebei/city',query : {a1 : hd[0],a2 : hd[1],a3 : hd[2],}}">邯郸</router-link>
</li><script setup>
// Vue3中使用ref创建响应式数组
import { ref } from 'vue';// 声明响应式数据,替代Vue2中的data()方法
const sjz = ref(['长安区3', '裕华区2', '新华区2']);
const hd = ref(['邯山区3', '复兴区2', '丛台区2']);
</script>
1.4 路由命名传参
<li><!-- query的对象方式传参 --><router-link active-class="selected" :to="{// 注意:如果使用name的话,要求必须使用对象形式传参。不能使用字符串形式。name : 'shi',//path : '/hebei/sjz',query : {a1 : sjz[0],a2 : sjz[1],a3 : sjz[2],}}">石家庄</router-link>
</li><li><router-link active-class="selected" :to="{name : 'han',//path : '/hebei/hd',query : {a1 : hd[0],a2 : hd[1],a3 : hd[2],}}">邯郸</router-link>
</li>
1.5 路由params传参
import { createRouter } from 'vue-router'
import HeBei from '../pages/HeBei.vue'
import HeNan from '../pages/HeNan.vue'
import City from '../pages/City.vue'// 创建路由实例
const router = createRouter({routes: [{path: '/hebei',component: HeBei,children: [{name: 'shi',// 动态路由参数(格式不变,仍使用:参数名)path: 'sjz/:a1/:a2/:a3',component: City},{name: 'han',path: 'hd/:a1/:a2/:a3',component: City},]},{path: '/henan',component: HeNan}]
})export default router
1.5.1 :to 动态路由
params传参,使用 :to 的时候,只能用name,不能用path
<li><!-- params方式传参:字符串形式,写死的字符串 --><!-- <router-link active-class="selected" to="/hebei/sjz/裕华区/新华区/长安区">石家庄</router-link> --><!-- params方式传参:字符串形式,拼接模板字符串`` --><!-- <router-link active-class="selected" :to="`/hebei/sjz/${sjz[0]}/${sjz[1]}/${sjz[2]}`">石家庄</router-link> --><!-- params方式传参:对象形式 --><router-link active-class="selected" :to="{// 强调:如果使用的是params方式传参,这里只能使用name,不能使用path//path : '/hebei/sjz',name : 'shi',params : {a1 : sjz[0],a2 : sjz[1],a3 : sjz[2],}}">石家庄</router-link>
</li>
2. 路由的props
2.1 路由配置文件router.js
import { createRouter, createWebHistory } from 'vue-router'
import HeBei from '../pages/HeBei.vue'
import City from '../pages/City.vue'const router = createRouter({history: createWebHistory(),routes: [{path: '/hebei',component: HeBei,children: [{name: 'shi',path: 'sjz/:a1/:a2/:a3', // 动态参数:a1/a2/a3 占位符component: City,// 三种props配置方式(三选一)// 1. 对象式:传递静态数据(固定值)/* props: {x: '张三', y: '李四' // 这些数据会作为props传给City组件} */// 2. 函数式:动态处理参数(支持params/query)/* props(route) { // route是当前路由对象return {a1: route.params.a1, // 从params取动态参数a2: route.params.a2,a3: route.params.a3,x: '额外数据' // 可添加静态数据}} */// 3. 布尔值:自动将params转为props(最常用)props: true },{name: 'han',path: 'hd/:a1/:a2/:a3',component: City,props: true // 同样启用params转props}]}]
})export default router
2.2 City组件
<template><div class="s2"><h2>区</h2><ul><!-- 直接用props接收参数,无需写$route.params --><li>{{ a1 }}</li> <li>{{ a2 }}</li><li>{{ a3 }}</li><!-- 如果用了对象式/函数式props,还能直接用x/y --><!-- <li>{{ x }}</li><li>{{ y }}</li> --></ul></div>
</template><script setup>// Vue3中显式声明接收的propsconst props = defineProps(['a1', 'a2', 'a3']) // 如果有x/y,就添加到数组:['a1', 'a2', 'a3', 'x', 'y']
</script>
2.3 跳转组件
<script setup>import { ref } from 'vue'const sjz = ref(['长安区', '裕华区', '新华区'])const hd = ref(['邯山区', '复兴区', '丛台区'])
</script><template><div><ul><!-- 跳转到石家庄路由,传递params参数 --><li><router-link active-class="selected" :to="{name: 'shi', // 对应路由的nameparams: { a1: sjz[0], // 动态参数值a2: sjz[1],a3: sjz[2]}}">石家庄</router-link></li><!-- 跳转到邯郸路由,传递params参数 --><li><router-link active-class="selected" :to="{name: 'han',params: { a1: hd[0], a2: hd[1], a3: hd[2] }}">邯郸</router-link></li></ul></div>
</template>
3. 浏览器历史记录存储
存储在栈这种数据结构当中的。包括两种模式:① push模式 (默认的);② replace模式。
3.1 replace模式开启
<router-link :replace=”true”/><router-link replace/>
3.2 前进后退
<script setup>import { useRouter } from 'vue-router'import MyHeader from './components/MyHeader.vue'// 获取路由实例const router = useRouter()// 前进const forward = () => {router.forward()}// 后退const back = () => {router.back()}// 前进2步const forwardTwo = () => {router.go(2)}// 后退2步const backTwo = () => {router.go(-2)}
</script><template><div><div> <h1>省</h1><button @click="forward">前进</button><button @click="back">后退</button><button @click="forwardTwo">前进2步</button><button @click="backTwo">后退2步</button><ul><li><router-link to="/hebei" active-class="selected">河北省</router-link></li><li><router-link to="/henan" active-class="selected">河南省</router-link></li></ul></div><router-view></router-view></div>
</template>
3.3 路由导航
<script setup>import { ref } from 'vue'import { useRouter } from 'vue-router'const router = useRouter()const sjz = ref(['长安区2', '裕华区1', '新华区1'])const hd = ref(['邯山区1', '复兴区1', '丛台区1'])// 编程式路由导航const goSjz = () => {router.push({name: 'shi',params: {a1: sjz.value[0],a2: sjz.value[1],a3: sjz.value[2],}})}const goHd = () => {router.replace({name: 'han',params: {a1: hd.value[0],a2: hd.value[1],a3: hd.value[2],}})}
</script><template><div class="s1"><div><h2>市</h2><ul><li><!-- 声明式的路由导航。只适合超链接。 --><router-link :replace="true" active-class="selected" :to="{name : 'shi',params : {a1 : sjz[0],a2 : sjz[1],a3 : sjz[2],}}">石家庄</router-link><button @click="goSjz">石家庄</button></li><li><router-link replace active-class="selected" :to="{name : 'han',params : {a1 : hd[0],a2 : hd[1],a3 : hd[2],}}">邯郸</router-link><button @click="goHd">邯郸</button></li><li>唐山</li><li>保定</li></ul></div><!-- 区组件 --><router-view></router-view></div>
</template>
3.4 缓存路由组件
默认情况下路由切换时,路由组件会被销毁。有时需要在切换路由组件时保留组件(缓存起来)。
<template><div><MyHeader></MyHeader><div> <h1>省</h1><button @click="forward">前进</button><button @click="back">后退</button><button @click="forwardTwo">前进2步</button><button @click="backTwo">后退2步</button><ul><li><router-link to="/hebei" active-class="selected">河北省</router-link></li><li><router-link to="/henan" active-class="selected">河南省</router-link></li></ul></div><!-- 这个标签,如果你这样编写的话,表示这个标签中所有的路由组件在切换的时候不会销毁。 --><!-- <keep-alive><router-view></router-view></keep-alive> --><!-- 注意:include="HeBei"表示在进行路由组件切换的时候,HeBei组件不被销毁。保留状态。 --><!-- <keep-alive include="HeBei"><router-view></router-view></keep-alive> --><!-- include也支持数组形式 --><keep-alive :include="['HeBei', 'HeNan']"><router-view></router-view></keep-alive></div>
</template>
4. 路由组件的钩子函数
只有“路由组件”才有的两个生命周期钩子函数。
路由组件被切换到的时候,activated被调用,一般情况下,路由组件切换的时候就会销毁。
路由组件被切走的时候,deactivated被调用。
mounted() {// 当前组件挂载完毕之后,创建一个定时器,每隔1秒打印一句话this.timer = setInterval(() => {console.log('我是一个定时器!')}, 1000)
},
beforeDestroy(){console.log('@河北省下面的市,这个组件被销毁了!')// 通常情况下,销毁之前删除定时器// 也就是keep-alive标签没写的时候//clearInterval(this.timer)
},
activated(){console.log('激活了')
},
deactivated(){console.log('失效了')// 删除定时器clearInterval(this.timer)
}// 使用 keep-alive 后,beforeDestroy 不再执行,所以需要用 deactivated 替代
5. 路由守卫
5.1 全局前置路由守卫
router.beforeEach(callback)
1. 代码写到哪里?在创建好router之后,以及暴露router之前
2. beforeEach中的callback什么时候被调用呢?
在初始化的时候执行一次,以后每一次在切换任意路由组件之前都会被调用。
3. callback可以是普通函数,也可以是箭头函数
4. callback函数有三个参数:to from next
from参数:from是一个路由对象,表示从哪来(从哪个路由切过来的),起点。
to参数:to也是一个路由对象,表示到哪儿去,重点
next参数:这是一个函数,调用这个函数之后,表示放行,可以继续向下走。
5. 给路由对象添加自定义属性的话,需要在路由对象的meta(路由元)中定义;如果目标路由有
isAuth: true,则验证用户权限;否则直接放行。
router.js
router.beforeEach((to, from, next) => {// 在每次路由切换后,根据路由配置更新页面标题//document.title = to.meta.title// 假设当前登录的用户是:let loginName = 'admin'// 目前来说只有shi和han这两个路由需要鉴权。// 如果当前项目中需要鉴权的路由比较多,这里的代码会比较繁琐,比较冗余。// 怎么解决?//if(to.name === 'shi' || to.name === 'han'){if(to.meta.isAuth){if(loginName === 'admin'){// 有权限的时候,再去修改title//document.title = to.meta.title// 放行next()}else{alert('对不起,您没有权限!')}}else{//document.title = to.meta.title// 放行next()}
})
5.2 全局后置路由守卫
由于一开始没有执行到全局后置守卫,所以可以在public文件夹下index.html文件中,修改title标签内容,避免出现标题闪烁情况。
router.js
// 1.afterEach中的回调函数什么时候执行?初始化的时候执行一次,以后每一次切换完任意一个路由组件之后被调用。
// 2.这个回调函数有两个参数:to, from。
// 3.这个回调函数没有next参数。因为没有必要了。
router.afterEach((to, from) => {// 如果前边为true,那么后边不考虑document.title = to.meta.title || '欢迎使用本系统'
})
5.3 局部路由守卫之path守卫
router.js
// 1. 这个局部路由守卫之path守卫,代码写到哪里?写到route对象(路由对象)中。
// 2. beforeEnter本身就是一个函数,参数上没有回调函数了。
// 3. 对于beforeEnter来说,参数仍然有三个:to from next
// 4. beforeEnter什么时候被调用?进入"shi"这个路由前被调用。
beforeEnter(to, from, next) {let loginName = 'admin'if (loginName === 'admin') {next()} else {alert('对不起,您没有权限!')}
},
5.4 局部路由守卫之component守卫
//1.代码写在哪里?局部路由守卫之component守卫,代码是写在component当中的(写到组件当中的Xxx.vue)
//2.beforeRouteEnter执行时机:进入路由组件之前执行。
//3.普通组件不会触发,必须是路由组件才会触发。
beforeRouteEnter(to, from, next){console.log(`进入路由组件之前:${to.meta.title}`)next()
},// beforeRouteLeave执行时机:离开路由组件之前执行。
beforeRouteLeave(to, from, next){console.log(`离开路由组件之前:${from.meta.title}`)next()
}
6. 前端路径问题
路径中#后面的路径称为hash。这个hash不会作为路径的一部分发送给服务器:
http://localhost:8080/vue/bugs/#/a/b/c/d/e (真实请求的路径是:http://localhost:8080/vue/bugs)
路由的两种路径模式:① hash模式 ② history模式
默认是hash模式,如何开启history模式
// Hash模式路由
const hashRouter = createRouter({history: createWebHashHistory(),routes
})// History模式路由
const historyRouter = createRouter({history: createWebHistory(),routes
})
特性 | Hash 模式 | History 模式 |
---|---|---|
URL 格式 |
|
|
服务器配置 | 无需特殊配置 | 需要配置支持SPA |
SEO 友好性 | 较差 | 较好 |
兼容性 | 所有浏览器 | 现代浏览器 |
实现原理 | 使用URL hash | 使用HTML5 History API |
7. 回调函数与Promise
7.1 回调函数
普通函数: 正常调用的函数,一般函数执行完毕后才会继续执行下一行代码;
<script>let fun1 = () =>{console.log("fun1 invoked")}// 调用函数fun1()// 函数执行完毕,继续执行后续代码console.log("other code processon")
</script>
回调函数: 基于事件的,自动调用函数;
未来才会执行的一些功能, 后续代码不会等待该函数执行完毕就开始执行了
<script>// 设置一个2000毫秒后会执行一次的定时任务// function(){}setTimeout(function (){console.log("setTimeout invoked")},2000)console.log("other code processon")
</script>
7.2 Promise简介
Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
(1)Promise对象代表一个异步操作,有三种状态:`Pending`(进行中)、`Resolved`(已完成,又称 Fulfilled)和`Rejected`(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是`Promise`这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从`Pending`变为`Resolved`和从`Pending`变为`Rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。
<script>/*resolve函数 在回调函数内如果调用resolve方法,promise会由pending转换为resolvedreject函数 在回调函数内如果调用reject方法,promise会由pending转换为reject*/let promise = new Promise(function (resolve, reject) {console.log("function invoked")resolve("haha")})console.log("other code1");// 等待promise对象状态发生改变时才会执行的代码promise.then(function (value) {// promise转换为resolved状态时会执行的函数console.log("promise success:" + value);},// function () {// // promise转换为rejected状态时会执行的函数// console.log("promise failed");// }).catch(function () {//当promise状态是reject或者promise出现异常时 会执行的函数console.log("promise catch:" + value);})console.log("other code2");</script>
7.3 aysnc的使用
1. async标识函数后,async函数的返回值会变成一个promise对象;
2. 如果函数内部返回的数据是一个非promise对象,async函数的结果会返回一个成功状态的promise
对象;return后的结果就是成功状态的返回值;
3. 如果函数内部返回的是一个promise对象,则async函数返回的状态与结果由该对象决定;
4. 如果函数内部抛出的是一个异常,则async函数返回的是一个失败的promise对象;
<script>async function fun1() {//return 10//throw new Error("something wrong")let promise = Promise.reject("heihei")return promise}let promise = fun1()promise.then(function (value) {console.log("success:" + value)}).catch(function (value) {console.log("fail:" + value)})
</script>
7.4 await的使用
1. await右侧的表达式一般为一个promise对象,但是也可以是一个其他值
2. 如果表达式是 promise 对象, await 返回的是 promise 成功的值
3. await会等右边的promise对象执行结束,然后再获取结果;后续代码也会等待await的执行
4. 如果表达式是其他值,则直接返回该值
5. await必须在async函数中,但是async函数中可以没有await
6. 如果await右边的promise失败了,就会抛出异常,需要通过 try ... catch捕获处理
<script>async function fun1() {return 10}let res = await "张三"let res = await Promise.resolve("张三")res = "张三"async function fun2() {try {let res = await fun1()//let res = await Promise.reject("something wrong")} catch (e) {console.log("catch got:" + e)}console.log("await got:" + res)}fun2()
</script>
8. Axios实现异步请求
Axios 的核心是 axios(config);
其中 config 是一个 JavaScript 对象(配置对象),里面包含了请求的所有细节。
axios({method: 'get', // 请求方法:get、post、put 等(小写)url: 'https://api.uomg.com/api/rand.qinghua?format=json', // 请求的 URLparams: { // 查询参数,会自动拼到 URL 后面(如 ?key=value)// 例如:format: 'json' 会变成 ?format=json},data: { // 请求体数据,通常用于 POST/PUT 等方法(GET 不常用)// 会自动转为 JSON 字符串放入请求体},headers: { // 自定义请求头'Content-Type': 'application/json' // 示例:告诉服务器数据是 JSON},// 其他可选配置...
}).then(response => {// 成功回调:response 是响应对象,包含 data(数据)、status(状态码)等
}).catch(error => {// 失败回调:error 是错误对象
});
8.1 get方法
axios.get(url, config):专门为 GET 请求设计的快捷方式。// 相当于
axios({ method: 'get', url: url, ...config })
axios.get("https://api.uomg.com/api/rand.qinghua?format=json", {params: { // 查询参数format: "json", // 虽然 URL 已有,但这里是额外示例username: "zhangsan",userPwd: "lisi"},headers: { // 请求头Accept: "application/json, text/plain"}
})
8.2 post方法
axios.post(url, data, config):专门为 POST 请求设计的快捷方式axios.post(url,{要放入请求体中的JSON串},{请求的其他信息})
axios.post("https://example.com/api", { // data 参数,直接是对象,会转 JSONname: "zhangsan",age: 18
}, {headers: { // 可选 configAuthorization: "Bearer token"}
})
8.3 参数config
method:请求类型,如 'get'(获取数据)、'post'(提交数据)、'put'(更新)、'delete'(删除)。
url:请求地址。"https://api.uomg.com/api/rand.qinghua?format=json",带了查询参数?format=json(返回 JSON 格式)。
params:查询参数,会自动拼到 URL 后(如 ?format=json&username=zhangsan)。适合 GET 请求。
data:请求体数据,通常用于 POST,会转为 JSON 放入请求体。如果是 POST,data 就是要提交的表单或 JSON。
headers:自定义请求头,告诉服务器你的请求类型或认证信息。 headers: {Accept: 'application/json, text/plain'},意思是“接受 JSON 或纯文本响应”。
其他常见参数:
- timeout(数字):请求超时时间(毫秒),如 5000(5秒)。
- responseType(字符串):响应数据类型,如 'json'(默认)、'text'。
- withCredentials(布尔):是否带 cookie 跨域请求。
8.4 Axios示例
<script setup>// 默认导出,不使用{}import axios from 'axios'import {ref, reactive} from 'vue'let message = ref('')// let message = reactive({// "code": 1,// "content": "我喜欢你,你喜欢我吗?不喜欢的话,我再想想办法。"// })function getLoveMessage() {// 使用axios发送请求获取土味情话// axios({设置请求的参数}) 请求三要素 1.请求的ur1 2.请求的方式 3.请求的参数 keyvalue json...let promise = axios({method:"get",url:"https://api.uomg.com/api/rand.qinghua?format=json", // 请求的urldata:{ // 当请求方式为post时,并且使用data,数据会以JSON串放入请求体// 当请求方式为get时,需要通过params进行发送// 请求方式是Post,这里的数据会放入请求体},params:{// 都是以键值对方式将数据放入url后}})promise.then(function(response){console.log(response);/*response 响应结果对象data 服务端响应回来的数据status响应状态码 200statusText 响应状态描述 OKheaders 本次响应的所有响应头config 本次请求的配置信息request 本次请求发送时所使用XMLHttpRequest对象*/console.log(response.data);console.log(response.data.code);console.log(response.data.content);message.value = response.data// message.value = response.data.content// message.content = response.data.content// 使用reactive时,可以使用下面方法将第二个的同名属性值赋值给第一个对象// Object.assign(message, response.data)}).catch(function(error){console.error("获取情话失败:", error);})}
</script><template><div><h1 v-text="message"></h1><!-- <h1 v-text="message.content"></h1> --><button @click="getLoveMessage()">变</button></div>
</template>
8.4.1 ref
和 reactive
的区别
特性 |
|
|
---|---|---|
创建方式 |
|
|
访问值 | 需要 | 直接访问属性 |
适用类型 | 基本类型和对象 | 仅对象 |
- 如果只需要显示内容,使用
ref
存储字符串:
let message = ref('')
message.value = response.data.content
- 如果需要处理完整响应数据,使用
reactive
存储对象:
let message = reactive({})
Object.assign(message, response.data)
变量类型 | 赋值方式 | 模板使用 | 适用场景 |
---|---|---|---|
|
|
| 只需显示内容 |
|
|
| 需要访问多个属性 |
|
|
| 需要完整响应数据 |
8.5 axios拦截器
import axios from 'axios'// 可以添加多个拦截器,按添加顺序执行(先进先出)// 使用axios函数可以创建一个发送请求的实例对象
const instance = axios.create({// 请求的基础路径// baseURL: "http://api.yomg.com/",
})// 设置请求拦截器
instance.interceptors.request.use(function (config) {// 请求之前,设置请求信息的方法config.headers.Accept = "application/json, text/plain, text/html, */*"//设置完毕,必须返回configreturn config},(error) => {// 返回一个失败状态的promisereturn Promise.reject("something Wrong!!!")}
)// 响应拦截器
instance.interceptors.response.use(function (response) {// 响应状态码为200要执行的方法// 最后要返回responsereturn response},function (error) {console.log("response fail!!!");// 最后一定要响应一个promisereturn Promise.reject("something wrong!!!")}
);// 使用默认导出暴露instance
export default instance
9. 过滤器Filter
1. doFilter方法中的请求和响应对象是以父接口的形式声明的,实际传入的实参就是
HttpServletRequest和HttpServletResponse子接口级别的,可以安全强转;
2. filterChain.doFilter(request,response); 这行代码的功能是放行请求,如果没有这一行代码,则
请求到此为止;
3. filterChain.doFilter(request,response);在放行时需要传入request和response,意味着请求和响应
对象要继续传递给后续的资源, 不再产生新的request和response对象;
9.1 过滤器使用
日志过滤器
public class LoggingFilter implements Filter {private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 参数父转子HttpServletRequest request =(HttpServletRequest) servletRequest;HttpServletResponse response =(HttpServletResponse) servletResponse;// 拼接日志文本String requestURI = request.getRequestURI();String time = dateFormat.format(new Date());String beforeLogging =requestURI+"在"+time+"被请求了";// 打印日志System.out.println(beforeLogging);// 获取系统时间long t1 = System.currentTimeMillis();// 放行请求filterChain.doFilter(request,response);// 获取系统时间long t2 = System.currentTimeMillis();// 拼接日志文本String afterLogging =requestURI+"在"+time+"的请求耗时:"+(t2-t1)+"毫秒";// 打印日志System.out.println(afterLogging);}
}
9.2 配置过滤器
-
filter-mapping标签中定义了过滤器对那些资源进行过滤
-
子标签url-pattern通过映射路径确定过滤范围
-
/servletA 精确匹配,表示对servletA资源的请求进行过滤
-
*.html 表示对以 .html 结尾的路径进行过滤
-
/* 表示对所有资源进行过滤
-
一个filter-mapping下可以配置多个url-pattern
-
-
子标签servlet-name通过servlet别名确定对那些servlet进行过滤
-
使用该标签确定目标资源的前提是servlet已经起了别名
-
一个filter-mapping下可以定义多个servlet-name
-
一个filter-mapping下,servlet-name和url-pattern子标签可以同时存在
-
9.2.1 XML配置方式实现
<!--配置filter,并为filter起别名-->
<filter><filter-name>loggingFilter</filter-name><filter-class>com.atguigu.filters.LoggingFilter</filter-class><!--配置filter的初始参数--><init-param><param-name>dateTimePattern</param-name><param-value>yyyy-MM-dd HH:mm:ss</param-value></init-param>
</filter>
<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping><filter-name>loggingFilter</filter-name><!--通过映射路径确定过滤资源--><url-pattern>/servletA</url-pattern><!--通过后缀名确定过滤资源--><url-pattern>*.html</url-pattern><!--通过servlet别名确定过滤资源--><servlet-name>servletBName</servlet-name>
</filter-mapping>
9.2.2 注解方式实现
@WebFilter(filterName = "loggingFilter",initParams = {@WebInitParam(name="dateTimePattern",value="yyyy-MM-dd HH:mm:ss")},urlPatterns = {"/servletA","*.html"},servletNames = {"servletBName"}
)
9.3 生命周期
阶段 | 对应方法 | 执行时机 | 执行次数 |
---|---|---|---|
创建对象 | 构造器 | web应用(项目)启动时 | 1 |
初始化方法 | void init(FilterConfig filterConfig) | 构造完毕 | 1 |
过滤请求 | void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) | 每次请求 | 多次 |
销毁 | default void destroy() | web应用关闭时 | 1 |
public interface Filter {/*接口可以包含有具体实现的方法;实现类不需要强制重写这些方法;允许向现有接口添加新方法而不破坏已有实现实现类可以根据需要选择是否重写*/default void init(FilterConfig filterConfig) throws ServletException {}void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;default void destroy() {}
}
@WebServlet("/*")
public class LifeCycleFilter implements Filter {public LifeCycleFilter(){System.out.println("LifeCycleFilter constructor method invoked");}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("LifeCycleFilter init method invoked");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("LifeCycleFilter doFilter method invoked");filterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {System.out.println("LifeCycleFilter destory method invoked");}
}
9.4 过滤器链的使用
1. 一个web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链。
2. 过滤器链中的过滤器的顺序由 <filter-mapping> 顺序决定 (请求经过的顺序,该标签越靠前就顺序越早) 如果使用注解实现,那么就根据文件名的顺序;
3. 每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的;
4. 如果某个Filter是使用 ServletName 进行匹配规则的配置,那么这个Filter执行的优先级要更低;
10. 监听器
专门用于对域对象对象身上发生的事件或状态改变进行监听和相应处理的对象;
10.1 监听器的分类
10.1.1 按监听的对象划分
-
application域监听器 ServletContextListener ServletContextAttributeListener
-
session域监听器 HttpSessionListener HttpSessionAttributeListener HttpSessionBindingListener HttpSessionActivationListener
-
request域监听器 ServletRequestListener ServletRequestAttributeListener
10.1.2 按监听的事件分
-
域对象的创建和销毁监听器 ServletContextListener HttpSessionListener ServletRequestListener
-
域对象数据增删改事件监听器 ServletContextAttributeListener HttpSessionAttributeListener ServletRequestAttributeListener
-
其他监听器 HttpSessionBindingListener HttpSessionActivationListener
10.2 application域监听器
@WebListener
public class ApplicationListener implements ServletContextListener , ServletContextAttributeListener {// 监听初始化@Overridepublic void contextInitialized(ServletContextEvent sce) {ServletContext application = sce.getServletContext();System.out.println("application"+application.hashCode()+" initialized");}// 监听销毁@Overridepublic void contextDestroyed(ServletContextEvent sce) {ServletContext application = sce.getServletContext();System.out.println("application"+application.hashCode()+" destroyed");}// 监听数据增加@Overridepublic void attributeAdded(ServletContextAttributeEvent scae) {String name = scae.getName();Object value = scae.getValue();ServletContext application = scae.getServletContext();System.out.println("application"+application.hashCode()+" add:"+name+"="+value);}// 监听数据移除@Overridepublic void attributeRemoved(ServletContextAttributeEvent scae) {String name = scae.getName();Object value = scae.getValue();ServletContext application = scae.getServletContext();System.out.println("application"+application.hashCode()+" remove:"+name+"="+value);}// 监听数据修改@Overridepublic void attributeReplaced(ServletContextAttributeEvent scae) {String name = scae.getName();Object value = scae.getValue();ServletContext application = scae.getServletContext();Object newValue = application.getAttribute(name);System.out.println("application"+application.hashCode()+" change:"+name+"="+value+" to "+newValue);}
}
10.2.1 定义触发监听器
// ServletA用于向application域中放入数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 向application域中放入数据ServletContext application = this.getServletContext();application.setAttribute("k1","v1");application.setAttribute("k2","v2");}
}// ServletB用于向application域中修改和移除数据
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {ServletContext appliation = getServletContext();// 修改application域中的数据appliation.setAttribute("k1","value1");// 删除application域中的数据appliation.removeAttribute("k2");}
}
10.3 钝化活化监听器
HttpSessionActivationListener 监听某个对象在Session中的序列化与反序列化。
HttpSessionEvent对象代表事件对象,通过getSession()方法获取事件涉及的HttpSession对象。
什么是钝化活化
-
session对象在服务端是以对象的形式存储于内存的,session过多,服务器的内存也是吃不消的
-
而且一旦服务器发生重启,所有的session对象都将被清除,也就意味着session中存储的不同客户端的登录状态丢失
-
为了分摊内存 压力并且为了保证session重启不丢失,可以设置将session进行钝化处理
-
在关闭服务器前或者到达了设定时间时,对session进行序列化到磁盘,这种情况叫做session的钝化
-
在服务器启动后或者再次获取某个session时,将磁盘上的session进行反序列化到内存,这种情况叫做session的活化
10.3.1 如何配置钝化活化
-
在web目录下,添加 META-INF下创建Context.xml
-
文件中配置钝化
<?xml version="1.0" encoding="UTF-8"?>
<Context><Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1"><Store className="org.apache.catalina.session.FileStore" directory="d:\mysession"></Store></Manager>
</Context>
-
请求servletA,获得session,并存入数据,然后重启服务器
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HttpSession session = req.getSession();// 添加数据session.setAttribute("k1","v1");}
}
-
请求servletB获取session,获取重启前存入的数据
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HttpSession session = req.getSession();Object v1 = session.getAttribute("k1");System.out.println(v1);
}
}