SpringBoot集成JWT實(shí)現(xiàn)Token登錄驗(yàn)證的示例代碼
一, JWT是什么?
在JWT官網(wǎng)中可以明確看到關(guān)于它的定義

JSON Web令牌(JWT)是一種開放的標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊而獨(dú)立的方式在各方之間安全地傳輸信息為JSON對象。該信息可以被驗(yàn)證和信任,因?yàn)樗菙?shù)字簽名的。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公開/私有密鑰類型簽名。 雖然JWT可以被加密以提供各方之間的保密,但我們將重點(diǎn)關(guān)注簽名的令牌。被簽名的令牌可以驗(yàn)證包含在其中的聲明的完整性,而加密的令牌對其他各方隱藏這些聲明。當(dāng)使用公鑰/私鑰對簽名時(shí),簽名還證明只有持有私鑰的一方才是簽名方。
1.1 JWT主要使用場景
授權(quán)(Authorization):這是使用JWT最常見的場景。一旦用戶登錄,每個(gè)后續(xù)請求都將包括JWT,允許用戶訪問該令牌允許的路由、服務(wù)和資源。
單點(diǎn)登錄(Single Sign On ):單點(diǎn)登錄是當(dāng)今廣泛使用的JWT特性,因?yàn)樗男∫?guī)模和易于跨不同領(lǐng)域使用的能力。
信息交換(lnformation Exchange):信息交換在通信的雙方之間使用JWT對數(shù)據(jù)進(jìn)行編碼是一種非常安全的方式,由于它的信息是經(jīng)過簽名的,可以確保發(fā)送者發(fā)送的信息是沒有經(jīng)過偽造的。
傳輸信息(transmitting information):在各方之間傳輸信息。由于JWT可以簽名--例如,使用公共/私鑰對--您可以確保發(fā)件人是他們所說的發(fā)送者。此外,由于簽名是使用頭和有效載荷計(jì)算的,您還可以驗(yàn)證內(nèi)容沒有被篡改。
1.2 JWT請求流程

- 用戶使用賬號和密碼發(fā)出post請求;
- 服務(wù)器使用私鑰創(chuàng)建一個(gè)jwt;
- 服務(wù)器返回這個(gè)jwt給瀏覽器;
- 瀏覽器將該jwt串在請求頭中像服務(wù)器發(fā)送請求;
- 服務(wù)器驗(yàn)證該jwt;
- 返回響應(yīng)的資源給瀏覽器。
1.3 JWT結(jié)構(gòu)
JWT是由三段信息構(gòu)成的,將這三段信息文本用.連接一起就構(gòu)成了JWT字符串。
就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT包含了三部分:
Header 頭部(標(biāo)題包含了令牌的元數(shù)據(jù),并且包含簽名和/或加密算法的類型)
Payload 負(fù)載 (類似于飛機(jī)上承載的物品)
Signature 簽名/簽證
Header
JWT的頭部承載兩部分信息:token類型和采用的加密算法。
{
"alg": "HS256",
"typ": "JWT"
} 聲明類型:這里是jwt
聲明加密的算法:通常直接使用 HMAC SHA256
加密算法是單向函數(shù)散列算法,常見的有MD5、SHA、HAMC。
MD5(message-digest algorithm 5) (信息-摘要算法)縮寫,廣泛用于加密和解密技術(shù),常用于文件校驗(yàn)。校驗(yàn)?不管文件多大,經(jīng)過MD5后都能生成唯一的MD5值
SHA (Secure Hash Algorithm,安全散列算法),數(shù)字簽名等密碼學(xué)應(yīng)用中重要的工具,安全性高于MD5
HMAC (Hash Message Authentication Code),散列消息鑒別碼,基于密鑰的Hash算法的認(rèn)證協(xié)議。用公開函數(shù)和密鑰產(chǎn)生一個(gè)固定長度的值作為認(rèn)證標(biāo)識,用這個(gè)標(biāo)識鑒別消息的完整性。常用于接口簽名驗(yàn)證
Payload
載荷就是存放有效信息的地方。
有效信息包含三個(gè)部分:
標(biāo)準(zhǔn)中注冊的聲明
公共的聲明
私有的聲明
標(biāo)準(zhǔn)中注冊的聲明 (建議但不強(qiáng)制使用) :
iss: jwt簽發(fā)者
sub: 面向的用戶(jwt所面向的用戶)
aud: 接收jwt的一方
exp: 過期時(shí)間戳(jwt的過期時(shí)間,這個(gè)過期時(shí)間必須要大于簽發(fā)時(shí)間)
nbf: 定義在什么時(shí)間之前,該jwt都是不可用的.
iat: jwt的簽發(fā)時(shí)間
jti: jwt的唯一身份標(biāo)識,主要用來作為一次性token,從而回避重放攻擊。
公共的聲明:
公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息.但不建議添加敏感信息,因?yàn)樵摬糠衷诳蛻舳丝山饷?
私有的聲明:
私有聲明是提供者和消費(fèi)者所共同定義的聲明,一般不建議存放敏感信息,因?yàn)閎ase64是對稱解密的,意味著該部分信息可以歸類為明文信息。
Signature
jwt的第三部分是一個(gè)簽證信息
這個(gè)部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進(jìn)行加鹽secret組合加密,然后就構(gòu)成了jwt的第三部分。最主要的目的:服務(wù)器應(yīng)用在接受到JWT后,會(huì)首先對頭部和載荷的內(nèi)容用同一算法再次簽名,如果服務(wù)器應(yīng)用對頭部和載荷再次以同樣方法簽名之后發(fā)現(xiàn),自己計(jì)算出來的簽名和接受到的簽名不一樣,那么就說明這個(gè)Token的內(nèi)容被別人動(dòng)過的,我們應(yīng)該拒絕這個(gè)Token,返回一個(gè)HTTP 401 Unauthorized響應(yīng)。
密鑰secret是保存在服務(wù)端的,服務(wù)端會(huì)根據(jù)這個(gè)密鑰進(jìn)行生成token和進(jìn)行驗(yàn)證,所以需要保護(hù)好。
這些信息在官網(wǎng)上也有相關(guān)信息的說明:

