springboot集成shiro遭遇自定義filter異常的解決
springboot集成shiro遭遇自定義filter異常
首先簡述springboot使用maven集成shiro
1、用maven添加shiro
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2、配置shiro
import com.yuntu.intelligent.log.service.QueryPermissionService;
import com.yuntu.intelligent.log.service.shiro.authc.AccountSubjectFactory;
import com.yuntu.intelligent.log.service.shiro.filter.AuthenticatedFilter;
import com.yuntu.intelligent.log.service.shiro.filter.QueryLimitFiter;
import com.yuntu.intelligent.log.service.shiro.realm.AccountRealm;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpSession;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* shiro權(quán)限管理的配置
*/
@Configuration
public class ShiroConfig {
@Bean
public AccountSubjectFactory accountSubjectFactory() {
return new AccountSubjectFactory();
}
/**
* 安全管理器
*/
@Bean
public DefaultWebSecurityManager securityManager(CookieRememberMeManager rememberMeManager, CacheManager cacheShiroManager, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(this.shiroAccountRealm());
securityManager.setCacheManager(cacheShiroManager);
securityManager.setRememberMeManager(rememberMeManager);
securityManager.setSessionManager(sessionManager);
securityManager.setSubjectFactory(this.accountSubjectFactory());
return securityManager;
}
/**
* session管理器(單機(jī)環(huán)境)
*/
@Bean
public DefaultWebSessionManager defaultWebSessionManager(CacheManager cacheShiroManager) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setCacheManager(cacheShiroManager);
sessionManager.setSessionValidationInterval(1800 * 1000);
sessionManager.setGlobalSessionTimeout(900 * 1000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
cookie.setName("shiroCookie");
cookie.setHttpOnly(true);
sessionManager.setSessionIdCookie(cookie);
return sessionManager;
}
/**
* 緩存管理器 使用Ehcache實(shí)現(xiàn)
*/
@Bean
public CacheManager getCacheShiroManager() {
return new MemoryConstrainedCacheManager();
}
/**
* 項(xiàng)目自定義的Realm
*/
@Bean
public AccountRealm shiroAccountRealm() {
return new AccountRealm();
}
/**
* rememberMe管理器, cipherKey生成見{@code Base64Test.java}
*/
@Bean
public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
CookieRememberMeManager manager = new CookieRememberMeManager();
manager.setCipherKey(Base64.decode("Z3VucwAAAAAAAAAAAAAAAA=="));
manager.setCookie(rememberMeCookie);
return manager;
}
/**
* 記住密碼Cookie
*/
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天
return simpleCookie;
}
/**
* Shiro的過濾器鏈
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager,CollectionPropertiesConfig collectionPropertiesConfig,QueryPermissionService queryPermissionService) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
/**
* 默認(rèn)的登陸訪問url
*/
shiroFilter.setLoginUrl("/login");
/**
* 登陸成功后跳轉(zhuǎn)的url
*/
shiroFilter.setSuccessUrl("/");
/**
* 沒有權(quán)限跳轉(zhuǎn)的url
*/
shiroFilter.setUnauthorizedUrl("/error/reject.html");
/**
* 覆蓋默認(rèn)的user攔截器(默認(rèn)攔截器解決不了ajax請(qǐng)求 session超時(shí)的問題,若有更好的辦法請(qǐng)及時(shí)反饋?zhàn)髡?
*/
HashMap<String, Filter> myFilters = new HashMap<>();
myFilters.put("query", new QueryLimitFiter(queryPermissionService));
myFilters.put("authc", new AuthenticatedFilter());
shiroFilter.setFilters(myFilters);
/**
* 配置shiro攔截器鏈
*
* anon 不需要認(rèn)證
* authc 需要認(rèn)證
* user 驗(yàn)證通過或RememberMe登錄的都可以
*
* 當(dāng)應(yīng)用開啟了rememberMe時(shí),用戶下次訪問時(shí)可以是一個(gè)user,但不會(huì)是authc,因?yàn)閍uthc是需要重新認(rèn)證的
*
* 順序從上到下,優(yōu)先級(jí)依次降低
*
*/
Map<String, String> hashMap = new LinkedHashMap<>();
hashMap.put("/login", "anon");
hashMap.put("/", "authc");
hashMap.put("/user*", "authc");
hashMap.put("/user/**", "authc");
hashMap.put("/post/**", "authc");
hashMap.put("/admin", "authc,perms[admin]");
hashMap.put("/admin/**", "authc,perms[admin]");
for(String uri:collectionPropertiesConfig.getQueryLogUrls()){
hashMap.put(uri,"query");
}
shiroFilter.setFilterChainDefinitionMap(hashMap);
return shiroFilter;
}
/**
* 在方法中 注入 securityManager,進(jìn)行代理控制
*/
@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean(DefaultWebSecurityManager securityManager) {
MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
bean.setArguments(new Object[]{securityManager});
return bean;
}
/**
* Shiro生命周期處理器:
* 用于在實(shí)現(xiàn)了Initializable接口的Shiro bean初始化時(shí)調(diào)用Initializable接口回調(diào)(例如:UserRealm)
* 在實(shí)現(xiàn)了Destroyable接口的Shiro bean銷毀時(shí)調(diào)用 Destroyable接口回調(diào)(例如:DefaultSecurityManager)
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 啟用shrio授權(quán)注解攔截方式,AOP式方法級(jí)權(quán)限檢查
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
3、實(shí)現(xiàn)自定義的Realm、filter、SubjectFactory等
import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
public class AccountAuthenticationInfo extends SimpleAuthenticationInfo{
private static final long serialVersionUID = 3405356595200877071L;
private OrganizationUser profile;
public AccountAuthenticationInfo(){
}
public AccountAuthenticationInfo(Object principal, Object credentials, String realmName){
super(principal, credentials, realmName);
}
public OrganizationUser getProfile() {
return profile;
}
public void setProfile(OrganizationUser profile) {
this.profile = profile;
}
}
import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.subject.support.WebDelegatingSubject;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class AccountSubject extends WebDelegatingSubject{
private OrganizationUser profile;
public AccountSubject(PrincipalCollection principals, boolean authenticated, String host, Session session,
boolean sessionEnabled, ServletRequest request, ServletResponse response, SecurityManager securityManager, OrganizationUser profile) {
super(principals, authenticated, host, session, sessionEnabled, request, response, securityManager);
this.profile = profile;
}
public String getUsername(){
return getPrincipal().toString();
}
public OrganizationUser getProfile() {
return profile;
}
}
import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser;
import com.yuntu.intelligent.log.service.UserService;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.mgt.SubjectFactory;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.subject.WebSubjectContext;
import org.springframework.beans.factory.annotation.Autowired;
public class AccountSubjectFactory implements SubjectFactory {
@Autowired
private UserService userService;
@Override
public Subject createSubject(SubjectContext context) {
WebSubjectContext wsc = (WebSubjectContext) context;
AuthenticationInfo info = wsc.getAuthenticationInfo();
OrganizationUser profile = null;
AccountSubject subject = null;
if (info instanceof AccountAuthenticationInfo) {
profile = ((AccountAuthenticationInfo) info).getProfile();
subject = doCreate(wsc, profile);
subject.getSession(true).setAttribute("profile", profile);
}else{
Session session = wsc.getSession();
if(session != null){
profile = (OrganizationUser)session.getAttribute("profile");
}
subject = doCreate(wsc, profile);
boolean isRemembered = subject.isRemembered();
if (session == null) {
wsc.setSessionCreationEnabled(true);
subject.getSession(true);
}
if (isRemembered && profile == null) {
Object username = subject.getPrincipal();
profile = userService.getUserByName((String) username);
subject.getSession(true).setTimeout(30 * 60 * 1000);
subject.getSession(true).setAttribute("profile", profile);
}
}
return doCreate(wsc, profile);
}
private AccountSubject doCreate(WebSubjectContext wsc, OrganizationUser profile) {
return new AccountSubject(wsc.resolvePrincipals(), wsc.resolveAuthenticated(), wsc.resolveHost(),
wsc.resolveSession(), wsc.isSessionCreationEnabled(), wsc.resolveServletRequest(),
wsc.resolveServletResponse(), wsc.resolveSecurityManager(), profile);
}
}
import org.apache.shiro.authc.AuthenticationToken;
public class AccountToken implements AuthenticationToken {
private static final long serialVersionUID = 1L;
private long id;
private String username;
public AccountToken() {
}
public AccountToken(long id, final String username) {
this(id, username, username);
}
public AccountToken(long id, final String username, String nickname) {
this.id = id;
this.username = username;
}
@Override
public Object getPrincipal() {
return username;
}
@Override
public Object getCredentials() {
return null;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.servlet.OncePerRequestFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Formatter;
/**
* @version 1.0.0
*/
public class AuthenticatedFilter extends OncePerRequestFilter {
private Logger LOG = LoggerFactory.getLogger(AuthenticatedFilter.class);
private static final String JS = "<script type='text/javascript'>var wp=window.parent; if(wp!=null){while(wp.parent&&wp.parent!==wp){wp=wp.parent;}wp.location.href='%1$s';}else{window.location.href='%1$s';}</script>";
private String loginUrl = "/login";
@Override
protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
LOG.info("開始權(quán)限驗(yàn)證");
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
chain.doFilter(request, response);
} else {
identifyGuest(subject, request, response, chain);
}
}
protected void identifyGuest(Subject subject, ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
redirectLogin(request, response);
}
protected void redirectLogin(ServletRequest request, ServletResponse response) throws IOException {
WebUtils.saveRequest(request);
String path = WebUtils.getContextPath((HttpServletRequest) request);
String url = loginUrl;
if (StringUtils.isNotBlank(path) && path.length() > 1) {
url = path + url;
}
if (isAjaxRequest((HttpServletRequest) request)) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print("您還沒有登錄!");
} else {
response.getWriter().write(new Formatter().format(JS, url).toString());
}
}
public String getLoginUrl() {
return loginUrl;
}
public void setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
}
/**
* 判斷是否為Ajax請(qǐng)求 <功能詳細(xì)描述>
*
* @param request
* @return 是true, 否false
* @see [類、類#方法、類#成員]
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
String header = request.getHeader("X-Requested-With");
if (header != null && "XMLHttpRequest".equals(header))
return true;
else
return false;
}
}
import com.yuntu.intelligent.log.service.QueryPermissionService;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.web.servlet.AbstractFilter;
import org.apache.shiro.web.servlet.ServletContextSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class QueryLimitFiter extends ServletContextSupport implements Filter {
private QueryPermissionService queryPermissionService;
private static final transient Logger log = LoggerFactory.getLogger(AbstractFilter.class);
protected FilterConfig filterConfig;
public QueryLimitFiter(QueryPermissionService queryPermissionService) {
this.queryPermissionService = queryPermissionService;
}
public FilterConfig getFilterConfig() {
return this.filterConfig;
}
public void setFilterConfig(FilterConfig filterConfig) {
this.filterConfig = filterConfig;
this.setServletContext(filterConfig.getServletContext());
}
protected String getInitParam(String paramName) {
FilterConfig config = this.getFilterConfig();
return config != null? org.apache.shiro.util.StringUtils.clean(config.getInitParameter(paramName)):null;
}
public final void init(FilterConfig filterConfig) throws ServletException {
this.setFilterConfig(filterConfig);
try {
this.onFilterConfigSet();
} catch (Exception var3) {
if(var3 instanceof ServletException) {
throw (ServletException)var3;
} else {
if(log.isErrorEnabled()) {
log.error("Unable to start Filter: [" + var3.getMessage() + "].", var3);
}
throw new ServletException(var3);
}
}
}
protected void onFilterConfigSet() throws Exception {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if(isAccessAllowed(servletRequest,servletResponse)){
filterChain.doFilter(servletRequest,servletResponse);
}else {
servletResponse.setContentType("application/json;charset=UTF-8");
servletResponse.getWriter().print("不允許查詢!");
}
}
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse) {
HttpServletRequest request = (HttpServletRequest)servletRequest;
String hallCode = request.getParameter("guanhao");
String uri = request.getRequestURI();
System.out.println(uri);
// if (collectionPropertiesConfig.getQueryLogUrls().contains(uri)) {
try {
if (StringUtils.isEmpty(hallCode)) {
servletResponse.setContentType("application/json;charset=UTF-8");
servletResponse.getWriter().print("需要輸入館號(hào)!");
return false;
}
if (queryPermissionService.permit(hallCode)) {
return true;
} else {
servletResponse.setContentType("application/json;charset=UTF-8");
servletResponse.getWriter().print("你沒有權(quán)限查詢此館!");
return false;
}
}catch (Exception e){
e.printStackTrace();
}
return false;
}
}
import com.yuntu.intelligent.log.model.sysmodel.OrganizationPrivilege;
import com.yuntu.intelligent.log.model.sysmodel.OrganizationResources;
import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser;
import com.yuntu.intelligent.log.service.RoleService;
import com.yuntu.intelligent.log.service.UserService;
import com.yuntu.intelligent.log.service.shiro.authc.AccountAuthenticationInfo;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
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 javax.annotation.Resource;
import java.util.List;
public class AccountRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService userRoleService;
public AccountRealm() {
super(new AllowAllCredentialsMatcher());
setAuthenticationTokenClass(UsernamePasswordToken.class);
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.fromRealm(getName()).iterator().next();
if (username != null) {
OrganizationUser user = userService.getUserByName(username);
if (user != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
OrganizationPrivilege role = userRoleService.getRole(user.getPrivilegeId());
List<OrganizationResources> roleResources = userRoleService.getRoleResources(user.getPrivilegeId());
//賦予角色
info.addRole(role.getName());
//賦予權(quán)限
roleResources.forEach(resource -> info.addStringPermission(resource.getPermission()));
return info;
}
}
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
OrganizationUser profile = getAccount(userService, token);
if (profile.getStatus() == 0) {
throw new LockedAccountException(profile.getUserId());
}
AccountAuthenticationInfo info = new AccountAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());
info.setProfile(profile);
return info;
}
protected OrganizationUser getAccount(UserService userService, AuthenticationToken token) {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
return userService.login(upToken.getUsername(), String.valueOf(upToken.getPassword()));
}
}
4、重點(diǎn)記錄filter配置中出現(xiàn)的問題
如上代碼,我有2個(gè)自定義的過濾器,AuthenticatedFilter可以拋開spring運(yùn)行,而QueryLimitFiter想使用一些spring管理的bean,所以一開始QueryLimitFiter是使用@Component注釋并且在ShiroConfig的@Bean方法中將其注入并配置進(jìn)shiro的過濾器鏈。
在使用的時(shí)候,原定只是在個(gè)別uri觸發(fā)的QueryLimitFiter,所有uri都會(huì)觸發(fā)它,反而AuthenticatedFilter失效了。查找原因,找到spring的filter鏈如圖,這是借用別人的圖,我的圖是將accessTokenFilter替換為queryLimitFiter:

總之就是自定義的過濾器QueryLimitFiter居然在shiroFilter之外而且運(yùn)行在shiroFilter之前了。。。
5、解決方案
如上代碼,將QueryLimitFiter不交給spring托管,使用new的方式添加到shiro的過濾器鏈中就沒有問題了。出現(xiàn)原因可能是spring會(huì)自動(dòng)將我們自定義的filter加載到它的過濾器鏈中(待深究?。?/p>
shiro自定義異常無效
一定要看一下自己重寫AuthorizingRealm中doGetAuthenticationInfo方法時(shí)候拋出什么異常,這拋出得是AuthenticationToken;
if (userName == null) {
throw new AuthenticationToken("用戶名不正確");
} else if (!userPwd.equals(password )) {
throw new AuthenticationToken("密碼不正確");
}
而我在controller里面拋出得卻是UnknownAccountException,這樣能捕捉到想要得自定義異常才有鬼了!
// 執(zhí)行認(rèn)證登陸
try {
subject.login(token);
} catch (UnknownAccountException uae) {
map.put("msg","賬號(hào)或密碼不對(duì)");
map.put("code","21000");
return map;
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java web實(shí)現(xiàn)網(wǎng)上手機(jī)銷售系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java web實(shí)現(xiàn)網(wǎng)上手機(jī)銷售系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
基于SpringMVC中的路徑參數(shù)和URL參數(shù)實(shí)例
這篇文章主要介紹了基于SpringMVC中的路徑參數(shù)和URL參數(shù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02
如何使用Idea中的 Deployment 實(shí)現(xiàn)打包自動(dòng)部署
這篇文章主要介紹了使用Idea中的 Deployment 實(shí)現(xiàn)打包自動(dòng)部署,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
Spring5源碼解析之Spring中的異步和計(jì)劃任務(wù)
本篇文章主要介紹了Spring5源碼解析之Spring中的異步和計(jì)劃任務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-10-10
SpringBoot整合Dubbo zookeeper過程解析
這篇文章主要介紹了SpringBoot整合Dubbo zookeeper過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
springboot多環(huán)境配置文件及自定義配置文件路徑詳解
這篇文章主要介紹了springboot多環(huán)境配置文件及自定義配置文件路徑,文中給大家介紹了classpath的基本概念講解及自定義springboot配置文件路徑的相關(guān)知識(shí),需要的朋友可以參考下2023-02-02
mybatis xml注釋sql的注意事項(xiàng)及說明
這篇文章主要介紹了mybatis xml注釋sql的注意事項(xiàng)及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
關(guān)于Springboot打成JAR包后讀取外部配置文件的問題
這篇文章主要介紹了關(guān)于Springboot打成JAR包后讀取外部配置文件的問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
基于FeignClient調(diào)用超時(shí)的處理方案
這篇文章主要介紹了基于FeignClient調(diào)用超時(shí)的處理方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07

