自定义注解实战-参数初始化

业务场景: 在使用Spring Security框架时,我们经常需要从SecurityContextHolder取出当前用户,每次取用的代码都是相同的,可以使用注解简化.
注解功能:

  • 标记在方法的参数上,可以自动将当前用户信息赋值给标记的参数
  • 提供一个isRequired参数,用来处理一些不是必须登录的业务场景

定义一个注解 @GetCurrentUser

import java.lang.annotation.*;

/**
 * 获取当前登录用户
 *
 * @author XuYang
 * @date 2023/9/14 10:35
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GetCurrentUser {

    /**
     * 是否必须登录
     * true:如果登录,正常初始化;如果没登录,抛出异常
     * false:如果登录,正常初始化;如果没登录,返回null
     */
    boolean isRequired() default true;

}

编写功能代码

用户模型类

package com.xuecheng.base.model;

import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 整个项目共用的当前用户的实体类
 *
 * @author XuYang
 * @date 2023/9/14 10:33
 */
@Data
public class CurrentUser implements Serializable {

    private static final long serialVersionUID = 1L;
    /**
     * id
     */
    private String id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 昵称
     */
    private String name;
    /**
     * 公司id
     */
    private String companyId;
    /**
     * 用户类型
     */
    private String utype;
    /**
     * 性别
     */
    private String sex;
    /**
     * 用户状态
     */
    private String status;
    /**
     * 注册时间
     */
    private LocalDateTime createTime;
}

主要逻辑

import cn.hutool.json.JSONUtil;
import com.xuecheng.base.model.CurrentUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

/**
 * 拿到参数后会将参数依次传入supportsParameter(),判断是否需要处理:
 * 不需要就下一个,需要则将该参数传入resolveArgument()
 * 在resolveArgument()可以对参数进行初始化操作。
 *
 * @author XuYang
 * @date 2023/9/14 11:24
 */
@Slf4j
public class GetCurrentUserResolver implements HandlerMethodArgumentResolver {

    /**
     * true表示需要初始化,false表示不需要初始化
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(GetCurrentUser.class);
    }

    /**
     * 初始化逻辑
     *
     * @param methodParameter 原参数对象
     * @return 初始化之后的参数
     */
    @Override
    public Object resolveArgument(MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest,
                                  WebDataBinderFactory webDataBinderFactory) {
        GetCurrentUser annotation = methodParameter.getParameterAnnotation(GetCurrentUser.class);
        //这里并不会空指针,因为supportsParameter已经判断了确定有这个参数,不然不会进入这个方法。
        boolean isRequire = annotation.isRequired();
        try {
            Object principalObj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            if (principalObj instanceof String) {
                return JSONUtil.toBean(principalObj.toString(), CurrentUser.class);
            }
        } catch (Exception e) {
            if (isRequire) {
                //抛出你的异常
                throw new RuntimeException("用户未登录!");
            }
            return null;
        }
        return null;
    }
}

让注解生效

package com.xuecheng.base.config;

import com.xuecheng.base.annotation.GetCurrentUserResolver;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * 将自己实现的注解参数处理器注册到WebMvcConfiguration中
 *
 * @author XuYang
 * @date 2023/9/14 10:21
 */
@Slf4j
@Configuration
public class AnnotationConfig implements WebMvcConfigurer {

    /**
     * 注册自定义的CurrentUserArgumentResolver
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new GetCurrentUserResolver());
        //这里可以添加更多自定义的参数处理器
    }
}

实战使用

  • 查询课程: 需要使用用户信息,来实现用户只能查询属于自己的课程
  • 这种情况,如果用户未登录,会直接抛出异常
    /**
     * 查询课程:分页查询、条件查询
     *
     * @author XuYang
     * @date 2023-02-28 16:56
     */
    @ApiOperation("查询课程:分页查询、条件查询")
    @PostMapping("/course/list")
    public PageResult<CourseBase> list(@GetCurrentUser CurrentUser user) {
        return courseBaseInfoService.queryCourseBaseList(user...);
    }
  • 查询视频: 收费视频需要用户登录之后判断是否购买过,免费课程则无所谓用户是否登录.
  • 这种情况,如果用户未登录,user为null,后续判断一下就可以根据登录状态进行业务逻辑处理
    /**
     * 获取视频
     *
     * @author XuYang
     * @date 2023/9/21 18:29
     */
    @ApiOperation("获取视频")
    @GetMapping("/open/learn/getvideo/...")
    public RestResponse<String> getVideo(@GetCurrentUser(isRequired = false) CurrentUser user) {
        //某些业务场景下允许不登录账号就能获取视频
        String userId = user != null ? user.getId() : null;
        return learningService.getVideo(userId,...);
    }

拓展知识

实现自定义注解获取当前用户的两种方式

  • 1.基于ElementType.PARAMETER和实现HandlerMethodArgumentResolver接口,就是本文展示的方法,这种可以直接将注解加在参数列表的参数上.
  • 2.基于ElementType.METHOD和AOP前置通知,这种方式需要遍历当前方法的参数列表再初始化参数。注解需要加在方法上,有点奇怪。

元注解

  • @Target(ElementType.PARAMETER)表示该注解将用于参数上
  • @Retention(RetentionPolicy.RUNTIME) 表示该注解将在程序运行时使用
  • @Documented 表示本注解参与生成Java文档

配置WebMvc

将自己实现的注解参数处理器注册到WebMvcConfiguration中时有两种方式:

  • extends WebMvcConfigurationSupport:提供了更高级的配置能力,适合进行全面的自定义配置。
  • implements WebMvcConfigurer:提供了对部分配置的扩展能力,适合进行简单的定制和扩展,并且可以保留默认的配置。

    这两种方式也是配置WebMvc的通用方式,一般我们都是使用implements WebMvcConfigurer.

评论

  1. 博主
    Windows Edge
    5月前
    2023-10-10 22:43:41

    哈哈红红火火恍恍惚惚

  2. 博主 置顶
    Windows Edge
    5月前
    2023-10-10 12:07:33

    红红火火恍恍惚惚哈哈哈哈哈哈哈哈ヾ(≧∇≦*)ゝ

发送评论 编辑评论


				
上一篇
下一篇