天天看点

手写 Spring MVC篇

上面的四篇文章介绍并写完了SpringBean的生命周期,现在开始来写Spring MVC 

Spring MVC我们只写一层 C层 即 Controller 控制层(没有父子容器)

众所周知 Spring MVC有一个中央处理器 叫 DispatcherServlet,实现了HttpServlet 是一个Servlet

既然是一个Servlet 肯定是要遵循Servlet的规范。

我们通常知道的服务器一般有tomcat 和 jetty等等 ,这两个容器也实现了Servlet的规范。

在容器运行完成会调用META-INF/services 下面的 javax.servlet.ServletContainerInitializer这个文件里面对象方法,这个对象必须实现 ServletContainerInitializer这个接口,而这个接口里面做的事情可以实现零配置启动一个SpringWeb项目

先来看下我们手写SpringMVC的流程,需要将我们写的spring项目打包并引入

启动tomcat需要做什么事情,我们仿照Spring boot的方式启动tomcat

@XxSpringBootApplication
public class SimpleMVCApplication {

    public static void main(String[] args) throws Exception{
        XxSpringApplication.run(SimpleMVCApplication.class);
    }
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@XxConfiguration
public @interface XxSpringBootApplication {
}
           

run方法执行的时候会将当前类传进去,传进去之后会初始化我们spring容器。再放到缓存中

public static void run(Class clazz){
    try {
        XxAnnotationConfigApplicationContext springContext = new XxAnnotationConfigApplicationContext(clazz);
        XxLocalCache.CONTEXT_CACHE.put("springContext",springContext);
        Tomcat tomcat = new Tomcat();
        tomcat.setPort(80);
        Context context = tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
        context.addLifecycleListener((LifecycleListener) Class.forName(tomcat.getHost().getConfigClass()).newInstance());
        tomcat.start();
        tomcat.getServer().await();
    }catch (Exception e){

    }
}
           

启动tomcat之后会执行Servlet规范的接口,得到了XxDispatcherServlet 并将它放到了容器里面

public class XxWebApplicationInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) {
        XxAnnotationConfigApplicationContext springContext = (XxAnnotationConfigApplicationContext) XxLocalCache.CONTEXT_CACHE.get("springContext");
        XxDispatcherServlet dispatcherServlet = springContext.getBean(XxDispatcherServlet.class);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", dispatcherServlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/*");
    }
}
           

而初始化 Spring容器的时候 XxDispatcherServlet会进行初始化,及完成扫描并填充映射的map集合。XxDispatcherServlet 实现了Spring 的一个Aware接口 来获取上下文实现扫描并注册beanDefinition的功能,又实现一个XxInitializingBean 接口来实现XxDispatcherServlet初始化后填充map映射的结果集

@XxComponent
public class XxDispatcherServlet extends HttpServlet implements XxInitializingBean, XxApplicationContextAware {

    public XxAnnotationConfigApplicationContext context;

    public Map<String, Method> handlerMapping = new ConcurrentHashMap<>(256);

    public Map<String,String[]> pathVariableMapping = new ConcurrentHashMap<>(256);

    public List<XxHandlerInterceptor> handlerInterceptors = new ArrayList<>();

    public static List<AnnotationTypeParameterParser> parameterParsers = new ArrayList<>();

    static {
        parameterParsers.add(new XxRequestParamParser());
        parameterParsers.add(new XxPathVariableParser());
        parameterParsers.add(new XxRequestBodyParser());
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatcher(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatcher(req, resp);
    }

    @Override
    public void afterPropertiesSet() {
        List<Class<?>> loadClass = context.getXxDefaultListableBeanFactory().loadRootResources();
        for (Class<?> aClass : loadClass) {
            if(aClass.isAnnotationPresent(XxController.class)){
                XxRequestMapping aClassAnnotation = aClass.getAnnotation(XxRequestMapping.class);
                String aClassValue = handleUrl(aClassAnnotation.value());
                Method[] methods = aClass.getDeclaredMethods();
                for (Method method : methods) {
                    if(method.isAnnotationPresent(XxRequestMapping.class)){
                        XxRequestMapping aMethodAnnotation = method.getAnnotation(XxRequestMapping.class);
                        String requestUrl = handleUrl(aClassValue + aMethodAnnotation.value());
                        handlerMapping.put(requestUrl,method);
                    }
                }
            }
        }
    }

    @Override
    public void setXxApplicationContext(XxAnnotationConfigApplicationContext context) {
        this.context = context;
    }

    private String handleUrl(String value){
        if(!value.startsWith("/")){
            value += "/";
        }
        if(value.endsWith("/")){
            value = value.substring(0,value.length()-1);
        }
        if(value.contains("/{") && value.contains("}")){
            String key = value.substring(0,value.indexOf("/{"));
            String values = value.substring(value.indexOf("/{")+1);
            pathVariableMapping.put(key,values.split("/"));
            value = key;
        }
        return value;
    }
    //doDispatcher......
}
           

handlerMapping  映射的结果集类级别XxRequestMapping值加上方法级XxRequestMapping值

/user + /get01

pathVariableMapping  存放处理路径包含值的映射结果集

/user + /get01/001/waf    001 代表id  waf = 名称 理解为通过id和名称去查询结果

handlerInterceptors  拦截器集合,在Spring扫描的时候会填充一个集合,集合的作用是通过一个接口获取到该接口的所有实现类

parameterParsers  参数解析器 本项目中实现了 XxRequestParam XxPathVariable XxRequestBody 三种注解的请求参数解析

具体就在执行方法的逻辑里面,响应参数由于进行了统一的封装,直接使用了json包进行返回的,如果不需要进行统一的返回封装,可直接串行化进行返回,会自动的解析成json字符串。

代码详情可参照gitee地址:https://gitee.com/wanganfen/xx-spring/tree/master/simple-mvc