API架构解析一 项目的启动和加载

逻辑本质

  1. 各API均为web项目,启动首先加载部署描述符文件web.xml,加载的过程中会读取并 加载servlet的配置文件。可以在<listener></listener>标签中配置启动listener初始化启动逻辑,或者在servlet的配置文件中配置<beanid=”apiService”></bean>并在ApiService的构造器中初始化启动逻辑。

加载拦截器Interceptor

  1. 定义拦截器接口
public interface ApiIntercept {
    Object invoke(ApiWrapper wrapper) throws Exception;
}
  • 拦截器接口中定义一个调用的方法,其形参是基于适配器模式构造的拦截器调用链,其中具体的API业务请求也封装到一个拦截器中,作为拦截器链的最后一环去执行业务逻辑,下文会详细介绍。
  • 可以实现ApiIntercept接口,并扩展其功能,如定义可配置过滤规则的拦截器,并将规则配置在配置文件中,作为所有接口通用的拦截配置,在项目启动的时候加载。

项目启动时加载类和类的方法

  1. 类似于SpringMVC的Controller,定义注解用于匹配接口访问的路径,注解中也可以配置Intercept用于实现接口级粒度的个性化拦截,暂时称这样的类为Adapter。
  2. 通过注解找到所有的Adapter类,解析Adapter类上的注解配置,并通过反射拿到Adapter的所有方法,解析方法上的注解配置,一个Adapter类的注解配置加上其某个方法的注解配置可以完整地定义到一次具体的http接口请求路径。
  3. 构造完成一次API请求的内存存储单元
  • 以Adapter类实例,类的注解配置,类中的方法和方法的注解配置,以及配置文件中的通用拦截器来构造一次接口调用的静态存储单元ApiModel,在项目启动的时候预先加载到内存中。
  • 要构造ApiModel首先要拿到方法的形参信息,为便于封装使用,可以定义方法形参的注解,用以标注引用类型的请求参数,对于非引用类型的参数通过其他的机制使参数可以正常传递进来,而方法形参列表的类型可以通过反射取到。
  • 至此可以定义一个ApiModel的大致结构
public class ApiModel {
    private final Api api;     //具体执行逻辑业务的Api结构
    private final String id;
    private final List<ApiIntercept> interceptList; //拦截器链
    private final Class paramClass; //形参类型,当前支持一个,后续扩展
    }

其中Api是执行具体接口逻辑调用的,可定义成如下接口

public interface Api<Result, Param> {
    Result execute(Param param, RequestContext context) throws Exception;
}

具体到针对不同业务场景的接口,可以定义实现此Api接口的功能类,重写其execute方法实现业务逻辑处理,下文会详细介绍。

  1. 加载的核心代码
private static final Map<String, ApiModel> apiMap = new ConcurrentHashMap<String, ApiModel>(128);//绝对api配置

private final List<FilterApiIntercept> apiIntercepts = new LinkedList<FilterApiIntercept>();

 private void initApi() throws Exception {
        //获取配置文件中的通用拦截器
        Map<String, FilterApiIntercept> interceptMap = SpringUtil.getBeansByType(FilterApiIntercept.class);
        List<FilterApiIntercept> tempIntercepts = new ArrayList<FilterApiIntercept>(interceptMap.values());
        Collections.sort(tempIntercepts);
        apiIntercepts.addAll(tempIntercepts);

        Map<String, ApiModel> apiModelMap = ApiLoadUtil.loadApi(apiIntercepts);
        apiMap.putAll(apiModelMap);
    }

 public static Map<String, ApiModel> loadApi(List<FilterApiIntercept> apiIntercepts) throws Exception {
        Map<String, ApiModel> apiMap = new HashMap<String, ApiModel>();//绝对api配置
        //扫描自定义注解@ApiDesc
        Map<String, Object> map = SpringUtil.getBeansWithAnnotation(ApiDesc.class);
        for (Object obj : map.values()) {
            Class clazz = obj.getClass();
            ApiDesc classApiDesc = obj.getClass().getAnnotation(ApiDesc.class);
            if (classApiDesc != null) {
                    for (Method method : clazz.getMethods()) {
                        int m = method.getModifiers();
                        if (Modifier.isPublic(m)) {
                            if (!method.isAccessible()) {
                                method.setAccessible(true);
                            }
                            ApiDesc methodApiDesc = method.getAnnotation(ApiDesc.class);
                            if (methodApiDesc != null) {
                                //根据类实例,方法,类注解,方法注解,通用拦截器配置生成静态存储单元ApiModel
                                ApiModel model = getApiModelByMethod(obj, method, classApiDesc, methodApiDesc, apiIntercepts);
                                if (apiMap.containsKey(model.getId())) {
                                    throw new Exception("api is repeat,id=" + classApiDesc.id() + "," +
                                            "class=" + obj.getClass());
                                } else {
                                    apiMap.put(model.getId(), model);
                                }
                            }
                        }
                    }
               
            } else {
                logger.error("ERROR!! Api class=" + clazz + " is not has ApiDesc");
            }
        }
        return apiMap;
    }
  • 生成静态存储单元ApiModel的核心代码如下
