jetty是HTTP服務,HTTP客戶端,和javax.servlet的容器。它自己被設計成嵌入式模式,應該將jetty集成到本身的應用,jetty自己能夠實例化,能像任何POJO同樣使用,用jetty就至關於把Http服務塞進了本身的應用html
jetty的口號「Don't deploy your application in Jetty, deploy Jetty in your application.」java
啓動jetty java -jar start.jar
。node
運行jetty java -jar start.jar等效於 java -jar start.jar etc/jetty.xml[默認的jetty配置文件]git
啓動jetty若須要的更多參數,能夠統一經過 start.ini 文件來配置github
#===========================================================
# Jetty start.ini example
#-----------------------------------------------------------
OPTIONS=Server
etc/jetty.xml
etc/jetty-http.xml
複製代碼
官網啓動Jetty
OPTIONS:指定構建過程當中這個目錄下面的全部jar都須要添加
etc/jetty.xml:它會添加到啓動start.jar命令的後頭web
在start.ini中同時能夠指定JVM的參數,只是必須添加 --exec
apache
#===========================================================
# Jetty start.jar arguments
#-----------------------------------------------------------
--exec
-Xmx512m
-XX:OnOutOfMemoryError='kill -3 %p'
-Dcom.sun.management.jmxremote
OPTIONS=Server,jmx,resources
etc/jetty-jmx.xml
etc/jetty.xml
etc/jetty-ssl.xml
複製代碼
這麼作是由於這裏添加的JVM 參數並無影響start.jar的啓動,而是另起一個新的JVM,會加上這些參數來運行bash
主要邏輯在Main.java中服務器
以包含java的參數運行爲例
// execute Jetty in another JVM
if (args.isExec()){
//獲取參數
CommandLineBuilder cmd = args.getMainArgs(true);
...
ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
StartLog.endStartLog();
final Process process = pbuilder.start();
...
process.waitFor();
System.exit(0); // exit JVM when child process ends.
return;
}
複製代碼
提取參數的過程當中,對於非JPMS,會在最後添加
cmd.addRawArg("-cp");
cmd.addRawArg(classpath.toString());
cmd.addRawArg(getMainClassname());
複製代碼
能夠追蹤MainClassname獲得
private static final String MAIN_CLASS = "org.eclipse.jetty.xml.XmlConfiguration";
複製代碼
後續新建一個進程,真正的去運行目的程序
pid = forkAndExec(launchMechanism.ordinal() + 1, //獲取系統類型
helperpath, //對於java來講就是獲取 java 命令地址
prog,
argBlock, argc,
envBlock, envc,
dir,
fds,
redirectErrorStream);
複製代碼
主要就是加載全部的xml文件,而後運行實現了LifeCycle接口的方法
List<Object> objects = new ArrayList<>(args.length);
for (int i = 0; i < args.length; i++)
{
if (!args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties") && (args[i].indexOf('=')<0))
{
XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURI().toURL());
if (last != null)
configuration.getIdMap().putAll(last.getIdMap());
if (properties.size() > 0)
{
Map<String, String> props = new HashMap<>();
for (Object key : properties.keySet())
{
props.put(key.toString(),String.valueOf(properties.get(key)));
}
configuration.getProperties().putAll(props);
}
Object obj = configuration.configure();
if (obj!=null && !objects.contains(obj))
objects.add(obj);
last = configuration;
}
}
// For all objects created by XmlConfigurations, start them if they are lifecycles.
for (Object obj : objects)
{
if (obj instanceof LifeCycle)
{
LifeCycle lc = (LifeCycle)obj;
if (!lc.isRunning())
lc.start(); //運行
}
}
複製代碼
對應着jetty.xml中的配置,他就是Server的start方法
它是默認的jetty配置文件,主要包括:
jetty支持多配置文件,每個配置文件中經過指定要初始化的服務器實例,ID來標識,每一個ID都會在同一個JVM中建立一個新的服務,若是在多個配置文件中用同一個ID,這些全部的配置都會用到同一個服務上
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
//<configure> 根元素,指定如下配置是給那個類,通常在jetty.xml中server,或者jetty-web.xml中的WebAppContext
<Configure id="foo" class="com.acme.Foo">
//<set> setter方法調用的標識。name屬性用來標識setter的方法名,若是這個方法沒有找到,就把name中值當作字段來使用。若是有屬性class代表這個set方法是靜態方法
<Set name="name">demo</Set>
<Set name="nested">
//<new>初始化對象,class決定new對象的類型,須要寫全路徑類名,沒有用<arg>則調用默認的構造函數
<New id="bar" class="com.acme.Bar //<arg> 做爲構造函數或者一個方法的參數,用於<call>和<new> <Arg>true</Arg> <Set name="wibble">10</Set> <Set name="wobble">xyz</Set> <Set name="parent"><Ref id="foo"/></Set> //<call>調用對象的某個方法,name屬性代表確確調用的方法的名字 <Call name="init"> <Arg>false</Arg> </Call> </New> </Set> //<ref>引用以前已經生成對象的id <Ref id="bar"> <Set name="wibble">20</Set> //<get>調用當前對象的get方法,同set <Get name="parent"> <Set name="name">demo2</Set> </Get> </Ref> </Configure> 複製代碼
它至關於java代碼
com.acme.Foo foo = new com.acme.Foo();
foo.setName("demo");
com.acme.Bar bar = new com.acme.Bar(true);
bar.setWibble(10);
bar.setWobble("xyz");
bar.setParent(foo);
bar.init(false);
foo.setNested(bar);
bar.setWibble(20);
bar.getParent().setName("demo2");
複製代碼
web服務指定的服務類通常爲 org.eclipse.jetty.server.Server
,而後構建對應的實例
這也是jetty整個架構的體現,Connector用來接收鏈接,Handler用來處理request和response
jetty的線程池默認使用的就是 QueuedThreadPool,它的構造函數以下
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("reservedThreads") int reservedThreads, @Name("queue") BlockingQueue<Runnable> queue, @Name("threadGroup") ThreadGroup threadGroup)
{
if (maxThreads < minThreads) {
throw new IllegalArgumentException("max threads ("+maxThreads+") less than min threads ("
+minThreads+")");
}
setMinThreads(minThreads);
setMaxThreads(maxThreads);
setIdleTimeout(idleTimeout);
setStopTimeout(5000);
setReservedThreads(reservedThreads);
if (queue==null)
{
int capacity=Math.max(_minThreads, 8);
queue=new BlockingArrayQueue<>(capacity, capacity);
}
_jobs=queue;
_threadGroup=threadGroup;
setThreadPoolBudget(new ThreadPoolBudget(this));
}
複製代碼
本質上也是使用最大線程最小線程阻塞隊列來實現
public ServerConnector(
@Name("server") Server server,
@Name("executor") Executor executor,
@Name("scheduler") Scheduler scheduler,
@Name("bufferPool") ByteBufferPool bufferPool,
@Name("acceptors") int acceptors,
@Name("selectors") int selectors,
@Name("factories") ConnectionFactory... factories)
{
super(server,executor,scheduler,bufferPool,acceptors,factories);
_manager = newSelectorManager(getExecutor(), getScheduler(),selectors);
addBean(_manager, true);//在ServerConnector啓動的過程當中,會被啓動
setAcceptorPriorityDelta(-2);
}
複製代碼
int cores = ProcessorUtils.availableProcessors();
if (acceptors < 0)
acceptors=Math.max(1, Math.min(4,cores/8));
if (acceptors > cores)
LOG.warn("Acceptors should be <= availableProcessors: " + this);
_acceptors = new Thread[acceptors];
複製代碼
處理web請求使用的handler,通常使用默認的構造函數,經過set方法來實例化對應的屬性。在jetty.xml中好比指定屬性configurationClasses
通常取值以下
<Array id="plusConfig" type="java.lang.String">
<Item>org.eclipse.jetty.webapp.WebInfConfiguration</Item>
<Item>org.eclipse.jetty.webapp.WebXmlConfiguration</Item>
<Item>org.eclipse.jetty.webapp.MetaInfConfiguration</Item>
<Item>org.eclipse.jetty.webapp.FragmentConfiguration</Item>
<Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
<Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
<Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
<Item>org.eclipse.jetty.webapp.JettyWebXmlConfiguration</Item>
<Item>org.eclipse.jetty.webapp.TagLibConfiguration</Item>
</Array>
複製代碼
好比web有兩個WebInfConfiguration和WebXmlConfiguration,從名字能夠感覺到,WebInfConfiguration就是對應web項目中的WEB-INF目錄,而WebXmlConfiguration就是對應着web.xml文件
Server類是Jetty的HTTP Servlet服務器,它實現了LifeCycle接口。調用的start實現真正執行的就是Server自身的doStart
//AbstractLifeCycle中
@Override
public final void start() throws Exception
{
synchronized (_lock)
{
...
doStart();
...
}
}
複製代碼
相似的後續的全部相關LifeCycle的start啓動,其實就是調用實現了它的類的doStart()方法
Server自己啓動
protected void doStart() throws Exception
{
...
//1. 保證JVM本身掛掉的時候,對應的Jetty進程也會關掉
ShutdownMonitor.register(this);
...
//2. 按照Server本身添加的bean的順序,來一個個的啓動他們
super.doStart();
...
//3. 啓動connector
for (Connector connector : _connectors)
{
try
{
connector.start();
}
catch(Throwable e)
{
mex.add(e);
}
}
...
}
複製代碼
對於server來講,它的bean會在每次調用對應的set方法都會執行,包括ThreadPool和Handler
//ContainerLifeCycle中
public boolean addBean(Object o)
{
if (o instanceof LifeCycle)
{
LifeCycle l = (LifeCycle)o;
//還沒有啓動的統一爲AUTO
return addBean(o,l.isRunning()?Managed.UNMANAGED:Managed.AUTO);
}
return addBean(o,Managed.POJO);
}
複製代碼
執行對應bean的啓動
//ContainerLifeCycle中
protected void doStart() throws Exception
{
...
// start our managed and auto beans
for (Bean b : _beans)
{
if (b._bean instanceof LifeCycle)
{
LifeCycle l = (LifeCycle)b._bean;
switch(b._managed)
{
case MANAGED:
if (!l.isRunning())
start(l);
break;
case AUTO:
if (l.isRunning())
unmanage(b);
else
{
manage(b);
start(l);
}
break;
default:
break;
}
}
}
super.doStart();
}
複製代碼
對於web來講,必定會配置一個handerWebAppContext
來加載對應的web.xml文件
下面着重介紹 WebAppContext
@Override
protected void doStart() throws Exception
{
_tryExecutor = new ReservedThreadExecutor(this,_reservedThreads);
addBean(_tryExecutor);
super.doStart();
_threadsStarted.set(0);
startThreads(_minThreads);
}
private boolean startThreads(int threadsToStart)
{
while (threadsToStart > 0 && isRunning())
{
...
Thread thread = newThread(_runnable);
thread.setDaemon(isDaemon());
thread.setPriority(getThreadsPriority());
thread.setName(_name + "-" + thread.getId());
_threads.add(thread);
_lastShrink.set(System.nanoTime());
thread.start();
started = true;
--threadsToStart;
...
}
return true;
}
複製代碼
能夠看到它會直接調用去啓動最小的線程數
Jetty9中它是主要的實現鏈接TCP/IP的類。能夠在父類中找到對應dostart
//AbstractNetworkConnector
protected void doStart() throws Exception
{
open();
super.doStart();
}
//ServerConnector
public void open() throws IOException
{
if (_acceptChannel == null)
{
_acceptChannel = openAcceptChannel();
_acceptChannel.configureBlocking(true);//阻塞接收鏈接的channel
_localPort = _acceptChannel.socket().getLocalPort();
if (_localPort <= 0)
throw new IOException("Server channel not bound");
addBean(_acceptChannel);
}
}
複製代碼
再向父類執行
//AbstractConnector
protected void doStart() throws Exception
{
...
//1. 選擇鏈接的類型
_defaultConnectionFactory = getConnectionFactory(_defaultProtocol);
...
SslConnectionFactory ssl = getConnectionFactory(SslConnectionFactory.class);
....
//2. 啓動本身的bean
super.doStart();
...
//3. 啓動接收請求的線程
for (int i = 0; i < _acceptors.length; i++)
{
Acceptor a = new Acceptor(i);
addBean(a);
getExecutor().execute(a);
}
...
}
複製代碼
在構造函數執行的時候,bean中添加了SelectorManager,它的實例是一個ServerConnectorManager,執行的構造函數以下
protected SelectorManager(Executor executor, Scheduler scheduler, int selectors)
{
if (selectors <= 0)
selectors = defaultSelectors(executor);
this.executor = executor;
this.scheduler = scheduler;
_selectors = new ManagedSelector[selectors];
_selectorIndexUpdate = index -> (index+1)%_selectors.length;
}
複製代碼
Selector
,一個Deque
protected void doStart() throws Exception
{
...
for (int i = 0; i < _selectors.length; i++)
{
ManagedSelector selector = newSelector(i);
_selectors[i] = selector;
addBean(selector);
}
//執行本身的bean啓動
super.doStart();
}
//新建的ManagedSelector
public ManagedSelector(SelectorManager selectorManager, int id)
{
_selectorManager = selectorManager;
_id = id;
SelectorProducer producer = new SelectorProducer();
Executor executor = selectorManager.getExecutor();
//producer就是SelectorProducer,後續用到
_strategy = new EatWhatYouKill(producer,executor);
addBean(_strategy,true);
setStopTimeout(5000);
}
複製代碼
再次看到ManagedSelector的啓動
@Override
protected void doStart() throws Exception
{
//1. EatWhatYouKill自己並無作特別的doStart實現
super.doStart();
//2.獲取一個JavaSelector
_selector = _selectorManager.newSelector();
// The producer used by the strategies will never
// be idle (either produces a task or blocks).
// The normal strategy obtains the produced task, schedules
// a new thread to produce more, runs the task and then exits.
//3.執行EatWhatYouKill的produce方法
_selectorManager.execute(_strategy::produce);
// Set started only if we really are started
//4.往Deque中塞一個Start事件,實質就是運行起來就標誌這Selector啓動了
Start start = new Start();
submit(start);
start._started.await();
}
複製代碼
這裏的_selectorManager.execute(_strategy::produce);即去獲取對應的鏈接創建後,處理鏈接事件
Acceptor就是集成了Runnable,它的核心就是調用accept方法,對應就是ServerConnector的實現
@Override
public void accept(int acceptorID) throws IOException
{
ServerSocketChannel serverChannel = _acceptChannel;
if (serverChannel != null && serverChannel.isOpen())
{
SocketChannel channel = serverChannel.accept();//等待鏈接的到來
accepted(channel);
}
}
複製代碼
對於web項目來講,處理請求的通常使用WebAppContext。 WebAppContext是用來協助其它的handlers的構建和配置,以實現標準的web應用配置。它繼承了ServletContextHandler,ServletContextHandler則支持標準的經過web.xml配置的session、security,listeners,filter,servlet和JSP
ServletContextHandler擁有 ServletHandler字段,並繼承了ContextHandler WebAppContext同時也實現了LifeCycle類,它的doStart方法核心
protected void doStart() throws Exception{
...
preConfigure();
super.doStart();
postConfigure();
...
}
複製代碼
public void preConfigure() throws Exception
{
// 加載全部的xml文件
loadConfigurations();
...
for (Configuration configuration : _configurations)
{
//對每一個設置的要加載的配置進行處理,好比`WebInfConfiguration`和`WebXmlConfiguration`
LOG.debug("preConfigure {} with {}",this,configuration);
configuration.preConfigure(this);
}
}
複製代碼
public void preConfigure(final WebAppContext context) throws Exception
{
//1. 建立Temp目錄
resolveTempDirectory(context);
//2. 進行一些解壓縮的工做,好比對war進行解壓縮
unpack (context);
//3. 找到容器下面classpath上的jar
findAndFilterContainerPaths(context);
//4. 找到沒有在 /WEB-INF/lib下面的jar
findAndFilterWebAppPaths(context);
//No pattern to appy to classes, just add to metadata
context.getMetaData().setWebInfClassesDirs(findClassDirs(context));
}
複製代碼
它主要是處理了/WEB-INF 目錄的相關工做
@Override
public void preConfigure (WebAppContext context) throws Exception
{
//parse webdefault.xml 這裏就是獲取默認的文件
String defaultsDescriptor = context.getDefaultsDescriptor();
if (defaultsDescriptor != null && defaultsDescriptor.length() > 0)
{
Resource dftResource = Resource.newSystemResource(defaultsDescriptor);
if (dftResource == null)
dftResource = context.newResource(defaultsDescriptor);
context.getMetaData().setDefaults (dftResource);
}
//parse, but don't process web.xml //查找web.xml文件 Resource webxml = findWebXml(context); ... } protected Resource findWebXml(WebAppContext context) throws IOException, MalformedURLException { ... //獲取web-inf目錄 Resource web_inf = context.getWebInf(); if (web_inf != null && web_inf.isDirectory()) { // do web.xml file Resource web = web_inf.addPath("web.xml"); ... } return null; } 複製代碼
這裏就能夠確確實實看到web.xml被加載了
沿着路徑網上能夠看到,在處理了一些類加載器以後
//ContextHandler
protected void doStart() throws Exception
{
...
startContext();
...
}
複製代碼
在WebAppContext中對應實現以下
protected void startContext()
throws Exception
{
//1. 調用對應的配置類的配置方法
configure();
//2. 解析xml
_metadata.resolve(this);
//3. 文件加載結束啓動web
startWebapp();
}
複製代碼
它的配置則是加載了一個標籤處理器
public void configure (WebAppContext context) throws Exception
{
...
context.getMetaData().addDescriptorProcessor(new StandardDescriptorProcessor());
}
//StandardDescriptorProcessor構造函數以下
public StandardDescriptorProcessor ()
{
try
{
registerVisitor("context-param", this.getClass().getMethod("visitContextParam", __signature));
registerVisitor("display-name", this.getClass().getMethod("visitDisplayName", __signature));
registerVisitor("servlet", this.getClass().getMethod("visitServlet", __signature));
registerVisitor("servlet-mapping", this.getClass().getMethod("visitServletMapping", __signature));
registerVisitor("session-config", this.getClass().getMethod("visitSessionConfig", __signature));
registerVisitor("mime-mapping", this.getClass().getMethod("visitMimeMapping", __signature));
registerVisitor("welcome-file-list", this.getClass().getMethod("visitWelcomeFileList", __signature));
registerVisitor("locale-encoding-mapping-list", this.getClass().getMethod("visitLocaleEncodingList", __signature));
registerVisitor("error-page", this.getClass().getMethod("visitErrorPage", __signature));
registerVisitor("taglib", this.getClass().getMethod("visitTagLib", __signature));
registerVisitor("jsp-config", this.getClass().getMethod("visitJspConfig", __signature));
registerVisitor("security-constraint", this.getClass().getMethod("visitSecurityConstraint", __signature));
registerVisitor("login-config", this.getClass().getMethod("visitLoginConfig", __signature));
registerVisitor("security-role", this.getClass().getMethod("visitSecurityRole", __signature));
registerVisitor("filter", this.getClass().getMethod("visitFilter", __signature));
registerVisitor("filter-mapping", this.getClass().getMethod("visitFilterMapping", __signature));
registerVisitor("listener", this.getClass().getMethod("visitListener", __signature));
registerVisitor("distributable", this.getClass().getMethod("visitDistributable", __signature));
registerVisitor("deny-uncovered-http-methods", this.getClass().getMethod("visitDenyUncoveredHttpMethods", __signature));
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
複製代碼
能夠看到這些標籤也就是日常寫web.xml所用到的
對應resolve則是獲取描述符處理器一個個的去處理對應的處理器,以web.xml的處理器來講就是StandardDescriptorProcessor.process
//StandardDescriptorProcessor父類IterativeDescriptorProcessor中
public void process(WebAppContext context, Descriptor descriptor)
throws Exception
{
...
XmlParser.Node root = descriptor.getRoot();
Iterator<?> iter = root.iterator();
XmlParser.Node node = null;
while (iter.hasNext())
{
Object o = iter.next();
if (!(o instanceof XmlParser.Node)) continue;
node = (XmlParser.Node) o;
visit(context, descriptor, node);
}
...
}
複製代碼
它會一個節點的遍歷並調用對應的visit方法,StandardDescriptorProcessor中對一個存在着相應的visit
以<listener>
爲例
public void visitListener(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
//讀取配置的listerclass的名字
String className = node.getString("listener-class", false, true);
EventListener listener = null;
try
{
if (className != null && className.length()> 0)
{
//Servlet Spec 3.0 p 74
//存在重複的名字不會構建重複的實例
for (ListenerHolder holder : context.getServletHandler().getListeners())
{
if (holder.getClassName().equals(className))
return;
}
((WebDescriptor)descriptor).addClassName(className);
//建立一個持有Listerner的類
ListenerHolder h = context.getServletHandler().newListenerHolder(new Source (Source.Origin.DESCRIPTOR, descriptor.getResource().toString()));
//設置持有的類名,即web.xml中配置的
h.setClassName(className);
//使得ServletHandler持有listener
context.getServletHandler().addListener(h);
context.getMetaData().setOrigin(className+".listener", descriptor);
}
}
catch (Exception e)
{
LOG.warn("Could not instantiate listener " + className, e);
return;
}
}
複製代碼
相似的ServletHandler會持有
private ServletHolder[] _servlets=new ServletHolder[0];
private FilterHolder[] _filters=new FilterHolder[0];
它會調用父類的startContext
protected void startContext() throws Exception
{
ServletContainerInitializerCaller sciBean = getBean(ServletContainerInitializerCaller.class);
if (sciBean!=null)
//1. 調用實現了接口ServletContainerInitializerCaller的start方法
sciBean.start();
if (_servletHandler != null)
{
//Ensure listener instances are created, added to ContextHandler
if(_servletHandler.getListeners() != null)
{
for (ListenerHolder holder:_servletHandler.getListeners())
{
//獲取持有listener的holder,這裏實際內部實現就經過反射去建立對應listener的class對象
holder.start();
//we need to pass in the context because the ServletHandler has not
//yet got a reference to the ServletContext (happens in super.startContext)
//對class對象進行初始化
holder.initialize(_scontext);
//將新建的Listener加入contextHandler的_eventListeners,作後續啓動用
addEventListener(holder.getListener());
}
}
}
//調用父類的startContext
super.startContext();
// 啓動ServletHandler中的filter,servlets,listiners
if (_servletHandler != null)
_servletHandler.initialize();
}
複製代碼
父類startContext最關鍵的在於
protected void startContext() throws Exception
{
...
if (!_servletContextListeners.isEmpty())
{
ServletContextEvent event = new ServletContextEvent(_scontext);
for (ServletContextListener listener : _servletContextListeners)
{
callContextInitialized(listener,event);//調用對應配置的listener的contextInitialized方法
_destroySerletContextListeners.add(listener);
}
}
}
複製代碼
最後執行全部相關的servlet,filter的holder啓動
public void initialize()
throws Exception
{
MultiException mx = new MultiException();
Stream.concat(Stream.concat(
Arrays.stream(_filters),
Arrays.stream(_servlets).sorted()),
Arrays.stream(_listeners))
.forEach(h->{
try
{
if (!h.isStarted())
{
h.start();
h.initialize();
}
}
catch (Throwable e)
{
LOG.debug(Log.EXCEPTION, e);
mx.add(e);
}
});
mx.ifExceptionThrow();
}
複製代碼
以ServletHolder爲例,它的實現相似於Listener,先經過反射獲取對應的類,而後新建對象,最後調用servlet的init方法
private synchronized void initServlet()
throws ServletException
{
...
if (_servlet==null)
_servlet=newInstance();
...
// Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
if (isJspServlet())
{
initJspServlet();
detectJspContainer();
}
else if (_forcedPath != null)
detectJspContainer();
initMultiPart();
...
//調用Servlet的init方法
_servlet.init(_config);
...
}
複製代碼
WebInfConfiguration和WebXmlConfiguration沒有實現,什麼都不作
pom中配置
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>jettyVersion</version>
<configuration>
<connectors>
<!--可選。配置了jetty的監聽接口,沒有特別指定,默認使用org.eclipse.jetty.server.nio.SelectChannelConnector ,端口是8080 -->
<connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>9999</port>
</connector>
</connectors>
<stopPort>8888</stopPort>
<stopKey>a</stopKey>
<!--可選。監控web項目是否改變的時間設置【有改變就熱啓動,單位是秒】,默認是0,禁止掃描,任何大於0的數字都是啓用【掃描的地方包括 pom.xml WEB-INF/lib WEB-INF/classes WEB-INF/web.xml】 -->
<scanIntervalSeconds>10</scanIntervalSeconds>
<webApp>
<contextPath>/</contextPath>
</webApp>
</configuration>
</plugin>
複製代碼
這樣能夠本地使用命令行 mvn jetty:run
運行jetty服務了