jetty分析

jetty内部比较重要的实现,jetty里面的内容比较的多,代码量也是比较大的,我们不可能做一个面面俱到的一个讲述,

我们不会去介绍他整个系统架构上的一些设计,作为一个servlet容器,它是怎么处理JSP,不是我们的重点,我们的重点是

他作为一个高性能的,吞吐量比较高的,这么一个HTTP服务器,他后面采用了什么样的技术,多线程的手段,很好的来提高

他系统的吞吐量,着眼点和立足点呢,是从这个角度去看,我们不会去讲他,系统当中,还有哪些模块,UML图,还有架构图,

毕竟多线程,并发这门课,他更多的还是会有实现细节,所以我们也是停留在代码的层面,做一些比较细节的东西,分析的

主要内容有三块,因为他是一个http的server,他也可以做一个嵌入式的实现,我们需要一个http server的时候呢,把jetty

给include进来,直接把这个server给new出来,http请求的这个处理,内部也是使用这个server去做的,http server包下的

这个类,主要分三块,如果我们想把一个server跑起来的话,并且能够正常的处理一些工作,首先我们把server给new出来,

把这个server的实例创建出来,创建实例我们要把它给start起来,server启动,等待http的请求,当有http请求之后呢它会做处理,

这三块就会把怎么跑起来的,处理请求的,比较完整的给理一遍,这两块内容是我们的重点,到底有些什么东西,如何排放的,

这个设计到请求处理,1请求处理之后,他后面一定是相当复杂的,涉及到service容器的JSP的处理,我们这块内容是讲的

比较简略的,我们把这个请求分给某个线程,那就中断了,线程怎么处理,这是一系列的Handler的调用,我们就不做进一步的

介绍了,jetty使用上的一些问题,这个看下代码也不是很困难的事情,我们可以看一下server实例的新建,大概最简单的

一个函数

在端口上做监听,这个里面到底做什么事情呢,他里面做的事情主要是有这四个,一个是初始化线程池,

什么叫初始化线程池呢,像jetty这样的一个服务端的,这么一个容器,服务端的一个产品,他后台执行的一定是

线程池的,不可能你有一个什么任务,他自己就new一个线程出来,这不是很靠谱的事情,但是这里一定要说明的是呢,

也没有使用JDK的线程池,他也可以配置JDK的线程池,他实现了一个新的线程池,QueueThreadPool,所以在初始化线程池的

时候呢,把自己的线程池给初始化了一下,他就实现了SizedThreadPool接口,也是jetty当中的一个接口,线程池我们知道,

他最重要的是execute方法,也就是执行,我们可以看到在这个执行当中,是怎么做的呢,如果你提交一个Runnable的接口,

Runnable的一个实例上去,他的做法对于线程池来讲,他只是把它放到队列当中去,除此之外没有更多的处理了,他只是把我们

要执行的任务,入队,这个jobs是什么呢,它是一个BlockingQueue,这个jobs是一个BlockingQueue,他保存需要由这个线程池执行的,

由此我们可以看到说,这个BlockingQueue的性能不会特别好,BlockingQueue不是一个高性能实现,从这里侧面也说明呢,execute方法

不会非常频繁的被调用,如果非常的频繁的执行execute方法的话,而且BlockingQueue还是一个性能提升的一个点,我印象当中这个

BlockingQueue,jetty当中还是实现了自己的BlockingQueue实现,这个性能也不怎么样,就是和JDK的BlockingQueue是大同小异的,

也是会做一些阻塞的操作,这个是线程池的初始化

ServerConnector的东西,服务端的一个连接,当然是用来处理http的一些连接,NIO ByteChannel的一些东西,

他继承自AbstractConnector,初始化他的时候会做什么事情呢,这里列举的是主要的工作,并不是所有的工作,

他的代码是非常的繁琐的,如果直接看代码会直接晕掉,所以还是做了一些整理,去掉一些不是很重要的东西,初始化

这么一个ScheduleExecutor,这个东西是干什么用的呢,我们知道JDK当中有一个ScheduledThreadPoolExecutor这个类,

他就是一个调度器,像服务器当中,不可避免的我们有些任务每隔一段时间执行一次的,每一分钟我们要检查一下东西,

诸如此类的一些任务,这是由Schedule来调度的,ByteBufferPool,ByteBuffer的一个池子,他为什么要初始化byteBuffer

