Today Web Mvc 路由设计

发布于: 2019年08月14日 00:03:58 | 分类: 轮子工厂 | 浏览: 84

系列博文:《Today Web Mvc 设计思想》之路由设计。

通过核心方法DispatcherServlet#lookupHandlerMapping()在运行时来寻找对应handler(Action)

protected HandlerMapping lookupHandlerMapping(final HttpServletRequest req) {
    // The key of handler
    String key = req.getMethod() + req.getRequestURI();

    final HandlerMappingRegistry registry = getHandlerMappingRegistry();
    final Integer i = registry.getIndex(key); // index of handler mapping
    if (i == null) {
        // path variable
        key = StringUtils.decodeUrl(key);// decode
        for (final RegexMapping regex : registry.getRegexMappings()) {
            // TODO path matcher pathMatcher.match(requestURI, requestURI)
            if (regex.pattern.matcher(key).matches()) {
                return registry.get(regex.index);
            }
        }
        log.debug("NOT FOUND -> [{}]", key);
        return null;
    }
    return registry.get(i.intValue());
}

使用请求方法+路径做key的方法查找handler。例如:GET/index。 对于path variable 使用正则表达式匹配,后面计划使用Path Matcher匹配。

记录Handler(Action)

使用 HandlerMappingRegistry 来记录所有handler Map<String, Integer> requestMappings将请求与handler映射起来。

@MissingBean(value = Constant.HANDLER_MAPPING_REGISTRY, type = HandlerMappingRegistry.class)
public class HandlerMappingRegistry implements RandomAccess {

    /** pool **/
    private HandlerMapping[] array;
    /** regex **/
    private RegexMapping[] regexMappings;
    /** mapping */
    private Map<String, Integer> requestMappings;

    public HandlerMappingRegistry setRegexMappings(Map<String, Integer> regexMappings) {
        this.regexMappings = new RegexMapping[regexMappings.size()];
        int i = 0;
        for (Entry<String, Integer> entry : regexMappings.entrySet()) {
            this.regexMappings[i++] = new RegexMapping(Pattern.compile(entry.getKey()), entry.getValue());
        }
        return this;
    }

    public HandlerMappingRegistry setRequestMappings(Map<String, Integer> requestMappings) {
        this.requestMappings = requestMappings;
        return this;
    }

    public HandlerMappingRegistry() {
        array = new HandlerMapping[0];
    }

    public final RegexMapping[] getRegexMappings() {
        return regexMappings;
    }

    /**
     * Get HandlerMapping count.
     * 
     * @return HandlerMapping count
     */
    public int size() {
        return array.length;
    }

    /**
     * Get handler index in array
     * 
     * @param key
     *            request method and request uri
     */
    public final Integer getIndex(String key) {
        return requestMappings.get(key);
    }

    /**
     * Get HandlerMapping instance.
     * 
     * @param index
     *            the HandlerMapping number
     */
    public final HandlerMapping get(int index) {
        return array[index];
    }

    /**
     * Add HandlerMapping to pool.
     * 
     * @param e
     *            HandlerMapping instance
     */
    public int add(HandlerMapping e) {

        for (int i = 0; i < array.length; i++) {
            if (e.equals(array[i])) {
                return i;
            }
        }
        HandlerMapping[] newArray = new HandlerMapping[array.length + 1];
        System.arraycopy(array, 0, newArray, 0, array.length);
        newArray[array.length] = e;

        array = newArray;

        return array.length - 1;
    }

    public String toString() {
        return Arrays.toString(array);
    }

}

DispatcherServlet 依赖一个HandlerMappingRegistry且被标注为一个@MissingBean说明你的应用里如果缺少一个类型为HandlerMappingRegistry的bean则默认实例化该类。

