最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot實現(xiàn)隱式參數(shù)注入的完整指南

 更新時間:2025年11月10日 08:18:35   作者:風(fēng)象南  
這篇文章主要為大家詳細(xì)介紹了SpringBoot實現(xiàn)隱式參數(shù)注入的相關(guān)方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

前言:一個痛點

想象一下這樣的場景:用戶請求帶著 JWT Token 進(jìn)入你的系統(tǒng),F(xiàn)ilter 層面解析 Token 得到用戶 ID,接下來需要:

  • 在 Controller 層獲取用戶信息
  • 在 Service 層進(jìn)行權(quán)限驗證
  • 在某些業(yè)務(wù)邏輯中記錄操作日志

每一個環(huán)節(jié)都需要知道"當(dāng)前用戶是誰",看看目前常用的解決方案。

傳統(tǒng)方案的"缺陷"

方案一:ThreadLocal

// 看起來很"Hack"
private static final ThreadLocal<Long> currentUser = new ThreadLocal<>();
  • 線程安全問題:在異步環(huán)境下可能出現(xiàn)數(shù)據(jù)錯亂
  • 內(nèi)存泄漏風(fēng)險:ThreadLocal 使用不當(dāng)可能導(dǎo)致內(nèi)存泄漏
  • 代碼可讀性差:隱式的數(shù)據(jù)傳遞方式讓代碼邏輯變得難以追蹤

方案二:HttpServletRequest.setAttribute()

// 看起來很"丑"
@GetMapping("/me")
public User getMyProfile(HttpServletRequest request) {
    Long userId = (Long) request.getAttribute("currentUserId");
    return userService.getById(userId);
}
  • 類型不安全:需要手動進(jìn)行類型轉(zhuǎn)換,容易出現(xiàn) ClassCastException
  • 代碼冗余:每個需要用戶信息的 Controller 都要重復(fù)相同的邏輯
  • 違反了 Controller 的"純凈性":引入了 Servlet API 依賴
  • 字符串魔法值:屬性名稱容易寫錯,編譯時無法檢查

Spring MVC:HandlerMethodArgumentResolver

Spring MVC 提供了一個解決方案:自定義參數(shù)解析器(HandlerMethodArgumentResolver)。

這個設(shè)計模式體現(xiàn)了 Spring 框架一貫的"約定優(yōu)于配置"的理念。它不要求我們改變 Filter 層面的實現(xiàn),而是在參數(shù)解析這個環(huán)節(jié)做文章,通過擴(kuò)展框架的能力來解決問題。

設(shè)計思路

Spring MVC 的 HandlerMethodArgumentResolver 機(jī)制實際上是一種"適配器模式"的應(yīng)用。它將不同來源的參數(shù)(Request 參數(shù)、Path 變量、Header 信息、Session 數(shù)據(jù)等)統(tǒng)一適配成 Controller 方法可以直接使用的形式。

這種設(shè)計的巧妙之處在于:

  • 職責(zé)分離:Filter 負(fù)責(zé)認(rèn)證和設(shè)置狀態(tài),Resolver 負(fù)責(zé)參數(shù)轉(zhuǎn)換
  • 可擴(kuò)展性:可以輕松添加新的參數(shù)解析邏輯
  • 無侵入性:不影響現(xiàn)有的代碼結(jié)構(gòu)

這個方案的核心思想是:將 request.getAttribute() 操作,封裝成類型安全的方法參數(shù)。

核心實現(xiàn)

第一步:創(chuàng)建自定義注解

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}

第二步:實現(xiàn)參數(shù)解析器

@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 只解析被 @CurrentUser 標(biāo)記的參數(shù)
        return parameter.hasParameterAnnotation(CurrentUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                 ModelAndViewContainer mavContainer,
                                 NativeWebRequest webRequest,
                                 WebDataBinderFactory binderFactory) throws Exception {
        // 從 request 中獲取之前設(shè)置的用戶ID
        return webRequest.getAttribute("currentUserId", WebRequest.SCOPE_REQUEST);
    }
}

第三步:注冊解析器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private CurrentUserArgumentResolver currentUserArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(currentUserArgumentResolver);
    }
}

方案示例