的一个池子呢,这个池子是什么东西呢,跟线程池一样,线程池里面放的什么呢,放的的线程,ByteBufferPool里面放的什么呢,

BuyteBuffer,ByteBuffer是什么呢,NIO的时候也说过,Buffer是个数组,简单理解成一个数组,ByteBuffer有两种,indirect是

在堆当中的,direct是在直接内存当中的,ByteBufferPool他其实就是一个对象池,池子当中的所有的ByteBuffer,实际上是可以

被复用的,对象池的一大好处,就是我可以减少GC,减少对象的产生,如果我每次需要bytebuffer,我接收某一个数据的时候,因为

我毕竟是一个http的server,坦白的讲我是一个TCP的服务器,我要去网络上读取某些数据,读数据之前我可能需要,new一个byteBuffer

出来,如果我写数据回去,写到通道里面去,把这个bytebuffer填满,把数据写到通道里面去,这个时候我们在系统中大量的去

new bytebuffer,那么new完之后,这样做也无可厚非,但是为了进一步的提高系统的性能,允许我们会想到说,

我们不需要每次都释放掉,先准备好一堆,有人需要用的时候就去拿一个,拿一个你就用,用完之后你也不用回收,

返给我byteBuffer这个池子就可以了,这样有个什么好处呢,我对象是复用的,我不会每次去new对象,

同时也会减少压力,否则会出现什么情况呢,如果我连接数非常强大的情况之下,

如果我要处理好多好多Channel的情况之下,那么我new这个数据,可能会不停的new他,你new其实是一个很快的操作,在JAVA当中

new这个关键字,它是被绝对的优化的,所以他的性能是很高的,大家不要认为new会影响太大的性能,但是回收会比较麻烦,

你可能会导致大量的碎片会回收,新生代的回收会更加的频繁,这个是我不愿意看到的,但是如果我们随随便便的去搞一个

对象池,哪有什么坏处呢,其实在很多情况之下,如果我们自己随便写一个对象池,还不如直接去new,回收来的好,因为对象池的

一个特点呢,必须要满足线程安全,因为你的对象不可能是一个人用的,你有好多线程在中间用,有一好多线程在用,你拿到对象,

跟我退还对象的时候,你是需要保证线程安全的,如果你简单的加synchronized,那基本上可以断定,除非你这个对象是非常特别的

对象,非常大的超级对象,在这种情况之下呢,如果你用synchronized粗暴的方式去做,你这个性能会比new好一点,否则的话呢,

你用synchronized做出来的线程池,对象池呢,性能是很差很差的,你还不如new一个对象来的划算,所以我们这里要学的一个核心呢,

线程池是线程安全的,并且是无锁的,他不是用synchronized来实现的,ByteBufferPool在Jetty里的实现呢,有几个,这里选一个

常用的ArrayByteBufferPool来讲

是ByteBufferPool的一个实现了,他和普通的对象池有什么不一样的呢,普通对象池当中,所有的对象都是对立的,

换句话说,我拿任何一个对象都是一样的,所有的对象都是等价的,就跟线程池里的线程,无论挑到那个给你执行,

都是一样的,数据库连接池当中,所有的线程,不论你挑到哪个给你执行,数据库连接对你来说都是一样的,但是ByteBufferPool,

所以在这一点来讲,比普通的对象池稍微复杂一些,因为你可能会使用,可能你的请求当中,需要1K的byteBuffer,你也可能需要

2K的byteBuffer,你也可能需要2M的byteBuffer,这都是有可能的,那我不可能把所有大小的byteBuffer都保存N份让你来使用,

这是不切实际的,换句话说呢,这个ArrayBytePool当中,他所有的对象,他并不是等价的,这就是他实现上会有非常麻烦的地方,

他这个地方是怎么做的呢,这个ArrayByteBufferPool他有三个参数

增量最大大小,最小大小是什么呢,我这个池子当中,我最小的那一个,byteBuffer,起始大小,增量指的是,

我最大的byteBuffer有多大呢,比如这里有64K,比如这里是0,默认是0,64K,我以1K为增量,在这个池子当中呢,

一直到64K为止,像这么大的buffer,会保存到那边,那么保存的哪些buffer,他怎么存放呢,当你把它去new出来的

