Spring?Boot?自定義注解實戰(zhàn)案例詳解
@RestController@Autowired@Transactional
自定義注解是一種強大的元編程工具,允許在不修改原有代碼邏輯的情況下,為程序添加額外的功能。通過AOP(面向切面編程)與自定義注解的結合,我們可以實現(xiàn)關注點分離,讓業(yè)務代碼更加清晰簡潔。
自定義注解有哪些好處?
代碼復用:將通用邏輯封裝到注解中
業(yè)務解耦:橫切關注點與核心業(yè)務邏輯分離
聲明式編程:通過注解配置行為,代碼更直觀
可維護性:通用邏輯集中管理,修改更方便
自定義注解的原理
Spring Boot 自定義注解的底層原理主要依賴于:
- Java 注解機制(@interface 定義注解)
- AOP(面向切面編程) 或 攔截器 結合反射來解析注解
- Spring 容器在運行時自動識別和織入邏輯
自定義注解的實現(xiàn)步驟
引入依賴
首先確保pom.xml中包含必要的依賴:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>定義自定義注解
以一個最基礎的自定義注解為例:
import java.lang.annotation.*;
@Target(ElementType.METHOD) // 注解作用目標:方法
@Retention(RetentionPolicy.RUNTIME) // 運行時生效
@Documented
public @interface MyAnnotation {
String value() default "default";
}注解說明:
@Target:指定注解作用的范圍(類、方法、字段、參數(shù)…)@Retention:指定注解生命周期(源碼、編譯期、運行時)@Documented:生成 Javadoc 時包含注解信息
常見的自定義注解案例
下面博主講完整演示幾個日常開發(fā)中我們常見的自定義注解案例來讓大家深入的了解
? 自定義日志注解
定義注解
效果:調用接口時,自動打印方法耗時和相關日志
/**
* 方法日志注解
* 用于自動記錄方法入?yún)?、出參和?zhí)行時間
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodLog {
String value() default "";
boolean printArgs() default true;
boolean printResult() default true;
boolean timing() default true;
}切面實現(xiàn)
這里僅僅以打印輸出為案例,實際生產(chǎn)環(huán)境中,小伙伴們可以結合數(shù)據(jù)庫、日志系統(tǒng)等將信息記錄入庫
@Aspect
@Component
@Slf4j
public class MethodLogAspect {
@Around("@annotation(methodLog)")
public Object around(ProceedingJoinPoint joinPoint, MethodLog methodLog) throws Throwable {
String methodName = getMethodName(joinPoint);
String className = joinPoint.getTarget().getClass().getSimpleName();
// 記錄開始時間
long startTime = System.currentTimeMillis();
if (methodLog.printArgs()) {
Object[] args = joinPoint.getArgs();
log.info("[{}#{}] 方法調用, 參數(shù): {}", className, methodName, Arrays.toString(args));
} else {
log.info("[{}#{}] 方法調用", className, methodName);
}
try {
Object result = joinPoint.proceed();
if (methodLog.printResult()) {
log.info("[{}#{}] 方法返回: {}", className, methodName, result);
}
if (methodLog.timing()) {
long cost = System.currentTimeMillis() - startTime;
log.info("[{}#{}] 方法執(zhí)行耗時: {}ms", className, methodName, cost);
}
return result;
} catch (Exception e) {
log.error("[{}#{}] 方法執(zhí)行異常: {}", className, methodName, e.getMessage());
throw e;
}
}
private String getMethodName(ProceedingJoinPoint joinPoint) {
return joinPoint.getSignature().getName();
}
}使用示例
以用戶接口為例,創(chuàng)建用戶的時候會記錄該接口會打印相關的信息日志
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping
@MethodLog(value = "創(chuàng)建用戶", printArgs = true, printResult = true, timing = true)
public User createUser(@RequestBody User user) {
// 業(yè)務邏輯
return userService.save(user);
}
@GetMapping("/{id}")
@MethodLog("根據(jù)ID查詢用戶")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}? 自定義參數(shù)校驗注解
通常我們在Controller中進行數(shù)據(jù)校驗都是用validation, 可以大大節(jié)省我們參數(shù)校驗的時間,雖然validation 默認的注解已經(jīng)足以應付我們工作中大部分場景,但還是會有一些參數(shù)校驗有其它的一些驗證要求,那么就可以用到自定義參數(shù)校驗注解。
你也查閱博主之前寫的 【Spring Boot數(shù)據(jù)校驗validation實戰(zhàn):寫少一半代碼,還更優(yōu)雅!】學習Spring Boot數(shù)據(jù)校驗
定義注解
效果:提交手機號不合法時,自動拋出校驗異常
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class) // 綁定校驗器
public @interface Phone {
String message() default "手機號格式錯誤";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}實現(xiàn)校驗器
這里就簡單驗證一下是否正確的手機號,小伙伴們可以加入自己需要的驗證邏輯,比如僅限移動用戶等
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneValidator implements ConstraintValidator<Phone, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && value.matches("^1[3-9]\\d{9}$");
}
}使用示例
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
@RestController
public class RegisterController {
@PostMapping("/register")
public String register(@Valid @RequestBody UserDTO userDTO) {
return "注冊成功";
}
public static class UserDTO {
@NotBlank
private String name;
@Phone
private String phone;
// getter/setter
}
}? 自定義權限校驗注解
本次我們模擬Spring Security中的@PreAuthorize注解,想完整學習@PreAuthorize注解用法的小伙伴可以參考博主Spring Security專欄下的 Spring Security方法級安全控制@PreAuthorize注解的靈活運用小結
這里我們就模擬一下全縣校驗的功能
定義注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckPermission {
String value(); // 權限標識
}實現(xiàn) AOP 權限校驗
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PermissionAspect {
@Before("@annotation(checkPermission)")
public void check(JoinPoint joinPoint, CheckPermission checkPermission) {
String requiredPermission = checkPermission.value();
// 模擬從上下文獲取當前用戶權限
String userPermission = "USER";
if (!userPermission.equals(requiredPermission)) {
throw new RuntimeException("權限不足,缺少:" + requiredPermission);
}
}
}使用示例
@RestController
public class AdminController {
@CheckPermission("ADMIN")
@GetMapping("/admin")
public String adminPage() {
return "管理員頁面";
}
}? 自定義分布式限流注解
定義注解
/**
* 限流注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
String key() default "";
int limit() default 100;
int timeWindow() default 60; // 時間窗口,單位:秒
String message() default "訪問過于頻繁,請稍后再試";
}
切面實現(xiàn)
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
private final Map<String, RateLimiter> limiterMap = new ConcurrentHashMap<>();
@Before("@annotation(rateLimit)")
public void rateLimitCheck(RateLimit rateLimit) {
String key = generateKey(rateLimit);
RateLimiter limiter = limiterMap.computeIfAbsent(key,
k -> RateLimiter.create(rateLimit.limit() / (double) rateLimit.timeWindow()));
if (!limiter.tryAcquire()) {
throw new RuntimeException(rateLimit.message());
}
}
private String generateKey(RateLimit rateLimit) {
String key = rateLimit.key();
if (StringUtils.isEmpty(key)) {
// 可以結合用戶信息、IP等生成唯一key
return "rate_limit:" + System.identityHashCode(rateLimit);
}
return "rate_limit:" + key;
}
}
// 簡單的令牌桶限流器實現(xiàn)
class RateLimiter {
private final double capacity;
private final double refillTokensPerOneMillis;
private double availableTokens;
private long lastRefillTimestamp;
public static RateLimiter create(double permitsPerSecond) {
return new RateLimiter(permitsPerSecond);
}
private RateLimiter(double permitsPerSecond) {
this.capacity = permitsPerSecond;
this.refillTokensPerOneMillis = permitsPerSecond / 1000.0;
this.availableTokens = permitsPerSecond;
this.lastRefillTimestamp = System.currentTimeMillis();
}
public synchronized boolean tryAcquire() {
refill();
if (availableTokens < 1) {
return false;
}
availableTokens -= 1;
return true;
}
private void refill() {
long currentTime = System.currentTimeMillis();
if (currentTime > lastRefillTimestamp) {
long millisSinceLastRefill = currentTime - lastRefillTimestamp;
double refill = millisSinceLastRefill * refillTokensPerOneMillis;
this.availableTokens = Math.min(capacity, availableTokens + refill);
this.lastRefillTimestamp = currentTime;
}
}
}使用示例
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/public/data")
@RateLimit(limit = 10, timeWindow = 60, message = "接口調用頻率超限")
public ApiResponse getPublicData() {
return ApiResponse.success("公開數(shù)據(jù)");
}
@PostMapping("/submit")
@RateLimit(key = "submit_limit", limit = 5, timeWindow = 30)
public ApiResponse submitData(@RequestBody Data data) {
// 處理提交
return ApiResponse.success("提交成功");
}
}? 自定義加解密注解
可參考博主之前寫的 SpringBoot整合Jasypt使用自定義注解+AOP實現(xiàn)敏感字段加解密進行學習,這里就不再贅述了!
總結
以上通過4個案例演示,完整講解了Spring Boot 自定義注解的使用,通過合理使用自定義注解,我們可以大幅提升代碼的可讀性、可維護性和復用性。在實際項目中,可以根據(jù)業(yè)務需求靈活組合和擴展這些注解,構建更加健壯和安全的應用程序。
到此這篇關于Spring Boot 自定義注解實戰(zhàn)案例詳解的文章就介紹到這了,更多相關Spring Boot 自定義注解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java使用JCommander實現(xiàn)解析命令行參數(shù)
jcommander?是一個只有幾十?kb?的?Java?命令行參數(shù)解析工具,可以通過注解的方式快速實現(xiàn)命令行參數(shù)解析,本文就來和大家介紹一下JCommander是如何解析命令行參數(shù)吧2023-06-06
Spring Boot 通過注解實現(xiàn)數(shù)據(jù)校驗的方法
這篇文章主要介紹了Spring Boot 通過注解實現(xiàn)數(shù)據(jù)校驗的方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09
Java?C++題解leetcode消失的兩個數(shù)字實例
這篇文章主要介紹了Java?C++題解leetcode消失的兩個數(shù)字實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09

