SpringMVC框架源码解析之三:手写一个简单的MVC框架

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方法(这个方法是个模板方法,它会将类的所有方法解析出来,然后回调传入的MetadataLookup,进行方法选取。
        Map<Method, ReqMappingInfo> methods = MethodIntrospector.selectMethods(type,
                (MethodIntrospector.MetadataLookup<ReqMappingInfo>) method -> {
                    try {
                    
                    	//找到方法上的@ReqMapping注解,封装为ReqMappingInfo对象。
                        ReqMapping reqMapping = AnnotatedElementUtils.findMergedAnnotation(method, ReqMapping.class);
                        if(Objects.isNull(reqMapping)){
                            return null;
                        }
                        if(StringUtils.isEmpty(reqMapping.url()) && StringUtils.isEmpty(reqMapping.value())){
                            throw new IllegalStateException("Invalid reqMapping 注解配置 on handler class [" +
                                    type.getName() + "]: " + method + ", url 和 value 必须配置一个。");
                        }
                        ReqMappingInfo mappingInfo = new ReqMappingInfo();
                        mappingInfo.setUrl(StringUtils.hasText(reqMapping.url()) ? reqMapping.url() : reqMapping.value());
                        return mappingInfo;
                    }
                    catch (Throwable ex) {
                        throw new IllegalStateException("Invalid mapping on handler class [" +
                                type.getName() + "]: " + method, ex);
                    }
                });
        if (log.isDebugEnabled()) {
            log.debug(methods.size() + " request handler methods found on " + type + ": " + methods);
        }
			
			//进行真正的注册。
        methods.forEach((method,reqMapping) -> {
            handlerRegistry.register(reqMapping.getUrl(), new Handler(context.getBean(beanName), method));
        });
    }

    /**
     * 判断类型是否是需要处理的Handler(这儿简单的认为标注了@Controller注解的是Handler)
     * @param type
     * @return
     */
    private boolean isHandler(Class<?> type) {
        return AnnotatedElementUtils.hasAnnotation(type, Controller.class);
    }

    /**
     * 处理get请求
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req, resp);
    }

    /**
     * 处理Post请求
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req, resp);
    }

    /**
     * 整体逻辑处理。
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
        try {
            Handler handler = getHandler(req);
            if(Objects.isNull(handler)){
                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }

            if(!applyBeforeInvoke(req,resp)){
                log.info("前置拦截为不通过,直接返回");
                return;
            }

            Object[] args = resolveParameters(handler, req, resp);
            Object result = handler.invoke(args);

            applyAfterInvoke(result, req,resp);

            handleResult(result, req, resp);

        } catch (Exception e) {
            log.error("处理发生异常,{}",e.getMessage());
            try {
                applyThrowException(e, req, resp);
                handleException( req, resp, e);
            } catch (IOException e1) {
                log.error("异常处理又发生异常,{}",e.getMessage());
            }
        }

    }

    /**
     * 应用正常调用完成时拦截
     * @param result
     * @param req
     * @param resp
     */
    private void applyAfterInvoke(Object result, HttpServletRequest req, HttpServletResponse resp) {
        for (Interceptor interceptor : interceptors) {
            interceptor.afterInvoke(result, req, resp);
        }
    }

    /**
     * 应用异常抛出时拦截
     * @param exception
     * @param req
     * @param resp
     */
    private void applyThrowException(Exception exception, HttpServletRequest req, HttpServletResponse resp) {
        for (Interceptor interceptor : interceptors) {
            interceptor.afterThrowException(exception, req, resp);
        }
    }

    /**
     * 应用前置拦截
     * @param req
     * @param resp
     * @return
     */
    private boolean applyBeforeInvoke(HttpServletRequest req, HttpServletResponse resp) {
        for (Interceptor interceptor : interceptors) {
            if(!interceptor.beforeInvoke(req, resp)){
                return true;
            }
        }
        return false;
    }

    /**
     * 异常时,异常信息返回。
     * @param req
     * @param resp
     * @param exception
     * @throws IOException
     */
    private void handleException(HttpServletRequest req, HttpServletResponse resp, Exception exception) throws IOException {
        resp.setHeader("Content-Type","text/plain;charset=UTF-8");
        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        resp.getWriter().print(exception.getMessage());
        resp.getWriter().flush();
    }

    /**
     * 解析参数
     * @param handler
     * @param req
     * @param resp
     * @return
     */
    private Object[] resolveParameters(Handler handler, HttpServletRequest req, HttpServletResponse resp) {
        Method targetMethod = handler.getTargetMethod();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(targetMethod);
        Class<?>[] parameterTypes = targetMethod.getParameterTypes();
        Object[] args = new Object[parameterNames.length];

        for (int i = 0; i < parameterNames.length; i++) {
            String parameterName = parameterNames[i];
            Class<?> parameterType = parameterTypes[i];
            args[i] = resolveValue(parameterName, parameterType, req,resp);
        }

        return args;
    }

    /**
     * 解析单个参数值。
     * 暂只支持字符串、HttpServletRequest、HttpServletResponse类型
     * @param parameterName
     * @param parameterType
     * @param req
     * @param resp
     * @return
     */
    private Object resolveValue(String parameterName, Class<?> parameterType,
                                HttpServletRequest req, HttpServletResponse resp) {

        if(HttpServletRequest.class.isAssignableFrom(parameterType)){
            return req;
        }
        if(HttpServletResponse.class.isAssignableFrom(parameterType)){
            return resp;
        }

        if(String.class.isAssignableFrom(parameterType)){
            return req.getParameter(parameterName);
        }
        //.... 其他基础类型的解析。

        //接受的是一个对象时的参数解析。
        throw  new  RuntimeException("暂不支持的参数类型,参数名称"+parameterName+",参数类型:"+parameterType);

    }

    /**
     * @param result
     * @param req
     * @param resp
     * @throws IOException
     */
    private void handleResult(Object result, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setHeader("Content-Type","text/plain;charset=UTF-8");
        resp.setStatus(HttpServletResponse.SC_OK);

        //返回是个String的话,直接输出到页面上
        Class<?> resultClass = result.getClass();
        if(String.class.isAssignableFrom(resultClass)){
            resp.getWriter().print(result);
        }else if(View.class.isAssignableFrom(resultClass)){
            ((View)result).render(req, resp);
        }else{
            //其他类型,返回对象的toString串
            resp.getWriter().print(result.toString());
        }

        resp.getWriter().flush();
    }

		//从handlerRegistry找请求对应的处理器。
    private Handler getHandler(HttpServletRequest req) {
        String urlPath = urlPathHelper.getLookupPathForRequest(req);
        Optional<Handler> handlerOptional = handlerRegistry.lookUp(urlPath);
        return handlerOptional.orElse(null);
    }

    public ApplicationContext getContext() {
        return context;
    }

    public void setContext(ApplicationContext context) {
        this.context = context;
    }

    public HandlerRegistry getHandlerRegistry() {
        return handlerRegistry;
    }

    public void setHandlerRegistry(HandlerRegistry handlerRegistry) {
        this.handlerRegistry = handlerRegistry;
    }

    public Class getSpringConfigClass() {
        return springConfigClass;
    }

    public void setSpringConfigClass(Class springConfigClass) {
        this.springConfigClass = springConfigClass;
    }
}