时候,你没有去存放他,你并没有把实际的byteBuffer去给他new出来,因为是没有必要的,也许你只用一种大小32K,

其他大小从来都不用的,这是有可能的,如果你把6K,64k都new出来,那是没有必要的,所以这里是延迟加载的策略,

你不同的大小的buffer,你放到pool当中呢,它是会放在一个叫做bucket的一个篮子里

我们可以把这个bucket理解成一个篮子,一个bucket可能就放一个大小的buffer,你用了64种大小的buffer呢,

那你自然需要64种byte,因为这种地方会涉及到说,是有可能是直接内存的,也有可能是堆的,有直接内存和堆的

两个的bucket,一个bucket里面有什么东西呢,bucket里面有两样,size就是byteBuffer有多大,还有实际的存放

byteBuffer的一个容器,一个queue,这个地方要注意的,高并发的ConcurrentLinkedQueue,大家应该比较清楚了,

他的并发性能很好,它是一个无锁的实现,所以当你有大量的请求的时候呢,他的性能是非常的不错的,在初始化

ByteBufferPool的时候呢,它是会把所有的bucket,都创建一遍,但是bucket里面queue里面的内容,它是延迟加载的,

额外Pool有两个非常重要的方法,就是acquire

我要池子里面申请一个buffer,就是我这个buffer用完了,我要归还你这个池子,我申请相当于我去new一个buffer出来,

这里有两个参数,一个是你要多大的一个buffer,第二个你是要从直接内存,还是从堆,这个acquire主要分三步,第一个我要

找到一个合适的bufferBucket,因为我知道没有一个bucket的大小都不一样,那我1到64K,就是当我64个Bucket,分别是1K,2K,

一直到64K,加入你只要了5.5K,那我要取多少呢,来给你用,那就应该给你6K的,就是我要找到一个合适的bucket来用,这个大小的

bucket是我要的,然后我要从这个bucket当中取到,我要的buffer,默认情况之下呢,当然是没有东西的,这是延迟加载的,在没有

东西的情况下呢,他就会新建,如果存在就返回

Queue当中有数据,不存在新建,新建出来,把这个buffer返回,应该说还是比较容易理解的,此外就是release,

release最重要的就是返回线程池,也是分三步,第一步就是我要取得合适的bucket,那我这个buffer有多大,

那我就要还到bucket一样的大小里面去,我得把bucket给取出来,取完之后我要清空buffer,因为buffer用完了,

limit,capacity,当前的position,全部都要清空,都要初始化,可以让别人拿到之后继续使用,最后归还到Queue

当中去,这样以后别人用的时候呢,就会拿到,此外他还可以支持另外的处理

你现在只有1到64K的buffer,现在突然出现一个线程说,128K你给不出来,如果说给不出来,

我会给你成功的申请,刚才看到的acquire当中,不存在是会给你申请的,我没有办法给你归还,

只是换不进去而已,还不进去怎么办呢,这个池子当中是不会有pool出现了,因此你还不进去的buffer,

在将来是必然会被系统回收的,只要你过了作用域之后,是会被回收掉,这是没有关系的,这个就是ByteBufferPool的

一个实现,这个实现我个人认为呢,是非常重要的,它主要体现的思想呢,一个是无锁,对象池在多线程的情况之下,

他必须是无锁的,否则他还不如去new一个来的快,第二个我们看到他比较好的处理了,各个不同的大小的buffer,它是

如何做一个处理,从这个角度上来讲呢,比普通的对象池呢,会复杂一些,这里是ByteBufferPool,接着他会初始化

ConnectionFactory

ConnectionFactory它是一个工厂,一个典型的ConnectionFactory呢,HttpConnectionFactory,他就用于创建连接,

当有人客户端连接上来,我自然要创建一个对象,来维护这个连接,这个就是Factory的一个作用,来创建连接的一个

对象,接着他取得CPU的一个数量,这个是很重要的,下面jetty会根据你CPU的数量,来决定我在这个系统当中,应该使用

多少个accept线程,他都要由这个cores算出来的,这也是对于一个高并发的程序来讲,你必须做的一个事情,你必须去

自适应CPU的数量,否则你在两个CPU上,你完全没有办法很好的去协调,然后你根据CPU的数量呢,更新或者是计算,acceptor

线程的数量,我又多少个线程用于accept,我们知道我们在做网络编程的时候,一个服务端你是要做accept等待的,那你有几个

