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

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

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

前言:一個(gè)痛點(diǎn)

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

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

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

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

方案一:ThreadLocal

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

方案二:HttpServletRequest.setAttribute()

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

Spring MVC:HandlerMethodArgumentResolver

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

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

設(shè)計(jì)思路

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

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

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

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

核心實(shí)現(xiàn)

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

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

第二步:實(shí)現(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);
    }
}

第三步:注冊(cè)解析器

@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 無(wú)效,繼續(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 被"魔法般"地自動(dòng)注入了!
        User user = userService.findById(userId);
        return ResponseEntity.ok(user);
    }

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

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

原理解析

HandlerMethodArgumentResolver 工作流程

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

方案優(yōu)勢(shì)

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

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

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

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

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

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

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

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

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

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

這里的核心思想是根據(jù) Controller 方法的參數(shù)類型動(dòng)態(tài)決定返回什么對(duì)象,這樣可以最大程度地提高代碼的靈活性和復(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);
    }
}

實(shí)戰(zhàn)技巧與注意事項(xiàng)

在實(shí)際項(xiàng)目中使用 HandlerMethodArgumentResolver 時(shí),還需要考慮一些實(shí)際的工程問(wèn)題。下面是一些常見(jiàn)的場(chǎng)景和解決方案。

1. 異常處理

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

@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è)級(jí)應(yīng)用中,我們使用 Spring Security 進(jìn)行認(rèn)證和授權(quán)。如何將 Spring Security 的用戶信息與我們的自定義 Resolver 結(jié)合使用是一個(gè)常見(jiàn)問(wèn)題。

Spring Security 提供了 SecurityContextHolder 來(lái)存儲(chǔ)當(dāng)前用戶的認(rèn)證信息,我們可以直接從中獲取用戶詳情,然后轉(zhuǎn)換成我們需要的格式。這種集成方式的優(yōu)勢(shì)是可以復(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存儲(chǔ)的是userId
        }

        return null;
    }
}

3. 多租戶場(chǎng)景

在 SaaS 應(yīng)用中,多租戶是一個(gè)常見(jiàn)的需求。除了當(dāng)前用戶信息,我們還需要知道當(dāng)前租戶的信息。通過(guò)創(chuàng)建多個(gè)自定義注解和對(duì)應(yīng)的 Resolver,我們可以輕松實(shí)現(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) {
    // 同時(shí)獲取當(dāng)前用戶和租戶信息
    List<Data> data = dataService.findByUserAndTenant(userId, tenantId);
    return ResponseEntity.ok(data);
}

總結(jié)

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

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

相關(guān)文章

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

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

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

    解決@Test注解在Maven工程的Test.class類中無(wú)法使用的問(wèn)題

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

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

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

    Java的集合LinkedHashSet詳解

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

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

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

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

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

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

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

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

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

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

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

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

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

最新評(píng)論

丹棱县| 商洛市| 清镇市| 平泉县| 临海市| 东台市| 新泰市| 方正县| 建德市| 鄂托克前旗| 西乌珠穆沁旗| 南平市| 栾川县| 成都市| 屏山县| 婺源县| 衢州市| 台东市| 措勤县| 米泉市| 田阳县| 新干县| 伊宁县| 綦江县| 邳州市| 青海省| 繁昌县| 盈江县| 虎林市| 崇信县| 施甸县| 榆树市| 濮阳市| 屏南县| 峨边| 固始县| 临城县| 惠来县| 大冶市| 新龙县| 武鸣县|