二,SpringBoot集成JWT具體實(shí)現(xiàn)過程
這里由于只涉及對驗(yàn)證功能的實(shí)現(xiàn),因此其他數(shù)據(jù)庫,業(yè)務(wù)類編寫一概省略,只對相關(guān)步驟做說明。
2.1添加相關(guān)依賴
既然要使用JWT我們肯定需要引入其依賴
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>2.2自定義跳出攔截器的注解
方便我們后續(xù)不用對在配置攔截器時(shí)排除每一個(gè)接口,只用自定義注解去標(biāo)記即可。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author young
* @date 2022/11/24 14:53
* @description: 自定義通過token注解,如果不加該注解直接攔截
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}@Retention注解說明
RetentionPolicy.SOURCE:這種類型的Annotations只在源代碼級別保留,編譯時(shí)就會(huì)被忽略,在class字節(jié)碼文件中不包含。
RetentionPolicy.CLASS:這種類型的Annotations編譯時(shí)被保留,默認(rèn)的保留策略,在class文件中存在,但JVM將會(huì)忽略,運(yùn)行時(shí)無法獲得。
RetentionPolicy.RUNTIME:這種類型的Annotations將被JVM保留,所以他們能在運(yùn)行時(shí)被JVM或其他使用反射機(jī)制的代碼所讀取和使用。
@Document:說明該注解將被包含在javadoc中
@Inherited:說明子類可以繼承父類中的該注解
2.3自定義全局統(tǒng)一返回值方法,異常類及相關(guān)枚舉
定義全局枚舉類
package com.yy.enums;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import java.util.UUID;
/**
* @author young
* @date 2022/8/19 21:36
* @description: 響應(yīng)結(jié)果枚舉
*/
@AllArgsConstructor
@Getter
public enum ResponseEnum {
/**響應(yīng)成功**/
SUCCESS(200, "操作成功"),
/**操作失敗*/
FAIL(201,"獲取數(shù)據(jù)失敗"),
NO_TOKEN(400,"無token,請重新登錄"),
TOKEN_EX(401,"token驗(yàn)證失敗,請重新登錄"),
USER_EX(402,"用戶不存在,請重新登錄"),
/**錯(cuò)誤請求**/
ERROR(400,"錯(cuò)誤請求");
/**響應(yīng)碼**/
private final Integer code;
/** 結(jié)果 **/
private final String resultMessage;
public static ResponseEnum getResultCode(Integer code){
for (ResponseEnum value : ResponseEnum.values()) {
if (code.equals(value.getCode())){
return value;
}
}
return ResponseEnum.ERROR;
}
/*
簡單測試一下
*/
public static void main(String[] args) {
ResponseEnum resultCode = ResponseEnum.getResultCode(100);
System.out.println(resultCode);
}
}定義全局異常類
package com.yy.util;
import com.yy.Enums.ResultEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author young
* @date 2022/9/25 19:15
* @description: 自定義運(yùn)行時(shí)異常
*/
@Data
@ApiModel(value = "自定義全局異常類")
public class MyException extends RuntimeException{
@ApiModelProperty(value = "異常狀態(tài)碼")
private final Integer code;
/**
* 通過狀態(tài)碼和異常信息創(chuàng)建異常對象
* @param code
* @param message
*/
public MyException(Integer code,String message) {
super(message);
this.code = code;
}
/**
* 接受枚舉類型對象
* @param resultEnum
*/
public MyException(ResponseEnum responseEnum){
super(responseEnum.getMessage());
this.code = responseEnum.getCode();
}
}package com.yy.Config;
import com.yy.utils.MyException;
import com.yy.utils.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @author young
* @date 2022/9/12 15:43
* @description: 自定義異常配置
*/
@RestControllerAdvice
public class GlobalExceptionConfig{
@ExceptionHandler(MyException.class)
public R<MyException> handle(MyException e){
e.printStackTrace();
return R.exception(e.getCode(),e.getMessage());
}
}定義統(tǒng)一返回結(jié)果類
package com.yy.utils;
import com.yy.enums.ResponseEnum;
import lombok.Data;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* @author young
* @date 2022/8/19 21:52
* @description: 統(tǒng)一返回結(jié)果的類
*/
@Data
public class R<T> implements Serializable {
private static final long serialVersionUID = 56665257248936049L;
/**響應(yīng)碼**/
private Integer code;
/**返回消息**/
private String message;
/**返回?cái)?shù)據(jù)**/
private T data;
private R(){}
/**
* 操作成功ok方法
*/
public static <T> R<T> ok(T data) {
R<T> response = new R<>();
response.setCode(ResponseEnum.SUCCESS.getCode()); response.setMessage(ResponseEnum.SUCCESS.getResultMessage());
response.setData(data);
return response;
}
/**
* 編譯失敗方法
*/
public static <T> R<T> buildFailure(Integer errCode, String errMessage){
R<T> response = new R<>();
response.setCode(errCode);
response.setMessage(errMessage);
return response;
}
public static <T> R<T> exception(Integer errCode, String errMessage){
R<T> response = new R<>();
response.setCode(errCode);
response.setMessage(errMessage);
return response;
}
}2.4編寫JWT工具類,用于生成Token令牌
package com.yy.utils;
import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
/**
* @author young
* @date 2022/9/12 14:46
* @description: 整合JWT生成token
*/
public class JwtTokenUtils {
private JwtTokenUtils() {
throw new IllegalStateException("Utility class");
}
/**
* 生成token
* @param userId
* @param sign
* @return
*/
public static String getToken(String userId,String sign){
return JWT.create()
//簽收者
.withAudience(userId)
//主題
.withSubject("token")
//2小時(shí)候token過期
.withExpiresAt(DateUtil.offsetHour(new Date(),2))
//以password作為token的密鑰
.sign(Algorithm.HMAC256(sign));
}
}Algorithm.HMAC256():使用HS256生成token,密鑰則是用戶的密碼,唯一密鑰的話可以保存在服務(wù)端。
withAudience():存入需要保存在token的信息,這里我們把用戶ID存入token中
2.5編寫攔截器并注入容器
package com.yy.Config.inteceptor;
import cn.hutool.core.text.CharSequenceUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.yy.enums.ResponseEnum;
import com.yy.admin.pojo.Admin;
import com.yy.admin.service.Impl.AdminServiceImpl;
import com.yy.utils.MyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @author young
* @date 2022/9/12 15:37
* @description: 獲取token并驗(yàn)證
*/
@Component
public class MyJwtInterceptor implements HandlerInterceptor {
@Autowired
private AdminServiceImpl adminService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//檢查是否通過有PassToken注解
if (method.isAnnotationPresent(PassToken.class)) {
//如果有則跳過認(rèn)證檢查
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
//否則進(jìn)行token檢查
if (CharSequenceUtil.isBlank(token)) {
throw new MyException(ResponseEnum.TOKEN_EX.getCode(), ResponseEnum.TOKEN_EX.getResultMessage());
}
//獲取token中的用戶id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new MyException(ResponseEnum.TOKEN_EX.getCode(), ResponseEnum.TOKEN_EX.getResultMessage());
}
//根據(jù)token中的userId查詢數(shù)據(jù)庫
Admin user = adminService.getById(userId);
if (user == null) {
throw new MyException(ResponseEnum.USER_EX.getCode(), ResponseEnum.USER_EX.getResultMessage());
}
//驗(yàn)證token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPwd())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new MyException(406, "權(quán)限驗(yàn)證失?。?);
}
return true;
}
}這里需要說明一下實(shí)現(xiàn)攔截器的方法,我們只需要實(shí)現(xiàn)HandlerInterceptor接口即可,它主要定義了三個(gè)方法:
boolean preHandle ():
預(yù)處理回調(diào)方法,實(shí)現(xiàn)處理器的預(yù)處理,第三個(gè)參數(shù)為響應(yīng)的處理器,自定義Controller,返回值為true表示繼續(xù)流程(如調(diào)用下一個(gè)攔截器或處理器)或者接著執(zhí)行postHandle()和afterCompletion();false表示流程中斷,不會(huì)繼續(xù)調(diào)用其他的攔截器或處理器,中斷執(zhí)行。
void postHandle():
后處理回調(diào)方法,實(shí)現(xiàn)處理器的后處理(DispatcherServlet進(jìn)行視圖返回渲染之前進(jìn)行調(diào)用),此時(shí)我們可以通過modelAndView(模型和視圖對象)對模型數(shù)據(jù)進(jìn)行處理或?qū)σ晥D進(jìn)行處理,modelAndView也可能為null。
void afterCompletion():
整個(gè)請求處理完畢回調(diào)方法,該方法也是需要當(dāng)前對應(yīng)的Interceptor的preHandle()的返回值為true時(shí)才會(huì)執(zhí)行,也就是在DispatcherServlet渲染了對應(yīng)的視圖之后執(zhí)行。用于進(jìn)行資源清理。
整個(gè)請求處理完畢回調(diào)方法。如性能監(jiān)控中我們可以在此記錄結(jié)束時(shí)間并輸出消耗時(shí)間,還可以進(jìn)行一些資源清理,類似于try-catch-finally中的finally,但僅調(diào)用處理器執(zhí)行鏈中。
這里我們主要需要調(diào)用預(yù)處理回調(diào)方法即可,如果有其他業(yè)務(wù)需求,也可自行更改。
主要流程:
從 http 請求頭中取出 token,
判斷是否映射到方法
檢查是否有passtoken注釋,有則跳過認(rèn)證
檢查有沒有需要用戶登錄的注解,有則需要取出并驗(yàn)證
認(rèn)證通過則可以訪問,不通過會(huì)報(bào)相關(guān)錯(cuò)誤信息
然后通過配置類將我們自定義的攔截類注入到spring容器中,并進(jìn)行攔截配置。
package com.yy.Config.inteceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author young
* @date 2022/9/12 15:36
* @description: JWT攔截配置
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
//攔截所有請求,通過判斷token來決定是否需要登陸
.addPathPatterns("/**");
}
@Bean
public MyJwtInterceptor jwtInterceptor(){
return new MyJwtInterceptor();
}
}
至此,我們對于JWT在SpringBoot中的基本配置就算完成了。我們只需在controller層在自己想要放行的api接口上添加我們自定義的放行注解,即可實(shí)現(xiàn)對api接口的放行,其他接口均要進(jìn)行Token令牌的驗(yàn)證判斷。如果沒有Token則返回自定義的異常信息。
三,測試
3.1放行一般類接口
這里我們先只對一個(gè)業(yè)務(wù)接口進(jìn)行放行。
/**
* 查找指定id信息
*
* @param id
* @return
*/
@GetMapping("/getOne/{id}")
@CostTime
@PassToken
public R<TestUser> selectOne(@PathVariable Integer id) {
TestUser user = testUserService.getById(id);
return R.ok(user);
}進(jìn)行接口測試后發(fā)現(xiàn),該接口獲取數(shù)據(jù)正常。