线程来做accept等待呢,这里大家可以看一下,jetty提供一个经验的一个公式,有几个线程来做这个等待,是比较合适的,首先

他取了一个最小值,第一个是4,换句话说,他认为,你用做accept的线程数量,应该是很少的,不应该是很多的,如果你CPU数量真的

很多,也不会超过四个,最多也不超过4个去做accept,这个也是我们在编程中可以借鉴的一个地方,有时候我们也要写一个网络

程序,我们也要accept一个东西,这个时候有几个线程去accept呢,不要超过4个,如果你不到4个,你有10个CPU,假设你只有10个

CPU,那你只能用一个线程,如果你有20个CPU,那你就去使用2个线程,这个数量不会太多,有了这个acceptor数量之后呢,他就要创建

一个acceptor线程数组,并没有初始化线程,只是把维护数组的线程给创建出来,其实它会初始化ServerConnectorManager的东西,

它是继承SelectorManager,什么叫SelectorManager呢,对Selector做一个管理,之前有讲过NIO,也说过Selector是干嘛用的,

就是当你通道准备好的时候呢,你有select到我,具体的NIO的副本呢,就不在这里展开了,我有多少个线程来做select这个事情,

你有这个通道准备好,他这里给了一个经验的值,之多也不超过4个,比如你有4个CPU的话呢,你可能有2个线程去做select,

但是很明显selector的数量比acceptor要多,selector不处理具体的业务逻辑,所以也不需要太多的线程,真正的把数据

拿下来之后呢,你还是会使用实际的线程去做操作,这里是初始化serverConnector,下一步就会设置port

我们需要在8080端口监听,关联server和Connector,这个是server初始化时候做的事情,我们看一下server启动

以后会做什么事情,server启动以后主要是做三个,前两个其实是没有意义的,设置启动状态,我用于系统的管理和

维护,正是启动过程,然后我启动完毕了,包括server在内,很多jetty当中的对象,他都是有生老病死的过程,生命周期

在里面,他们都会继承lifeCircle,所以我们没有在文档当中给展示出来,只是给大家做了一个介绍,而系统当中也会

有一些manager,会有一些管理器,会统一的对lifeCircle的对象呢,管理做维护,start,end,这个就有点类似在Spring

当中,对Bean声明周期的管理,有点接近,server.start最核心的呢,就是doStart方法,它是真正启动要做的事情,主要是

这么几步

第一个注册shutdownMonitor,允许你远程把jetty服务给关掉,作为一款商用的,它是一个比较成熟的server,

要提供一些比较强大的功能,Monitor线程就是用来做这个事情的,继而我们拿到线程池,这里只是把它拿出来,

有点类似Spring的方法,但是他用的不是Spring,自己自成了一套管理体系,他也是归整个系统托管,设置selector

的数量,因为我们前面设置了selector的数量,那只是一个ServerConnector当中,计算这个selector数量,事实上server

当中呢,jetty允许有多个connector,累计所有的需求

就是你所需要的线程数量,如果线程大于200呢,你这个程序就直接终止掉了,因为这个时候觉得

已经没有执行下去的价值了,因为200个线程去做selector,跟做acceptor,性能也是很差很差的那种了,

然后会维护这个bean

它会启动ThreadPool,启动调用doStart方法,ThreadPool他做什么事情呢,它是会启动若干个线程,把你所需要的线程

都给建立起来,它会做三件事,第一个创建线程,创建线程使用Runnable的接口,设置线程的属性,比如优先级,线程是否是

Daemon的线程,设置线程的名字,我这个设置线程的名字我认为是非常重要的,可以方便调试,最后启动线程,把线程起来,

创建线程它使用的是Runnable的接口

这个Runnable接口中做的事情是什么呢,去jobs当中去拿我们的任务,这就是QueueThreadPool里面的,

就是去jobs当中取Runnable的任务,jobs中的任务是哪里来的呢,就是execute方法当中塞进去的,所以

execute的时候他并没有执行,他只是塞到jobs队列当中去,只有当你线程起来之后,这里每一个真实执行的

线程,他才会不停的从jobs里面拿任务,他这里肯定是一个死循环,作用是不停的去jobs里面获取,另外一个

bean呢就表示WebAppContext,如果有需要的时候呢,Servlet容器所规范的内容,最后会启动Connector