请求映射注册

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 注册器默认实现
 */
@Slf4j
public class DefaultHandlerRegistry implements HandlerRegistry {

    private Map<String, Handler> handlerMap = new ConcurrentHashMap<>();

    @Override
    public boolean register(String url, Handler handler) {
        Assert.notNull(url, "注册的URL不能为null");
        Assert.notNull(handler, "注册的handler不能为null");

        if(log.isDebugEnabled()){
            log.debug("注册Handler, {} --> {}", url, handler);
        }

        handlerMap.put(url, handler);

        return true;
    }

    @Override
    public boolean unRegister(String url) {
        Assert.notNull(url, "注册的URL不能为null");

        if(log.isDebugEnabled()){
            log.debug("取消注册Handler, {} ", url);
        }

        handlerMap.remove(url);
        return false;
    }

    @Override
    public Optional<Handler> lookUp(String url) {
        Assert.notNull(url, "注册的URL不能为null");

        Optional<Handler> handler = Optional.ofNullable(handlerMap.get(url));

        if(log.isDebugEnabled()){
            log.debug("查找Handler, {} --> {} ", url, handler.isPresent() ? handler.get() : "null");
        }

        return handler;
    }
}

处理器

@Slf4j
@Data
public class Handler {

    //目标对象
    private Object targetObject;

    //目标方法
    private Method targetMethod;

    //缓存toString方法返回串
    private String displayName;

    public Handler(Object targetObject, Method targetMethod) {
        Assert.notNull(targetObject, "目标对象不能为null");
        Assert.notNull(targetMethod, "目标对象不能为null");
        this.targetObject = targetObject;
        this.targetMethod = targetMethod;
    }

