All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zhuanglide.micrboot.mvc.ApiDispatcher Maven / Gradle / Ivy

There is a newer version: 1.0.1
Show newest version
package com.zhuanglide.micrboot.mvc;

import com.zhuanglide.micrboot.http.HttpContextRequest;
import com.zhuanglide.micrboot.http.HttpContextResponse;
import com.zhuanglide.micrboot.http.MediaType;
import com.zhuanglide.micrboot.mvc.annotation.ApiCommand;
import com.zhuanglide.micrboot.mvc.annotation.ApiMethod;
import com.zhuanglide.micrboot.mvc.interceptor.ApiInterceptor;
import com.zhuanglide.micrboot.mvc.resolver.ApiMethodParamResolver;
import com.zhuanglide.micrboot.mvc.resolver.ExceptionResolver;
import com.zhuanglide.micrboot.mvc.resolver.ViewResolver;
import com.zhuanglide.micrboot.mvc.resolver.param.ApiMethodPathVariableResolver;
import com.zhuanglide.micrboot.util.HttpUtils;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cglib.proxy.Proxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.*;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;

/**
 * Created by wwj on 17/3/2.
 */
public class ApiDispatcher implements ApplicationContextAware,InitializingBean {
    private Map> commandMap;
    private Map> cachePathMap = new HashMap>();
    private AntPathMatcher matcher = new AntPathMatcher();
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private static final String DEFAULT_STRATEGIES_PATH = "DefaultStrategies.properties";
    private static final Properties defaultStrategies;
    private ApplicationContext context;
    private List apiInterceptors;
    private List viewResolvers;
    private List apiMethodParamResolvers;
    private List exceptionResolvers;
    private LocalVariableTableParameterNameDiscoverer paramNamesDiscoverer = new LocalVariableTableParameterNameDiscoverer();
    static {
        try {// 加载默认配置
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ApiDispatcher.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException ex) {
            throw new IllegalStateException("Could not load 'DefaultStrategies.properties': " + ex.getMessage());
        }
    }

    /**
     * 分发
     * @param request
     * @return
     */
    public ApiMethodMapping dispatcher(HttpContextRequest request) throws Exception{
        String methodValue = request.getHttpMethod();
        ApiMethod.HttpMethod httpMethod = ApiMethod.HttpMethod.ALL;
        //根据method 和 path分发请求
        for (ApiMethod.HttpMethod _httpMethod : ApiMethod.HttpMethod.values()) {
            if(_httpMethod.equals(methodValue)){
                httpMethod = _httpMethod;
                break;
            }
        }

        return findApiMethodMapping(request.getRequestUrl(),httpMethod);
    }

    /**
     * core method
     * @param request
     * @param response
     * @throws Exception
     */
    public void doService(HttpContextRequest request, HttpContextResponse response) throws Exception {
        HandlerExecuteChain chain = new HandlerExecuteChain(apiInterceptors,exceptionResolvers);
        Throwable handlerEx = null;
        try {
            try {
                chain.setResult(doProcess0(request, response, chain, true));
            } catch (Exception e) {
                handlerEx = e;
            } catch (Throwable throwable) {
                handlerEx = throwable;
            }
            //gen view
            processDispatchResult(chain, request, response, handlerEx);
        } catch (Exception ex) {
            chain.triggerAfterCompletion(request, response, ex);
        }
    }

    /**
     * do process用于外部调用
     */
    public Object doProcess(HttpContextRequest request, HttpContextResponse response) throws Exception{
        return doProcess0(request, response, null, true);
    }

    /**
     * do process用于外部调用
     * @param request
     * @param response
     * @param withInterceptor if true ,interceptors are usable,false -> disabled
     * @return
     * @throws Exception
     */
    public Object doProcess(HttpContextRequest request, HttpContextResponse response,boolean withInterceptor) throws Exception{
        return doProcess0(request, response, null, withInterceptor);
    }

    /**
     * do doProcess0 ,just
     * @param request
     * @param response
     * @param chain
     * @param withInterceptor if true ,interceptors are usable,false -> disabled
     * @return
     * @throws Exception
     */
    public Object doProcess0(HttpContextRequest request, HttpContextResponse response, HandlerExecuteChain chain, boolean withInterceptor) throws Exception{
        if(null == chain) {
            chain = new HandlerExecuteChain(apiInterceptors, exceptionResolvers);
        }

        ApiMethodMapping mapping;
        try {
            if(withInterceptor) {
                //pre dispatch ,you can do some to change the invoke method
                if (!chain.applyPreDispatch(request, response)) {
                    return null;
                }
            }
            //do dispatcher, url->method
            mapping = dispatcher(request);
            chain.setMapping(mapping); //set mapping
            if (null == mapping) {
                logger.error("can't find match for method={},path={}", request.getHttpMethod(), request.getRequestUrl());
                response.setStatus(HttpResponseStatus.NOT_FOUND);
                return null;
            }
            if(withInterceptor) {
                //post handler,you can change params
                chain.applyPostHandle(request, response);
            }
            //invoke & handler
            return handler(mapping,request,response);
        } catch (Exception e) {
            throw e;
        } catch (Throwable cause) {
            throw new Exception(cause);
        }
    }