@Autowired
public DispatcherServlet(//
        ExceptionResolver exceptionResolver, //
        HandlerMappingRegistry handlerMappingRegistry, //
        WebServletApplicationContext applicationContext,
        HandlerInterceptorRegistry handlerInterceptorRegistry) //
{
    if (exceptionResolver == null) {
        throw new ConfigurationException("You must provide an 'exceptionResolver'");
    }
    this.exceptionResolver = exceptionResolver;

    this.applicationContext = applicationContext;
    this.handlerMappingRegistry = handlerMappingRegistry;
    this.handlerInterceptorRegistry = handlerInterceptorRegistry;
}

识别Handler(Action)

一个控制器(Controller)包含多个Handler(Action)。(基本概念命名查看《Today Web Mvc 设计思想》),如何标识一个控制器,一个Handler呢?

我认为Java注解最为方便简洁。通过@Controller注解标识控制器(Controller),然后查找@ActionMapping根注解。标识一个Handler(Action)。本质上@RequestMapping注解是@ActionMapping根注解的一个别名,本程序可以任意定义自己的路由注解。例如:

自定义路由注解

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ActionMapping(method = RequestMethod.GET)
public @interface MyGET {

    /** urls */
    String[] value() default Constant.BLANK;

    /** Exclude url on class */
    boolean exclude() default false;

}

标识路由

/**
 * @author TODAY <br>
 *         2018-12-09 22:53
 */
@Slf4j
@Controller("modelController")
@RequestMapping(value = { "model", "and", "view" }, method = RequestMethod.GET)
public class ModelAndViewController {

    @Autowired
    private HttpServletRequest request;

    @RequestMapping
    public ModelAndView model() {
        return new ModelAndView("/model/index", "key", "World").setContentType("text/html;charset=UTF-8");
    }

    @RequestMapping("index")
    public void index(ModelAndView modelAndView) {
        modelAndView.setView(request.getHttpServletMapping());
        modelAndView.setView(request.getRequestURL());
    }

    @RequestMapping("nothing")
    public ModelAndView nothing() {
        log.info("nothing");
        return new ModelAndView();
    }

    @RequestMapping("/script")
    @ResponseStatus(value = 500, msg = "出错啦")
    public void script(ModelAndView modelAndView) {
        modelAndView.setContentType("text/html;charset=UTF-8");
        modelAndView.setView(new StringBuilder("<script>alert('HELLO, 你好');</script>"));
    }

    @RequestMapping("/display")
    public void display(ModelAndView modelAndView) throws IOException {
        modelAndView.setView(ImageIO.read(new File("D:/WebSite/data/doc/upload/logo.png")));
    }
}

RESTful路由

本程序支持RESTful风格路由。使用@PathVariable标注路由参数,使用{}作为占位符标识。

@ActionMapping(value = { "/path/{id}" }, method = RequestMethod.GET)
public String pathVariable(@PathVariable Integer id) {
    return "id -> " + id;
}

在运行时使用正则表达式遍历匹配路由。后期准备采用Spring 的PathMatcher匹配。

key = StringUtils.decodeUrl(key);// decode
for (final RegexMapping regex : registry.getRegexMappings()) {
    // TODO path matcher pathMatcher.match(requestURI, requestURI)
    if (regex.pattern.matcher(key).matches()) {
        return registry.get(regex.index);
    }
}
log.debug("NOT FOUND -> [{}]", key);
return null;

解析PathVariable

启动时