private static ApiModel getApiModelByMethod(Object obj, Method method, ApiDesc classApiDesc, ApiDesc methodApiDesc,
                                                List<FilterApiIntercept> filterApiIntercepts) throws Exception {
        String aid = classApiDesc.id() + methodApiDesc.id();
        //通过反射拿到方法的形参列表的类型,这里List的泛型参数V2<V1,V2>是自定义的泛型工具类,好使
        List<V2<Class, Annotation[]>> paramsDesc = getParamDescByMethod(method);
        Class paramClass = Object.class;
        //ParamGen是定义的参数生成器接口,根据请求上下文对象RequestContext,组合生成参数
        List<ParamGen> list = new ArrayList<ParamGen>(paramsDesc.size());
        for (V2<Class, Annotation[]> v : paramsDesc) {
            if (paramClass == Object.class && v.getV2() != null) {
                for (Annotation annotation : v.getV2()) {
                    //@Param是自定义的标志方法参数是引用类型的注解,目前暂时支持一个
                    if (annotation instanceof Param) {
                        paramClass = v.getV1();
                        break;
                    }
                }
            }
            //ParamGenEnum是参数生成器接口ParamGen的实现枚举类,其中预制了接口请求方法中支持的的形参类型,包括注解@Param声明的,和其他参数类型
            //每种参数类型对应一个枚举值,并重写了ParamGen接口的参数生成方法,根据RequestContext,生成具体的参数。
            if (v.getV1() == paramClass) {
                list.add(ParamGenEnum.QParam); //@Param注解标志对应的枚举类型
            } else {
                list.add(ParamGenEnum.getParamGenByClass(v.getV1())); //根据形参类型,获取其对应的枚举值
            }
        }
        //ApiHandler是定义具体接口接口执行轨迹接口Api的一个实现类,其中定义了较为通用的接口逻辑处理方法,通过反射执行。
        ApiHandler api = new ApiHandler(aid, obj, method, list);
        List<Class<? extends ApiIntercept>> classes = new LinkedList<Class<? extends ApiIntercept>>(Arrays.asList(classApiDesc.intercept()));
        classes.addAll(Arrays.asList(methodApiDesc.intercept())); //解析标注于注解@ApiDesc上的类注解级别和方法注解级别的拦截器
        //构造环绕拦截器调用链,并把最终的逻辑业务执行轨迹的Api,封装在调用链的最后一环。
        List<ApiIntercept> intercepts = InterceptUtil.getInterceptBy(aid, api, filterApiIntercepts, classes);

        return new ApiModel(api, aid, intercepts, paramClass);
    }
    
    
     private static List<ApiIntercept> genApiInterceptList(Api api,     List<ApiIntercept> list) throws Exception {
        ArrayList<ApiIntercept> result = new ArrayList<ApiIntercept>(list.size() + 1);
        result.addAll(list);
        result.add(new BaseIntercept(api));
        return result;
    }
    
    
     public class BaseIntercept implements ApiIntercept {
        private final Api api;
    
        public BaseIntercept(Api api) {
            this.api = api;
        }
    
        @Override
        public Object invoke(ApiWrapper wrapper) throws Exception {
            RequestContext context = wrapper.getContext();
            return api.execute(context.getParam(), context);
        }
    }
    
    
    public final class ApiHandler implements Api<Object, Object> {
        private static final Object[] emptyParams = new Object[0];
        private final Method apiMethod;
        private final Object apiObj;
        private final List<ParamGen> paramGenList;
        private final String aid;
    
    
        @Override
        public Object execute(Object o, RequestContext context) throws Exception {
            Object params[] = getParams(paramGenList, context); //根据对应的枚举值从请求上下文中获取参数
            return apiMethod.invoke(apiObj, params); //反射执行业务
        }
    
        private static Object[] getParams(List<ParamGen> paramGenList, RequestContext context) {
            if (ListUtil.isNotEmpty(paramGenList)) {
                Object[] result = new Object[paramGenList.size()];
                for (int i = 0; i < result.length; i++) {
                    result[i] = paramGenList.get(i).genParam(context); //枚举重写生成器接口ParamGen的genParam方法
                }
                return result;
            }
            return emptyParams;
        }
    }
  • 其中BaseIntercept是整个InterceptChain的最后一环,其包装了业务执行轨迹Api实例,在这里BaseIntercept持有的Api实例指向Api接口实现类ApiHandler的一个引用。在接口执行时,ApiHandler将会通过预制的参数枚举类从RequestContext中获取对应的真实参数,然后通过反射执行具体的业务逻辑方法。
  • 请求上下文的构造: 基于servlet的web请求,其请求参数可以通过HttpServletRequest拿到,这些请求参数包括file类型的数据,可以存储在自定义的请求上下文的数据结构中(RequestContext)。API项目启动后,已经加载了Adapter类和其方法,其方法上的形参类型也可以通过反射拿到;在一次web请求中,发现方法形参是引用类型,可以将RequestContext中的参数JSON序列化成指定的引用类型。

转载于:https://my.oschina.net/wangyusheng/blog/1530038

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值