spring cloud alibaba实用攻略一 Gateway中基于ThreadLocal实现Controller参数的AOP注解注入

文章目录

  • ReactiveRequestContextFilter
  • ReactiveRequestContextHolder
  • 自定义注解及基于注解的AOP处理
    • 需要注入的信息枚举
    • 自定义注解
    • aop切面

项目在GateWay服务中集成了权限认证以及用户信息等接口,需要经常使用Headers里的信息,但reactor环境下没有实现类似于mvc环境下的RequestContextHolder。通过ReactiveRequestContextFilter获取ServerHttpRequest,基于ThreadLocal实现了ReactiveRequestContextHolder,以便于在AOP中获取Request信息并注入Controller。

ReactiveRequestContextFilter

package com.taylor.gateway.config.context;

import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;

/**
 *
 * reactor does not contain a {@link RequestContextHolder} object that has the same effect as mvc.
 * If you need to get the current request from the context, you need to manually configure it.
 *
 * Bind ServerWebExchange to {@link Context} via {@link ReactiveRequestContextHolder} after intercepting with filter.
 *
 * from {https://gitee.com/596392912/mica/tree/master/mica-boot/src/main/java/net/dreamlu/mica/reactive/context}
 * 使用 {@link Context} 的方式不能解决此问题 存入成功之后使用 {@link Mono#subscriberContext} 进行获取是无法成功的
 *
 * 改为ThreadLocal的方式
 *
 */
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class ReactiveRequestContextFilter implements WebFilter, Ordered {
     

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
     
        ServerHttpRequest request = exchange.getRequest();
        ReactiveRequestContextHolder.put(request);
        return chain.filter(exchange)
                //Clear request save in ThreadLocal to prevent memory overflow
                .doFinally(s -> ReactiveRequestContextHolder.reset());

        //Invalid way
//        return chain.filter(exchange)
//                .subscriberContext(ctx -> ctx.put(ReactiveRequestContextHolder.CONTEXT_KEY, request));
    }

    @Override
    public int getOrder() {
     
        return -20;
    }
}

ReactiveRequestContextHolder

package com.taylor.gateway.config.context;

import org.springframework.core.NamedThreadLocal;
import org.springframework.http.server.reactive.ServerHttpRequest;

import java.util.Objects;

/**
 *
 * Used to bind the current request and context and provide a way to get the current request
 *
 */
public class ReactiveRequestContextHolder {
     

//    private static final Class CONTEXT_KEY = ServerWebExchange.class;
//
//    /**
//     * Gets the {@code Mono} from Reactor {@link Context}
//     *
//     * @return the {@code Mono}
//     */
//    public static Mono getExchange() {
     
//        return Mono.subscriberContext()
//                .map(ctx -> ctx.get(CONTEXT_KEY));
//    }
//
//    /**
//     * Gets the {@code Mono} from Reactor {@link Context}
//     *
//     * @return the {@code Mono}
//     */
//    public static Mono getRequest() {
     
//        return ReactiveRequestContextHolder.getExchange()
//                .map(ServerWebExchange::getRequest);
//    }
//
//    /**
//     * Put the {@code ServerWebExchange} to Reactor {@link Context}
//     *
//     * @param context  Context
//     * @param exchange ServerWebExchange
//     * @return the Reactor {@link Context}
//     */
//    public static Context put(Context context, ServerWebExchange exchange) {
     
//        return context.put(CONTEXT_KEY, exchange);
//    }

    private static final ThreadLocal<ServerHttpRequest> requests = new NamedThreadLocal<>("Thread ServerHttpRequest");

    /**
     * Store {@link ServerHttpRequest} to {@link ThreadLocal} in the current thread
     *
     * @param request {@link ServerHttpRequest}
     */
    public static void put(ServerHttpRequest request){
     

        //When the request time out, the reset will be invalid
        //because the timeout thread is the thread of hystrix, not the thread of HTTP
        if(Objects.nonNull(get())) {
     
            reset();
        }

        if(request != null){
     
            requests.set(request);
        }
    }

    /**
     * Get the current thread {@link ServerHttpRequest} from {@link ThreadLocal}
     *
     * @return {@link ServerHttpRequest}
     */
    public static ServerHttpRequest get(){
     
        ServerHttpRequest request = requests.get();
        return request;
    }

    /**
     * Clear the current thread {@link ServerHttpRequest} from {@link ThreadLocal}
     */
    public static void reset(){
     
        requests.remove();
    }
}