private boolean doMappingPathVariable(String regexUrl, //
        MethodParameter[] methodParameters, Method method, int index, String requestMethod_) //
{

    if (!(regexUrl.indexOf('*') > -1 || regexUrl.indexOf('{') > -1)) { //
        return false; // not a path variable
    }

    String methodUrl = regexUrl; // copy regex url

    regexUrl = regexUrl.replaceAll(Constant.ANY_PATH, Constant.ANY_PATH_REGEXP);
    regexUrl = regexUrl.replaceAll(Constant.ONE_PATH, Constant.ONE_PATH_REGEXP);
    boolean hasSet = false;

    Parameter[] parameters = method.getParameters();
    for (int i = 0; i < parameters.length; i++) {
        Parameter parameter = parameters[i];
        MethodParameter methodParameter = methodParameters[i];
        if (!methodParameter.isAnnotationPresent(PathVariable.class)) {
            continue;
        }
        Class<?> parameterClass = methodParameter.getParameterClass();

        PathVariable pathVariable = parameter.getAnnotation(PathVariable.class);
        if (pathVariable == null) {
            throw new ConfigurationException(//
                    "You must specify a @PathVariable Like this: [public String update(@PathVariable int id, ..) {...}]"//
            );
        }
        String regex = pathVariable.pattern(); // customize regex
        if (StringUtils.isEmpty(regex)) {
            regex = pathVariable.regex();
        }
        if (StringUtils.isEmpty(regex)) {
            if (parameterClass == String.class) {
                regex = Constant.STRING_REGEXP;
            }
            else {
                regex = Constant.NUMBER_REGEXP;
            }
        }

        String parameterName = methodParameter.getName();
        regexUrl = regexUrl.replace('{' + parameterName + '}', regex);

        String[] splitRegex = methodUrl.split(Constant.PATH_VARIABLE_REGEXP);
        String tempMethodUrl = methodUrl;

        for (String reg : splitRegex) {
            tempMethodUrl = tempMethodUrl.replaceFirst(reg, Constant.REPLACE_REGEXP);
        }

        String[] regexArr = tempMethodUrl.split(Constant.REPLACE_REGEXP);
        for (int j = 0; j < regexArr.length; j++) {
            if (regexArr[j].equals('{' + parameterName + '}')) {
                methodParameter.setPathIndex(j);
            }
        }
        if (!hasSet) {
            methodParameter.setSplitMethodUrl(//
                    methodUrl.replace(requestMethod_, Constant.BLANK).split(Constant.PATH_VARIABLE_REGEXP)//
            );
            hasSet = true;
        }
    }

    // fix
    if (regexUrl.indexOf('{') > -1 && regexUrl.indexOf('}') > -1) { // don't have a parameter name named ''
        throw new ConfigurationException("Check @PathVariable configuration on method: [" + method + "]");
    }

    this.regexUrls.put(regexUrl, index);
    log.info("Mapped [{}] -> [{}]", regexUrl, method);
    return true;
}

运行时

/**
 * @author TODAY <br>
 *         2019-07-09 22:49
 */
public class PathVariableParameterResolver implements OrderedParameterResolver {

    @Override
    public boolean supports(final MethodParameter parameter) {
        return parameter.isAnnotationPresent(PathVariable.class);
    }

    /**
     * Resolve Path Variable parameter.
     */
    @Override
    public Object resolveParameter(final RequestContext requestContext, final MethodParameter parameter) throws Throwable {
        try {

            final String pathVariable;
            final String[] pathVariables = requestContext.pathVariables();
            if (pathVariables == null) {
                String requestURI = StringUtils.decodeUrl(requestContext.requestURI()); // TODO avoid double decode
                for (final String regex : parameter.getSplitMethodUrl()) {
                    requestURI = requestURI.replace(regex, Constant.REPLACE_SPLIT_METHOD_URL);
                }
                pathVariable = requestContext.pathVariables(requestURI.split(Constant.REPLACE_REGEXP))[parameter.getPathIndex()];
            }
            else {
                pathVariable = pathVariables[parameter.getPathIndex()];
            }

            return ConvertUtils.convert(pathVariable, parameter.getParameterClass());
        }
        catch (Throwable e) {
            throw WebUtils.newBadRequest("Path variable", parameter.getName(), e);
        }
    }

    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

未完

版权声明:本文为作者原创文章,转载时请务必声明出处并添加指向此页面的链接。
分享:
发表评论

目前您尚未登录,请 登录 后进行评论

评论信息