- 12.1 Jetty的基本架构
它有一个基本数据模型Handler,所有可以被扩展的组件都可以作为一个Handler添加到Server中,Jetty将帮你管理这些Handler。
Jetty的基本架构简介
整个Jetty的核心由Server和Connector两个组件构成,整个Server组件是基于Handler容器工作的,它类似Tomcat的Container容器。Connector组件负责接受客户端的连接请求,并将请求分配给一个处理队列去执行。
整个Jetty的核心围绕着Server类来构建,Server类继承了Handler,关联了Connector和Container,Container是管理Mbean的容器。Jetty的Server的扩展主要是实现一个个Handler并将Handler加到Server中,Server中提供了调用这些Handler的访问规则。
每个组件都会持有一个观察者(在这里是Listener类)集合,当start、fail或stop等事件被触发时,这些Listener将会调用。
Handler的体系结构
Handler体系结构影响着整个Jetty的方方面面。主要有两种Handler类型。一种是HandlerWrapper,它可以将一个Handler委托给另一个类去执行,如我们将一个Handler加到Jetty中,那么必须将这个Handler委托给Server去调用。配合ScopeHandler类可以拦截Handler的执行,在调用Handler之前或之后可以做另外一件事,类似于Tomcat的Valve。另一种Handler类型是HandlerCollection,这个Handler类可以将多个Handler组装在一起,构成一个Handler链。
- 12.2 Jetty的启动过程
Jetty的入口是Server类,Server类启动完了,就代表Jetty能提供服务了。在Jetty的配置文件我们可以发现,配置Jetty的过程就是将那些类配置到Server的过程。
因为Jetty的所有组件都会继承LifeCycle,所以Server的start方法就会调用所有已经注册到Server的组件,Server启动其他组件的顺序:首先启动设置Server的Handler,通常这个Handler会有多个子Handler,接着会启动注册在Server上JMX的Mbean,让Mbena也一起工作,最后启动Connector,打开端口,接受客户端请求。
- 12.3 接受请求
Jetty作为一个独立的Servlet引擎可以独立提供Web服务,但是它也可以与其他Web应用服务器集成,所以它可以基于两种协议工作,一个是HTTP,另一个是AJP协议。
基于HTTP协议工作
如果前端没有其他Web服务器,那么Jetty应该基于HTTP协议工作,也就是当Jetty接收到一个请求时,必须按照HTTP协议请求和封装返回的数据。
我们设置Jetty的Connector实现类为org.eclipse.jetty.server.bi.SocketConnector,让Jetty以BIO的方式工作。Jetty在启动BIO的处理方式处理连接请求,ServerSocket用于建立Socket连接以接受和传送数据,Executor用于处理连接的线程池,它负责处理每一个请求队列中任务。acceptorThread监听连接请求,一有Socket连接,它将进入下面处理流程。
当Socket被真正执行时,HttpConnection将被调用.
Jetty创建接受连接环境需要三个步骤:
1)创建一个队列线程池,用于处理每个建立连接的任务,这个线程池可以由用户来指定。
2)创建ServerSocket,用于准备接受客户端的Socket请求,以及客户端用来包装这个Socket的一些辅助类。
3)创建一个或多个监听器,用来监听访问端口是否有连接进来
当建立连接的环境已经准备好就可以接受HTTP请求了,当Acceptor接受到Socket连接后转入下图流程。
Acceptor线程将会为这个请求创建ConnectorEndPoint,HttpConnection用来表示这个连接是一个HTTP连接,它会创建HttpParse类解析HTTP协议,并且会创建符合HTTP协议的Request和Response对象。接下去将线程交给队列线程池去执行。
基于AJP工作
通常一个Web服务站点的后端服务器不是将Java的应用服务器直接暴露给服务者,而是在应用服务器的前面再加一个Web服务器,可以做日志分析、负载均衡、权限控制、防止恶意请求以及静态资源预加载等。
这种架构下Servlet引擎就不需要解析和封装返回的HTTP协议,因为HTTP协议的解析工作已经在Apache或Naginx服务器上完成,Jboss只要基于更加简单的AJP协议工作就行了,这样就能加快请求的响应速度。
实际上AJP处理请求相比于HTTP唯一不同就是Socket数据包时如何来转换这个数据包,按照HTTP协议的包格式来解析就是HttpParser,按照AJP协议来解析就是Ajp13Parserer。
让Jetty工作在AJP协议下,需要配置connector的实现类为Ajp13SocketConnector,这个类继承了SocketConnector类,覆盖了父类的newConnection方法,为的是创建Ajp13Connection对象而不是HttpConnection。
与HTTP方式唯一不同的地方就是SocketConnector类替换成Ajp13SocketConnector类,改成Ajp13SocketConnector的目的是可以创建Ajp13Connection类,就表示当前这个连接使用的是AJP协议,所以需要用Ajp13Parser类解析AJP协议,处理连接的逻辑都是一样的。
基于NIO方式工作
Jetty默认connector方式是NIO方式。NIO工作原型:
Selector selector = Selector.open(); ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT); ServerSocketChannel ss = (ServerSocketChannel) key.channel(); SocketChannel sc = ssc.accept(); sc.configureBlocking(false); SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ); Set selectedKeys = selector.selectedKeys();
创建一个Selector相当于一个观察者打开一个Server端通道,把这个Server通道注册到观察者上并且指定监听事件,。然后遍历这个观察者观察到的事件,取出感兴趣的事件再处理。这里有个最核心的是我们不需要为每个被观察者创建一个线程来监控它随时发生的事件,而是把这些被观察者都注册一个地方统一管理,再由把它触发的事件统一发给感兴趣的程序模块。这里的核心是能够统一地管理每个被观察者的事件,所以我们就可以把服务端的每个建立的连接传送和接受数据作为一个事件统一管理,这样就不必每个链接需要一个线程来维护了。
这里需要注意的是,很多人认为监听SelectionKey.OP_ACCEPT事件就已经是非阻塞方式了,其实Jetty仍然用一个线程来监听客户端的连接请求,当接收到请求后,把这个请求再注册到Seletor上,然后才以非阻塞方式执行。还有一个容易误解的地方,即认为Jetty以NIO方式工作只会有一个线程来处理所有的请求,甚至认为不同用户会在服务端共享一个线程从而导致基于ThreadLocal的程序会出现问题。其实从Jetty源码中发现,真正共享一个线程的处理只是在监听不同连接的数据传送事件上,如有多个连接已经建立,传统方式是当没有数据传输时,线程是阻塞的,也就是一直在等待下一个数据的到来,从而NIO的处理方式是只有一个线程在等待所有来凝结的数据的到来,而当某个连接数据到来时Jetty会把它分配给这个连接对应的处理线程去处理,所以不同连接的处理线程仍然是独立的。
- 12.4 处理请求
实际上Jetty的工作方式非常简单,当Jetty接收到一个请求时,Jetty就把这个请求交给在Server中注册的代理Handler去执行,如何执行你注册的Handler同样是由自己决定,Jetty要做的就是调用你注册的第一个Handler的handle(String target,Request baseRequest,HttpServlterRequest request,HttpServletResponse response),方法,接下来完全由你决定怎么做。
要能接受一个Web请求访问,首先要创建一个ContextHandler,如下代码所示:
Server server = new Server(8080); ContextHanler context = new ContextHandler(); context.setContextPath("/"); context.setResourceBase("."); context.setClassLoader(Thread.currentThread().getContextClassLoader()); server.setHandler(context); context.setHandler(new HelloHandler()); server.start(); server.join();
当我们在浏览器中输入http:localhost:8080时请求将会代理到Server类的handle方法,Server的handle方法将请求代理给ContextHandler的handle方法,ContextHandler又调用HelloHandler的handle方法。这个调用方式是和Servlet的工作方式类似,在启动之前初始化,创建对象后调用Servlet的service方法。在Servlet的API中我通常只实现它的一个包装好的类,在Jetty中也是如此,虽然ContextHandler也只是一个Handler,但是这个Handler通常由Jetty帮你实现。
访问一个Servlet的代码:
Server server= new Server(); Connector connector = new SelectChannelConnector(); connector.setPort(8080); server.setConnectors(new Connector[]{ connector}); ServletContextHandler(null,"/",ServletContextHandler.SESSIONS); server.setHandler(root); root.addServlet(root); root.addServlet(new ServletHolder(new org.eclipse.jetty.embeded.HelloServlet("Hello")), "/"); server.start(); server.join();
这段代码中创建了一个ServletContextHandler并给这个Handler添加一个Servlet,这里的Servletholder是Servlet的一个装饰类。
从图中可以看出,Jetty处理请求的过程就是Handler链上handle方法的执行过程。这里解释ScopeHandler的处理规则,ServletContextHandler、SessionHandler和ServletHandler都继承了ScopeHandler.handle->ServletContextHandler.doHandle->SessionHandler.doHandle->ServletHandler.doHandle
- 12.5 与Jboss集成
Jboss是基于JMX的架构,所以只要符合JM规范的系统或框架都可以作为一个组件加到Jboss,扩展Jboss功能。Jetty作为主要的Servlet引擎当然支持与Jboss集成。
Jetty作为一个独立的Servlet引擎集成到Jboss需要Jboss的AbstractWebContainer类,这个类实现的是模板模式,其中有一个抽象方法需要子类去实现,它是getDeployer,可以指定创建Web服务的Deployer。Jetty工程中有个jetty-jboss模块,编译这个模块就会产生一个SAR包。
SAR包下面jboss-jetty-6.19目录下有一个webdefault.xml配置文件,是Jetty默认Web.xml配置,在META-INF目录下有一个jboss-service.xml文件,这个文件配置了MBean,如下:
<mbean code="org.jboss.jetty.JettyService" name="jboss.web:service=WebServer" xmbean-dd="META-INF/webserver-xmbean.xml"> </mbean>
同样这个org.jboss.jetty.JettyService类也继承了org.jboss.web.AbstractWebContainer类,覆盖了父类的startService方法,这个方法直接调用jetty.start启动Jetty。
- 12.6 与tomcat的比较
架构比较
设计模板角度,Handler的设计实际上就是一个责任链模式,接口类HandlerCollection可以帮助开发者构建一个链,而另一个接口类ScopeHandler可以帮助开发者控制这个链的访问顺序。另一个设计模板就是观察者模式,用这个设计模式控制了整个Jetty的生命周期,只要继承了LifeCycle接口。对象就可以交给Jetty来统一管理。
Tomcat的核心是容器的设计,从server到Service再到engine等container容器,将应用服务器的内部结构暴露给外部使用者,使得如果想扩展就必须得了解整体结构。
性能比较
Tomcat在处理少数非常繁忙的连接上更有优势
Jetty同时处理大量连接而且可以长时间保持这些连接,使用的是NIO,在处理I/O请求上更占优势
特性比较
作为一个标准的Servlet引擎,都支持标准的Servlet规范,还有Java EE规范也都支持,Tomcat使用广泛,支持更全面。Jetty修改简单,应变快速。