学习了很多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框架的一些语法糖。