源碼地址:https://github.com/huangyichun/remotetransfer
本文涉及知識(shí)點(diǎn):
- 自定義注解
- 動(dòng)態(tài)代理
- Spring bean加載
- Java 8 優(yōu)化的策略模式
項(xiàng)目背景:由于原直播系統(tǒng)采用SpringCloud部署在虛擬機(jī)(僅使用SpringCloud的注冊(cè)中心)车胡,現(xiàn)打算使用k8s部署恨搓,由于k8s和SpringCloud功能很多重合了方仿,如果在k8s系統(tǒng)使用SpringCloud將無(wú)法用到k8s的注冊(cè)中心、負(fù)載均衡等功能都弹,而且項(xiàng)目部署也過(guò)臃腫。 因此有2種方案:
-
方案一
采用spring-cloud-starter-kubernetes 組件替換SpringCloud匙姜,該方案存在一個(gè)問(wèn)題畅厢,研發(fā)需要熟悉k8s功能,而且未來(lái)項(xiàng)目必須使用k8s部署氮昧。
-
方案二
采用Http請(qǐng)求替換SpringCloud的注冊(cè)中心Feign調(diào)用框杜。由于接口較多浦楣,如果每個(gè)接口都改動(dòng)工作量較大,也不利于代碼維護(hù)咪辱。因此使用自定義注解來(lái)替換@FeignClient注冊(cè)振劳。
具體實(shí)現(xiàn):
-
創(chuàng)建自定義注解
/** * 遠(yuǎn)程調(diào)用,替換SpringCloud Feign * @Author: huangyichun * @Date: 2021/2/22 */ @Target(ElementType.TYPE) @Inherited @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RemoteTransfer { String hostName(); }
-
定義handler接口用于處理實(shí)際業(yè)務(wù)邏輯
/** * @Author: huangyichun * @Date: 2021/2/24 */ public interface RemoteTransferHandler { Object handler(String host, Method method, Object[] args); }
- 采用反射動(dòng)態(tài)創(chuàng)建類油狂,并注冊(cè)到Spring容器中
創(chuàng)建 RemoteTransferRegister類历恐,并實(shí)現(xiàn)BeanFactoryPostProcessor接口,該接口的方法postProcessBeanFactory是在bean被實(shí)例化之前被調(diào)用的专筷。這樣保證了自定義注解聲明的Bean能被其他模塊Bean依賴弱贼。
/**
* @Author: huangyichun
* @Date: 2021/2/23
*/
@Slf4j
@Component
public class RemoteTransferRegister implements BeanFactoryPostProcessor {
/**
* 設(shè)置掃描的包
*/
private static final String SCAN_PATH = "com.huang.web";
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
RemoteTransferInvoke transferInvoke = new RemoteTransferInvoke(invokeRestTemplate());
//掃描注解聲明類
Set<Class<?>> classSet = new Reflections(SCAN_PATH).getTypesAnnotatedWith(RemoteTransfer.class);
for (Class<?> cls : classSet) {
log.info("create proxy class name:{}", cls.getName());
//動(dòng)態(tài)代理的handler
InvocationHandler handler = (proxy, method, args) -> transferInvoke.invoke(cls, method, args);
//生成代理類
Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{cls}, handler);
//注冊(cè)到Spring容器
beanFactory.registerSingleton(cls.getName(), proxy);
}
}
private RestTemplate invokeRestTemplate() {
//生成restTemplate 省略代碼
}
}
-
創(chuàng)建RemoteTransferInvoke類,處理動(dòng)態(tài)代理類的請(qǐng)求
支持解析@GetMapping磷蛹、@PostMapping吮旅、@PutMapping、以及@DeleteMapping味咳。使用策略模式+Map優(yōu)化 if else 語(yǔ)句庇勃。具體代碼如下
@Slf4j @Data public class RemoteTransferInvoke { private final RestTemplate restTemplate; /** * 用于實(shí)際遠(yuǎn)程調(diào)用處理 */ private Map<Class<? extends Annotation>, RemoteTransferHandler> requestMethodMap = new HashMap<>(); public RemoteTransferInvoke(RestTemplate restTemplate) { this.restTemplate = restTemplate; init(); } /** * 使用策略模式+Map 去除 */ private void init() { requestMethodMap.put(GetMapping.class, (host, method, args) -> { GetMapping methodAnnotation = method.getAnnotation(GetMapping.class); String path = methodAnnotation.value()[0]; return getOrDeleteMapping(method, args, host, path, HttpMethod.GET); }); requestMethodMap.put(PutMapping.class, (host, method, args) -> { PutMapping methodAnnotation = method.getAnnotation(PutMapping.class); String path = methodAnnotation.value()[0]; return putOrPostMapping(method, args, host, path, HttpMethod.PUT); }); requestMethodMap.put(PostMapping.class, (host, method, args) -> { PostMapping methodAnnotation = method.getAnnotation(PostMapping.class); String path = methodAnnotation.value()[0]; return putOrPostMapping(method, args, host, path, HttpMethod.POST); }); requestMethodMap.put(DeleteMapping.class, (host, method, args) -> { DeleteMapping methodAnnotation = method.getAnnotation(DeleteMapping.class); String path = methodAnnotation.value()[0]; return getOrDeleteMapping(method, args, host, path, HttpMethod.DELETE); }); } /** * 動(dòng)態(tài)代理調(diào)用方法 * @param tClass 類 * @param method 方法 * @param args 請(qǐng)求參數(shù) * @return 返回值 */ public Object invoke(Class<?> tClass, Method method, Object[] args) { RemoteTransfer remoteAnnotation = tClass.getAnnotation(RemoteTransfer.class); String host = RemoteTransferConfig.map.get(remoteAnnotation.hostName()); Annotation[] annotations = method.getAnnotations(); Optional<Annotation> first = Arrays.stream(annotations).filter(annotation1 -> requestMethodMap.containsKey(annotation1.annotationType())).findFirst(); Preconditions.checkArgument(first.isPresent(), "注解使用錯(cuò)誤"); Annotation methodAnnotation = first.get(); return requestMethodMap.get(methodAnnotation.annotationType()).handler(host, method, args); } private Object putOrPostMapping(Method method, Object[] args, String host, String url, HttpMethod httpMethod) { url = RemoteTransferUtil.dealPathVariable(method, args, url); UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(host + url); HttpHeaders httpHeaders = new HttpHeaders(); HttpEntity<JSONObject> entity = new HttpEntity<>(RemoteTransferUtil.extractBody(method, args), httpHeaders); ResponseEntity<?> exchange = restTemplate.exchange(builder.toUriString(), httpMethod, entity, method.getReturnType()); return exchange.getBody(); } private Object getOrDeleteMapping(Method method, Object[] args, String host, String url, HttpMethod httpMethod) { UriComponentsBuilder builder = buildGetUrl(method, args, host, url); HttpHeaders httpHeaders = new HttpHeaders(); HttpEntity<JSONObject> entity = new HttpEntity<>(null, httpHeaders); return this.restTemplate.exchange(builder.toUriString(), httpMethod, entity, method.getReturnType()).getBody(); } private UriComponentsBuilder buildGetUrl(Method method, Object[] args, String host, String url) { url = RemoteTransferUtil.dealPathVariable(method, args, url); UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(host + url); for (Map.Entry<String, String> entry : RemoteTransferUtil.extractParams(method, args).entrySet()) { builder.queryParam(entry.getKey(), entry.getValue()); } return builder; }
-
配置類
用于獲取注解的host地址
@Component public class RemoteTransferConfig { public static Map<String, String> map = new HashMap<>(); @Value("${remote.transfer.host}") private String port; @PostConstruct public void init() { map.put("TRADE-PLAY", port); } }
-
工具類
/** * @Author: huangyichun * @Date: 2021/2/23 */ @Slf4j public class RemoteTransferUtil { /** * 獲取Path參數(shù) * @param method 方法 * @param args 值 * @return */ public static Map<String, String> extractPath(Method method, Object[] args) { Map<String, String> params = new HashMap<>(); Parameter[] parameters = method.getParameters(); if (parameters.length == 0) { return params; } for (int i = 0; i < parameters.length; i++) { PathVariable param = parameters[i].getAnnotation(PathVariable.class); if (param != null) { params.put(param.value(), String.valueOf(args[i])); } } return params; } /** * 獲取請(qǐng)求body * @param method * @param args * @return */ public static JSONObject extractBody(Method method, Object[] args) { JSONObject object = new JSONObject(); Parameter[] parameters = method.getParameters(); if (parameters.length == 0) { return null; } for (int i = 0; i < parameters.length; i++) { RequestBody param = parameters[i].getAnnotation(RequestBody.class); if (param != null) { String returnStr = JSON.toJSONString(args[i], SerializerFeature.WriteMapNullValue, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteDateUseDateFormat); object = JSONObject.parseObject(returnStr); } } return object; } /** * 處理url的Path * @param method 方法 * @param args 值 * @param url url * @return */ public static String dealPathVariable(Method method, Object[] args, String url) { for (Map.Entry<String, String> entry : RemoteTransferUtil.extractPath(method, args).entrySet()) { if (url.contains("{" + entry.getKey() + "}")) { url = url.replace("{" + entry.getKey() + "}", entry.getValue()); } } return url; } /** * 處理請(qǐng)求參數(shù) * @param method 方法 * @param args 值 * @return */ public static LinkedHashMap<String, String> extractParams(Method method, Object[] args) { LinkedHashMap<String, String> params = new LinkedHashMap<>(); Parameter[] parameters = method.getParameters(); if (parameters.length == 0) { return params; } for (int i = 0; i < parameters.length; i++) { RequestParam param = parameters[i].getAnnotation(RequestParam.class); if (param != null) { params.put(param.value(), String.valueOf(args[i])); } } return params; } }
注解的具體使用:
@RemoteTransfer(hostName = "TRADE-PLAY")
public interface TradePlayServiceApi {
@GetMapping("/open/get")
String test(@RequestParam("type") String type);
@PostMapping("/open/post")
ResponseResult post(@RequestBody PostRequest request);
}
測(cè)試類:
@SpringBootTest
class WebApplicationTests {
@Autowired
private TradePlayServiceApi tradePlayServiceApi;
@Test
public void get() {
String type = tradePlayServiceApi.test("type");
System.out.println(type);
}
@Test
public void post() {
PostRequest request = new PostRequest();
request.setId("id");
PostRequest.Person person = new PostRequest.Person("name", "age");
request.setPerson(person);
ResponseResult post = tradePlayServiceApi.post(request);
System.out.println(post);
}
}
最終測(cè)試通過(guò),請(qǐng)求正常返回莺葫。