Filter 層面

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtService jwtService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                   HttpServletResponse response,
                                   FilterChain filterChain) throws ServletException, IOException {

        String authorizationHeader = request.getHeader("Authorization");

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            String token = authorizationHeader.substring(7);
            try {
                Long userId = jwtService.extractUserId(token);
                // 標(biāo)準(zhǔn):使用 request.setAttribute 設(shè)置
                request.setAttribute("currentUserId", userId);
            } catch (Exception e) {
                // token 無效,繼續(xù)執(zhí)行后續(xù)邏輯
            }
        }

        filterChain.doFilter(request, response);
    }
}

Controller 層面

@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/me")
    public ResponseEntity<User> getCurrentUser(@CurrentUser Long userId) {
        // userId 被"魔法般"地自動注入了!
        User user = userService.findById(userId);
        return ResponseEntity.ok(user);
    }

    @PutMapping("/me")
    public ResponseEntity<User> updateCurrentUser(@CurrentUser Long userId,
                                                 @RequestBody UserUpdateRequest request) {
        // 每個 Controller 方法都可以直接使用 @CurrentUser
        User updatedUser = userService.updateUser(userId, request);
        return ResponseEntity.ok(updatedUser);
    }

    @GetMapping("/permissions")
    public ResponseEntity<List<Permission>> getUserPermissions(@CurrentUser Long userId) {
        // 完全類型安全,無需手動類型轉(zhuǎn)換
        List<Permission> permissions = userService.getUserPermissions(userId);
        return ResponseEntity.ok(permissions);
    }
}

原理解析

HandlerMethodArgumentResolver 工作流程

在這個流程中,Spring MVC 會按照注冊的順序遍歷所有的 HandlerMethodArgumentResolver,對于每個需要解析的參數(shù),都會調(diào)用 supportsParameter() 方法判斷是否支持,如果支持則調(diào)用 resolveArgument() 方法進(jìn)行實際的參數(shù)解析。

方案優(yōu)勢

這個方案的優(yōu)勢不僅體現(xiàn)在代碼層面,更重要的是它符合軟件工程的多個重要原則:

1. 類型安全:編譯時檢查,避免運行時類型轉(zhuǎn)換錯誤。當(dāng)你錯誤地將 @CurrentUser Long 寫成 @CurrentUser String 時,編譯器會立刻提醒你。

2. 代碼簡潔:Controller 方法專注于業(yè)務(wù)邏輯。不再需要每次都寫 request.getAttribute() 的樣板代碼,讓業(yè)務(wù)邏輯更加清晰。

3. 可測試性:Mock 變得簡單直接。在單元測試中,你只需要模擬參數(shù)值,而不需要構(gòu)建整個 HttpServletRequest 對象。

4. 可維護(hù)性:統(tǒng)一的用戶信息獲取方式。當(dāng)需要修改用戶信息的獲取邏輯時,只需要修改 Resolver,而不需要修改每個 Controller 方法。

5. 擴(kuò)展性:輕松支持更多用戶相關(guān)屬性。通過修改 Resolver 的邏輯,可以支持返回 User 對象、用戶權(quán)限、用戶偏好設(shè)置等復(fù)雜信息。

6. 關(guān)注點分離:Filter 專注于認(rèn)證邏輯,Resolver 專注于參數(shù)解析,Controller 專注于業(yè)務(wù)邏輯,各司其職,代碼結(jié)構(gòu)更加清晰。

進(jìn)階用法:傳遞完整用戶對象

在真實的項目中,我們往往需要的不僅僅是用戶 ID,而是完整的用戶信息、權(quán)限數(shù)據(jù)、或者用戶偏好設(shè)置。HandlerMethodArgumentResolver 的強(qiáng)大之處就在于它可以智能地根據(jù)參數(shù)類型返回不同的對象。

擴(kuò)展解析器支持復(fù)雜對象

這里的核心思想是根據(jù) Controller 方法的參數(shù)類型動態(tài)決定返回什么對象,這樣可以最大程度地提高代碼的靈活性和復(fù)用性。

