Spring cloud 通过Feign直接调用服务层的方法(类似RPC)

1.服务提供者实现(Controller接口)
package com.vz.controller.controller;

import cn.hutool.core.util.StrUtil;
import com.vz.common.constant.ErrorEnum;
import com.vz.common.exception.ServiceException;
import com.vz.common.util.R;
import com.vz.service.UserService;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
 * @author visy.wang
 * @date 2020/12/18 16:52
 */
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/invoke")
public class InvokeController {
	private static final Gson GSON = new Gson();
	private final UserService userService;
	//忽略的方法名前缀
	private static final String LAMBDA_PREFIX = "lambda$";
	private static final String CGLIB_PREFIX = "CGLIB$";	
	//反射实体及方法缓存
	private static Map<String, Object> servicesCache = Maps.newHashMap();
	private static Map<String, Method> methodsCache = Maps.newHashMap();

	@PostMapping("/invoke/{service}/{method}")
	public R<String> doInvoke(
		@PathVariable("service") String service,
		@PathVariable("method") String method,
		@RequestBody String params) {

		log.info("调用入参:{}.{}: {}", service, method, params);

		initServiceMethodCache();

		Object serviceObj = servicesCache.get(service);
		if(serviceObj == null){
			return new R<>("服务不存在", ErrorEnum.PARAM_ERROR.getCode());
		}

		String methodPath = service+"."+method;

		Method m;
		if((m = methodsCache.get(methodPath)) == null){
		    //R 是自定义的返回体(包含code, data, msg属性)
			return new R<>("方法不存在", ErrorEnum.PARAM_ERROR.getCode());
		}

		Class<?>[] paramTypes = m.getParameterTypes();

		try{
			List<Object> paramList = Lists.newArrayList();
			if(StrUtil.isNotBlank(params)){
				JSONArray jsonArr = new JSONArray(params);
				for(int i=0; i<jsonArr.length(); i++){
					String param = jsonArr.get(i).toString();
					paramList.add(GSON.fromJson(param, paramTypes[i]));
				}
			}

			String result = GSON.toJson(m.invoke(serviceObj, paramList.toArray()));
			return new R<>(result);
		}catch (Exception e){
			if(e instanceof InvocationTargetException
				&& ((InvocationTargetException)e).getTargetException() instanceof ServiceException){

				ServiceException serviceException
					= (ServiceException)(((InvocationTargetException)e).getTargetException());

				// 业务异常单独处理, 我的业务异常是ServiceException,
				// 可结合自己项目的业务异常改写 catch里的内容
				return new R<>(serviceException.getMessage(), serviceException.getErrorCode());

			}else{
				return new R<>("方法调用出错:"+e.getMessage(), ErrorEnum.SYSTEM_ERROR.getCode());
			}
		}
	}

    //反射相关变量缓存
	private void initServiceMethodCache(){
		if(!servicesCache.isEmpty()){
			return;
		}

		Class<?> clazz = userService.getClass();
		servicesCache.put("userService", userService);

		for(Method m: clazz.getDeclaredMethods()){
			m.setAccessible(true);
			methodsCache.put(getMethodPath("userService", m), m);
		}
	}

	private String getMethodPath(String serviceName, Method m){
		//末尾拼接参数数量,用以区分参数个数不同的重载函数
		//参数个数相同的重载函数暂无法区分,出现的话会覆盖,可自行提供实现
		String name = m.getName();
		if(name.startsWith(CGLIB_PREFIX) || name.startsWith(LAMBDA_PREFIX)){
			return null;
		}
		return serviceName + "." + name + "_" + m.getParameters().length;
	}
}
2.Feign接口实现
package com.vz.feign;

import com.vz.feign.factory.RemoteServiceFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * @author visy.wang
 * @date 2020/12/18 14:50
 */
@FeignClient(name = "服务提供方在注册中心的名称", fallbackFactory = RemoteServiceFactory.class)
public interface RemoteService {

	/**
	 * 方法统一调用接口
	 * @param method 方法名
	 * @param params 参数
	 * @return 返回结果
	 */
	@PostMapping("/invoke/invoke/{service}/{method}")
	String invoke(@PathVariable("service") String service,
						  @PathVariable("method") String method,
						  @RequestBody String params);

}

//以下是Feign调用异常处理(非必须)

package com.vz.feign.factory;

import com.vz.feign.RemoteService;
import com.vz.feign.fallback.RemoteServiceFallBackImpl;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