    private void processDispatchResult(HandlerExecuteChain chain, HttpContextRequest request, HttpContextResponse response,Throwable throwable) throws Exception {
        if (throwable == null) {
            // Did the handler return a view to render?
            if (chain.getResult() != null) {
                if (!render(chain.getResult(), request, response)) {
                    response.setStatus(HttpResponseStatus.NOT_FOUND);
                    response.setContent("can't find view with path="+request.getRequestUrl());
                    logger.warn("no view found with path={}", chain.getMapping().getUrlPattern());
                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("no modelView returned to ApiDispatcher with path={},", request.getRequestUrl());
                }
            }
        }
        chain.triggerAfterCompletion(request, response, throwable);
    }

    protected boolean render(Object result, HttpContextRequest request, HttpContextResponse response) throws Exception {
        boolean resolver = false;
        if (null != result && result instanceof ModelAndView) {
            ModelAndView mv = (ModelAndView) result;
            if(mv.getMediaType()!=null) {
                String contentType = request.getHeader(HttpHeaderNames.CONTENT_TYPE);
                if (null != contentType) {
                    mv.setMediaType(MediaType.valueOf(contentType));
                }
            }
        }
        for (ViewResolver viewResolver : this.viewResolvers) {
            ModelAndView mv = viewResolver.resolve(result);
            if (null != mv) {
                viewResolver.render(mv, request, response);
                if (null != viewResolver.getContentType() && !response.containsHeader(HttpHeaderNames.CONTENT_TYPE)) {
                    response.addHeader(HttpHeaderNames.CONTENT_TYPE, viewResolver.getContentType());
                }
                resolver = true;
                break;
            }
        }
        return resolver;
    }

    /**
     * init
     * @param context
     */
    protected void initStrategies(ApplicationContext context) {
        initApiInterceptor(context);
        initApiMethodParamResolvers(context);
        initViewResolver(context);
        initExceptionResolvers(context);
        Assert.isTrue(loadApiCommand(context));
    }

    /**
     * load from annotation
     * @param context
     * @return
     */
    private boolean loadApiCommand(ApplicationContext context) {
        if (context != null) {
            commandMap = new HashMap>();
            Map objectMap = context.getBeansWithAnnotation(ApiCommand.class);
            for (Map.Entry entry : objectMap.entrySet()) {
                try {
                    Object bean = entry.getValue();
                    if (AopUtils.isAopProxy(bean)) {
                        bean = AopUtils.getTargetClass(bean);
                    }
                    Method[] methodArray = bean.getClass().getMethods();
                    ApiCommand apiCommand = bean.getClass().getAnnotation(ApiCommand.class);
                    for (Method method : methodArray) {
                        ApiMethod apiMethod = AnnotationUtils.findAnnotation(method, ApiMethod.class);
                        if (apiMethod != null) {
                            ApiMethodMapping apiCommandMapping = new ApiMethodMapping();
                            apiCommandMapping.setBean(entry.getValue());
                            apiCommandMapping.setProxyTargetBean(bean);
                            apiCommandMapping.setMethod(method);
                            apiCommandMapping.setUrlPattern(HttpUtils.joinOptimizePath(apiCommand.value(), apiMethod.value()));
                            apiCommandMapping.setParamNames(paramNamesDiscoverer.getParameterNames(method));
                            apiCommandMapping.setParamAnnotations(method.getParameterAnnotations());
                            apiCommandMapping.setParameterTypes(method.getParameterTypes());
                            Map requestMethodMap = commandMap.get(apiCommandMapping.getUrlPattern());
                            if (requestMethodMap == null) {
                                requestMethodMap = new HashMap();
                                commandMap.put(apiCommandMapping.getUrlPattern(), requestMethodMap);
                            }
                            requestMethodMap.put(apiMethod.httpMethod(), apiCommandMapping);
                        }
                    }

                } catch (Exception e) {
                    logger.error("init error", e);
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * findApiMethodMapping from cache or create new when first init
     */
    public ApiMethodMapping findApiMethodMapping(String url, ApiMethod.HttpMethod requestMethod) {
        Map map = cachePathMap.get(url);
        ApiMethodMapping apiMethodMapping = null;
        if (null == map) {
            synchronized (cachePathMap) {
                map = cachePathMap.get(url);
                if (null == map) {
                    for (Map.Entry> entry : commandMap.entrySet()) {
                        if(matcher.match(entry.getKey(), url)) {
                            map = entry.getValue();
                            if(checkCache()) {
                                cachePathMap.put(url, map);
                            }
                            break;
                        }
                    }
                }
            }
        }

        if(null != map){
            apiMethodMapping = map.get(requestMethod);
            if(null == apiMethodMapping && requestMethod != ApiMethod.HttpMethod.ALL) {
                apiMethodMapping = map.get(ApiMethod.HttpMethod.ALL);
            }
        }

        return apiMethodMapping;
    }
    private boolean checkCache(){
        return cachePathMap.size()<50000;
    }


    protected void initViewResolver(ApplicationContext context){
        this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
        Map matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.viewResolvers.addAll(matchingBeans.values());
        }
        AnnotationAwareOrderComparator.sort(this.viewResolvers);
    }

    protected void initApiMethodParamResolvers(ApplicationContext context){
        this.apiMethodParamResolvers = getDefaultStrategies(context, ApiMethodParamResolver.class);
        Map matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ApiMethodParamResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.apiMethodParamResolvers.addAll(matchingBeans.values());
        }
        AnnotationAwareOrderComparator.sort(this.apiMethodParamResolvers);
    }

    protected void initExceptionResolvers(ApplicationContext context) {
        Map matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.exceptionResolvers = new ArrayList(matchingBeans.values());
        }

        if (this.exceptionResolvers == null) {
            this.exceptionResolvers = getDefaultStrategies(context, ExceptionResolver.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No ExceptionResolver found ,using default");
            }
        }
        AnnotationAwareOrderComparator.sort(this.exceptionResolvers);
    }


    protected void initApiInterceptor(ApplicationContext context){
        Map matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ApiInterceptor.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.apiInterceptors = new ArrayList(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.apiInterceptors);
        }
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        initStrategies(context);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    protected  T getDefaultStrategy(ApplicationContext context, Class strategyInterface) {
        List strategies = getDefaultStrategies(context, strategyInterface);
        if (strategies.size() != 1) {
            throw new BeanInitializationException(
                    "ApiDispatcher needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
        }
        return strategies.get(0);
    }

    protected  List getDefaultStrategies(ApplicationContext context, Class strategyInterface) {
        String key = strategyInterface.getName();
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List strategies = new ArrayList(classNames.length);
            for (String className : classNames) {
                try {
                    Class clazz = ClassUtils.forName(className, ApiDispatcher.class.getClassLoader());
                    Object strategy = createDefaultStrategy(context, clazz);
                    strategies.add((T) strategy);
                }
                catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException(
                            "Could not find ApiDispatcher's default strategy class [" + className +
                                    "] for interface [" + key + "]", ex);
                }
                catch (LinkageError err) {
                    throw new BeanInitializationException(
                            "Error loading ApiDispatcher's default strategy class [" + className +
                                    "] for interface [" + key + "]: problem with class file or dependent class", err);
                }
            }
            return strategies;
        }
        else {
            return new LinkedList();
        }
    }