@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {

    @Autowired
    private UserService userService;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                 ModelAndViewContainer mavContainer,
                                 NativeWebRequest webRequest,
                                 WebDataBinderFactory binderFactory) throws Exception {

        Long userId = (Long) webRequest.getAttribute("currentUserId", WebRequest.SCOPE_REQUEST);

        if (userId == null) {
            return null; // 或者拋出異常
        }

        // 根據(jù)參數(shù)類型決定返回什么
        Class<?> parameterType = parameter.getParameterType();

        if (parameterType == Long.class || parameterType == long.class) {
            return userId;
        } else if (parameterType == User.class) {
            return userService.findById(userId);
        } else if (parameterType == UserProfile.class) {
            return userService.getUserProfile(userId);
        }

        throw new IllegalArgumentException("Unsupported parameter type: " + parameterType);
    }
}

Controller 中的多種用法

@RestController
@RequestMapping("/api")
public class AdvancedUserController {

    @GetMapping("/user/id")
    public ResponseEntity<String> getUserId(@CurrentUser Long userId) {
        return ResponseEntity.ok("User ID: " + userId);
    }

    @GetMapping("/user/info")
    public ResponseEntity<User> getUserInfo(@CurrentUser User user) {
        return ResponseEntity.ok(user);
    }

    @GetMapping("/user/profile")
    public ResponseEntity<UserProfile> getUserProfile(@CurrentUser UserProfile profile) {
        return ResponseEntity.ok(profile);
    }
}

實戰(zhàn)技巧與注意事項

在實際項目中使用 HandlerMethodArgumentResolver 時,還需要考慮一些實際的工程問題。下面是一些常見的場景和解決方案。

1. 異常處理

在用戶未登錄或者 Token 無效的情況下,我們需要優(yōu)雅地處理異常情況,而不是讓系統(tǒng)拋出難以理解的錯誤信息。

@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public Object resolveArgument(...) throws Exception {
        Long userId = (Long) webRequest.getAttribute("currentUserId", WebRequest.SCOPE_REQUEST);

        if (userId == null) {
            throw new UnauthorizedException("用戶未登錄");
        }

        return userId;
    }
}

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UnauthorizedException.class)
    public ResponseEntity<String> handleUnauthorized(UnauthorizedException e) {
        return ResponseEntity.status(401).body(e.getMessage());
    }
}

2. 與 Spring Security 集成

在許多企業(yè)級應(yīng)用中,我們使用 Spring Security 進(jìn)行認(rèn)證和授權(quán)。如何將 Spring Security 的用戶信息與我們的自定義 Resolver 結(jié)合使用是一個常見問題。

Spring Security 提供了 SecurityContextHolder 來存儲當(dāng)前用戶的認(rèn)證信息,我們可以直接從中獲取用戶詳情,然后轉(zhuǎn)換成我們需要的格式。這種集成方式的優(yōu)勢是可以復(fù)用 Spring Security 的完整認(rèn)證體系,包括各種認(rèn)證方式(JWT、OAuth2、Session 等)。

@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public Object resolveArgument(...) throws Exception {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication != null && authentication.isAuthenticated()) {
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            return Long.parseLong(userDetails.getUsername()); // 假設(shè)username存儲的是userId
        }

        return null;
    }
}

3. 多租戶場景

在 SaaS 應(yīng)用中,多租戶是一個常見的需求。除了當(dāng)前用戶信息,我們還需要知道當(dāng)前租戶的信息。通過創(chuàng)建多個自定義注解和對應(yīng)的 Resolver,我們可以輕松實現(xiàn)這種需求。

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentTenant {
}

@Component
public class CurrentTenantArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentTenant.class);
    }

    @Override
    public Object resolveArgument(...) throws Exception {
        return webRequest.getAttribute("currentTenantId", WebRequest.SCOPE_REQUEST);
    }
}

@GetMapping("/tenant/data")
public ResponseEntity<List<Data>> getTenantData(@CurrentUser Long userId,
                                               @CurrentTenant Long tenantId) {
    // 同時獲取當(dāng)前用戶和租戶信息
    List<Data> data = dataService.findByUserAndTenant(userId, tenantId);
    return ResponseEntity.ok(data);
}

總結(jié)

通過 Spring MVC 的 HandlerMethodArgumentResolver我們實現(xiàn)了一個可以"跨 Filter 與 Controller 傳參"的技術(shù)實現(xiàn)方案。將底層 Servlet API 的 request.getAttribute() 操作抽象為編譯時類型安全的方法參數(shù)注入,實現(xiàn)了框架層面的參數(shù)解析適配,既保持了架構(gòu)的純凈性,又提供了強(qiáng)大的擴(kuò)展能力。