    /**
     * 目标方法调用
     * @param args
     * @return
     */
    public Object invoke(Object... args){
        try {
            return targetMethod.invoke(targetObject, args);
        } catch (Exception e) {
            log.error("控制器目标方法调用异常,异常信息:{}", e.getMessage());
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toString() {
        if(StringUtils.hasText(displayName)){
            return displayName;
        }
        displayName = targetObject.getClass().getName() + "." + targetMethod.getName();
        return displayName;
    }
}

注解和注解信息

/**
 * 映射注解
 * 使用到业务方法上
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReqMapping {

    @AliasFor("url")
    String value() default "";

    @AliasFor("value")
    String url() default "";
}
/**
 * @ReqMapping 注解信息的封装
 */
@Data
public class ReqMappingInfo {
    private String url;
}

视图接口

/**
 * Created by zhouyinyan on 2020/1/14.
 */
@FunctionalInterface
public interface View {
    /**
     * 视图渲染
     * @param req
     * @param resp
     */
    void render(HttpServletRequest req, HttpServletResponse resp) throws IOException;
}

拦截器接口

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 拦截器接口
 */
public interface Interceptor {

    /**
     * 调用前拦截
     * @param request
     * @param response
     * @return
     */
    boolean beforeInvoke(HttpServletRequest request, HttpServletResponse response);

    /**
     * 调用成功后拦截
     * @param request
     * @param response
     * @param result
     * @return
     */
    void  afterInvoke(Object result, HttpServletRequest request, HttpServletResponse response);

    /**
     * 抛异常之后拦截
     * @param exception
     * @param request
     * @param response
     * @return
     */
    void  afterThrowException(Exception exception, HttpServletRequest request, HttpServletResponse response);
}

框架测试

业务控制器

import com.zyy.sc.analysis.yymvc.ReqMapping;
import com.zyy.sc.analysis.yymvc.View;
import lombok.AllArgsConstructor;
import lombok.ToString;
import org.springframework.stereotype.Controller;

import javax.servlet.http.HttpServletRequest;

/**
 * Created by zhouyinyan on 2020/1/7.
 */
@Controller
public class TestController {

    @ReqMapping("/test")
    public String test(String name, HttpServletRequest request){
        return "hello, "+ name  + " -- " + request.getParameter("name") + ", welcome to YY MVC";
    }

    @ReqMapping("/test3")
    public View test3(String name, String location){
        return (req, resp) -> {
            Person person = new Person(name, 28, location);
            resp.getWriter().print(person);
        };
    }


    @ReqMapping("/test2")
    public Person test2(String name, HttpServletRequest request){
        return new Person(name, 28, request.getParameter("location"));
    }

    public String test4(){
        return "test4";
    }

    @ToString
    @AllArgsConstructor
    public static class Person{
        private String name;
        private int age;
        private String location;
    }
}

拦截器实现

import com.zyy.sc.analysis.yymvc.Interceptor;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by zhouyinyan on 2020/1/14.
 */
@Slf4j
public class TestInterceptor implements Interceptor {
    @Override
    public boolean beforeInvoke(HttpServletRequest request, HttpServletResponse response) {
        log.info("----执行:{}.{},进行前置拦截----", "TestInterceptor", "beforeInvoke");
        return false;
    }

    @Override
    public void afterInvoke(Object result, HttpServletRequest request, HttpServletResponse response) {
        log.info("----执行:{}.{},进行后置拦截----", "TestInterceptor", "afterInvoke");
    }

    @Override
    public void afterThrowException(Exception exception, HttpServletRequest request, HttpServletResponse response) {
        log.info("----执行:{}.{},进行抛出异常时拦截----", "TestInterceptor", "afterThrowException");
    }
}

配置类

import com.zyy.sc.analysis.yymvc.Interceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * Created by zhouyinyan on 2020/1/7.
 */
@Configuration
@ComponentScan(basePackageClasses = {YYMvcConfig.class})
public class YYMvcConfig {
    @Bean
    public Interceptor testInterceptor(){
        return new TestInterceptor();
    }
}

测试类

注意,测试使用的内嵌的tomcat来启动的,当然你也可以使用独立的tomcat或其他容器。

import com.zyy.sc.analysis.yymvc.YYServlet;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;

/**
 * Created by zhouyinyan on 2020/1/8.
 */
public class EmbeddedTomcatMain {
    public static void main(String[] args) throws LifecycleException {
        EmbeddedTomcatMain.run();
    }

    private static void run() throws LifecycleException {
        Tomcat tomcat = new Tomcat();

        tomcat.setPort(8080);

        String docBase = "/Users/zhouyinyan/devspace/workspace/gitee/spring-stack-source-code-analysis/src/main/resources";
        Context context = tomcat.addContext("/", docBase);

        YYServlet servlet = new YYServlet();
        servlet.setSpringConfigClass(YYMvcConfig.class);

        tomcat.addServlet("/", "dispatcher", servlet);
        context.addServletMappingDecoded("/", "dispatcher");

        tomcat.start();
        tomcat.getServer().await();
    }
}
发布了36 篇原创文章 · 获赞 12 · 访问量 940
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 游动-白 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览