Spring Boot 與 Vue.js 整合實踐
一直都想嘗試做前後端分離,我之前一直是學 Java 的,所以後端選擇了 Spring Boot;前端選擇了 Vue.js 這個輕量、易上手的框架。網上其實已經有了不少 Spring Boot 和 Vue.js 整合的資料,Github 上就有好多 repo,但是每當我指望按圖索驥的時候就會出現各種各樣奇怪的 bug,上 Stack Overflow 問了也沒人搭理。前前後後研究了差不多三個星期,現在總算是理清楚了。
本文重點介紹我在實踐過程中的基本流程,以及我遇到的一個困擾了我好久的問題,就是如何 CORS。
框架版本
- Spring Boot: 2.0.4.RELEASE(JDK 是1.8)
- Vue.js: 2.x
基本流程
前端:編寫 Vue 元件
首先用 vue-cli 搭好腳手架,我這個 Demo 用到的第三方庫有:
然後寫一個登入元件:
<!-- 下面是我直接從 bootstrap-vue 文件抄下來的模板--> <template> <div> <b-form @submit="onSubmit" @reset="onReset" v-if="show"> <b-form-group id="exampleInputGroup1" label="Username:" label-for="exampleInput1"> <b-form-input id="exampleInput1" type="text" v-model="form.username" required placeholder="Enter username"> </b-form-input> </b-form-group> <b-form-group id="exampleInputGroup2" label="Password:" label-for="exampleInput2"> <b-form-input id="exampleInput2" type="text" v-model="form.password" required placeholder="Enter password"> </b-form-input> </b-form-group> <b-form-group id="exampleGroup4"> <b-form-checkbox-group v-model="form.checked" id="exampleChecks"> <b-form-checkbox value="me">Check me out</b-form-checkbox> <b-form-checkbox value="that">Check that out</b-form-checkbox> </b-form-checkbox-group> </b-form-group> <b-button type="submit" variant="primary">Submit</b-button> <b-button type="reset" variant="danger">Reset</b-button> </b-form> </div> </template> <script> //... </script>
我現在想實現的就是使用者登入成功之後導航到另一個元件,所以我就又寫了一個歡迎元件:
<template> <div> <h1>Welcome!</h1> </div> </template>
記得配置路由:
// src/router/index.js import Vue from 'vue' import Router from 'vue-router' import Login from '@/components/Login.vue' import Information from '@/components/Information.vue' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Login', component: Login }, { path: '/information', name: 'Information', component: Information } ] })
後端:提供 RESTful API
因為只有後端提供了介面,前端才能呼叫,所以現在要進行後端開發。RESTful 是現在很流行的 API 設計風格,所以我這裡也實踐了一下。下面是 controller 的程式碼,完整原始碼地址附在文末。
@RestController @RequestMapping("/api") public class LoginController { @RequestMapping(path = "/login", method = RequestMethod.POST) @ResponseBody public String login(@RequestParam String username, @RequestParam String password) { // 簡單處理一下,實際開發中肯定是要用到資料庫的 if (username.equals("123") && password.equals("123")) { return "successful"; } else { return "failed"; } } }
後端的 API 現在有了,就差前端呼叫了。但是沒這麼簡單,接下來就要解決我前面提到的問題。
實現 CORS
在這個 Demo 中前端佔用的埠是8080,後端是 8088。這就存在跨域的問題,如果不解決的話後端就沒法接收前端的請求。
我參考了這個例子 ,通過配置 Spring MVC 實現了 CORS:
@Configuration public class CORSConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins(ALL) .allowedMethods(ALL) .allowedHeaders(ALL) .allowCredentials(true); } }
後端配置好了還不行,前端也要有一些配置,要用 axios 順利地傳送請求並保證後端能接收到,需要對請求引數做處理。我參考這個回答 用 qs 庫對請求引數做了處理:
qs.stringify({ 'username': this.form.username, 'password': this.form.password })
現在只需完善前端呼叫後端 API 的程式碼:
// Login.vue <script> export default { data () { return { form: { username: '', password: '', checked: [] }, show: true } }, methods: { onSubmit (evt) { evt.preventDefault(); // 關鍵就在於要對引數進行處理 axios.post('http://localhost:8088/api/login',qs.stringify({ 'username': this.form.username, 'password': this.form.password })).then((response) => { var status = response.data; if(status === 'successful') { this.$router.push('/information'); } else { alert(response.data.message); } console.log(response); }).catch((error) => { console.log(response); }); } } } </script>
至此,終於實現了前後端的分離,並且保證前後端能夠順利互動。
題外話
讓 controller 能獲取請求引數
controller 可能無法獲取請求引數,3600" target="_blank" rel="nofollow,noindex">這篇文章 提供了一種解決方案。我這個 Demo 中並沒有出現 controller 收不到請求引數的問題,但也把這個問題記錄下來,以後可能遇上也說不準。
axios 方法中的 this
我這個 Demo 中還試著用 axios 發 GET 請求,然後獲取後端響應的 JSON 資料。
// Information.vue <template> <div> <h1>Welcome!</h1> <div> <b-button @click="getInfo()">Get your information</b-button> <h2 v-if="username !== ''">Your username is: {{ username }}</h2> <h2 v-if="email !== ''">Your email is: {{ email }}</h2> </div> </div> </template> <script> import axios from 'axios' export default { data () { return { username: '', email: '' }; }, methods: { getInfo () { axios.get('http://localhost:8088/api/information') .then(function(response) { this.username = response.data['username']; this.email = response.data['email']; console.log(response); }).catch(function(error) { console.log(error); }); } } } </script>
一開始我是這麼寫的,乍一看沒什麼問題,但是 JavaScript 就一直報錯:
typeError: Cannot set property 'username' of undefined
搞了很久都沒有解決,直到看到這篇文章
,才明白原來是this
作用域的問題(JavaScript 的this
是真的複雜啊!!!)。改成下面這樣就沒問題了:
axios.get('http://localhost:8088/api/information') .then((response) => { this.username = response.data['username']; this.email = response.data['email']; console.log(response); }).catch((error) => { console.log(error); });
後來 Stack Overflow 上有人說不用箭頭函式也行,只需提前把指向 Vue 例項的this
儲存在一個變數就行了:
var vue = this; axios.get('http://localhost:8088/api/information') .then(function (response) { vue.username = response.data['username']; vue.email = response.data['email']; console.log(response); }).catch((error) => { console.log(error); });
經實踐,這樣也是可以的。