Spring boot整合shiro+jwt實(shí)現(xiàn)前后端分離
本文實(shí)例為大家分享了Spring boot整合shiro+jwt實(shí)現(xiàn)前后端分離的具體代碼,供大家參考,具體內(nèi)容如下
這里內(nèi)容很少很多都為貼的代碼,具體內(nèi)容我經(jīng)過了看源碼和帖子加了注釋。帖子就沒用太多的內(nèi)容
先下載shiro和jwt的jar包
<!-- shiro包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency> <!--JWT依賴--> <!--JWT--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> <!--JJWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
創(chuàng)建shiro的自定義的Realm
代碼如下:
package com.serverprovider.config.shiro.userRealm;
import com.spring.common.auto.autoUser.AutoUserModel;
import com.spring.common.auto.autoUser.extend.AutoModelExtend;
import com.serverprovider.config.shiro.jwt.JWTCredentialsMatcher;
import com.serverprovider.config.shiro.jwt.JwtToken;
import com.serverprovider.service.loginService.LoginServiceImpl;
import com.util.Redis.RedisUtil;
import com.util.ReturnUtil.SecretKey;
import com.util.encryption.JWTDecodeUtil;
import io.jsonwebtoken.Claims;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class UserRealm extends AuthorizingRealm {
private Logger logger = Logger.getLogger(UserRealm.class);
@Autowired private LoginServiceImpl loginService;
public UserRealm(){
//這里使用我們自定義的Matcher驗(yàn)證接口
this.setCredentialsMatcher(new JWTCredentialsMatcher());
}
/**
* 必須重寫此方法,不然Shiro會(huì)報(bào)錯(cuò)
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* shiro 身份驗(yàn)證
* @param token
* @return boolean
* @throws AuthenticationException 拋出的異常將有統(tǒng)一的異常處理返回給前端
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)throws AuthenticationException {
/**
* AuthenticationToken
* JwtToken重寫了AuthenticationToken接口 并創(chuàng)建了一個(gè)接口token的變量
* 因?yàn)樵趂ilter我們將token存入了JwtToken的token變量中
* 所以這里直接getToken()就可以獲取前端傳遞的token值
*/
String JWTtoken = ((JwtToken) token).getToken();
/**
* Claims對(duì)象它最終是一個(gè)JSON格式的對(duì)象,任何值都可以添加到其中
* token解密 轉(zhuǎn)換成Claims對(duì)象
*/
Claims claims = JWTDecodeUtil.parseJWT(JWTtoken, SecretKey.JWTKey);
/**
* 根據(jù)JwtUtil加密方法加入的參數(shù)獲取數(shù)據(jù)
* 查詢數(shù)據(jù)庫獲得對(duì)象
* 如為空:拋出異常
* 如驗(yàn)證失敗拋出 AuthorizationException
*/
String username = claims.getSubject();
String password = (String) claims.get("password");
AutoModelExtend principal = loginService.selectLoginModel(username,password);
return new SimpleAuthenticationInfo(principal, JWTtoken,"userRealm");
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = null;
/**
* PrincipalCollection對(duì)象
* 文檔里面描述:返回從指定的Realm 僅作為Collection 返回的單個(gè)Subject的對(duì)象,如果沒有來自該領(lǐng)域的任何對(duì)象,則返回空的Collection。
* 在登錄接口放入權(quán)限注解返回的錯(cuò)誤信息:Subject.login(AuthenticationToken)或SecurityManager啟用'Remember Me'功能后成功自動(dòng)獲取這些標(biāo)識(shí)主體
* 當(dāng)調(diào)用Subject.login()方法成功后 PrincipalCollection會(huì)自動(dòng)獲得該對(duì)象 如沒有認(rèn)證過或認(rèn)證失敗則返回空的Collection并拋出異常
* getPrimaryPrincipal():返回在應(yīng)用程序范圍內(nèi)使用的主要對(duì)象,以唯一標(biāo)識(shí)擁有帳戶。
*/
Object principal = principals.getPrimaryPrincipal();
/**
* 得到身份對(duì)象
* 查詢?cè)撚脩舻臋?quán)限信息
*/
AutoUserModel user = (AutoUserModel) principal;
List<String> roleModels = loginService.selectRoleDetails(user.getId());
try {
/**
* 創(chuàng)建一個(gè)Set,來放置用戶擁有的權(quán)限
* 創(chuàng)建 SimpleAuthorizationInfo, 并將辦好權(quán)限列表的Set放入.
*/
Set<String> rolesSet = new HashSet();
for (String role : roleModels) {
rolesSet.add(role);
}
info = new SimpleAuthorizationInfo();
info.setStringPermissions(rolesSet); // 放入權(quán)限信息
}catch (Exception e){
throw new AuthenticationException("授權(quán)失敗!");
}
return info;
}
}
這個(gè)授權(quán)方法遇到的坑比較少,就是在最終驗(yàn)證的時(shí)候網(wǎng)上很照抄過來的帖子一點(diǎn)都沒有驗(yàn)證就粘貼賦值,在這里嚴(yán)重吐槽。
在使用jwt最為token而取消shiro傳統(tǒng)的session時(shí)候,我們的需要重寫shiro的驗(yàn)證接口 CredentialsMatcher,在 自定義的realm
中我們加入我們重寫的驗(yàn)證方法,在調(diào)用SimpleAuthenticationInfo()方法進(jìn)行驗(yàn)證的時(shí)候,shiro就會(huì)使用重寫的驗(yàn)證接口。
此處為大坑。
貼上代碼如下:
import com.spring.common.auto.autoUser.extend.AutoModelExtend;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
/**
* CredentialsMatcher
* 接口由類實(shí)現(xiàn),該類可以確定是否提供了AuthenticationToken憑證與系統(tǒng)中存儲(chǔ)的相應(yīng)帳戶的憑證相匹配。
* Shiro 加密匹配 重寫匹配方法CredentialsMatcher 使用JWTUtil 匹配方式
*/
public class JWTCredentialsMatcher implements CredentialsMatcher {
private Logger logger = Logger.getLogger(JWTCredentialsMatcher.class);
/**
* Matcher中直接調(diào)用工具包中的verify方法即可
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
String token = (String) ((JwtToken)authenticationToken).getToken();
AutoModelExtend user = (AutoModelExtend)authenticationInfo.getPrincipals().getPrimaryPrincipal();
//得到DefaultJwtParser
Boolean verify = JwtUtil.isVerify(token, user);
logger.info("JWT密碼效驗(yàn)結(jié)果="+verify);
return verify;
}
}
shiro的配置項(xiàng) ShiroConfiguration代碼如下:
import com.serverprovider.config.shiro.shiroSysFile.JwtFilter;
import com.serverprovider.config.shiro.userRealm.UserRealm;
import org.apache.log4j.Logger;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.*;
@Configuration
public class ShiroConfiguration {
private Logger logger = Logger.getLogger(ShiroConfiguration.class);
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//攔截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不會(huì)被攔截的鏈接 順序判斷
filterChainDefinitionMap.put("/login/**", "anon");
// 添加自己的過濾器并且取名為jwt
Map<String, Filter> filterMap = new HashMap<String, Filter>();
filterMap.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
//<!-- 過濾鏈定義,從上向下順序執(zhí)行,一般將/**放在最為下邊
filterChainDefinitionMap.put("/**", "jwt");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 禁用session, 不保存用戶登錄狀態(tài)。保證每次請(qǐng)求都重新認(rèn)證。
* 需要注意的是,如果用戶代碼里調(diào)用Subject.getSession()還是可以用session
*/
@Bean
protected SessionStorageEvaluator sessionStorageEvaluator(){
DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
sessionStorageEvaluator.setSessionStorageEnabled(false);
return sessionStorageEvaluator;
}
@Bean("securityManager")
public SecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
/*
* 關(guān)閉shiro自帶的session,詳情見文檔
* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
/**
* 創(chuàng)建自定義的UserRealm @bean
*/
@Bean("userRealm")
public UserRealm shiroRealm() {
UserRealm shiroRealm = new UserRealm();
return shiroRealm;
}
//自動(dòng)創(chuàng)建代理,沒有這個(gè)鑒權(quán)可能會(huì)出錯(cuò)
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setProxyTargetClass(true);
return autoProxyCreator;
}
/**
* 開啟shiro aop注解支持.
* 使用代理方式;所以需要開啟代碼支持;
*
* @param
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
到這里shiro配置就全部完成了
下面開始配置jwt:
首先我們需要重寫 AuthenticationToken接口 此接口的作用
負(fù)責(zé)把shiro中username,password生成用于驗(yàn)證的token的封裝類
所有我們需要去實(shí)現(xiàn)這個(gè)接口,封裝我們自己生成的JWT生成的token
import org.apache.shiro.authc.AuthenticationToken;
/**
* AuthenticationToken: shiro中負(fù)責(zé)把username,password生成用于驗(yàn)證的token的封裝類
* 我們需要自定義一個(gè)對(duì)象用來包裝token。
*/
public class JwtToken implements AuthenticationToken {
private String token;
public JwtToken(String token) {
this.token = token;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return null;
}
@Override
public Object getCredentials() {
return null;
}
}
因?yàn)槲覀兪乔昂蠖朔蛛x項(xiàng)目所有我們不存在session驗(yàn)證之類的 所有每次請(qǐng)求都需要攜帶token,所以每次請(qǐng)求我們都需要去驗(yàn)證token的真實(shí)性,所有我們需要去實(shí)現(xiàn) BasicHttpAuthenticationFilter過濾器
BasicHttpAuthenticationFilter繼承 AuthenticatingFilter 過濾器其能夠自動(dòng)地進(jìn)行基于所述傳入請(qǐng)求的認(rèn)證嘗試。此實(shí)現(xiàn)是每個(gè)基本HTTP身份驗(yàn)證規(guī)范的Java實(shí)現(xiàn) , 通過此過濾器得到HTTP請(qǐng)求資源獲取Authorization傳遞過來的token參數(shù) 獲取subject對(duì)象進(jìn)行身份驗(yàn)證
代碼如下:
import com.alibaba.fastjson.JSONObject;
import com.serverprovider.config.shiro.jwt.JwtToken;
import com.util.Util.utilTime;
import org.apache.log4j.Logger;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
/**
*
* BasicHttpAuthenticationFilter繼承 AuthenticatingFilter 過濾器
* 其能夠自動(dòng)地進(jìn)行基于所述傳入請(qǐng)求的認(rèn)證嘗試。
* BasicHttpAuthenticationFilter 基本訪問認(rèn)證過濾器
* 此實(shí)現(xiàn)是每個(gè)基本HTTP身份驗(yàn)證規(guī)范的Java實(shí)現(xiàn)
* 通過此過濾器得到HTTP請(qǐng)求資源獲取Authorization傳遞過來的token參數(shù)
* 獲取subject對(duì)象進(jìn)行身份驗(yàn)證
*
*
*/
public class JwtFilter extends BasicHttpAuthenticationFilter {
Logger logger = Logger.getLogger(JwtFilter.class);
/**
* 應(yīng)用的HTTP方法列表配置基本身份驗(yàn)證篩選器。
* 獲取 request 請(qǐng)求 拒絕攔截登錄請(qǐng)求
* 執(zhí)行登錄認(rèn)證方法
*
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String requestURI = httpServletRequest.getRequestURI();
if (requestURI.equals("/user/login/verifyUser") || requestURI.equals("/user/register")) {
return true;
} else {
try {
executeLogin(request, response);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
/**
* Authorization攜帶的參數(shù)為token
* JwtToken實(shí)現(xiàn)了AuthenticationToken接口封裝了token參數(shù)
* 通過getSubject方法獲取 subject對(duì)象
* login()發(fā)送身份驗(yàn)證
*
* 為什么需要在Filter中調(diào)用login,不能在controller中調(diào)用login?
* 由于Shiro默認(rèn)的驗(yàn)證方式是基于session的,在基于token驗(yàn)證的方式中,不能依賴session做為登錄的判斷依據(jù).
* @param request
* @param response
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
try{
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("Authorization");
JwtToken jwtToken = new JwtToken(token);
// 提交給realm進(jìn)行登入,如果錯(cuò)誤他會(huì)拋出異常并被捕獲
Subject subject = getSubject(request, response);
subject.login(jwtToken);
logger.info("JWT驗(yàn)證用戶信息成功");
// 如果沒有拋出異常則代表登入成功,返回true
return true;
}catch (Exception e){
/* *
* 這個(gè)問題糾結(jié)了好久
* 原生的shiro驗(yàn)證失敗會(huì)進(jìn)入全局異常 但是 和JWT結(jié)合以后卻不進(jìn)入了 之前一直想不通
* 原因是 JWT直接在過濾器里驗(yàn)證 驗(yàn)證成功與否 都是直接返回到過濾器中 成功在進(jìn)入controller
* 失敗直接返回進(jìn)入springboot自定義異常處理頁面
*/
JSONObject responseJSONObject = new JSONObject();
responseJSONObject.put("result","401");
responseJSONObject.put("resultCode","token無效,請(qǐng)重新獲取。");
responseJSONObject.put("resultData","null");
responseJSONObject.put("resultTime", utilTime.StringDate());
PrintWriter out = null;
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
logger.info("返回是");
logger.info(responseJSONObject.toString());
out = httpServletResponse.getWriter();
out.append(responseJSONObject.toString());
}
return false;
}
/**
* 對(duì)跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域時(shí)會(huì)首先發(fā)送一個(gè)option請(qǐng)求,這里我們給option請(qǐng)求直接返回正常狀態(tài)
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
貼上 加密解密校驗(yàn)的工具類
import com.spring.common.auto.autoUser.extend.AutoModelExtend;
import com.util.ReturnUtil.SecretKey;
import io.jsonwebtoken.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;;
/* @author hw
* @create 2019-04-16 10.12
* @desc JWT工具類
**/
public class JwtUtil {
/**
* 用戶登錄成功后生成Jwt
* 使用Hs256算法 私匙使用用戶密碼
*
* @param ttlMillis jwt過期時(shí)間
* @param user 登錄成功的user對(duì)象
* @return
*/
public static String createJWT(long ttlMillis, AutoModelExtend user) {
//指定簽名的時(shí)候使用的簽名算法,也就是header那部分,jjwt已經(jīng)將這部分內(nèi)容封裝好了。
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成JWT的時(shí)間
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//創(chuàng)建payload的私有聲明(根據(jù)特定的業(yè)務(wù)需要添加,如果要拿這個(gè)做驗(yàn)證,一般是需要和jwt的接收方提前溝通好驗(yàn)證方式的)
Map<String, Object> map = new HashMap<String, Object>();
map.put("id", user.getId());
map.put("username", user.getAuto_username());
map.put("password", user.getAuto_password());
//生成簽名的時(shí)候使用的秘鑰secret,這個(gè)方法本地封裝了的,一般可以從本地配置文件中讀取,
// 切記這個(gè)秘鑰不能外露哦。它就是你服務(wù)端的私鑰,在任何場景都不應(yīng)該流露出去。一旦客戶端得知這個(gè)secret, 那就意味著客戶端是可以自我簽發(fā)jwt了。
String key = SecretKey.JWTKey;
//生成簽發(fā)人
String subject = user.getAuto_username();
//下面就是在為payload添加各種標(biāo)準(zhǔn)聲明和私有聲明了
//這里其實(shí)就是new一個(gè)JwtBuilder,設(shè)置jwt的body
JwtBuilder builder = Jwts.builder()
//如果有私有聲明,一定要先設(shè)置這個(gè)自己創(chuàng)建的私有的聲明,這個(gè)是給builder的claim賦值,一旦寫在標(biāo)準(zhǔn)的聲明賦值之后,就是覆蓋了那些標(biāo)準(zhǔn)的聲明的
.setClaims(map)
//設(shè)置jti(JWT ID):是JWT的唯一標(biāo)識(shí),根據(jù)業(yè)務(wù)需要,這個(gè)可以設(shè)置為一個(gè)不重復(fù)的值,主要用來作為一次性token,從而回避重放攻擊。
.setId(UUID.randomUUID().toString())
//iat: jwt的簽發(fā)時(shí)間
.setIssuedAt(now)
//代表這個(gè)JWT的主體,即它的所有人,這個(gè)是一個(gè)json格式的字符串,可以存放什么userid,roldid之類的,作為什么用戶的唯一標(biāo)志。
.setSubject(subject)
//設(shè)置簽名使用的簽名算法和簽名使用的秘鑰
.signWith(signatureAlgorithm, key);
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
//設(shè)置過期時(shí)間
builder.setExpiration(exp);
}
return builder.compact();
}
/**
* 校驗(yàn)token
* 在這里可以使用官方的校驗(yàn),我這里校驗(yàn)的是token中攜帶的密碼于數(shù)據(jù)庫一致的話就校驗(yàn)通過
*
* @param token
* @return
*/
public static Boolean isVerify(String token, AutoModelExtend userModelExtend) {
try {
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//設(shè)置簽名的秘鑰
.setSigningKey(SecretKey.JWTKey)
//設(shè)置需要解析的jwt
.parseClaimsJws(token).getBody();
if (claims.get("password").equals(userModelExtend.getAuto_password())) {
return true;
}
} catch (Exception exception) {
return false;
}
return null;
}
/**
* Token的解密
* @param token 加密后的token
* @param secret 簽名秘鑰,和生成的簽名的秘鑰一模一樣
* @return
*/
public static Claims parseJWT(String token, String secret) {
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//設(shè)置簽名的秘鑰
.setSigningKey(secret)
//設(shè)置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
}
到這里shiro jwt整合就完成了。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
解決Beanutils.copyproperties實(shí)體類對(duì)象不一致的問題
這篇文章主要介紹了解決Beanutils.copyproperties實(shí)體類對(duì)象不一致的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
詳解Intellij IDEA的Facets和Artifacts
這篇文章主要介紹了Intellij IDEA的Facets和Artifacts的相關(guān)知識(shí),本文通過實(shí)例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-09-09
SpringMVC Restful api接口實(shí)現(xiàn)的代碼
本篇文章主要介紹了SpringMVC Restful api接口實(shí)現(xiàn)的代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09

