spring security集成cas實(shí)現(xiàn)單點(diǎn)登錄過(guò)程
cas流程
如下
用戶(hù)發(fā)送請(qǐng)求,后臺(tái)服務(wù)器驗(yàn)證ticket(票據(jù)信息),未登錄時(shí),用戶(hù)沒(méi)有攜帶,所以驗(yàn)證失敗,將用戶(hù)重定向到cas服務(wù)器去登錄換取票據(jù)憑證
用戶(hù)獲取憑證后攜帶憑證進(jìn)行請(qǐng)求,后臺(tái)服務(wù)器拿著ticket票據(jù)信息去cas服務(wù)器驗(yàn)證,驗(yàn)證成功后并獲取用戶(hù)信息,后臺(tái)服務(wù)器再將獲取到的用戶(hù)信息返回給瀏覽器
下圖,路線:1->5->6; 1->2->3->4; 1->2->7->8->9->1;
下面代碼解決的問(wèn)題
1.完成第三方客戶(hù)的cas登錄,
2.并將用戶(hù)信息同步到自己數(shù)據(jù)庫(kù),
3.同時(shí)將登錄用戶(hù)的憑證轉(zhuǎn)換成自己系統(tǒng)識(shí)別的憑證返回給自己系統(tǒng)的前端使用
具體代碼使用
pom.xml
<!-- spring security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- cas --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> </dependency>
application.yml
security: cas: server: host: https://test-login.xx.com login: ${security.cas.server.host}/login logout: ${security.cas.server.host}/logout service: webHost: http://test-xx.xx.com login: /login/cas logout: /logout
CasServerConfig CAS服務(wù)器配置
@Data @Configuration public class CasServerConfig { @Value("${security.cas.server.host}") private String host; @Value("${security.cas.server.login}") private String login; @Value("${security.cas.server.logout}") private String logout; }
CasServiceConfig 項(xiàng)目后臺(tái)服務(wù)器配置
@Data @Configuration public class CasServiceConfig { @Value("${security.cas.service.webHost}") private String webHost; @Value("${security.cas.service.login}") private String login; @Value("${security.cas.service.logout}") private String logout; private Boolean sendRenew = false; }
CasWebSecurityConfiguration配置
@Configuration @Order(SecurityProperties.BASIC_AUTH_ORDER) public class CasWebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private CasAuthenticationEntryPoint casAuthenticationEntryPoint; @Autowired private CasAuthenticationProvider casAuthenticationProvider; @Autowired private CasAuthenticationFilter casAuthenticationFilter; @Autowired private LogoutFilter logoutFilter; @Autowired private CasServerConfig casServerConfig; @Autowired private HttpParamsFilter httpParamsFilter; private static final String[] AUTH_WHITELIST = { // -- swagger ui "/swagger-resources/**", "/swagger-ui.html", "/v2/api-docs", "/webjars/**", "/v3/api-docs" }; @Override protected void configure(HttpSecurity http) throws Exception { http.headers().frameOptions().disable(); http.csrf().disable(); http.authorizeRequests() .requestMatchers(CorsUtils::isPreFlightRequest) .permitAll() .antMatchers("/static/**") .permitAll() .antMatchers(AUTH_WHITELIST) .permitAll() // 不攔截靜態(tài)資源 // .antMatchers("/config/**").permitAll() // 不攔截動(dòng)態(tài)配置文件 .antMatchers("/api/**") .permitAll() // 不攔截對(duì)外API .antMatchers("/login/**") .permitAll() .anyRequest() .authenticated(); // 所有資源都需要登陸后才可以訪問(wèn)。 http.logout().permitAll(); // 不攔截注銷(xiāo) http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint); // 單點(diǎn)注銷(xiāo)的過(guò)濾器,必須配置在SpringSecurity的過(guò)濾器鏈中,如果直接配置在Web容器中,貌似是不起作用的。我自己的是不起作用的。 SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter(); singleSignOutFilter.setArtifactParameterName(this.casServerConfig.getHost()); http.addFilter(casAuthenticationFilter) .addFilterBefore(logoutFilter, LogoutFilter.class) .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class); http.addFilterBefore(httpParamsFilter, FilterSecurityInterceptor.class); http.antMatcher("/**"); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(casAuthenticationProvider); } @Bean public ServletListenerRegistrationBean<CasSignOutHttpSessionListener> singleSignOutHttpSessionListener() { ServletListenerRegistrationBean<CasSignOutHttpSessionListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(); servletListenerRegistrationBean.setListener(new CasSignOutHttpSessionListener()); return servletListenerRegistrationBean; } /** * 自定義UserDetailsService,授權(quán) * * @return */ @Bean public CustomUserDetailsService customUserDetailsService() { return new CustomUserDetailsService(); } /** * AuthenticationManager * * @return * @throws Exception */ @Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
@Configuration public class SecurityConfiguration { @Autowired private CasServerConfig casServerConfig; @Autowired private CasServiceConfig casServiceConfig; @Bean public ServiceProperties serviceProperties() { ServiceProperties serviceProperties = new ServiceProperties(); serviceProperties.setService( this.casServiceConfig.getWebHost() + this.casServiceConfig.getLogin()); serviceProperties.setSendRenew(this.casServiceConfig.getSendRenew()); return serviceProperties; } @Bean public CasAuthenticationFilter casAuthenticationFilter( AuthenticationManager authenticationManager, ServiceProperties serviceProperties) { CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter(); casAuthenticationFilter.setAuthenticationManager(authenticationManager); casAuthenticationFilter.setServiceProperties(serviceProperties); casAuthenticationFilter.setFilterProcessesUrl( this.casServiceConfig.getLogin()); casAuthenticationFilter.setContinueChainBeforeSuccessfulAuthentication(false); casAuthenticationFilter.setAuthenticationSuccessHandler( new UrlAuthenticationSuccessHandler("/")); return casAuthenticationFilter; } @Bean public CasAuthenticationEntryPoint casAuthenticationEntryPoint( ServiceProperties serviceProperties) { CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint(); entryPoint.setLoginUrl(this.casServerConfig.getLogin()); entryPoint.setServiceProperties(serviceProperties); return entryPoint; } @Bean public Cas20ServiceTicketValidator cas20ServiceTicketValidator() { return new Cas20ServiceTicketValidator(this.casServerConfig.getHost()); } @Bean public CasAuthenticationProvider casAuthenticationProvider( AuthenticationUserDetailsService<CasAssertionAuthenticationToken> userDetailsService, ServiceProperties serviceProperties, Cas20ServiceTicketValidator ticketValidator) { CasAuthenticationProvider provider = new CasAuthenticationProvider(); provider.setKey("casProvider"); provider.setServiceProperties(serviceProperties); provider.setTicketValidator(ticketValidator); provider.setAuthenticationUserDetailsService(userDetailsService); return provider; } @Bean public LogoutFilter logoutFilter() { String logoutRedirectPath = this.casServerConfig.getLogout() + "?service=" + this.casServiceConfig.getWebHost(); CasLogoutFilter logoutFilter = new CasLogoutFilter(logoutRedirectPath, new CasSecurityContextLogoutHandler()); logoutFilter.setFilterProcessesUrl(this.casServiceConfig.getLogout()); return logoutFilter; } }
驗(yàn)證成功后處理器UrlAuthenticationSuccessHandler
public class UrlAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { public UrlAuthenticationSuccessHandler() { super(); } public UrlAuthenticationSuccessHandler(String defaultTargetUrl) { super(defaultTargetUrl); } @Override protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) { if (isAlwaysUseDefaultTargetUrl()) { return this.getDefaultTargetUrl(); } // Check for the parameter and use that if available String targetUrl = null; if (this.getTargetUrlParameter() != null) { targetUrl = request.getParameter(this.getTargetUrlParameter()); if (StringUtils.hasText(targetUrl)) { logger.debug("Found targetUrlParameter in request: " + targetUrl); return targetUrl; } } //根據(jù)自己需求寫(xiě) if (!StringUtils.hasText(targetUrl)) { HttpSession session = request.getSession(); targetUrl = (String) session.getAttribute(HttpParamsFilter.REQUESTED_URL); int firstUrl = targetUrl.indexOf("?url"); if (firstUrl > 0) { targetUrl = targetUrl.substring(firstUrl + 5); } int secondUrl = targetUrl.indexOf("?url"); if (firstUrl >= 0 && secondUrl >= 0 && firstUrl != secondUrl) targetUrl = targetUrl.substring(secondUrl + 5); } if (!StringUtils.hasText(targetUrl)) { targetUrl = this.getDefaultTargetUrl(); logger.debug("Using default Url: " + targetUrl); } //驗(yàn)證成功后,請(qǐng)求后面的ticket信息去除掉 if (targetUrl.contains("ticket")) { targetUrl = targetUrl.substring(0, targetUrl.indexOf("?ticket")); } //根據(jù)自己需求進(jìn)行判斷 if (targetUrl.contains("#")) { targetUrl += targetUrl.substring(targetUrl.indexOf("#")); } return targetUrl; } }
退出登錄處理器 CasSecurityContextLogoutHandler
public class CasSecurityContextLogoutHandler implements LogoutHandler { protected final Log logger = LogFactory.getLog(this.getClass()); private boolean invalidateHttpSession = true; private boolean clearAuthentication = true; public CasSecurityContextLogoutHandler() { } public void logout( HttpServletRequest request, HttpServletResponse response, Authentication authentication) { Assert.notNull(request, "HttpServletRequest required"); if (this.invalidateHttpSession) { HttpSession session = request.getSession(false); if (session != null) { this.logger.debug("Invalidating session: " + session.getId()); //發(fā)布退出登錄事件,自己增加,監(jiān)聽(tīng)此事件處理一些事情 session.invalidate(); LogoutEvent event = new LogoutEvent(); event.setSessionId(session.getId()); EventPublisherUtil.publish(event); } } if (this.clearAuthentication) { SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication((Authentication)null); } SecurityContextHolder.clearContext(); } public boolean isInvalidateHttpSession() { return this.invalidateHttpSession; } public void setInvalidateHttpSession(boolean invalidateHttpSession) { this.invalidateHttpSession = invalidateHttpSession; } public void setClearAuthentication(boolean clearAuthentication) { this.clearAuthentication = clearAuthentication; } }
增加自己攔截器,處理自己的邏輯HttpParamsFilter
@Component @Slf4j public class HttpParamsFilter implements Filter { public static final String REQUESTED_URL = "CasRequestedUrl"; @Autowired private CasServiceConfig casServiceConfig; //下面三個(gè)依賴(lài)我這里主要做了憑證轉(zhuǎn)換,可刪掉或替換成自己的邏輯 //我自己的服務(wù),可替代成自己的或者刪除掉 @Autowired private TenantTokenService tenantTokenService; //我自己的服務(wù),可替代成自己的,或者刪除 @Autowired private TenantManager tenantManager; //我自己的服務(wù),可替代成自己的,或者刪除 @Autowired private UserManager userManager; @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) servletRequest; final HttpServletResponse response = (HttpServletResponse) servletResponse; HttpSession session = request.getSession(); // String requestPath = request.getServletPath();+ "/" +requestPath StringBuffer requestURL = request.getRequestURL(); log.info("請(qǐng)求地址:" + requestURL); Cookie[] cookies = request.getCookies(); boolean flag = true; if (cookies != null) { for (Cookie cookie : cookies) { log.info("cookieDomain:" + cookie.getDomain()); // && requestURL.toString().contains(cookie.getDomain()) if (cookie.getName().contains("token")) { String value = cookie.getValue(); log.info("token:" + cookie.getDomain()); if (cookie.getDomain() != null && requestURL.toString().contains(cookie.getDomain())) { //緩存憑證使用的 TenantManager.sessionTokenMap.put(session.getId(), value); flag = false; } } // && requestURL.toString().contains(cookie.getDomain()) if (cookie.getName().contains("pcpopclub")) { String value = cookie.getValue(); log.info("pcpopclub:" + cookie.getDomain()); if (cookie.getDomain() != null && requestURL.toString().contains(cookie.getDomain())) { //緩存憑證使用的 TenantManager.sessionTokenMap.put(session.getId(), value); flag = false; } } } } Map<String, String[]> parameterMap = request.getParameterMap(); for (Entry<String, String[]> params : parameterMap.entrySet()) { session.setAttribute(params.getKey(), params.getValue()); requestURL.append("?").append(params.getKey()).append("=").append(params.getValue()[0]); } //cas服務(wù)器獲取用戶(hù)信息 User user = SecurityUtils.getUser(); session.setAttribute(REQUESTED_URL, requestURL.toString()); if (user != null) { //查詢(xún)本地服務(wù)是否有此用戶(hù) AppUser appUser = tenantManager.getUserByUserCode(user.getUser_code()); // 都是自己加的邏輯,不需要可以刪除掉 if (appUser == null) { // 本地服務(wù)不存在該用戶(hù),調(diào)用遠(yuǎn)程接口查詢(xún)用戶(hù)信息,并添加到本地服務(wù) UserQry qry = new UserQry(); qry.setIds(user.getUcid()); UserInfoResponse userBatchInfo = userManager.getUserInfo(qry); List<UserDTO> data = userBatchInfo.getData(); if (ListUtils.isNotEmpty(data)) { tenantManager.addUser(BeanUtils.copy(data.get(0), UserDTO.class)); appUser = tenantManager.getUserByUserCode(user.getUser_code()); } } String token = TenantManager.sessionTokenMap.get(session.getId()); if (token == null) { UserTokenVO userTokenVO = new UserTokenVO(); userTokenVO.setPayload(appUser.getId()); SingleResponse<String> generate = tenantTokenService.generate(userTokenVO); TenantManager.sessionTokenMap.put(session.getId(), generate.getData()); } } else if (flag) { session.setAttribute(REQUESTED_URL, casServiceConfig.getWebHost() + requestURL); } if (user == null) { TenantManager.sessionTokenMap.remove(session.getId()); } else { Cookie ck = new Cookie("homeworld_front_ke_token", user.getBusinessToken()); ck.setDomain( casServiceConfig .getWebHost() .substring(casServiceConfig.getWebHost().indexOf("://") + 3)); response.addCookie(ck); } chain.doFilter(request, response); } @Override public void destroy() {} }
附上獲取用戶(hù)的類(lèi) SecurityUtils
public class SecurityUtils { public static Authentication getAuthentication() { return SecurityContextHolder.getContext().getAuthentication(); } public static Collection<? extends GrantedAuthority> getAllPermission() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); return authorities; } public static boolean hasPermission(String permission) { if (StringUtils.isEmpty(permission)) { return false; } Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); boolean hasPermission = false; for (GrantedAuthority grantedAuthority : authorities) { String authority = grantedAuthority.getAuthority(); if (authority.equals(permission)) { hasPermission = true; } } return hasPermission; } public static User getUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if ("anonymousUser".equals(principal)) { return null; } return (User)principal; } public static void logout() { SecurityContextHolder.clearContext(); } }
附上用戶(hù)類(lèi) User
@Data public class User implements UserDetails, CredentialsContainer { private static final long serialVersionUID = 530L; private static final Log logger = LogFactory .getLog(org.springframework.security.core.userdetails.User.class); private String password; private String username; private Set<GrantedAuthority> authorities; private boolean accountNonExpired; private boolean accountNonLocked; private boolean credentialsNonExpired; private boolean enabled; private String birthday; private String accountSystemId; private String gender; private String displayName; private String businessToken; private String avatar; private String passedMfaReason; private String version; private String tgtId; private String realName; private String uid; private String ucname; private String user_code; private String phone; private String mfaAuthMethodLevel; private String id; private String state; private String account; private String email; private String ucid; private String serviceUrl; public User(){ } public User(String username, String password, Collection<? extends GrantedAuthority> authorities) { this(username, password, true, true, true, true, authorities); } public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { if (username != null && !"".equals(username) && password != null) { this.username = username; this.password = password; this.enabled = enabled; this.accountNonExpired = accountNonExpired; this.credentialsNonExpired = credentialsNonExpired; this.accountNonLocked = accountNonLocked; this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities)); } else { throw new IllegalArgumentException("Cannot pass null or empty values to constructor"); } } public void setAuthorities(List<GrantedAuthority> grantedAuthorities) { this.authorities = Collections.unmodifiableSet(sortAuthorities(grantedAuthorities)); } public Collection<GrantedAuthority> getAuthorities() { return this.authorities; } public String getPassword() { return this.password; } public String getUsername() { return this.username; } public boolean isEnabled() { return this.enabled; } public boolean isAccountNonExpired() { return this.accountNonExpired; } public boolean isAccountNonLocked() { return this.accountNonLocked; } public boolean isCredentialsNonExpired() { return this.credentialsNonExpired; } public void eraseCredentials() { this.password = null; } private static SortedSet<GrantedAuthority> sortAuthorities( Collection<? extends GrantedAuthority> authorities) { Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection"); SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet(new AuthorityComparator()); Iterator var2 = authorities.iterator(); while (var2.hasNext()) { GrantedAuthority grantedAuthority = (GrantedAuthority) var2.next(); Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements"); sortedAuthorities.add(grantedAuthority); } return sortedAuthorities; } public boolean equals(Object rhs) { return rhs instanceof User ? this.username.equals(((User) rhs).username) : false; } public int hashCode() { return this.username.hashCode(); } public String toString() { StringBuilder sb = new StringBuilder(); sb.append(super.toString()).append(": "); sb.append("Username: ").append(this.username).append("; "); sb.append("Password: [PROTECTED]; "); sb.append("Enabled: ").append(this.enabled).append("; "); sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; "); sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired).append("; "); sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; "); if (!this.authorities.isEmpty()) { sb.append("Granted Authorities: "); boolean first = true; Iterator var3 = this.authorities.iterator(); while (var3.hasNext()) { GrantedAuthority auth = (GrantedAuthority) var3.next(); if (!first) { sb.append(","); } first = false; sb.append(auth); } } else { sb.append("Not granted any authorities"); } return sb.toString(); } public static UserBuilder withUsername(String username) { return builder().username(username); } public static User.UserBuilder builder() { return new User.UserBuilder(); } /** * @deprecated */ @Deprecated public static User.UserBuilder withDefaultPasswordEncoder() { logger.warn( "User.withDefaultPasswordEncoder() is considered unsafe for production and is only intended for sample applications."); PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); User.UserBuilder var10000 = builder(); encoder.getClass(); return var10000.passwordEncoder(encoder::encode); } public static User.UserBuilder withUserDetails(UserDetails userDetails) { return withUsername(userDetails.getUsername()).password(userDetails.getPassword()) .accountExpired(!userDetails.isAccountNonExpired()) .accountLocked(!userDetails.isAccountNonLocked()).authorities(userDetails.getAuthorities()) .credentialsExpired(!userDetails.isCredentialsNonExpired()) .disabled(!userDetails.isEnabled()); } public static class UserBuilder { private String username; private String password; private List<GrantedAuthority> authorities; private boolean accountExpired; private boolean accountLocked; private boolean credentialsExpired; private boolean disabled; private Function<String, String> passwordEncoder; private UserBuilder() { this.passwordEncoder = (password) -> { return password; }; } public UserBuilder username(String username) { Assert.notNull(username, "username cannot be null"); this.username = username; return this; } public UserBuilder password(String password) { Assert.notNull(password, "password cannot be null"); this.password = password; return this; } public UserBuilder passwordEncoder(Function<String, String> encoder) { Assert.notNull(encoder, "encoder cannot be null"); this.passwordEncoder = encoder; return this; } public UserBuilder roles(String... roles) { List<GrantedAuthority> authorities = new ArrayList(roles.length); String[] var3 = roles; int var4 = roles.length; for (int var5 = 0; var5 < var4; ++var5) { String role = var3[var5]; Assert.isTrue(!role.startsWith("ROLE_"), () -> { return role + " cannot start with ROLE_ (it is automatically added)"; }); authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); } return this.authorities((Collection) authorities); } public UserBuilder authorities(GrantedAuthority... authorities) { return this.authorities((Collection) Arrays.asList(authorities)); } public UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) { this.authorities = new ArrayList(authorities); return this; } public UserBuilder authorities(String... authorities) { return this.authorities((Collection) AuthorityUtils.createAuthorityList(authorities)); } public UserBuilder accountExpired(boolean accountExpired) { this.accountExpired = accountExpired; return this; } public UserBuilder accountLocked(boolean accountLocked) { this.accountLocked = accountLocked; return this; } public UserBuilder credentialsExpired(boolean credentialsExpired) { this.credentialsExpired = credentialsExpired; return this; } public UserBuilder disabled(boolean disabled) { this.disabled = disabled; return this; } public UserDetails build() { String encodedPassword = (String) this.passwordEncoder.apply(this.password); return new User(this.username, encodedPassword, !this.disabled, !this.accountExpired, !this.credentialsExpired, !this.accountLocked, this.authorities); } } private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable { private static final long serialVersionUID = 530L; private AuthorityComparator() { } public int compare(GrantedAuthority g1, GrantedAuthority g2) { if (g2.getAuthority() == null) { return -1; } else { return g1.getAuthority() == null ? 1 : g1.getAuthority().compareTo(g2.getAuthority()); } } } }
存憑證的容器
public static Map<String, String> sessionTokenMap = new ConcurrentHashMap<>();
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java服務(wù)限流算法的6種實(shí)現(xiàn)
服務(wù)限流是指通過(guò)控制請(qǐng)求的速率或次數(shù)來(lái)達(dá)到保護(hù)服務(wù)的目的,本文主要介紹了Java服務(wù)限流算法的6種實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-05-05java動(dòng)態(tài)代理(jdk與cglib)詳細(xì)解析
靜態(tài)代理:由程序員創(chuàng)建或特定工具自動(dòng)生成源代碼,再對(duì)其編譯。在程序運(yùn)行前,代理類(lèi)的.class文件就已經(jīng)存在了2013-09-09非常詳細(xì)的Java異常處理機(jī)制知識(shí)整理大全
Java異常指在程序運(yùn)行時(shí)可能出現(xiàn)的一些錯(cuò)誤,比如試圖打開(kāi)一個(gè)根本不存在的文件等,異常處理將會(huì)改變程序的控制流程,讓程序有機(jī)會(huì)對(duì)錯(cuò)誤做出處理,下面這篇文章主要給大家介紹了關(guān)于Java異常處理機(jī)制知識(shí)整理的相關(guān)資料,需要的朋友可以參考下2022-11-11java如何給對(duì)象按照字符串屬性進(jìn)行排序
這篇文章主要介紹了java如何給對(duì)象按照字符串屬性進(jìn)行排序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Java常用鎖synchronized和ReentrantLock的區(qū)別
這篇文章主要介紹了Java常用鎖synchronized和ReentrantLock的區(qū)別,二者的功效都是相同的,但又有很多不同點(diǎn),下面我們就進(jìn)入文章了解具體的相關(guān)內(nèi)容吧。需要的小伙伴也可以參考一下2022-05-05解決spring?data?jpa?saveAll()?保存過(guò)慢問(wèn)題
這篇文章主要介紹了解決spring?data?jpa?saveAll()保存過(guò)慢問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Mybatis Plus 自定義方法實(shí)現(xiàn)分頁(yè)功能的示例代碼
這篇文章主要介紹了Mybatis Plus 自定義方法實(shí)現(xiàn)分頁(yè)功能的示例代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08