程序员社区

动手撸一个 mvc 框架2


title: 动手撸一个 mvc 框架2
date: 2020/04/22 17:06


本节内容 & 思考题

本节我们将要带大家先过一遍 initHandlerMappings(context) 方法,从中引出我们今天要做的 HandlerMapping

SpringMVC 请求流程?

动手撸一个 mvc 框架2插图

组合模式

建造者模式

Spring MVC 4.0

1、初始化处理器映射器

动手撸一个 mvc 框架2插图1
DispatcherServlet#initHandlerMappings

2、请求来了怎么处理

动手撸一个 mvc 框架2插图2
动手撸一个 mvc 框架2插图3
动手撸一个 mvc 框架2插图4

我们先看 tag1

动手撸一个 mvc 框架2插图5
动手撸一个 mvc 框架2插图6
动手撸一个 mvc 框架2插图7

看到这里你是不是有点疑惑,这个 MappingRegistry 是什么东西。

动手撸一个 mvc 框架2插图8
动手撸一个 mvc 框架2插图9

那 mappingLookup 中的数据到底是什么时候注册的呢?

当创建 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping 对象的时候 InitializingBean#afterPropertiesSet

动手撸一个 mvc 框架2插图10
动手撸一个 mvc 框架2插图11

注:RequestMappingHandlerMapping 是怎么放到 bdMap 中的呢? 自己去看 @EnableMvc 注解,我相信你的聪明才智

tag2 与拦截器相关,我们下次在讲

我们实现一下 RequestMappingHandlerMapping 初始化阶段的代码

也就是 InitializingBean#afterPropertiesSet 方法引出的一系列代码。

1、先准备 2 个注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Controller {

    /**
     * The value may indicate a suggestion for a logical component name,
     * to be turned into a Spring bean in case of an autodetected component.
     * @return the suggested component name, if any
     */
    String value() default "";

}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {

    /**
     * 此注释表示的主要映射。
     */
    String[] value() default {};

    /**
     * 要映射到的HTTP请求方法,从而缩小了主要映射:
     * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
     */
    RequestMethod[] method() default {};
}

2、新增 HandlerMapping 相关抽象

/**
 * 由定义请求和处理程序对象之间的映射关系的对象实现的接口。
 *
 * @author yujx
 * @date 2020/04/23 11:29
 */
public interface HandlerMapping {

    /**
     * 返回此请求的处理程序和所有拦截器。可以根据请求URL,会话状态或实现类选择的任何因素进行选择。
     */
    HandlerExecutionChain getHandler(HttpServletRequest request);
}


public abstract class AbstractHandlerMapping implements HandlerMapping {
    /**
     * 返回此请求的处理程序和所有拦截器。可以根据请求URL,会话状态或实现类选择的任何因素进行选择。
     */
    @Override
    public final HandlerExecutionChain getHandler(HttpServletRequest request) {

        // 根据请求获取处理程序
        Object handler = this.getHandlerInternal(request);
        if (handler == null) {
            return null;
        }

        // TODO: 2020/4/24 这个地方和拦截器有关,以后再说 
        // return this.getHandlerExecutionChain(handler, request);
        return null;
    }

    /**
     * 查找给定请求的处理程序,如果未找到特定请求,则返回 null。
     */
    protected abstract Object getHandlerInternal(HttpServletRequest request);
}

3、新增 RequestMappingHandlerMapping

本节我们只介绍 RequestMappingHandlerMapping 中的 MappingRegistry 内部类数据进行注册部分。

public class RequestMappingHandlerMapping extends AbstractHandlerMapping implements InitializingBean, ApplicationContextAware {

    private ApplicationContext applicationContext;

    // 映射的注册中心
    private final MappingRegistry mappingRegistry = new MappingRegistry();