其他沒有加@PassToken的接口由于沒有token進(jìn)行驗(yàn)證,均會(huì)被攔截器攔截,并返回我們預(yù)期的異常信息"token驗(yàn)證失敗,請重新登錄"

因此,同理我們只需要在登陸注冊或其他不需要token驗(yàn)證的接口上添加自定義注解即可實(shí)現(xiàn)攔截。
為了達(dá)到業(yè)務(wù)需求,我們需要在用戶登錄成功后獲取到token,然后將token信息存放在每次的接口請求頭(headers)上,這樣就能實(shí)現(xiàn)對用戶接口信息基本保護(hù)了。
3.2放行登錄接口
在業(yè)務(wù)層處理token,將生成的token信息帶到用戶實(shí)體類中,這樣登錄獲取用戶信息時(shí)就能讀取到token信息了
package com.yy.admin.service.Impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yy.admin.pojo.Admin;
import com.yy.admin.service.AdminService;
import com.yy.admin.dao.AdminDao;
import com.yy.utils.JwtTokenUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Optional;
/**
* @author young
* @description 針對表【admin】的數(shù)據(jù)庫操作Service實(shí)現(xiàn)
* @createDate 2022-09-05 13:41:54
*/
@Service
@Slf4j
public class AdminServiceImpl extends ServiceImpl<AdminDao, Admin>
implements AdminService{
@Resource
private AdminDao adminDao;
@Override
public Admin getMsg(String username, String pwd){
Admin admin = adminDao.selectByUsernameAndPwd(username, pwd);
Optional.ofNullable(admin).ifPresent(u->{
//添加token信息設(shè)置到用戶實(shí)體上
String token = JwtTokenUtils.getToken(String.valueOf(admin.getId()),pwd);
log.info("token的值為:{}",token);
admin.setToken(token);
});
return admin;
}
}
編寫用戶登錄的業(yè)務(wù)接口并放行Token
@PostMapping("/login")
@PassToken
public Object loginStatus(@RequestParam("username") String username, @RequestParam("password") String password){
JSONObject object = new JSONObject();
Admin admin = adminService.getMsg(username, password);
if (admin!=null){
object.put("code",1);
object.put("msg","登陸成功");
object.put("success",true);
object.put("type","success");
object.put("userMsg",admin);
}else {
object.put("code",0);
object.put("success",false);
object.put("msg","用戶名或密碼錯(cuò)誤");
object.put("type","error");
}
return object;
}進(jìn)行接口測試

