Spark FrameWork源码研究

暨成双
2023-12-01

Spark FrameWork源码研究

学习了很多Java框架,比较喜欢Spark的风格,lambda表达式使用比较简单,内置了Jetty容器可以方便我们开发,大家喜欢可以访问网站看文档
Spark没有ORM所以比较轻量级,网站上的快速开始看起来非常简单

import static spark.Spark.*;

public class HelloWorld {
    public static void main(String[] args) {
        get("/hello", (req, res) -> "Hello World");
    }
}

直接运行就可以使用了,如何使用官网的文档很清楚,我用了Spark一段时间也学着Spark写一些小的路由映射器玩,但还没有认真研究它的源码,今天看一看源码希望可以学习一下它的思想。

运用内置容器运行

Spark内置了Jetty,我们可以直接运行程序,就用上面的程序来分析源码

get("/hello", (req, res) -> "Hello World");
------------------------------------------------
public static void get(final String path, final Route route) {
      getInstance().get(path, route);
}
//getInstance()只是获取了一个SparkInstance()单例,看SparkInstance()的源码我们就会知道SparkInstance()是一个配置类
final class SparkInstance extends Routable
------------------------------------------------
public void get(final String path, final Route route) {
        addRoute(HttpMethod.get.name(), RouteImpl.create(path, route));
    }

我们可以看到getInstance()继承了Routable()抽象类,调用get方法用addRoute添加信息,其他框架都是把URL和类的匹配放在集合中,方便后面调用,不知道Spark框架会不会这样呢?

static RouteImpl create(final String path, final Route route) {
        return create(path, DEFAULT_ACCEPT_TYPE, route);
}
------------------------------------------------
static RouteImpl create(final String path, String acceptType, final Route route) {
    if (acceptType == null) {
        acceptType = DEFAULT_ACCEPT_TYPE;
    }
    return new RouteImpl(path, acceptType) {
        @Override
        public Object handle(Request request, Response response) throws Exception {
            return route.handle(request, response);
        }
    };
}

知道Java lambda表达式的会知道调用route.handle(request, response)方法就会执行get(“/hello”, (req, res) -> “Hello World”);中的 (req, res) -> “Hello World”,程序将request, response传进去,但是代码上只是new RouteImpl对象,我们知道调用RouteImpl的handle方法就会找到用户写的代码

public void addRoute(String httpMethod, RouteImpl route) {
      init();
      routeMatcher.parseValidateAddRoute(httpMethod + " '" + route.getPath() + "'", route.getAcceptType(), route);
}
------------------------------------------------
public synchronized void init() {
    if (!initialized) {
        routeMatcher = RouteMatcherFactory.get();

        if (!ServletFlag.isRunningFromServlet()) {
            new Thread(() -> {

                server = EmbeddedServers.create(embeddedServerIdentifier, hasMultipleHandlers());
                server.configureWebSockets(webSocketHandlers, webSocketIdleTimeoutMillis);

                server.ignite(
                        ipAddress,
                        port,
                        sslStores,
                        latch,
                        maxThreads,
                        minThreads,
                        threadIdleTimeoutMillis);
            }).start();
        }
        initialized = true;
    }
}
------------------------------------------------
public static synchronized SimpleRouteMatcher get() {
        if (routeMatcher == null) {
            LOG.debug("creates RouteMatcher");
            routeMatcher = new SimpleRouteMatcher();
        }
        return routeMatcher;
    }
------------------------------------------------
public SimpleRouteMatcher() {
  routes = new ArrayList<RouteEntry>();
}

我们来看一下初始化方法的作用,从RouteMatcherFactory中获取SimpleRouteMatcher对象,从SimpleRouteMatcher的构造器中能看出SimpleRouteMatcher是RouteEntry的集合,RouteEntry是方法接受请求的条件,请求方法,请求链接,请求类型等,还有一个匹配链接路径的方法