启动Connector又可以分为这么几步,第一步会去的ConnectorFactory,ConnectionFactory在前面维护

起来了,然后会创建Connector的线程并启动,因为之前已经计算了Connector线程的数量,所以可以根据数量创建

Connector线程,这里要注意的是,就是ManagerSelector,就是一个线程,但是他封装了对于NIO来讲,他封装了Selector

的操作,一个可管理的selector,然后把它给启动,接着会创建Acceptor线程

Acceptor线程的作用就是用来等待客户端连接,那我们来看看Acceptor线程,做了什么事情,你要创建

几个acceptor线程,然后把Acceptor线程给创建出来,去做这个执行,Acceptor线程做什么呢,首先他要设置

线程的名字,这是一个非常好的习惯,都带有acceptor的关键字,设置优先级,并且将自己加入到acceptors

数组当中去

然后监听端口,在server上面做accept,在这个地方做等待,如果你只有一个线程,那你只有一个线程在这里等待,

如果你有两个accept,那就有两个accept在这里做等待,这里还有一种情况呢,就是没有Acceptor的情况,在默认的

情况之下呢,Acceptor线程数量是有的,是大于0个的,但是也有可能accptor的数量是0,如果没有专门的线程,用来做

acceptor操作,对于NEW IO来讲,accept本身也可以是非阻塞的,当有线程真正accept上来的时候呢,他在做一个select

的一个返回,来告诉你有人accept上来了,但是在默认情况之下,他是一个阻塞的操作

当acceptor线程数量为0,他就会把acceptChannel配置成非阻塞的形式

把server启动完了,然后http请求,http请求是和accept相关的,我们可以看到这个地方,成功以后就会去做一些配置,

它会把拿到的channel配置成非阻塞模式,然后去配置一些socket

设置为非阻塞模式,然后配置socket,最后做一些正是的处理,那正式处理当中呢,他要把channel交给SelectorManager

做处理

SelectorManager在处理的时候呢,它会选择一个可用的线程,其实就封装了selector操作,那么在选择这个线程的时候呢,

他这里有一个非常有意思的注释,它会根据你不同的selectIndex,每次都会++,这是一个成员变量,把这个值映射到select

当中去,这是一个线程,这个操作本身并不是原子操作,因此在多线程的时候,但是这并没有关系的,因为他只需要这个值变化

就行了,你变了多变了少呢,关系不大,因为他并不需要一个精确地需求,只要你这个值在变,只要你在变化呢,我就能够保证呢,

在绝大部分场合,我们其实并不需要一些线程安全的手段,在你可有可无的情况之下呢,应该忽略线程安全的,因为这样性能是

最好的,如果你在这里做一个同步,哪怕你这里用一个原子的类来做,性能还不如这样做来的好,下面就是ManagerSelector来做

处理,他呢是一个线程,封装了Selector的使用,在这个Selector当中呢,他提交一个任务,这个任务是和accept相关的,提交任务

之后呢,最终是会加入到Queue队列当中去,changes是ConcurrentArrayQueue的队列

它是jetty当中自己实现的,这个性能和ConcurrentLinkedQueue是非常类似的,他也是使用了无锁的实现,但是他有一个

比较好的地方是什么呢,对于ConcurrentLinkedQueue来讲它是一个链表,当你去保存某些数据的时候,你其实是需要在elements

的外面,你要套一个node上去,但是对于ConccurentArrayQueue来讲呢,他没有这个概念,直接存放的就是这个元素了,当你有大量的

元素压入队列的时候呢,其实要少一般,因为除了elements之外还有node,而这个是不需要,所以对于GC来讲性能要好一些,这也是我们

可以值得思考的一个地方,对于ConcurrentArrayQueue大家可以去看一下源码的实现,我们介绍了并发包里的容器,很多也都是

大同小异的,它是一个高并发的实现,所以即便你有大量的连接上来,大量的任务提交上来

处理请求使用的是ManagerSelector的run方法,也就是在这个地方,他提交任务,但是他不处理任务,

他只把任务压到队列当中去,在线程执行过程当中,他才会去做任务的处理,他里面的代码比较多,只截取了两行,

主要是select,只要你还在running,那你就要不停的select,select做什么呢,就是runchange,什么叫changes呢,

当有任务上来,就应该runchanges

