Spark FrameWork源码研究
2016-03-06 11:21
405 查看
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框架的一些语法糖。
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 从源码安装Mysql/Percona 5.5
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序