自定义注解及基于注解的AOP处理

需要注入的信息枚举

package com.taylor.gateway.config.annotation;

import lombok.Getter;

/**
 *
 * 参数名
 */
@Getter
public enum Param {
     

    /**
     * 来源系统
     */
    PLATFORM("Platform"),
    /**
     * token
     */
    ACCESSTOKEN("Authentication"),
    /**
     * 身份ID
     */
    IDENTITYID("IdentityID"),
    /**
     * 账号ID
     */
    USERID("userId"),
    /**
     * 账号编号
     */
    USERNO("userNo"),
    /**
     * 用户名
     */
    USERNAME("userName");

    String name;

    Param(String name) {
     
        this.name = name;
    }
}

自定义注解

package com.taylor.gateway.config.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *
 * 指定要注入的参数
 *
 */
@Target({
     ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionParam {
     

    Param name();

    boolean required() default true;

}


package com.taylor.gateway.config.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *
 * 注解切入点
 *
 */
@Target({
     ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionParamAutowired {
     }

aop切面

package com.taylor.gateway.config.annotation;

import com.taylor.gateway.config.context.ReactiveRequestContextHolder;
import com.taylor.gateway.config.exception.MissingServerRequestParameterException;
import com.taylor.gateway.security.token.JwtAccessTokenInfo;
import com.taylor.gateway.security.token.JwtClientUserInfo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import java.lang.reflect.Parameter;
import java.util.Objects;

/**
 *
 * 对接口进行切面进行参数的注入,使其能在接口中正常获取到需要的常用参数
 * 目前仅对常规参数类型添加了支持
 *
 */
@Aspect
@Component
public class SessionParamWrapper {
     

    @Autowired
    private RedisTemplate redisTemplate;

    @Pointcut("@within(com.taylor.gateway.config.annotation.SessionParamAutowired) || @annotation(com.taylor.gateway.config.annotation.SessionParamAutowired)")
    public void annotationWrapper(){
     }

    @Around("annotationWrapper()")
    public Object annotationHandler(ProceedingJoinPoint joinPoint) throws Throwable {
     

        ServerHttpRequest request = ReactiveRequestContextHolder.get();

        Objects.requireNonNull(request, "Cannot retrieve the current request from the context");

        Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
        Object[] args = joinPoint.getArgs();

        for (int i = 0; i < parameters.length; i++) {
     
            SessionParam sessionParam = parameters[i].getAnnotation(SessionParam.class);
            if (Objects.nonNull(sessionParam)) {
     
                Param name = sessionParam.name();
                Object value = this.getValue(name, sessionParam.required(), request);
                args[i] = value;
            }
        }

        return joinPoint.proceed(args);
    }

    private Object getValue(Param name, boolean required, ServerHttpRequest request) {
     
        Object value = null;
        if (Param.PLATFORM == name) {
     
            value = request.getHeaders().getFirst(Param.PLATFORM.getName());
        } else if (Param.IDENTITYID == name) {
     
            value = request.getHeaders().getFirst(Param.IDENTITYID.getName());
        } else if (Param.ACCESSTOKEN == name) {
     
            value = request.getHeaders().getFirst(Param.ACCESSTOKEN.getName());
        } else {
     
            //获取用户相关信息
            //这里要改一下 不应该每次获取值都去序列化一次 如果需要用户信息的地方比较多 就提前进行获取
            String jti = request.getHeaders().getFirst(Param.IDENTITYID.getName());
            Objects.requireNonNull(jti, "Unable to obtain identity id from request information");

            Object obj = redisTemplate.opsForValue().get(jti);
            Objects.requireNonNull(obj, "Unable to read information based on identity id, probably because id has expired");

            JwtAccessTokenInfo tokenInfo = (JwtAccessTokenInfo) obj;
            JwtClientUserInfo userInfo = tokenInfo.getUserInfo();
            switch (name) {
     
                case USERID : value = userInfo.getUserId(); break;
                case USERNO : value = userInfo.getUserNo(); break;
                case USERNAME : value = userInfo.getUsername(); break;
                default:
                    break;
            }
        }

        if (value == null && required) {
     
            throw new MissingServerRequestParameterException
                    ("Cannot get the required value from the request information, -> " + name.getName());
        }
        return value;
    }
}

这里注解的功能较为简单,后续将设计更多功能,实现map以及pojo对象属性的注入.

你可能感兴趣的:(知识总结,aop,spring,java,gateway,react)