/**
 * @author visy.wang
 * @date 2020/12/18 14:53
 */
@Component
public class RemoteServiceFactory implements FallbackFactory<RemoteService> {

	@Override
	public RemoteService create(Throwable throwable) {
		RemoteServiceFallBackImpl remoteServiceFallBack = new RemoteServiceFallBackImpl();
		remoteServiceFallBack.setCause(throwable);
		return remoteServiceFallBack;
	}
}
package com.vz.feign.fallback;

import com.vz.common.constant.ErrorEnum;
import com.vz.common.util.R;
import com.vz.feign.RemoteService;
import com.google.gson.Gson;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @author visy.wang
 * @date 2020/12/18 14:52
 */
@Component
@Slf4j
public class RemoteServiceFallBackImpl implements RemoteService {
	private static final Gson GSON = new Gson();

	@Setter
	private Throwable cause;

	@Override
	public String invoke(String service, String method, String params) {
		return GSON.toJson(new R<>(cause.getMessage(), ErrorEnum.SYSTEM_ERROR.getCode()));
	}
}
3.调用:
package com.vz.service.impl;

import com.vz.common.exception.ServiceException;
import com.vz.common.util.R;
import com.vz.feign.RemoteService;
import com.vz.service.RemoteServiceAdapter ;
import com.google.gson.Gson;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * @author visy.wang
 * @date 2020/12/18 17:48
 */
@Service("remoteServiceAdapter ")
@AllArgsConstructor
public class RemoteServiceAdapter implements RemoteServiceAdapter {
	private RemoteService remoteService;
	private static final Gson GSON = new Gson();
	private static final String SUCCESS = "200";
	
	/**
	 * serviceName: 服务名称
	 * methodName:方法名
	 * clazz:返回类型Class
	 * args: 方法参数
	 */
    @Overrride
	public<E> E invoke(String serviceName, String methodName, Class<E> clazz, Object... args){
		String result = remoteService.invoke(serviceName, methodName+"_"+args.length, GSON.toJson(args));
		R r = GSON.fromJson(result, R.class);
		if(SUCCESS.equals(r.getCode().toString())){
			return GSON.fromJson(String.valueOf(r.getData()), clazz);
		}
		throw new ServiceException(r.getMsg(), r.getCode());
	}
}

在Controller调用

User user = new User();
//user.setXXX ...

//java.lang.Void代表方法没有返回值
remoteServiceAdapter.invoke("userService", "saveUser", Void.class, user );

Long userId = 100L;
User saveUser = remoteServiceAdapter.invoke("userService", "getById", User.class, userId);
4.附上一张自己画的远程服务方法调用的设计思路图

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Cloud是一个开源的分布式系统框架,用于构建和管理基于微服务架构的应用程序。RPC(远程过程调用)是一种在分布式系统中用于不同进程之间通信的技术。 在Spring Cloud中,RPC调用方式有几种可选择的方式,包括基于RestTemplate的同步调用和基于Feign的声明式调用。 1. 基于RestTemplate的同步调用:RestTemplate是Spring框架提供的用于发送 HTTP 请求的类,用于实现同步的RPC调用。通过创建RestTemplate对象,我们可以使用其提供的各种方法向特定URL发送请求,并获取响应结果。 使用RestTemplate进行RPC调用的步骤如下: - 创建一个RestTemplate对象。 - 根据需要设置RestTemplate的属性,如超时时间、拦截器等。 - 使用RestTemplate的方法发送HTTP请求并获取响应结果。 2. 基于Feign的声明式调用FeignSpring Cloud提供的基于RESTful风格的HTTP客户端,用于简化RPC调用过程。通过在接口上使用注解来定义HTTP请求的方式,Feign会自动帮我们生成实现类,并将接口方法转换为HTTP请求。 使用Feign进行RPC调用的步骤如下: - 在项目中添加Feign的依赖。 - 创建一个接口,使用@FeignClient注解标识服务的名称。 - 在接口中定义需要调用方法,使用注解来配置HTTP请求的细节。 - 注入该接口的实例,即可调用服务提供者的方法。 总结来说,Spring Cloud提供了多种方式实现RPC调用,包括基于RestTemplate的同步调用和基于Feign的声明式调用。开发人员可以根据具体需求选择适合的方式来进行RPC调用,以实现分布式应用程序的各个微服务之间的通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值