文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Feign 请求动态URL方式

2024-04-02 19:55

关注

Feign 请求动态URL

注意事项

代码如下:

FeignClient类:

@CompileStatic
@FeignClient(name = "xxxxClient")
public interface XxxFeignClient {
    @RequestLine("POST")
    ResponseDto notifySomething(URI baseUri, ApproveNotifyDto notifyDto);
    
    @RequestLine("GET")
    ResponseDto getSomething(URI baseUri, @QueryMap Map<String, String> queryMap)
  
}

ClientCaller类:

@CompileStatic
@Slf4j
@Component
@Import(FeignClientsConfiguration.class)
public class CallerService {
    private XxxFeignClient xxxFeignClient
    @Autowired
    public CallerService(Decoder decoder, Encoder encoder) {
        xxxFeignClient = Feign.builder()
                //.client(client)
                .encoder(encoder)
                .decoder(decoder)
                .target(Target.EmptyTarget.create(XxxFeignClient.class))
    }
    public ResponseDto notifySomething(String url, XxxxDto dto) {
        return xxxFeignClient.notifySomething(URI.create(url), dto)
    }
    
    public String getSomething(String url, String userId) {
        return xxxFeignClient.getSomething(URI.create(url), ["userId": userId])
    }
}

Feign重写URL以及RequestMapping

背景

由于项目采用的是 消费层 + API层(FeignClient) +服务层 的方式。 

导致原项目有太多代码冗余复制的地方。例如FeignClient上要写@RequestMapping,同时要写请求路径,消费层还要写上RequestBody等等,比较麻烦。遂改进了一下方案,简化日常代码开发的工作量

场景

项目的架构是微服务架构,SpringBoot与SpringCloud均为原生版本。

效果展示

feign层无需写RequestMapping以及RequestBody或者RequestParam等

public interface UserDemoApi {
    
    ApiResponse<UserDemo> add(UserDemo userDemo);
    
    ApiResponse<UserDemo> findByUserIdAndUsername(String userId,String username);
    
    ApiResponse<PageResult<UserDemo>> findByCondition(UserDemoReqVo reqVo);
}
@FeignClient(value = "${api.feign.method.value}",fallback = UserDemoFeignApiFallbackImpl.class)
public interface UserDemoFeignApi extends UserDemoApi {
}

整体思路

实现

Feign的服务端层

1. 继承RequestMappingHandlerMapping,重写getMappingForMethod

重写的内容

public class CustomerRequestMapping extends RequestMappingHandlerMapping {
    private final static String METHOD_MAPPING = "/remote/%s/%s";
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method,handlerType);
        if(requestMappingInfo!=null){
            if(handlerType.isAnnotationPresent(CustomerAnnotation.class)){
                if(requestMappingInfo.getPatternsCondition().getPatterns().isEmpty()||
                        requestMappingInfo.getPatternsCondition().getPatterns().contains("")){
                    String[] path = getMethodPath(method, handlerType);
                    requestMappingInfo = RequestMappingInfo.paths(path).build().combine(requestMappingInfo);
                }
            }
        }else{
            if(handlerType.isAnnotationPresent(CustomerAnnotation.class)){
                String[] path = getMethodPath(method, handlerType);
                requestMappingInfo = RequestMappingInfo.paths(path).methods(RequestMethod.POST).build();
            }
        }
        return requestMappingInfo;
    }
    private String[] getMethodPath(Method method, Class<?> handlerType) {
        Class<?>[] interfaces = handlerType.getInterfaces();
        String methodClassName = interfaces[0].getSimpleName();
        methodClassName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, methodClassName);
        String[] path = {String.format(METHOD_MAPPING,methodClassName, method.getName())};
        return path;
    }

覆盖RequestMappingHandlerMapping

@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class VersionControlWebMvcConfiguration implements WebMvcRegistrations {
    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new CustomerRequestMapping();
    }
}

2. 服务层Controller

@RestController
@CustomerAnnotation
@RequestMapping
public class UserDemoController implements UserDemoFeignApi {
    private final static Logger logger = LoggerFactory.getLogger(UserDemoController.class);
    @Resource
    private UserDemoService userDemoService;
    @Override
    public ApiResponse<UserDemo> add(UserDemo userDemo) {
        logger.info("request data:<{}>",userDemo);
        return userDemoService.add(userDemo);
    }
    @Override
    public ApiResponse<UserDemo> findByUserIdAndUsername(String userId,String username) {
        logger.info("request data:<{}>",userId+":"+username);
        return ApiResponse.success(new UserDemo());
    }
    @Override
    public ApiResponse<PageResult<UserDemo>> findByCondition(UserDemoReqVo reqVo) {
        logger.info("request data:<{}>",reqVo);
        return userDemoService.findByCondition(reqVo);
    }
}

自此,Fiegn的服务端的配置已经配置完毕。现在我们来配置Feign的客户端

Feign的客户端配置

重写Feign的上下文SpringMvcContract

核心代码为重写processAnnotationOnClass的内容。

特殊情况处理:在正常情况下,多参数且没有@RequestParams参数注解的情况下,Feign会直接抛异常且终止启动。所以需要对多参数做额外处理