去做这个任务的执行,它会把changes队列当中的任务呢,拿出来去做这个执行,执行的时候就是做了一个核心的操作呢,

相互注册一下,绑定一下,得到SelectionKey,然后去创建这个连接,代表这个连接的endPoint,这样在后面就能够拿到我们的key,

继续做这个处理,返回给endpoint,这个是做runchanges,然后他会做select,实际上它是关联了channel和selector,现在你要等待

read或者write,它是一个可用的状态,需要做select操作,一旦发现有任何select可用,任何一个channel的select可用的话呢,

他就会去做处理,已经准备好的selectKey拿出来,会使用新的线程做HTTP的处理

 

Jetty 欢迎访问Jetty文档 Wiki. Jetty是一个开源项目,提供了http服务器、http客户端和java servlet容器。 这个wiki提供jetty的入门教程、基础配置、功能特性、优化、安全、JavaEE、监控、常见问题、故障排除帮助等等。它包含教程、使用手册、视频、特征描述、参考资料以及常见问题。 Jetty文档 ---------------- 入门: 下载Download, 安装, 配置, 运行 Jetty入门(视频) 下载和安装Jetty 如何安装一个Jetty包 如何配置Jetty – 主要文档 如何运行Jetty 用JConsole监控Jetty 如何使用Jetty开发 Jetty HelloWorld教程 Jetty和Maven HelloWorld教程 Jetty(6)入门 (www.itjungle.com) Jetty Start.jar 配置Jetty 如何设置上下文(Context Path) 如何知道使用了那些jar包 如何配置SSL 如何使用非root用户监听80端口 如何配置连接器(Connectors) 如何配置虚拟主机(Virtual Hosts) 如何配置会话ID(Session IDs) 如何序列化会话(Session) 如何重定向或移动应用(Context) 如何让一个应用响应一个特定端口 使用JNDI 使用JNDI 在JNDI中配置据源(DataSource) 内嵌Jetty服务器 内嵌Jetty教程 内嵌Jetty的HelloWorld教程 内嵌Jetty视频 优化Jetty 如何配置垃圾收集 如何配置以支持高负载 在Jetty中部署应用 部署管理器 部署绑定 热部署 Context提供者 如何部署web应用 webApp提供者 如何部署第三方产品 部署展开形式的web应用 使用Jetty进行开发 如何使用Jetty进行开发 如何编写Jetty中的Handlers 使用构建工具 如何在Maven中使用Jetty 如何在Ant中使用Jetty Maven和Ant的更多支持 Jetty Maven插件(Plugin) Jetty Jspc Maven插件(Plugin) Maven web应用工程原型 Ant Jetty插件(Plugin) 使用集成开发环境(IDEs) 在Eclipse中使用Jetty 在IntelliJ中使用Jetty 在Eclipse中工作 在Eclipse中开发Jetty Jetty WTP插件(Plugin) JettyOSGi SDK for Eclipse-PDE EclipseRT Jetty StarterKit SDK OSGi Jetty on OSGi, RFC66 基于Jetty OSGi的产品 OSGi贴士 Equinox中使用Jetty实现HTTP Service Felix中使用Jetty实现HTTP Service PAX中使用Jetty实现HTTP Srevice ProSyst mBedded Server Equinox Edition Spring Dynamic Modules里的Jetty JOnAS5里的Jetty 配置Ajax、Comet和异步Servlets 持续和异步Servlets 100 Continue和102 Processing WebSocket Servlet 异步的REST Stress Testing CometD 使用Servlets和Filters Jetty中绑定的Servlets Quality of Service Filter Cross Origin Filter 配置安全策略(Security Policies) 安全领域(Security Realms) 安全域配置教程 Java Authentication and Authorization Service (JAAS) JAAS配置教程 JASPI 安全模型(Secure Mode) 存储在文件中的安全密码以及编程教程 如何开启或禁止Jetty中的SSL功能 如何在Jetty中安全存储密码 如何安全终止Jetty 如何配置Spnego Application Server Integrations(集成) Apache Geronimo JEE 配置Apache httpd和Jetty教程 配置Apache mod_proxy和Jetty 配置Jetty中的AJP13 在JBoss中配置Jetty Remote Glassfish EJBs from Jetty Jetty and Spring EJB3 (Pitchfork) JBoss EJB3 ObjectWeb EasyBeans
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值