Spring Security自定義用戶名密碼登錄頁(yè)面全過(guò)程
在現(xiàn)代 Web 應(yīng)用開(kāi)發(fā)中,安全性是不可忽視的重要環(huán)節(jié)。Spring Security 作為 Spring 生態(tài)中最成熟、最廣泛使用的安全框架,為開(kāi)發(fā)者提供了強(qiáng)大的認(rèn)證(Authentication)與授權(quán)(Authorization)能力。默認(rèn)情況下,Spring Security 會(huì)提供一個(gè)自動(dòng)生成的登錄頁(yè)面,但在實(shí)際項(xiàng)目中,我們幾乎總是需要根據(jù)業(yè)務(wù)需求和 UI/UX 設(shè)計(jì),定制屬于自己的登錄界面。
本文將深入探討如何在 Spring Boot 項(xiàng)目中使用 Spring Security 實(shí)現(xiàn)完全自定義的用戶名密碼登錄頁(yè)面。我們將從基礎(chǔ)配置開(kāi)始,逐步構(gòu)建一個(gè)完整的登錄流程,涵蓋表單設(shè)計(jì)、后端處理、錯(cuò)誤提示、記住我功能、CSRF 防護(hù)、登錄成功/失敗跳轉(zhuǎn)策略等核心內(nèi)容,并通過(guò)代碼示例和流程圖幫助你徹底掌握這一關(guān)鍵技能。
為什么需要自定義登錄頁(yè)面?
默認(rèn)的 Spring Security 登錄頁(yè)面雖然功能完整,但存在以下問(wèn)題:
- 外觀簡(jiǎn)陋:無(wú)法匹配企業(yè)級(jí)應(yīng)用的 UI 風(fēng)格;
- 響應(yīng)式缺失:不支持移動(dòng)端適配;
- 多語(yǔ)言困難:難以集成國(guó)際化(i18n);
- 擴(kuò)展性差:無(wú)法添加驗(yàn)證碼、第三方登錄等額外功能;
- 安全細(xì)節(jié)暴露:默認(rèn)路徑和參數(shù)可能被攻擊者利用。
因此,自定義登錄頁(yè)面不僅是美觀需求,更是安全性和用戶體驗(yàn)的必要實(shí)踐。
小知識(shí):Spring Security 的默認(rèn)登錄頁(yè)面路徑是 /login,提交表單的 action 是 /login,用戶名字段為 username,密碼字段為 password。這些都可以被覆蓋。
項(xiàng)目初始化
我們使用 Spring Boot 3.x(基于 Jakarta EE 9+,使用 jakarta.* 包而非 javax.*)來(lái)搭建項(xiàng)目。確保你的開(kāi)發(fā)環(huán)境已安裝 JDK 17+ 和 Maven/Gradle。
依賴配置(Maven)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
</dependencies>Thymeleaf 官方文檔 提供了與 Spring Security 集成的詳細(xì)說(shuō)明。
基礎(chǔ)安全配置
首先,我們需要禁用 Spring Security 的默認(rèn)登錄頁(yè)面,并指定我們自己的登錄頁(yè)面路徑。
創(chuàng)建 SecurityConfig 類
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/login", "/css/**", "/js/**", "/images/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login") // 指定自定義登錄頁(yè)
.loginProcessingUrl("/do-login") // 表單提交地址
.usernameParameter("username") // 用戶名參數(shù)名
.passwordParameter("password") // 密碼參數(shù)名
.defaultSuccessUrl("/dashboard", true) // 登錄成功后跳轉(zhuǎn)
.failureUrl("/login?error=true") // 登錄失敗跳轉(zhuǎn)
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout=true")
.permitAll()
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("123456")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
注意:User.withDefaultPasswordEncoder() 僅用于演示!生產(chǎn)環(huán)境必須使用強(qiáng)哈希算法(如 BCrypt)。
自定義登錄頁(yè)面(Thymeleaf)
我們?cè)?src/main/resources/templates 目錄下創(chuàng)建 login.html 文件。
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>用戶登錄</title>
<link rel="stylesheet" th:href="@{/css/login.css}" rel="external nofollow" >
</head>
<body>
<div class="login-container">
<h2>系統(tǒng)登錄</h2>
<!-- 錯(cuò)誤信息提示 -->
<div th:if="${param.error}" class="alert alert-error">
用戶名或密碼錯(cuò)誤,請(qǐng)重試!
</div>
<!-- 成功登出提示 -->
<div th:if="${param.logout}" class="alert alert-success">
您已成功退出系統(tǒng)。
</div>
<form th:action="@{/do-login}" method="post">
<!-- CSRF Token 自動(dòng)注入 -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<div class="form-group">
<label for="username">用戶名</label>
<input type="text" id="username" name="username" required autofocus/>
</div>
<div class="form-group">
<label for="password">密碼</label>
<input type="password" id="password" name="password" required/>
</div>
<div class="form-group remember-me">
<input type="checkbox" id="remember-me" name="remember-me"/>
<label for="remember-me">記住我</label>
</div>
<button type="submit">登錄</button>
</form>
</div>
</body>
</html>關(guān)鍵點(diǎn)說(shuō)明:
th:action="@{/do-login}":表單提交到/do-login,與loginProcessingUrl一致;- CSRF Token 通過(guò) Thymeleaf 自動(dòng)注入,防止跨站請(qǐng)求偽造;
name="remember-me"是 Spring Security “記住我”功能的默認(rèn)參數(shù)名;- 使用
param.error和param.logout獲取 URL 參數(shù)以顯示提示。
靜態(tài)資源處理
為了讓登錄頁(yè)面樣式正常加載,需在 SecurityConfig 中放行靜態(tài)資源路徑:
.requestMatchers("/login", "/css/**", "/js/**", "/images/**", "/favicon.ico").permitAll()
并在 src/main/resources/static/css 下創(chuàng)建 login.css:
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.login-container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
width: 300px;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
}
.form-group input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.remember-me {
display: flex;
align-items: center;
}
.remember-me input[type="checkbox"] {
margin-right: 0.5rem;
}
button {
width: 100%;
padding: 0.75rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
.alert {
padding: 0.75rem;
margin-bottom: 1rem;
border-radius: 4px;
}
.alert-error {
background-color: #f8d7da;
color: #721c24;
}
.alert-success {
background-color: #d4edda;
color: #155724;
}登錄流程解析
理解 Spring Security 的登錄流程對(duì)調(diào)試和擴(kuò)展至關(guān)重要。
關(guān)鍵組件說(shuō)明:
UsernamePasswordAuthenticationFilter:負(fù)責(zé)攔截/do-login請(qǐng)求;UserDetailsService:加載用戶信息(可對(duì)接數(shù)據(jù)庫(kù));AuthenticationProvider:驗(yàn)證密碼是否匹配;- 成功/失敗處理器決定跳轉(zhuǎn)邏輯。
實(shí)現(xiàn)數(shù)據(jù)庫(kù)用戶認(rèn)證
前面我們使用了內(nèi)存用戶,現(xiàn)在將其替換為數(shù)據(jù)庫(kù)查詢。
1. 添加 JPA 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>2. 創(chuàng)建 User 實(shí)體
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class AppUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private boolean enabled = true;
// getters and setters
}
3. 創(chuàng)建 UserRepository
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<AppUser, Long> {
AppUser findByUsername(String username);
}
4. 自定義 UserDetailsService
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public CustomUserDetailsService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AppUser user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用戶不存在: " + username);
}
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword()) // 已加密
.authorities("ROLE_USER")
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(!user.isEnabled())
.build();
}
}
5. 更新 SecurityConfig
移除 userDetailsService() 方法,注入 CustomUserDetailsService:
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// ... 其他配置不變
http.userDetailsService(customUserDetailsService);
return http.build();
}
6. 密碼編碼器配置
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
安全建議:永遠(yuǎn)不要存儲(chǔ)明文密碼!BCrypt 是目前推薦的密碼哈希算法。
處理登錄成功與失敗
Spring Security 允許我們自定義登錄成功和失敗的行為,而不僅僅是跳轉(zhuǎn)。
自定義成功處理器
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// 可記錄日志、更新最后登錄時(shí)間等
System.out.println("用戶 " + authentication.getName() + " 登錄成功");
// 默認(rèn)跳轉(zhuǎn)
response.sendRedirect("/dashboard");
}
}
自定義失敗處理器
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
System.out.println("登錄失敗: " + exception.getMessage());
// 可記錄失敗次數(shù)、鎖定賬戶等
response.sendRedirect("/login?error=" + exception.getClass().getSimpleName());
}
}
在 SecurityConfig 中注冊(cè)
@Autowired
private CustomAuthenticationSuccessHandler successHandler;
@Autowired
private CustomAuthenticationFailureHandler failureHandler;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/do-login")
.successHandler(successHandler)
.failureHandler(failureHandler)
.permitAll()
);
// ...
}
應(yīng)用場(chǎng)景:
- 成功時(shí)記錄 IP、設(shè)備信息;
- 失敗時(shí)限制嘗試次數(shù),防止暴力.破 解;
- 根據(jù)角色跳轉(zhuǎn)不同首頁(yè)。
“記住我”功能實(shí)現(xiàn)
“記住我”允許用戶在關(guān)閉瀏覽器后仍保持登錄狀態(tài)(通常通過(guò)持久化 Cookie 實(shí)現(xiàn))。
1. 數(shù)據(jù)庫(kù)表結(jié)構(gòu)
CREATE TABLE persistent_logins (
username VARCHAR(64) NOT NULL,
series VARCHAR(64) PRIMARY KEY,
token VARCHAR(64) NOT NULL,
last_used TIMESTAMP NOT NULL
);2. 配置 RememberMeServices
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
// 自動(dòng)建表(僅首次)
// tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.rememberMe(remember -> remember
.key("myAppKey") // 必須設(shè)置,用于加密
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(7 * 24 * 3600) // 7天
.userDetailsService(customUserDetailsService)
);
// ...
}
原理:登錄時(shí)勾選“記住我”,服務(wù)器生成 series 和 token 存入數(shù)據(jù)庫(kù),并設(shè)置 Cookie。下次訪問(wèn)時(shí),通過(guò) Cookie 中的憑證自動(dòng)登錄。
CSRF 防護(hù)與自定義表單
Spring Security 默認(rèn)啟用 CSRF(跨站請(qǐng)求偽造)防護(hù),要求每個(gè)修改狀態(tài)的請(qǐng)求(POST/PUT/DELETE)攜帶有效的 CSRF Token。
在 Thymeleaf 表單中,我們通過(guò)以下方式自動(dòng)注入:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>如果你使用的是純 HTML 或 AJAX,需手動(dòng)處理:
AJAX 示例(JavaScript)
// 從 meta 標(biāo)簽獲取 CSRF Token
var csrfToken = document.querySelector("meta[name='_csrf']").getAttribute("content");
var csrfHeader = document.querySelector("meta[name='_csrf_header']").getAttribute("content");
// 發(fā)送請(qǐng)求時(shí)添加 Header
fetch('/do-login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
[csrfHeader]: csrfToken
},
body: 'username=admin&password=123456'
});對(duì)應(yīng)的 HTML 需添加:
<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>重要:不要禁用 CSRF!除非你明確知道自己在做什么(如 REST API 使用 JWT)。
國(guó)際化(i18n)支持
為了讓登錄頁(yè)面支持多語(yǔ)言,我們可以集成 Spring 的 MessageSource。
1. 創(chuàng)建 messages.properties
# src/main/resources/messages.properties login.title=用戶登錄 login.username=用戶名 login.password=密碼 login.remember=記住我 login.button=登錄 error.bad.credentials=用戶名或密碼錯(cuò)誤
2. messages_zh_CN.properties
login.title=用戶登錄 login.username=用戶名 login.password=密碼 login.remember=記住我 login.button=登錄 error.bad.credentials=用戶名或密碼錯(cuò)誤
3. 在 Thymeleaf 中使用
<h2 th:text="#{login.title}">用戶登錄</h2>
<label for="username" th:text="#{login.username}">用戶名</label>
<!-- 其他類似 -->
<div th:if="${param.error}" class="alert alert-error" th:text="#{error.bad.credentials}"></div>
4. 配置 LocaleResolver
@Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setDefaultLocale(Locale.CHINA);
return resolver;
}
提示:瀏覽器通過(guò) Accept-Language 頭自動(dòng)選擇語(yǔ)言。
登錄驗(yàn)證碼集成(可選)
雖然 Spring Security 本身不提供驗(yàn)證碼,但我們可以輕松集成。
1. 添加 Kaptcha 依賴(示例)
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>2. 配置 Kaptcha
@Bean
public DefaultKaptcha kaptcha() {
DefaultKaptcha kaptcha = new DefaultKaptcha();
Properties props = new Properties();
props.setProperty("kaptcha.border", "no");
props.setProperty("kaptcha.textproducer.font.color", "black");
props.setProperty("kaptcha.textproducer.char.space", "10");
kaptcha.setConfig(new Config(props));
return kaptcha;
}
3. 驗(yàn)證碼 Controller
@GetMapping("/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("image/jpeg");
String capText = kaptcha.createText();
request.getSession().setAttribute("captcha", capText);
BufferedImage image = kaptcha.createImage(capText);
ImageIO.write(image, "jpg", response.getOutputStream());
}
4. 修改登錄表單
<img th:src="@{/captcha}" onclick="this.src='/captcha?' + Math.random()" alt="驗(yàn)證碼"/>
<input type="text" name="captcha" placeholder="請(qǐng)輸入驗(yàn)證碼" required/>5. 自定義 AuthenticationFilter
繼承 UsernamePasswordAuthenticationFilter,在 attemptAuthentication 中校驗(yàn)驗(yàn)證碼。
注意:驗(yàn)證碼校驗(yàn)應(yīng)在密碼校驗(yàn)之前,避免無(wú)效請(qǐng)求消耗資源。
常見(jiàn)問(wèn)題與調(diào)試技巧
1. 登錄后無(wú)限重定向
原因:defaultSuccessUrl 指向的頁(yè)面未放行,導(dǎo)致再次跳轉(zhuǎn)到登錄頁(yè)。
? 解決:確保 /dashboard 被 authenticated() 保護(hù),且用戶有權(quán)限訪問(wèn)。
2. CSRF Token 無(wú)效
現(xiàn)象:提交表單報(bào) 403 Forbidden。
? 檢查:
- 表單是否包含
<input type="hidden" th:name="${_csrf.parameterName}" ... /> - 是否使用了正確的 POST 方法;
- Session 是否過(guò)期(Token 綁定 Session)。
3. 密碼不匹配
? 檢查:
- 數(shù)據(jù)庫(kù)存儲(chǔ)的密碼是否經(jīng)過(guò) BCrypt 加密;
PasswordEncoder是否與加密時(shí)一致;- 不要使用
User.withDefaultPasswordEncoder()。
4. 日志調(diào)試
在 application.properties 中開(kāi)啟安全日志:
logging.level.org.springframework.security=DEBUG
安全最佳實(shí)踐
強(qiáng)制 HTTPS:生產(chǎn)環(huán)境務(wù)必使用 HTTPS,防止密碼嗅探。
http.requiresChannel(channel -> channel.anyRequest().requiresSecure());
密碼策略:前端+后端雙重校驗(yàn)(長(zhǎng)度、復(fù)雜度)。
賬戶鎖定:多次失敗后臨時(shí)鎖定(可通過(guò)
DaoAuthenticationProvider擴(kuò)展)。
Session 管理:
http.sessionManagement(session -> session
.maximumSessions(1)
.expiredUrl("/login?expired=true")
);
安全頭設(shè)置:
http.headers(headers -> headers
.frameOptions().deny()
.contentTypeOptions().and()
.xssProtection().and()
.cacheControl()
);
OWASP Top 10 是 Web 安全的權(quán)威指南,值得每位開(kāi)發(fā)者閱讀。
總結(jié)
通過(guò)本文,我們系統(tǒng)地學(xué)習(xí)了如何在 Spring Security 中實(shí)現(xiàn)完全自定義的用戶名密碼登錄頁(yè)面。從基礎(chǔ)配置、頁(yè)面設(shè)計(jì)、數(shù)據(jù)庫(kù)集成,到高級(jí)功能如“記住我”、CSRF 防護(hù)、國(guó)際化和驗(yàn)證碼,每一步都配有可運(yùn)行的代碼示例。
自定義登錄頁(yè)面不僅是 UI 的替換,更是安全架構(gòu)的重要組成部分。合理的錯(cuò)誤處理、日志記錄、會(huì)話管理和輸入驗(yàn)證,能顯著提升應(yīng)用的安全性與用戶體驗(yàn)。
記?。?strong>安全不是功能,而是持續(xù)的過(guò)程。隨著業(yè)務(wù)發(fā)展,不斷審視和加固你的認(rèn)證機(jī)制,才能抵御日益復(fù)雜的網(wǎng)絡(luò)威脅。
下一步建議:
- 集成 OAuth2 / 微信登錄;
- 實(shí)現(xiàn)多因素認(rèn)證(MFA);
- 使用 JWT 替代表單登錄(適用于前后端分離)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot整合通用Mapper簡(jiǎn)化單表操作詳解
這篇文章主要介紹了springboot整合通用Mapper簡(jiǎn)化單表操作,通用Mapper是一個(gè)基于Mybatis,將單表的增刪改查通過(guò)通用方法實(shí)現(xiàn),來(lái)減少SQL編寫的開(kāi)源框架,需要的朋友可以參考下2019-06-06
使用原生JDBC動(dòng)態(tài)解析并獲取表格列名和數(shù)據(jù)的方法
這篇文章主要介紹了使用原生JDBC動(dòng)態(tài)解析并獲取表格列名和數(shù)據(jù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
詳解使用spring cloud config來(lái)統(tǒng)一管理配置文件
這篇文章主要介紹了詳解使用spring cloud config來(lái)統(tǒng)一管理配置文件,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12
Quarkus的Spring擴(kuò)展快速改造Spring項(xiàng)目
這篇文章主要為大家介紹了Quarkus的Spring項(xiàng)目擴(kuò)展,帶大家快速改造Spring項(xiàng)目示例演繹,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02
Java解析照片拿到GPS位置數(shù)據(jù)的詳細(xì)步驟
這篇文章主要介紹了Java解析照片拿到GPS位置數(shù)據(jù),本文給大家介紹代碼環(huán)境及核心代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
java 裝飾模式(Decorator Pattern)詳解
這篇文章主要介紹了java 裝飾模式(Decorator Pattern)詳解的相關(guān)資料,需要的朋友可以參考下2016-10-10