    protected Object createDefaultStrategy(ApplicationContext context, Class clazz) {
        return context.getAutowireCapableBeanFactory().createBean(clazz);
    }


    /**
     * 处理
     * @return
     */
    private Object handler(ApiMethodMapping mapping,HttpContextRequest request,HttpContextResponse response) throws Throwable {
        Method method = mapping.getMethod();
        Type[] parameterTypes = mapping.getParameterTypes();
        Annotation[][] annotations = mapping.getParamAnnotations();
        String[] paramNames = mapping.getParamNames();
        Object[] values = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            Type type = parameterTypes[i];
            Annotation[] paramAnnotations = annotations[i];
            String paramName = paramNames[i];
            ApiMethodParam apiMethodParam = new ApiMethodParam();
            apiMethodParam.setMethod(method);
            apiMethodParam.setParamAnnotations(paramAnnotations);
            apiMethodParam.setParamType(type);
            apiMethodParam.setParamName(paramName);
            Object paramObjectValue = null;

            if(!ObjectUtils.isEmpty(apiMethodParamResolvers)){
                for (ApiMethodParamResolver resolver : apiMethodParamResolvers) {
                    if (resolver.support(apiMethodParam)) {
                        //PathVariable need doPathVariableParse
                        if(resolver instanceof ApiMethodPathVariableResolver){
                            resolver.prepare(mapping, request, matcher);
                        }
                        paramObjectValue = resolver.getParamObject(apiMethodParam, request, response);
                        break;
                    }
                }
            }

            values[i] = paramObjectValue;
        }
        Object bean = mapping.getBean();
        Object result;
        //invoke
        try {
            if (AopUtils.isAopProxy(bean)) {
                if (AopUtils.isJdkDynamicProxy(bean)) {
                    result = Proxy.getInvocationHandler(bean).invoke(bean, method, values);
                } else { //cglib
                    result = method.invoke(bean, values);
                }
            } else {
                result = ReflectionUtils.invokeMethod(method, bean, values);
            }
            return result;
        } catch (Exception e) {
            StringBuffer val = new StringBuffer();
            for (int i = 0; i < parameterTypes.length; i++) {
                val.append(parameterTypes).append(":").append(values[i]).append(";");
            }
            logger.error("invoke exception,object="+bean+",method="+method+",values"+val.toString(), e);
            throw new Exception("invoke exception,url=" + request.getRequestUrl());
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy