纯Java网站开发改造为nodejs混合编程
2015-05-17 19:24
609 查看
早已对纯JAVA版的网站不满了,不管是繁重的代码编写量,和无谓的3层代码编写都让我提不起兴趣。但是提到nodejs我就有兴趣来做了,原来的网站是放到云服务器上的,由于CPU和内存的限制进一步影响了网站速度和承载量。达到什么程度呢,就是3个人同时应用就会造成访问慢或卡死。
于是我想到nodejs将原来网站重写。将来就会加速网站和体现并发数优势。很多人反对我这样做,说nodejs不适合重逻辑的部分,但好了,89%的应用都是直接从用户获得参数直接传透到数据库,为啥要写那么多代码,什么时候运行过其他计算。偶尔也是对参数进行个加减而已。为啥不将几百行代码缩减到几行。
既然要动手就开始,准备好nodejs,在nodejs.org官网下在nodejs1.2X安装好之后,下在javascript编辑器,或文本都行。我喜欢用文本直接编辑。
罗列了以下几个步骤
:
1.
安装nodejsmysql包
(网上很多教程注意先安装git)
2.
修改java工程文件中的web,添加跨域反问,并将跨域限制为本机
3.
修改原工程jquery的ajax调用,使用访问本机127.0.0.1:1337端口访问
4.
提供nodejs直接调用数据库的调用方法
5.
修改调用返回的处理
最后就是写一个工具在原网站上进行500个轮询访问的效率查询,用以鉴定以前的效率和现在效率的差别。
这棵树就是我们需要改造的原因。原来的反问原理是,通过spring->访问controller->访问helper层->访问dao层->访问mysql->再依次将结果json返回页面处理。
上面就是全部步骤,其实我说错了,上面还不是整个过程。因为树是存在一张表中的。其中只有id,pried,name,leve,orderid等,所以关系都是锁在同一张表里,意味着要把所有树排列好一次拿出来是可以的。只要按默认顺序将树整个解析出来即可。但目前存在客户要求树也要按顺序列出来,也就是按order指定的顺序排列,那么一次将树拿出来解析就不可以了。
因此我采用先将根结点读数据库拿出来,在生成根结点界面的时候程序回调再去查询所有子节点,并从数据库返回结果生成整棵树。
这样本来一次调用却变成了N次调用,往返于服务器之间,登陆几个用户打开几次页面我的程序基本就慢的要死。
第一步介绍:我只介绍注意事项即可,其他的请搜索网上吧,而且都有很好的文章。安装好nodejs使用npm装载mysql模块是报错的,因为没装git,使用git后才能安装,输入以下命令
npminstallfelixge/node-mysql
完成安装mysql;
完成之后试写mysql功能简单调用一下:
var
mysql
=require('mysql');
调用完成后看一下你是否能读出结果,测试成功则nodejs和mysql模块都装好了。
第二步:修改java原来的tomcat,因为在一个页面下以前用jquery的ajax调用spring对应的controller,所以现在需要改成调用nodejs本地下的一个端口。我设置为127.0.0.1:1337下来访问我定义的nodejs代码块。
第一就直接修改了,例如将如下:
$.ajax({async:false,type:"post",url:"employee.getUnDeparment.do",data:"",dataType:"text",success:function(msg){mydata=eval("("+msg+")");//alert(msg);$.each(mydata,function(idx,item){
unuser=item.count;});
改成:
$.ajax({async:false,type:"get",url:"http://127.0.0.1:1337/employee_getUnDepartment",data:"",dataType:"text",success:function(msg){mydata=eval("("+msg+")");//alert(msg);
$.each(mydata,function(idx,item){unuser=item.count;});
然后就不出数据了,按下IE的F12,看到提示CORS错误!nodejs写的http模块使用http://
127.0.0.1:1337/employee_getUnDepartment是直接可以返回json串的,怎么到这里就不行了呢?!原来还需要改造一下java的web.xml配置,和加入两个jar包才行。
网上下载:cors-filter-2.4.jar和java-property-utils-1.9.1.jar;放入项目工程里的libs目录下,并引用这两个包。并将如下代码加到web.xml里:
<filter>
<filter-name>CORS</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>http://127.0.0.1</param-value>
</init-param>
<init-param>
<param-name>cors.supportedMethods</param-name>
<param-value>GET,POST,HEAD,PUT,DELETE</param-value>
</init-param>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>Accept,Origin,X-Requested-With,Content-Type,Last-Modified</param-value>
</init-param>
<init-param>
<param-name>cors.exposedHeaders</param-name>
<param-value>Set-Cookie</param-value>
</init-param>
<init-param>
<param-name>cors.supportsCredentials</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意我写的是127.0.0.1,也就是说我允许跨域到本机127.0.0.1位置。设置完了后再次调用,怎么回事,nodejs控制台已经返回了查询结果,但IE报一个ajax错误,查了之后发现如果是跨域访问,则需要返回的内容加上文件头。于是在返回结果的模块里加了头如下:
加上这句,返回的串就可以显示到原来的界面上。速度嘛,当然比以前块几百毫秒,但调试变简单了,Ctrl+C终止程序,按上键显示上句命令,回车就完成了再次启动nodejs程序。
而且不受以前tomcat的影响,只要程序是nodejs里的,直接关闭nodejs再启动调试,使用者基本感觉不到你在一步步调试程序,他们其他的java应用里的程序还正常执行。
第三步修改ajax调用为nodejs远程:
$.ajax({async:false,type:"get",url:"http://127.0.0.1:1337/node_employee_getsubtree_do",data:"citylist="+session_citylist+"&did="+id,success:function(msg){
alert(msg);}
});
这里有一个坑,就是type:get;如果你不注意原来用的是post的话,那么在nodejs处理比较复杂,因为post是流发送,也就是有个开始投送,到接收完毕的过程,在nodejs里需要处理开始,和回调函数,这样整个改造过程就比较麻烦了。因为没有牵扯到需要post的表单,所以直接用get,否则参数会接收不到。当然如果你用了express
的话当然可以用里面包含的接收post包装好的方法。
第四步提供调用subtree的nodejs方法:
这里有两个坑,不小心你就掉里面了,第一个坑就是返回json串的方法,在nodejs里把结果集合改成json是用JSON.stringifv();方法格式化结果集。第二个坑就是Header必须写在前面,否则跨域不接受数据。我写的*是允许所有操作(GET
UPDATEDELETEPOST等)跨域提供数据。
第五步,修改JAVA程序适合调用返回nodejs程序:
其实这步根本不需要做,为什么还需要这步,是因为,以前java调用dao返回结果结集的时候字段名称有大写有小写,有混合写的。但用nodejs调用后直接都是数据库里怎么写的字段名返回就是怎么写的。所以departMent有可能变成department,因此要详细核对一下,这个坑我已经掉进去过了。
这就是一个简单的混合程序完成了。但只是比java的快了一点点,那么怎么优化呢?下面介绍一下优化。
优化思路:
减少数据库调用à减少ajax调用
这个大方向走,首先是否使用redis,想了半天,还是算了,只是为了优化一棵树,何必动用神器。自己搞个HashTabls算了。首先采用变量来优化基础查询,如下:
可以看出采用了nodejs全局变量condmaintree,因为所有人只有权限不同的才会需要重新加载树,所以可以这样做,改完之后只有第一次读取需要查数据库,否则直接http返回存在condmaintree里的json串。子树也是这样优化的,但字树的分支读取次数很多,需要很多全局变量,这不切合实际。怎么办,引用自己编写的HashTable,nodejs版如下HashTable.js:
调用过程如下:
这样做以后,首次60次的ajax调用确实访问了数据库,但第二次的刷新树的时候就不会调用数据库了。但这也不是最终的方法,我还是决定要去掉远程调用,那么在index.jsp框架页面里引入HashTable.js,但这个版本和nodejs用的稍微有点不同。代码如下:
HashTable.js
functionHashTable()
{
varsize=0;
varentry=newObject();
this.add=function(key,value)
{
if(!this.containsKey(key))
{
size++;
}
entry[key]=value;
}
this.getValue=function(key)
{
returnthis.containsKey(key)?entry[key]:null;
}
this.remove=function(key)
{
if(this.containsKey(key)&&(deleteentry[key]))
{
size--;
}
}
this.containsKey=function(key)
{
return(keyinentry);
}
this.containsValue=function(value)
{
for(varpropinentry)
{
if(entry[prop]==value)
{
returntrue;
}
}
returnfalse;
}
this.getValues=function()
{
varvalues=newArray();
for(varpropinentry)
{
values.push(entry[prop]);
}
returnvalues;
}
this.getKeys=function()
{
varkeys=newArray();
for(varpropinentry)
{
keys.push(prop);
}
returnkeys;
}
this.getSize=function()
{
returnsize;
}
this.clear=function()
{
size=0;
entry=newObject();
}
}
改造index.jsp
加入下列代码:
<scripttype="text/javascript">
//树缓存
varsubtreeHashTabls=newHashTable();
</script>
在具体调用的方法里加入hashtable查询的过程,如下:
functiongetsubTree(id){
varresult=subtreeHashTabls.getValue(id);
varstr="";
if(result==null){//alert("远程取!");
$.ajax({async:false,type:"get",url:"http://127.0.0.1:1337/node_employee_getsubtree_do",data:"citylist="+session_citylist+"&did="+id,success:function(msg){
subtreeHashTabls.add(id,msg);//alert(msg);
}});}
result=subtreeHashTabls.getValue(id);
varmydata=eval(result);
…
…}
经过这样的改造后,只需要读取一次树,其他时候读取树完全由内存里的HashTable读取,根本都不需要访问ajax跟服务器发生交互。
改造完毕后,我的页面首次加载比原来快1秒,再次加载快3秒,当然并发量我并没有测试,应该部署后会比原来强大许多,这就是nodejs优势,当然HashTable也尽了很大的力。
早已对纯JAVA版的网站不满了,不管是繁重的代码编写量,和无谓的3层代码编写都让我提不起兴趣。但是提到nodejs我就有兴趣来做了,原来的网站是放到云服务器上的,由于CPU和内存的限制进一步影响了网站速度和承载量。达到什么程度呢,就是3个人同时应用就会造成访问慢或卡死。
于是我想到nodejs将原来网站重写。将来就会加速网站和体现并发数优势。很多人反对我这样做,说nodejs不适合重逻辑的部分,但好了,89%的应用都是直接从用户获得参数直接传透到数据库,为啥要写那么多代码,什么时候运行过其他计算。偶尔也是对参数进行个加减而已。为啥不将几百行代码缩减到几行。
既然要动手就开始,准备好nodejs,在nodejs.org官网下在nodejs1.2X安装好之后,下在javascript编辑器,或文本都行。我喜欢用文本直接编辑。
罗列了以下几个步骤
:
1.
安装nodejsmysql包
(网上很多教程注意先安装git)
2.
修改java工程文件中的web,添加跨域反问,并将跨域限制为本机
3.
修改原工程jquery的ajax调用,使用访问本机127.0.0.1:1337端口访问
4.
提供nodejs直接调用数据库的调用方法
5.
修改调用返回的处理
最后就是写一个工具在原网站上进行500个轮询访问的效率查询,用以鉴定以前的效率和现在效率的差别。
这棵树就是我们需要改造的原因。原来的反问原理是,通过spring->访问controller->访问helper层->访问dao层->访问mysql->再依次将结果json返回页面处理。
上面就是全部步骤,其实我说错了,上面还不是整个过程。因为树是存在一张表中的。其中只有id,pried,name,leve,orderid等,所以关系都是锁在同一张表里,意味着要把所有树排列好一次拿出来是可以的。只要按默认顺序将树整个解析出来即可。但目前存在客户要求树也要按顺序列出来,也就是按order指定的顺序排列,那么一次将树拿出来解析就不可以了。
因此我采用先将根结点读数据库拿出来,在生成根结点界面的时候程序回调再去查询所有子节点,并从数据库返回结果生成整棵树。
这样本来一次调用却变成了N次调用,往返于服务器之间,登陆几个用户打开几次页面我的程序基本就慢的要死。
第一步介绍:我只介绍注意事项即可,其他的请搜索网上吧,而且都有很好的文章。安装好nodejs使用npm装载mysql模块是报错的,因为没装git,使用git后才能安装,输入以下命令
npminstallfelixge/node-mysql
完成安装mysql;
完成之后试写mysql功能简单调用一下:
var
mysql
=require('mysql');
varpool=mysql.createPool({ connectionLimit:30, host:'localhost', user:'root', password:xxxx });
pool.query('SELECT*FROMzd.alga_cs;',function(err,rows,fields){
if(err)throwerr;
console.log('Thesolutionis:',rows);
});
调用完成后看一下你是否能读出结果,测试成功则nodejs和mysql模块都装好了。
第二步:修改java原来的tomcat,因为在一个页面下以前用jquery的ajax调用spring对应的controller,所以现在需要改成调用nodejs本地下的一个端口。我设置为127.0.0.1:1337下来访问我定义的nodejs代码块。
第一就直接修改了,例如将如下:
$.ajax({async:false,type:"post",url:"employee.getUnDeparment.do",data:"",dataType:"text",success:function(msg){mydata=eval("("+msg+")");//alert(msg);$.each(mydata,function(idx,item){
unuser=item.count;});
改成:
$.ajax({async:false,type:"get",url:"http://127.0.0.1:1337/employee_getUnDepartment",data:"",dataType:"text",success:function(msg){mydata=eval("("+msg+")");//alert(msg);
$.each(mydata,function(idx,item){unuser=item.count;});
然后就不出数据了,按下IE的F12,看到提示CORS错误!nodejs写的http模块使用http://
127.0.0.1:1337/employee_getUnDepartment是直接可以返回json串的,怎么到这里就不行了呢?!原来还需要改造一下java的web.xml配置,和加入两个jar包才行。
网上下载:cors-filter-2.4.jar和java-property-utils-1.9.1.jar;放入项目工程里的libs目录下,并引用这两个包。并将如下代码加到web.xml里:
<filter>
<filter-name>CORS</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>http://127.0.0.1</param-value>
</init-param>
<init-param>
<param-name>cors.supportedMethods</param-name>
<param-value>GET,POST,HEAD,PUT,DELETE</param-value>
</init-param>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>Accept,Origin,X-Requested-With,Content-Type,Last-Modified</param-value>
</init-param>
<init-param>
<param-name>cors.exposedHeaders</param-name>
<param-value>Set-Cookie</param-value>
</init-param>
<init-param>
<param-name>cors.supportsCredentials</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意我写的是127.0.0.1,也就是说我允许跨域到本机127.0.0.1位置。设置完了后再次调用,怎么回事,nodejs控制台已经返回了查询结果,但IE报一个ajax错误,查了之后发现如果是跨域访问,则需要返回的内容加上文件头。于是在返回结果的模块里加了头如下:
res.writeHeader(200,{
'Access-Control-Allow-Origin':'*'//先写header否则返回无效在跨域访问时
});
加上这句,返回的串就可以显示到原来的界面上。速度嘛,当然比以前块几百毫秒,但调试变简单了,Ctrl+C终止程序,按上键显示上句命令,回车就完成了再次启动nodejs程序。
而且不受以前tomcat的影响,只要程序是nodejs里的,直接关闭nodejs再启动调试,使用者基本感觉不到你在一步步调试程序,他们其他的java应用里的程序还正常执行。
第三步修改ajax调用为nodejs远程:
$.ajax({async:false,type:"get",url:"http://127.0.0.1:1337/node_employee_getsubtree_do",data:"citylist="+session_citylist+"&did="+id,success:function(msg){
alert(msg);}
});
这里有一个坑,就是type:get;如果你不注意原来用的是post的话,那么在nodejs处理比较复杂,因为post是流发送,也就是有个开始投送,到接收完毕的过程,在nodejs里需要处理开始,和回调函数,这样整个改造过程就比较麻烦了。因为没有牵扯到需要post的表单,所以直接用get,否则参数会接收不到。当然如果你用了express
的话当然可以用里面包含的接收post包装好的方法。
第四步提供调用subtree的nodejs方法:
//调用子树
if(pathname=="/node_employee_getsubtree_do"){
varstr=arg.citylist;
vardid=arg.did;
varsubtree="";
pool.query('selectidcity,cityName,b.departmentid,departmentName,departmentlimit,count(c.employeeId)countfromzd.cityasaleftouterjoinzd.departmentasbona.idcity=b.cityIdleftouterjoinzd.employeeconb.departmentId=c.departmentIdwhere1=1and('+str+')andb.pre='+did+'andb.level=1groupbyidcity,cityName,b.departmentid,departmentName,departmentlimitorderbya.idcityasc,b.orderidasc;',function(err,rows,fields){
if(err)throwerr;
subtree=JSON.stringify(rows);
res.writeHeader(200,{
'Access-Control-Allow-Origin':'*'//先写header否则返回无效在跨域访问时
});
res.end(subtree);
});
}
这里有两个坑,不小心你就掉里面了,第一个坑就是返回json串的方法,在nodejs里把结果集合改成json是用JSON.stringifv();方法格式化结果集。第二个坑就是Header必须写在前面,否则跨域不接受数据。我写的*是允许所有操作(GET
UPDATEDELETEPOST等)跨域提供数据。
第五步,修改JAVA程序适合调用返回nodejs程序:
其实这步根本不需要做,为什么还需要这步,是因为,以前java调用dao返回结果结集的时候字段名称有大写有小写,有混合写的。但用nodejs调用后直接都是数据库里怎么写的字段名返回就是怎么写的。所以departMent有可能变成department,因此要详细核对一下,这个坑我已经掉进去过了。
这就是一个简单的混合程序完成了。但只是比java的快了一点点,那么怎么优化呢?下面介绍一下优化。
优化思路:
减少数据库调用à减少ajax调用
这个大方向走,首先是否使用redis,想了半天,还是算了,只是为了优化一棵树,何必动用神器。自己搞个HashTabls算了。首先采用变量来优化基础查询,如下:
res.writeHeader(200,{
'Access-Control-Allow-Origin':'*'//先写header否则返回无效在跨域访问时
});
//判断主树是否需要缓寸
if(condmaintree!=str){
condmaintree=str;
//console.log('citylist:',str);
pool.query('selectidcity,cityName,b.departmentid,departmentName,departmentlimit,count(c.employeeId)countfromzd.cityasaleftouterjoinzd.departmentasbona.idcity=b.cityIdleftouterjoinzd.employeeconb.departmentId=c.departmentIdwhere1=1and('+str+')andb.level=0groupbyidcity,cityName,b.departmentid,departmentName,departmentlimitorderbya.idcityasc,b.orderidasc;',function(err,rows,fields){
if(err)throwerr;
console.log('读数据库!');
memmaintree=JSON.stringify(rows);
res.end(memmaintree);//数组和json之间的数据转换
});
}else{
console.log('直接返回!');
res.end(memmaintree);
}
可以看出采用了nodejs全局变量condmaintree,因为所有人只有权限不同的才会需要重新加载树,所以可以这样做,改完之后只有第一次读取需要查数据库,否则直接http返回存在condmaintree里的json串。子树也是这样优化的,但字树的分支读取次数很多,需要很多全局变量,这不切合实际。怎么办,引用自己编写的HashTable,nodejs版如下HashTable.js:
varsize=0;
varentry=newObject();
exports.add=function(key,value)
{
if(!this.containsKey(key))
{
size++;
}
entry[key]=value;
}
exports.getValue=function(key)
{
returnthis.containsKey(key)?entry[key]:null;
}
exports.remove=function(key)
{
if(this.containsKey(key)&&(deleteentry[key]))
{
size--;
}
}
exports.containsKey=function(key)
{
return(keyinentry);
}
exports.containsValue=function(value)
{
for(varpropinentry)
{
if(entry[prop]==value)
{
returntrue;
}
}
returnfalse;
}
exports.getValues=function()
{
varvalues=newArray();
for(varpropinentry)
{
values.push(entry[prop]);
}
returnvalues;
}
exports.getKeys=function()
{
varkeys=newArray();
for(varpropinentry)
{
keys.push(prop);
}
returnkeys;
}
exports.getSize=function()
{
returnsize;
}
exports.clear=function()
{
size=0;
entry=newObject();
}
调用过程如下:
varMhashTable=require('./HashTable.js');
//调用子树
if(pathname=="/node_employee_getsubtree_do"){
varstr="";
varcitylist=arg.citylist;
vardid=arg.did;
varsubtree="";
if(citylist==""){
str+="idcity=-1";
}else{
str+="idcity=";
citylist=citylist.replace(/,/g,"oridcity=");
str+=citylist;
}
subtree=MhashTable.getValue(did);//获得变量如果为null则访问数据库
if(subtree==null){
pool.query('selectidcity,cityName,b.departmentid,departmentName,departmentlimit,count(c.employeeId)countfromzd.cityasaleftouterjoinzd.departmentasbona.idcity=b.cityIdleftouterjoinzd.employeeconb.departmentId=c.departmentIdwhere1=1and('+str+')andb.pre='+did+'andb.level=1groupbyidcity,cityName,b.departmentid,departmentName,departmentlimitorderbya.idcityasc,b.orderidasc;',function(err,rows,fields){
if(err)throwerr;
subtree=JSON.stringify(rows);
MhashTable.add(did,subtree);//将结果存入hashtable
console.log('哈希没找到!:',subtree);
res.writeHeader(200,{
'Access-Control-Allow-Origin':'*'//先写header否则返回无效在跨域访问时
});
res.end(subtree);
});
}else{
res.writeHeader(200,{
'Access-Control-Allow-Origin':'*'//先写header否则返回无效在跨域访问时
});
console.log('哈希找到!:',subtree);
res.end(subtree);
}
}
这样做以后,首次60次的ajax调用确实访问了数据库,但第二次的刷新树的时候就不会调用数据库了。但这也不是最终的方法,我还是决定要去掉远程调用,那么在index.jsp框架页面里引入HashTable.js,但这个版本和nodejs用的稍微有点不同。代码如下:
HashTable.js
functionHashTable()
{
varsize=0;
varentry=newObject();
this.add=function(key,value)
{
if(!this.containsKey(key))
{
size++;
}
entry[key]=value;
}
this.getValue=function(key)
{
returnthis.containsKey(key)?entry[key]:null;
}
this.remove=function(key)
{
if(this.containsKey(key)&&(deleteentry[key]))
{
size--;
}
}
this.containsKey=function(key)
{
return(keyinentry);
}
this.containsValue=function(value)
{
for(varpropinentry)
{
if(entry[prop]==value)
{
returntrue;
}
}
returnfalse;
}
this.getValues=function()
{
varvalues=newArray();
for(varpropinentry)
{
values.push(entry[prop]);
}
returnvalues;
}
this.getKeys=function()
{
varkeys=newArray();
for(varpropinentry)
{
keys.push(prop);
}
returnkeys;
}
this.getSize=function()
{
returnsize;
}
this.clear=function()
{
size=0;
entry=newObject();
}
}
改造index.jsp
加入下列代码:
<scripttype="text/javascript">
//树缓存
varsubtreeHashTabls=newHashTable();
</script>
在具体调用的方法里加入hashtable查询的过程,如下:
functiongetsubTree(id){
varresult=subtreeHashTabls.getValue(id);
varstr="";
if(result==null){//alert("远程取!");
$.ajax({async:false,type:"get",url:"http://127.0.0.1:1337/node_employee_getsubtree_do",data:"citylist="+session_citylist+"&did="+id,success:function(msg){
subtreeHashTabls.add(id,msg);//alert(msg);
}});}
result=subtreeHashTabls.getValue(id);
varmydata=eval(result);
…
…}
经过这样的改造后,只需要读取一次树,其他时候读取树完全由内存里的HashTable读取,根本都不需要访问ajax跟服务器发生交互。
改造完毕后,我的页面首次加载比原来快1秒,再次加载快3秒,当然并发量我并没有测试,应该部署后会比原来强大许多,这就是nodejs优势,当然HashTable也尽了很大的力。
相关文章推荐
- nodejs+java混合网站开发实践
- javaWeb开发过程中常用网站收藏
- Java开发牛人十大必备网站
- Java笔记--开发人员必备的案头网站
- Java开发牛人十大必备网站
- 使用nodejs、mongodb、express、linux搭建社区网站(4)-mongodb连接及注册的开发
- Java开发必备的9个英文网站
- Java开发网站总结——Servlet实现图片上传、验证码实现
- Java开源工具之网站开发工具清单
- Java开发视频网站大概需要多少钱?
- 用JAVA进行网站开发的课程设计心得体会
- 某电商网站Java面试题-2(高级开发,架构级别)
- Java网站开发环境搭建
- Java开源工具:网站开发工具清单
- Java开发牛人十大必备网站
- Java开发牛人十大必备网站
- Java Web 高性能开发,第 3 部分: 网站优化实战
- Java Web 高性能开发,第 3 部分: 网站优化实战
- Java+MyEclipse+Tomcat (一)配置过程及jsp网站开发入门