public void parseValidateAddRoute(String route, String acceptType, Object target) {
   try {
       int singleQuoteIndex = route.indexOf(SINGLE_QUOTE);
       String httpMethod = route.substring(0, singleQuoteIndex).trim().toLowerCase(); // NOSONAR
       String url = route.substring(singleQuoteIndex + 1, route.length() - 1).trim(); // NOSONAR

       // Use special enum stuff to get from value
       HttpMethod method;
       try {
           method = HttpMethod.valueOf(httpMethod);
       } catch (IllegalArgumentException e) {
           LOG.error("The @Route value: "
                             + route
                             + " has an invalid HTTP method part: "
                             + httpMethod
                             + ".");
           return;
       }
       addRoute(method, url, acceptType, target);
   } catch (Exception e) {
       LOG.error("The @Route value: " + route + " is not in the correct format", e);
   }
}
------------------------------------------------
private void addRoute(HttpMethod method, String url, String acceptedType, Object target) {
        RouteEntry entry = new RouteEntry();
        entry.httpMethod = method;
        entry.path = url;
        entry.target = target;
        entry.acceptedType = acceptedType;
        LOG.debug("Adds route: " + entry);
        // Adds to end of list
        routes.add(entry);
    }

parseValidateAddRoute方法很好理解,它只是把我们前面写的get请求的方法要求的请求方法,链接,接受类型等信息分装进了routes集合,方便以后查找
现在框架的准备工作就结束了,因为我们没有设置一些其他条件,所有代码比较简单
下面就比较复杂了,有请求进来时我们要封装request,response,session,cookies等信息
上面我们new了一个线程,启动了Jetty,当我们访问http://0.0.0.0:4567/hello时,在spark.embeddedserver.jetty.JettyHandler类doHandle方法将会被调用,内部如何实现是Jetty的内容,我们就不介绍了。

@Override
public void doHandle(
        String target,
        Request baseRequest,
        HttpServletRequest request,
        HttpServletResponse response) throws IOException, ServletException {

    HttpRequestWrapper wrapper = new HttpRequestWrapper(request);
    filter.doFilter(wrapper, response, null);

    if (wrapper.notConsumed()) {
        baseRequest.setHandled(false);
    } else {
        baseRequest.setHandled(true);
    }

}

首先将request对象封装进HttpRequestWrapper对象中,在调用filter.doFilter方法,如果我们读了官方文档,我们就会知道如果我们不想用内置的Jetty,想用Tomcat等我们可以在web.xml中定义一个过滤器,当我们访问时先执行init方法,像我们前面做的工作,封装代码与链接的对应关系,不过它使用了反射的方法,与前面讲的有一些不同,再调用doFilter方法就和我们现在讲的一样了,可见这部分的代码两种方法是公用的。

HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;

        // handle static resources
        boolean consumedByStaticFile = StaticFiles.consume(httpRequest, httpResponse);

        if (consumedByStaticFile) {
            return;
        }

        String method = getHttpMethodFrom(httpRequest);

        String httpMethodStr = method.toLowerCase();
        String uri = httpRequest.getPathInfo();
        String acceptType = httpRequest.getHeader(ACCEPT_TYPE_REQUEST_MIME_HEADER);

        Body body = Body.create();

        RequestWrapper requestWrapper = RequestWrapper.create();
        ResponseWrapper responseWrapper = ResponseWrapper.create();

        Response response = RequestResponseFactory.create(httpResponse);

        HttpMethod httpMethod = HttpMethod.get(httpMethodStr);

        RouteContext context = RouteContext.create()
                .withMatcher(routeMatcher)
                .withHttpRequest(httpRequest)
                .withUri(uri)
                .withAcceptType(acceptType)
                .withBody(body)
                .withRequestWrapper(requestWrapper)
                .withResponseWrapper(responseWrapper)
                .withResponse(response)
                .withHttpMethod(httpMethod);

        try {

            BeforeFilters.execute(context);
            Routes.execute(context);
            AfterFilters.execute(context);

        } catch (HaltException halt) {

            Halt.modify(httpResponse, body, halt);

        } catch (Exception generalException) {

            GeneralError.modify(httpResponse, body, requestWrapper, responseWrapper, generalException);

        }

        // If redirected and content is null set to empty string to not throw NotConsumedException
        if (body.notSet() && responseWrapper.isRedirected()) {
            body.set("");
        }

        if (body.notSet() && hasOtherHandlers) {
            if (servletRequest instanceof HttpRequestWrapper) {
                ((HttpRequestWrapper) servletRequest).notConsumed(true);
                return;
            }
        }

        if (body.notSet() && !externalContainer) {
            LOG.info("The requested route [" + uri + "] has not been mapped in Spark");
            httpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
            body.set(String.format(NOT_FOUND));
        }

        if (body.isSet()) {
            body.serializeTo(httpResponse, serializerChain, httpRequest);

        } else if (chain != null) {
            chain.doFilter(httpRequest, httpResponse);
        }