到此這篇關(guān)于SpringBoot實現(xiàn)隱式參數(shù)注入的完整指南的文章就介紹到這了,更多相關(guān)SpringBoot隱式參數(shù)注入內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java加速讀取復(fù)制超大文件

    Java加速讀取復(fù)制超大文件

    這篇文章主要為大家詳細(xì)介紹了Java加速讀取復(fù)制超大文件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • 解決@Test注解在Maven工程的Test.class類中無法使用的問題

    解決@Test注解在Maven工程的Test.class類中無法使用的問題

    這篇文章主要介紹了解決@Test注解在Maven工程的Test.class類中無法使用的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • spring-boot實現(xiàn)增加自定義filter(新)

    spring-boot實現(xiàn)增加自定義filter(新)

    本篇文章主要介紹了spring-boot實現(xiàn)增加自定義filter(新),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • Java的集合LinkedHashSet詳解

    Java的集合LinkedHashSet詳解

    這篇文章主要介紹了Java的集合LinkedHashSet詳解,LinkedHashSet介于HashSet和TreeSet之間,它也是一個hash表,但是同時維護(hù)了一個雙鏈表來記錄插入的順序,需要的朋友可以參考下
    2023-09-09
  • JVM內(nèi)存與CPU占用過高問題定位及解決

    JVM內(nèi)存與CPU占用過高問題定位及解決

    文章介紹了一種通過排查思路定位Java程序中CPU和內(nèi)存占用過高的問題的方法,通過運行一個模擬無限添加byte[]的代碼,使用top命令查看進(jìn)程占用情況,然后通過查看線程列表和輸出堆棧信息,最終定位到具體的代碼方法
    2026-01-01
  • java使用htmlparser提取網(wǎng)頁純文本例子

    java使用htmlparser提取網(wǎng)頁純文本例子

    這篇文章主要介紹了java使用htmlparser提取網(wǎng)頁純文本例子,需要的朋友可以參考下
    2014-04-04
  • javaweb項目如何實現(xiàn)手機(jī)短信登錄

    javaweb項目如何實現(xiàn)手機(jī)短信登錄

    這篇文章主要介紹了javaweb項目如何實現(xiàn)手機(jī)短信登錄,手機(jī)號登錄在現(xiàn)在的項目中用的場景非常多,實現(xiàn)起來也不難,今天我們就一起來通過演示實現(xiàn)登錄過程,需要的朋友可以參考下
    2019-07-07
  • 解決spring中redistemplate不能用通配符keys查出相應(yīng)Key的問題

    解決spring中redistemplate不能用通配符keys查出相應(yīng)Key的問題

    這篇文章主要介紹了解決spring中redistemplate不能用通配符keys查出相應(yīng)Key的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • java錯誤:無效的源發(fā)行版:18解決辦法圖文詳解

    java錯誤:無效的源發(fā)行版:18解決辦法圖文詳解

    在Java開發(fā)中,如果你遇到錯誤: 無效的源發(fā)行版,這通常意味著你正在使用的Java編譯器(通常是javac)被配置為編譯一個比你的JDK 版本更高,這篇文章主要給大家介紹了關(guān)于java錯誤:無效的源發(fā)行版:18的解決辦法,需要的朋友可以參考下
    2024-08-08
  • Spring AOP之@Around,@AfterReturning使用、切不進(jìn)去的解決方案

    Spring AOP之@Around,@AfterReturning使用、切不進(jìn)去的解決方案

    這篇文章主要介紹了Spring AOP之@Around,@AfterReturning使用、切不進(jìn)去的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05

最新評論

阳原县| 白山市| 新巴尔虎左旗| 印江| 南开区| 林周县| 前郭尔| 崇明县| 普安县| 民权县| 南皮县| 鄱阳县| 红安县| 密云县| 永清县| 大名县| 柏乡县| 阿克| 桦甸市| 调兵山市| 会理县| 名山县| 商丘市| 德阳市| 南江县| 乾安县| 建水县| 长岛县| 丹东市| 法库县| 嵩明县| 长丰县| 瓮安县| 鹤山市| 禄劝| 洪湖市| 中阳县| 宜昌市| 牟定县| 中卫市| 安吉县|