    /**
     * 注入 ApplicationContext
     */
    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        this.applicationContext = ctx;
    }

    class MappingRegistry {

        private final Map<RequestMappingInfo, HandlerMethod> mappingLookup = new LinkedHashMap<>();

        private final Multimap<String, RequestMappingInfo> urlLookup = ArrayListMultimap.create();

        public void register(String beanName, Class<?> beanType, Method method, RequestMappingInfo requestMappingInfo) {

            // 创建方法对象
            HandlerMethod handlerMethod = new HandlerMethod(beanName, beanType, applicationContext, method);

            // 添加到 mappingLookup 中
            mappingLookup.put(requestMappingInfo, handlerMethod);

            // 将路径匹配放到 urlLookup 中
            for (String pattern : requestMappingInfo.getPatternsCondition().getPatterns()) {
                urlLookup.put(pattern, requestMappingInfo);
            }
        }

        /**
         * 一个 url 对应多个 RequestMappingInfo 的情况一般出现在请求方式不同的情况下
         */
        public List<RequestMappingInfo> getMappingsByUrl(String urlPath) {
            return new ArrayList<>(this.urlLookup.get(urlPath));
        }

        public Map<RequestMappingInfo, HandlerMethod> getMappings() {
            return mappingLookup;
        }
    }

    /**
     * 设置所有提供的bean属性后,由BeanFactory调用。
     */
    @Override
    public void afterPropertiesSet() {

        Map<String, Object> beansOfType = applicationContext.getBeansOfType(Object.class);
        beansOfType.forEach((bdName, bean) -> {

            Class<?> beanType = bean.getClass();

            // 判断这个类上是否包含 @Controller 注解或者 @RequestMapping 注解
            if (this.isHandler(beanType)) {
                // 如果包含,则解析它方法上的 @RequestMapping 注解,进行注册
                this.detectHandlerMethods(bdName, beanType);
            }
        });
    }

    private boolean isHandler(Class<?> clazz) {
        Controller controller = AnnotationUtil.getAnnotation(clazz, Controller.class);
        if (ObjectUtil.isNotNull(controller)) {
            return true;
        }

        RequestMapping requestMapping = AnnotationUtil.getAnnotation(clazz, RequestMapping.class);
        if (ObjectUtil.isNotNull(requestMapping)) {
            return true;
        }

        return false;
    }

    /**
     * 找到标注 @RequestMapping 的方法,注册到注册中心
     */
    private void detectHandlerMethods(String beanName, Class<?> beanType) {

        // 使用 Map 装的目的是为了去重
        Map<Method, RequestMappingInfo> methodMap = new LinkedHashMap<Method, RequestMappingInfo>();
        Queue<Class<?>> handlerTypes = new LinkedList<>();

        handlerTypes.add(beanType);

        while (!handlerTypes.isEmpty()) {
            Class<?> handlerType = handlerTypes.remove();

            // 将他的父类或者接口
            handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
            Class<?> superclass = handlerType.getSuperclass();
            if (ObjectUtil.isNotNull(superclass)) {
                handlerTypes.add(superclass);
            }

            for (Method method : handlerType.getMethods()) {
                // 判断当前 method 是否有 @RequestMapping 注解,如果有返回 RequestMappingInfo,没有返回 null
                RequestMappingInfo info = this.createRequestMappingInfo(method);
                if (ObjectUtil.isNotNull(info)) {
                    RequestMappingInfo classRequestMappingInfo = this.createRequestMappingInfo(handlerType);
                    if (ObjectUtil.isNotNull(classRequestMappingInfo)) {
                        // 使两者结合
                        info = info.combine(classRequestMappingInfo);
                    }
                }

                if (ObjectUtil.isNotNull(info)) {
                    // 放进 Map 中
                    methodMap.put(method, info);
                }
            }
        }

        methodMap.forEach(((method, info) -> {
            // 注册到映射注册中心
            mappingRegistry.register(beanName, beanType, method, info);
        }));
    }

    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        RequestMapping requestMapping = AnnotationUtil.getAnnotation(element, RequestMapping.class);
        if (ObjectUtil.isNull(requestMapping)) {
            return null;
        }

        return RequestMappingInfo
                .paths(requestMapping.value())
                .methods(requestMapping.method())
                .build();
    }

    /**
     * 查找给定请求的处理程序,如果未找到特定请求,则返回 null。
     */
    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) {
        throw new RuntimeException("没有为当前请求找到处理器!");
    }
}

4、相关的对象

/**
 * 请求条件对象
 */
public interface RequestCondition<T> {

    /**
     * 将两个 RequestCondition 对象结合
     */
    T combine(T other);

    /**
     * 检查此条件是否与给定请求匹配,并返回潜在的新请求条件,其中包含适合当前请求的内容。
     * <p>
     * 不匹配返回 null
     */
    T getMatchingCondition(HttpServletRequest request);

}

和 3 个实现:

public final class PatternsRequestCondition implements RequestCondition<PatternsRequestCondition> {

    private final Set<String> patterns = new HashSet<>();

    public PatternsRequestCondition(String... paths) {
        this(Arrays.asList(paths));
    }

    public PatternsRequestCondition(Collection<String> patterns) {
        this.patterns.addAll(patterns);
    }

    public Set<String> getPatterns() {
        return this.patterns;
    }

    /**
     * 将两个 RequestCondition 对象结合
     */
    @Override
    public PatternsRequestCondition combine(PatternsRequestCondition other) {
        Set<String> result = new HashSet<>();
        result.addAll(this.patterns);
        result.addAll(other.patterns);

        return new PatternsRequestCondition(result);
    }

    /**
     * 检查此条件是否与给定请求匹配,并返回潜在的新请求条件,其中包含适合当前请求的内容。
     * <p>
     * 不匹配返回 null
     */
    @Override
    public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
        String lookupPath = request.getServletPath();

        Set<String> matches = new HashSet<>();
        for (String pattern : this.patterns) {
            String match = this.getMatchingPattern(pattern, lookupPath);
            if (match != null) {
                matches.add(match);
            }
        }

        return matches.isEmpty() ? null : new PatternsRequestCondition(matches);
    }

    private String getMatchingPattern(String pattern, String lookupPath) {
        if (ObjectUtil.equal(pattern, lookupPath)) {
            return pattern;
        }

        // 解析 {} 的情况
        // TODO: 2020/4/24 我实在不想写啊

        return null;
    }
}

public class RequestMethodsRequestCondition implements RequestCondition<RequestMethodsRequestCondition> {

    private final Set<RequestMethod> methods = new HashSet<>();

    public RequestMethodsRequestCondition(RequestMethod... methods) {
        this(Arrays.asList(methods));
    }

    private RequestMethodsRequestCondition(Collection<RequestMethod> requestMethods) {
        this.methods.addAll(requestMethods);
    }

    /**
     * 将两个 RequestCondition 对象结合
     */
    @Override
    public RequestMethodsRequestCondition combine(RequestMethodsRequestCondition other) {
        Set<RequestMethod> result = new HashSet<>();
        result.addAll(this.methods);
        result.addAll(other.methods);

        return new RequestMethodsRequestCondition(result);
    }

    /**
     * 检查此条件是否与给定请求匹配,并返回潜在的新请求条件,其中包含适合当前请求的内容。
     * <p>
     * 不匹配返回 null
     */
    @Override
    public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {
        if (methods.isEmpty()) {
            return null;
        }

        RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod());
        if (methods.contains(requestMethod)) {
            return new RequestMethodsRequestCondition(requestMethod);
        }

        return null;
    }
}

public class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {

    // 请求路径条件 /rest/xxx
    private PatternsRequestCondition patternsCondition;

    // 请求方法条件 GET、POST
    private RequestMethodsRequestCondition requestMethodsRequestCondition;

    private RequestMappingInfo(PatternsRequestCondition patternsCondition, RequestMethodsRequestCondition requestMethodsRequestCondition) {
        this.patternsCondition = patternsCondition;
        this.requestMethodsRequestCondition = requestMethodsRequestCondition;
    }

    /**
     * 将两个 RequestCondition 对象结合
     */
    @Override
    public RequestMappingInfo combine(RequestMappingInfo other) {
        PatternsRequestCondition patternsRequestCondition = this.patternsCondition.combine(other.patternsCondition);
        RequestMethodsRequestCondition requestMethodsRequestCondition = this.requestMethodsRequestCondition.combine(other.requestMethodsRequestCondition);

        return new RequestMappingInfo(patternsRequestCondition, requestMethodsRequestCondition);
    }

    /**
     * 检查此条件是否与给定请求匹配,并返回潜在的新请求条件,其中包含适合当前请求的内容。
     * <p>
     * 不匹配返回 null
     */
    @Override
    public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
        PatternsRequestCondition pattern = this.patternsCondition.getMatchingCondition(request);
        if (ObjectUtil.isNull(pattern)) {
            return null;
        }

        RequestMethodsRequestCondition requestMethods = this.requestMethodsRequestCondition.getMatchingCondition(request);
        if (ObjectUtil.isNull(requestMethods)) {
            return null;
        }

        return new RequestMappingInfo(pattern, requestMethods);
    }

    public static Builder paths(String... paths) {
        return new Builder(paths);
    }

    public PatternsRequestCondition getPatternsCondition() {
        return patternsCondition;
    }

    public RequestMethodsRequestCondition getRequestMethodsRequestCondition() {
        return requestMethodsRequestCondition;
    }

    public static class Builder {

        private String[] paths;

        private RequestMethod[] methods;

        public Builder(String... paths) {
            this.paths = paths;
        }

        public Builder paths(String... paths) {
            this.paths = paths;
            return this;
        }

        public Builder methods(RequestMethod... methods) {
            this.methods = methods;
            return this;
        }

        public RequestMappingInfo build() {

            PatternsRequestCondition patternsCondition = paths != null ? new PatternsRequestCondition(this.paths) : new PatternsRequestCondition();
            RequestMethodsRequestCondition requestMethodsRequestCondition = methods != null ? new RequestMethodsRequestCondition(methods) : new RequestMethodsRequestCondition();

            return new RequestMappingInfo(patternsCondition, requestMethodsRequestCondition);
        }
    }
}

5、相关对象2

public class HandlerMethod {

    private final String beanName;

    private final BeanFactory beanFactory;

    private final Class<?> beanType;

    private final Method method;

    private final MethodParameter[] parameters;

    public HandlerMethod(String beanName, Class<?> beanType, BeanFactory beanFactory, Method method) {
        this.beanName = beanName;
        this.beanType = beanType;
        this.beanFactory = beanFactory;
        this.method = method;
        this.parameters = this.initMethodParameters();
    }

    private MethodParameter[] initMethodParameters() {
        int count = this.method.getParameterTypes().length;
        MethodParameter[] result = new MethodParameter[count];
        for (int i = 0; i < count; i++) {
            result[i] = new MethodParameter(method, i);
        }
        return result;
    }
}


public class MethodParameter {

    // 方法
    private final Method method;

    // 参数对应的索引位置
    private final int parameterIndex;

    public MethodParameter(Method method, int parameterIndex) {
        this.method = method;
        this.parameterIndex = parameterIndex;
    }
}
赞(0) 打赏
未经允许不得转载:IDEA激活码 » 动手撸一个 mvc 框架2

一个分享Java & Python知识的社区