這樣就可以看到生成的Token信息了,然后我們將token信息設(shè)置在請求頭上對其他接口進(jìn)行測試。

此時(shí)就能看到,接口請求成功。
注意:這里的參數(shù)名token對應(yīng)攔截器配置String token=request.getHeader("token")中的getHeader中設(shè)置的的參數(shù)名。
到此這篇關(guān)于SpringBoot集成JWT實(shí)現(xiàn)Token登錄驗(yàn)證的示例代碼的文章就介紹到這了,更多相關(guān)SpringBoot JWT Token登錄驗(yàn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis-Plus的應(yīng)用場景描述及注入SQL原理分析
MyBatis-Plus是一個(gè) MyBatis 的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)、提高效率而生,本文重點(diǎn)給大家介紹Mybatis-Plus的應(yīng)用場景及注入SQL原理分析,感興趣的朋友跟隨小編一起學(xué)習(xí)吧2021-05-05
如何使用Java模擬退火算法優(yōu)化Hash函數(shù)
為了解決局部最優(yōu)解問題,1983年,Kirkpatrick等提出了模擬退火算法(SA)能有效的解決局部最優(yōu)解問題。模擬退火算法包含兩個(gè)部分即Metropolis算法和退火過程。Metropolis算法就是如何在局部最優(yōu)解的情況下讓其跳出來,是退火的基礎(chǔ)2021-06-06
在Spring Boot2中使用CompletableFuture的方法教程
這篇文章主要給大家介紹了關(guān)于在Spring Boot2中使用CompletableFuture的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧2019-01-01
MyBatis #{}和${} |與數(shù)據(jù)庫連接池使用詳解
本文將為大家說說關(guān)于 #{} 和 ${},這個(gè)是 MyBatis 在面試中最常問的面試題,以及數(shù)據(jù)庫連接池相關(guān)的知識,感興趣的朋友跟隨小編一起看看吧2024-01-01
Java8?Stream?collect(Collectors.toMap())的使用
這篇文章主要介紹了Java8?Stream?collect(Collectors.toMap())的使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
SpringBoot DBUnit 單元測試(小結(jié))
這篇文章主要介紹了SpringBoot DBUnit 單元測試(小結(jié)),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09
序列化版本號serialVersionUID的作用_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Java序列化是將一個(gè)對象編碼成一個(gè)字節(jié)流,反序列化將字節(jié)流編碼轉(zhuǎn)換成一個(gè)對象,這篇文章主要介紹了序列化版本號serialVersionUID的作用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05