@Component
public class CustomerContact extends SpringMvcContract {
    private final static Logger logger = LoggerFactory.getLogger(CustomerContact.class);
    private final static String METHOD_PATTERN_BY = "By";
    private final static String METHOD_PATTERN_AND = "And";
    private final Map<String, Method> processedMethods = new HashMap<>();
    private final static String METHOD_MAPPING = "/remote/%s/%s";
    private Map<String, Integer> parameterIndexMap = new ConcurrentHashMap<>(100);
    public CustomerContact() {
        this(Collections.emptyList());
    }
    public CustomerContact(List<AnnotatedParameterProcessor> annotatedParameterProcessors) {
        this(annotatedParameterProcessors,new DefaultConversionService());
    }
    public CustomerContact(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
        super(annotatedParameterProcessors, conversionService);
    }
    
    @Override
    protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
        RequestTemplate template = data.template();
        String configKey = data.configKey();
        if(StringUtils.isBlank(template.url())){
            template.append(getTemplatePath(configKey));
            template.method(RequestMethod.POST.name());
        }
        // 构造查询条件
        templateQuery(template,data);
        super.processAnnotationOnClass(data, clz);
    }
    
    private void templateQuery(RequestTemplate template,MethodMetadata data){
        try{
            String configKey = data.configKey();
            if(manyParameters(data.configKey())){
                Method method = processedMethods.get(configKey);
                String methodName = method.getName();
                String key = getTemplatePath(configKey);
                Integer parameterIndex = 0;
                if(parameterIndexMap.containsKey(key)){
                    parameterIndexMap.put(key,parameterIndex++);
                }else{
                    parameterIndexMap.put(key,parameterIndex);
                }
                int index = methodName.indexOf(METHOD_PATTERN_BY);
                if(index>=0){
                    String[] parametersName = methodName.substring(index+METHOD_PATTERN_BY.length()).split(METHOD_PATTERN_AND);
                    String parameterName = parametersName[parameterIndex];
                    String caseName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, parameterName);
                    Collection<String> param = addTemplatedParam(template.queries().get(parameterName), caseName);
                    template.query(caseName,param);
                    setNameParam(data,caseName,parameterIndex);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
            logger.error("template construct query failed:<{}>",e.getMessage());
        }
    }
    
    private String getTemplatePath(String configKey) {
        Method method = processedMethods.get(configKey);
        int first = configKey.indexOf("#");
        String apiName = configKey.substring(0,first);
        String methodClassName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, apiName);
        return String.format(METHOD_MAPPING,methodClassName,method.getName());
    }
    @Override
    public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        this.processedMethods.put(Feign.configKey(targetType, method), method);
        return super.parseAndValidateMetadata(targetType,method);
    }
    @Override
    protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
        if(manyParameters(data.configKey())){
            return true;
        }
        return super.processAnnotationsOnParameter(data,annotations,paramIndex);
    }
    private void setNameParam(MethodMetadata data, String name, int i) {
        Collection<String>
                names =
                data.indexToName().containsKey(i) ? data.indexToName().get(i) : new ArrayList<String>();
        names.add(name);
        data.indexToName().put(i, names);
    }
    
    private boolean manyParameters(String configKey){
        Method method = processedMethods.get(configKey);
        return method.getParameterTypes().length > 1;
    }

最后还有一处修改

由于我们在方法上没有写上RequestBody注解,所以此处需要进行额外的处理

@Configuration
public class CustomerArgumentResolvers implements HandlerMethodArgumentResolver {
    // 基本数据类型
    private static final Class[] BASE_TYPE = new Class[]{String.class,int.class,Integer.class,boolean.class,Boolean.class, MultipartFile.class};
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //springcloud的接口入参没有写@RequestBody,并且是自定义类型对象 也按JSON解析
        if (AnnotatedElementUtils.hasAnnotation(parameter.getContainingClass(), FeignClient.class)) {
            if(parameter.getExecutable().getParameters().length<=1){
                return true;
            }
        }
        return false;
    }
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        final Type type = parameter.getGenericParameterType();
        String parameters = getParameters(nativeWebRequest);
        if(applyType(type)){
            return parameters;
        }else {
            return JSON.parseObject(parameters,type);
        }
    }
    private String getParameters(NativeWebRequest nativeWebRequest) throws Exception{
        HttpServletRequest servletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        String jsonBody = "";
        if(servletRequest!=null){
            ServletInputStream inputStream = servletRequest.getInputStream();
            jsonBody =  IOUtils.toString(inputStream);
        }
        return jsonBody;
    }
    private boolean applyType(Type type){
        for (Class classType : BASE_TYPE) {
            if(type.equals(classType)){
                return true;
            }
        }
        return false;
    }
}
@Configuration
public class CustomerConfigAdapter implements WebMvcConfigurer {
    @Resource
    private CustomerArgumentResolvers customerArgumentResolvers;
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(customerArgumentResolvers);
    }
}

以上就是配置的所有内容,整体代码量很少。

但可能需要读下源码才能理解 

这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