代码有点长,不过很好理解,我们来分析一下,先判断请求是不是静态资源,如果是直接返回,再从request对象中取出一些有用的信息,我们可以不是很理解下面几行代码

BeforeFilters.execute(context);
Routes.execute(context);
AfterFilters.execute(context);

官网中有下面的内容

Filters
Before-filters are evaluated before each request, and can read the request and read/modify the response.
To stop execution, use halt:
before((request, response) -> {
    boolean authenticated;
    // ... check if authenticated
    if (!authenticated) {
        halt(401, "You are not welcome here");
    }
});
After-filters are evaluated after each request, and can read the request and read/modify the response:

after((request, response) -> {
    response.header("foo", "set by after filter");
});
Filters optionally take a pattern, causing them to be evaluated only if the request path matches that pattern:

before("/protected/*", (request, response) -> {
    // ... check if authenticated
    halt(401, "Go Away!");
});

我们可以知道BeforeFilters.execute,AfterFilters.execute,就想过滤器一样在程序的前面和最后的执行,因为我们使用的程序没有定义,所有没有讲到,我们重点看一下Routes.execute(context)。
代码比较长我们按部分讲,想看完整代码可以看spark.http.matching.Routes类

Object content = context.body().get();//先取出body值
RouteMatch match = context.routeMatcher().findTargetForRequestedRoute(context.httpMethod(), context.uri(), context.acceptType());//findTargetForRequestedRoute方法的主要作用是匹配链接,链接格式是否可以接受,还会进行最佳URL匹配
//head方法也可以看做get方法使用
Object target = null;
        if (match != null) {
            target = match.getTarget();
        } else if (context.httpMethod() == HttpMethod.head && context.body().notSet()) {
            // See if get is mapped to provide default head mapping
            content =
                    context.routeMatcher().findTargetForRequestedRoute(HttpMethod.get, context.uri(), context.acceptType())
                            != null ? "" : null;
        }
//如果匹配上了
if (target != null) {
            Object result = null;
            if (target instanceof RouteImpl) {
                RouteImpl route = ((RouteImpl) target);
                //判断是否有request,没有就创建request对象,有就改成现在的request对象,不是很理解,个人觉得起跳转方面的作用
                if (context.requestWrapper().getDelegate() == null) {
                    Request request = RequestResponseFactory.create(match, context.httpRequest());
                    context.requestWrapper().setDelegate(request);
                } else {
                    context.requestWrapper().changeMatch(match);
                }

                context.responseWrapper().setDelegate(context.response());
                //调用handle启动匹配的代码,获取返回的对象
                Object element = route.handle(context.requestWrapper(), context.responseWrapper());

                result = route.render(element);
            }
            if (result != null) {
                content = result;
            }
        }
//将返回的内容放在body中
context.body().set(content);

我们回到前面的dofilter方法了,可以看到程序判断是否返回值并根据不同的情况进行匹配,重要的是下面几行

if (body.isSet()) {
    body.serializeTo(httpResponse, serializerChain, httpRequest);

} else if (chain != null) {
    chain.doFilter(httpRequest, httpResponse);
}

如果有返回内容就直接序列化返回,如果没有就执行chain.doFilter释放拦截,程序代码还是比较多的,我们只是讲了必须的一部分,不过根据这些内容,我们也可以很容易理解其他部分,看了源码我才知道它对安全套接字ssl链接提供了方法,看来看源码还是很重要的,下一篇我将前面没讲到的定义拦截器初始化的代码进行分析,再写一些Spark框架的一些语法糖。

 类似资料: