SpringMVC框架源码解析之三:手写一个简单的MVC框架
为了加上对SpringMVC的理解,本文通过手写一个简单的MVC框架,来直观的理解MVC的原理。
如下为MVC框架要支持的功能:
源码位于:https://gitee.com/cq-laozhou/spring-stack-source-code-analysis/tree/master/src/main/java/com/zyy/sc/analysis/yymvc
话不多说,直接进入主题。
YYMVC框架源码
建议读者按照两条线来分析源码:第一条线是看框架的启动过程,第二条线再看请求的执行过程。
对于YYMVC框架来说,启动过程(或者servlet初始化)集中在init()方法中。而请求的执行流程集中在doDispatch()方法中。
注意,YYMVC只支持JDK8及以上版本。
前端控制器
这儿直接贴出写完的代码,实际写代码的情况是,不断的修改和完善。
package com.zyy.sc.analysis.yymvc;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UrlPathHelper;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* Created by zhouyinyan on 2020/1/14.
*
* 前端控制器
*
*/
@Slf4j
public class YYServlet extends HttpServlet {
//Spring容器,提供直接注入外部的ApplicationContext,或者通过指定主配置类,自己新建一个ApplicationContext。
private ApplicationContext context;
//spring的主配置类,用于初始化spring容器
private Class springConfigClass;
//注册器
private HandlerRegistry handlerRegistry = new DefaultHandlerRegistry();
//urlPath工具
private UrlPathHelper urlPathHelper = new UrlPathHelper();
//方法参数名称工具
private ParameterNameDiscoverer parameterNameDiscoverer ;
//拦截器列表
private List<Interceptor> interceptors = new ArrayList<>();
/**
* 初始化
* 注册handler
* @throws ServletException
*/
@Override
public void init() throws ServletException {
//init spring context
if(Objects.isNull(getContext())){
Assert.notNull(springConfigClass, "请提供Spring的主配置类,或者注入Spring容器到YYServlet中");
context = new AnnotationConfigApplicationContext(springConfigClass);
}
//找出容器中所有的bean名称,然后看是否有@Controller注解,如果有的话,从它的所有方法中找出有@ReqMapping注解的方法,然后封装为Handler,注册到handlerRegistry上。
//register handlers
String[] allBeanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class);
Arrays.stream(allBeanNames).forEach(beanName -> {
Class<?> type = context.getType(beanName);
if(Objects.nonNull(type) && isHandler(type)){
detectHandler(beanName);
}
});
// parameterNameDiscoverer = context.getBean(ParameterNameDiscoverer.class);
if(Objects.isNull(parameterNameDiscoverer)){
parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
}
//init interceptors
String[] interceptorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Interceptor.class);
Arrays.stream(interceptorNames).forEach(name -> {
interceptors.add(context.getBean(name, Interceptor.class));
});
}
/**
* 找出给出bean的所有方法,如果有ReqMapping注解,这注册url和handler到handlerRegistry中。
* @param beanName
*/
private void detectHandler(String beanName) {
Class<?> type = context.getType(beanName);
//借用MethodIntrospector.selectMethods方法(这个方法是个模板方法