前言
实际开发中一定存在新老系统交互,并且一些公司有专门的接口开放门面,一些都是一个渠道URL入口,通过业务代码&操作代码区分业务功能。这种存量系统一般都会提供clinetSDK,并且是接入方解析JSON。操作起来十分繁琐,开发人员在处理业务时还需关心底层连接&数据转换。导致开发成本增加并且后续维护难度增加,项目组兄弟们其他合作公司同事多次和我讨论这个问题。工作中遇见过这类情况,十分头疼。难道接口就不能优雅访问维护?类似于Mybatis的Mapper还有Feign,面向接口编程,开发人员只需要考虑业务领域的变化!当然feign不能异步访问,也没有业务接口线程pool级的隔离,每个接口的客制化超时比较繁琐!有了想法,并且工作需要咱们说干就干着手实现。
解决方案
思路
相关远程接口维护成一个大业务接口,咱们使用CGLIB/JDK的代理,增强每一个接口方法。然后统一的访问远程接口。下面是Feign功能的逻辑图。
代码实现
首先模拟Feign启动Feign
import com.example.order.common.scanner.MyRemoteInterfaceRegistrar;
import org.springframework.context.annotation.Import;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 自定义远程访问扫描器注解
*/
@Retention(RUNTIME)
@Target(TYPE)
//通过这玩意去注册bean
@Import(MyRemoteInterfaceRegistrar.class)
public @interface MyRemoteInterfaceScan {
/**
* 扫描包
* @return
*/
String[] value();
}
标记客户端
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 标记远程接口类的 ,后续想办法通过这玩意 注解继承 BaseMyRemoteInterface
*/
@Retention(RUNTIME)
@Target(TYPE)
public @interface MyRemoteInterfaceClient {
/**
* 类似于feignClient
* 然后支持默认的 HttpClint/timeout/retry 例如 okhttp 或者 doub OR netty 。。。。。。
*/
/**
* 根path
* @return
*/
String rootPath()default "";
int retry() default 0;
boolean isSync() default false;
int connTimeOut() default 1000;
int readTime()default 5000;
/**
* 后续使用枚举 model先简单搞
* @return
*/
String httpClientType() default "httpClient";
}
方法信息说明(异步&隔离线程&超时)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 说明 使用 TODO 什么restFu风格 ,retry配置 async配置
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRemoteInterfaceMethodAn {
String url();
/**
* 报文格式 json xml binary ....
* @return
*/
String messageType() default "json";
int retry() default 0;
boolean isSync() default false;
int connTimeOut() default 1000;
int readTime()default 5000;
/**
* 后续使用枚举 model先简单搞
* @return
*/
String httpClientType() default "httpClient";
}
1.告诉Spring有IOC注册器
import com.example.order.common.annotation.MyRemoteInterfaceScan;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* 注册bean 注册器
*/
public class MyRemoteInterfaceRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 拿扫描的信息
AnnotationAttributes ana = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MyRemoteInterfaceScan.class.getName()));
// List<String> basePacks = new LinkedList<>();
// basePacks.addAll(Arrays.asList(ana.getStringArray("value")));
MyRemoteInterfaceScanner scanner = new MyRemoteInterfaceScanner(registry);
scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
scanner.doScan( ana.getStringArray("value"));
}
}
2.自定义扫描器
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import java.lang.invoke.MethodHandles;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
/**
* 扫描器
*/
public class MyRemoteInterfaceScanner extends ClassPathBeanDefinitionScanner {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public MyRemoteInterfaceScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
@Override
public Set<BeanDefinitionHolder> doScan(String...basePacks){
Set<BeanDefinitionHolder> dbhs = super.doScan(basePacks);
Set<BeanDefinitionHolder> copy = new LinkedHashSet<>();
if(Objects.isNull(dbhs) || dbhs.size() <= 0){
LOGGER.warn("MyRemoteInterfaceScanner was not found in ':{}' package. ",basePacks);
}else {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : dbhs) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(MyRemoteInterfaceFactoryBean.class);
//支持注入
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
/**
* TODO 实现接口必须被 @MyRemoteInterfaceClient 标记 OR 继承BaseMyRemoteInterface
* 万一不小心扫描错误了就尴尬了
*/
copy.add(holder);
}
}
return copy;
}
/**
* 我们按feign一样只扫描那个接口类型的
* @param beanDefinition 信息
* @return
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition){
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
}
3.对远程接口做工厂Bean实现
远程接口都是接口,java是不允许实例化的,我在这里通过工厂bean拿他的代理对象。
mport com.example.order.common.inerface.MyRemoteInterfaceRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.invoke.MethodHandles;
/**
* MyRemoteInterface的FactoryBean
*/
public class MyRemoteInterfaceFactoryBean<T> implements FactoryBean<T> , InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private Class<T> target;
@Autowired
private MyRemoteInterfaceRegistry registrar;
public MyRemoteInterfaceFactoryBean() {
// intentionally empty
}
public MyRemoteInterfaceFactoryBean(Class<T> t) {
this.target = t;
}
@Override
public T getObject() throws Exception {
return registrar.getRemoteInterface(target);
}
@Override
public Class<?> getObjectType() {
return this.target;
}
@Override
public void afterPropertiesSet() throws Exception {
// 初始化ok注册这玩意 s
LOGGER.info("{}初始化成功现在注册",target.getName());
registrar.addRemoteInterface(target);
}
}
4.远程服务接口注册
import com.example.order.common.annotation.MyRemoteInterfaceScan;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 服务类注册中心
*/
@Component
@ConditionalOnBean(annotation = MyRemoteInterfaceScan.class)
@Slf4j
public class MyRemoteInterfaceRegistry {
/**
* 引用所有的远程接口类
*/
private final Map<Class<?>,MyRemoteInterfaceProxyFactory<?>> ALL_REMOTE_INTERFACE = new ConcurrentHashMap<>();
/**
* 存在这个远程接口不囖
* @param type 我想要判断的类型
* @param <T>
* @return
*/
public <T> boolean hasRemoteInterface(Class<T> type){
return this.ALL_REMOTE_INTERFACE.keySet().contains(type);
}
public <T> T getRemoteInterface(Class<T> type){
final MyRemoteInterfaceProxyFactory proxy = this.ALL_REMOTE_INTERFACE.get(type);
if(null == proxy){
throw new RuntimeException("xxx不存在");
}
try{
return (T) proxy.newInstance();
}catch (Exception e){
throw new RuntimeException("init err 。。。。");
}
}
public <T> void addRemoteInterface(Class<T> type){
log.info("MyRemoteInterface添加:{}",type.getName());
if(type.isInterface()){
if(hasRemoteInterface(type)){
throw new RuntimeException(type.getName()+"已存在不要。。。。。");
}
ALL_REMOTE_INTERFACE.put(type,new MyRemoteInterfaceProxyFactory(type));
}
}
}
5.使用方法级的增强
对类增强没有意义,我们的关注点是某一个接口,当前类没有意义,接口相关信息会尽量放的
MyRemoteInterfaceMethodAn传递
import com.example.order.common.annotation.MyRemoteInterfaceMethodAn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Objects;
/**
* 代理的InvocationHandler
* @param <T>
*/
public class MyRemoteInterfaceProxy<T> implements InvocationHandler, Serializable {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/**
* 每一个方法信息缓存
可以自己new
*/
private final Map<Method, MyRemoteInterfaceMethod> methodCache;
public MyRemoteInterfaceProxy(Map<Method, MyRemoteInterfaceMethod> methodCache) {
this.methodCache = methodCache;
}
/**
* 方法调用
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw t;
}
// 拿到了方法信息 真正的远程访问吧
return this.remoteRpc(proxy,method,args,cachedMethod(method));
}
private Object remoteRpc(Object proxy, Method method, Object[] args,MyRemoteInterfaceMethod methodInfo){
LOGGER.info("start-MyRemoteInterface-class:{},method:{},MethodInfo:{},req:{}",
proxy.getClass(),method.getName(),methodInfo,args);
//TODO
// step1 通过spring getBean 获取通讯处理器/ 开发门面ClientSDK
// step2 注入httpClient适配器
// STEP3 while( retry){重试通讯}
return null;
}
private MyRemoteInterfaceMethod cachedMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> getMethod(method));
}
private MyRemoteInterfaceMethod getMethod(Method method){
/**
* 解析 @MyRemoteInterfaceMethod
*/
MyRemoteInterfaceMethod mm = new MyRemoteInterfaceMethod(method.getGenericReturnType(),
method.getReturnType());
MyRemoteInterfaceMethodAn an = (MyRemoteInterfaceMethodAn)method.
getAnnotation(MyRemoteInterfaceMethodAn.class);
if(Objects.isNull(an)){
throw new RuntimeException("方法必须被MyRemoteInterfaceMethodAn标记");
}
mm.setAsync(an.isSync());
mm.setConnTimeOut(an.connTimeOut());
mm.setHttpClientType(an.httpClientType());
//......
return mm;
}
/**
* 调用默认方法 to See mybatis MapperProxy 这里是看mybatis的
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE
| MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
/**
* 是否为默认方法
*
* @param method
* @return
*/
private boolean isDefaultMethod(Method method) {
return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
&& method.getDeclaringClass().isInterface();
}
}
6.创建工厂
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/***
* 代理工厂
* @param <T> 被代理的东东
*/
public class MyRemoteInterfaceProxyFactory<T> {
private final Class<T> target;
/**
* 方法缓存啊 可以不需要 让 MyRemoteInterfaceProxy自己创建
*
*/
private final Map<Method,MyRemoteInterfaceMethod> methodCache = new ConcurrentHashMap<>();
public MyRemoteInterfaceProxyFactory(Class<T> t) {
this.target = t;
}
public Class<T> getTarget(){
return target;
}
public Map<Method,MyRemoteInterfaceMethod> getMethodCache(){
return this.methodCache;
}
@SuppressWarnings("unchecked" )
protected T newInstance(MyRemoteInterfaceProxy<T> proxy){
return (T) Proxy.newProxyInstance(target.getClassLoader(),new Class[]{target},proxy);
}
public T newInstance(){
return newInstance(new MyRemoteInterfaceProxy<T>(getMethodCache()));
}
}
7.接口信息实体
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.lang.reflect.Type;
/**
* 方法处理特性
* 例如: 支持 sync 和 重试次数 ,20s timeOut ....
*/
@Data
@ToString
public class MyRemoteInterfaceMethod implements Serializable {
// 返回值类型,可以获取泛型
private final Type rtType;
// 返回值类型
private final Class<?> rtClass;
/**
* 重试次数。。。。
*/
private Integer retry = 0 ;
private String url;
/**
* 报文格式 json xml binary ....
* @return
*/
private String messageType ;
private boolean isSync ;
private int connTimeOut ;
private int readTime ;
/**
* 后续使用枚举 model先简单搞
* @return
*/
private String httpClientType ;
private boolean isAsync = false;
public MyRemoteInterfaceMethod(Type t ,Class<?> c ){
this.rtClass = c ;
this.rtType = t ;
}
}
实操
1初始化信息
2启动项目
顺利注册代理
3.使用接口
第一次访问无Method信息
2023-02-04 17:49:39.557 INFO 14012 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2023-02-04 17:49:39.688 INFO 14012 --- [nio-8080-exec-1] c.e.o.c.inerface.MyRemoteInterfaceProxy : start-MyRemoteInterface-class:class com.sun.proxy.$Proxy116,method:getUserById,MethodInfo:MyRemoteInterfaceMethod(rtType=class java.lang.String, rtClass=class java.lang.String, retry=0, url=null, messageType=null, isSync=false, connTimeOut=1000, readTime=0, httpClientType=Netty, isAsync=false),req:[1231231dsfdf]
第二次有Method信息
总结
这种接口试代理得方案不仅仅只是针远程接口也可以应用在其他业务场景,如果你存在不同的想法OR方案请在评论区留下你的想法。代码规范不要说了,这是自己研究的东西,而且也只是用了2个多小时。