blogjava-凯发k8网页登录http://www.blogjava.net/yongboy/category/48044.html记录工作/学习的点点滴滴。zh-cnmon, 01 jun 2015 21:43:57 gmtmon, 01 jun 2015 21:43:57 gmt60servlet 3.0笔记之文件下载的那点事情http://www.blogjava.net/yongboy/archive/2012/01/19/369577.htmlnieyongnieyongthu, 19 jan 2012 02:21:00 gmthttp://www.blogjava.net/yongboy/archive/2012/01/19/369577.htmlhttp://www.blogjava.net/yongboy/comments/369577.htmlhttp://www.blogjava.net/yongboy/archive/2012/01/19/369577.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/369577.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/369577.html使用servlet 3.0 提供文件下载,当然了任何servlet版本,都可以做到,这里仅仅作为知识的积累。下面贴出代码,防止忘却。

一。常规方式文件下载示范


很普通,简单易用,不多说。一般情况下,够用了。

二。伪零拷贝(zero copy)方式文件下载示范

变化不大,关键代码在于:利用
filechannel.transferto(long position, long count, writablebytechannel target)
方法达到零拷贝(zero copy)目的。把httpservletresponse的输出对象(servletoutputstream)利用channels.newchannel(outputstream out)工具,构建一个writablebytechannel对象而已。
outputstream out = response.getoutputstream();
writablebytechannel outchannel = channels.newchannel(out);
测试代码:

心存疑虑的是,这个是伪零拷贝方式实现。查看一下channels.newchannel的源码:
public static writablebytechannel newchannel(final outputstream out) {
if (out == null) {
throw new nullpointerexception();
}

if (out instanceof fileoutputstream &&
fileoutputstream.class.equals(out.getclass())) {
return ((fileoutputstream)out).getchannel();
}

return new writablebytechannelimpl(out);
}
因为输入的方法参数为servletoutputstream类型实例,因此只能返回一个新构建的writablebytechannelimpl对象。具体构建:
private static class writablebytechannelimpl
extends abstractinterruptiblechannel // not really interruptible
implements writablebytechannel
{
outputstream out;
private static final int transfer_size = 8192;
private byte buf[] = new byte[0];
private boolean open = true;
private object writelock = new object();

writablebytechannelimpl(outputstream out) {
this.out = out;
}

public int write(bytebuffer src) throws ioexception {
int len = src.remaining();
int totalwritten = 0;
synchronized (writelock) {
while (totalwritten < len) {
int bytestowrite = math.min((len - totalwritten),
transfer_size);
if (buf.length < bytestowrite)
buf = new byte[bytestowrite];
src.get(buf, 0, bytestowrite);
try {
begin();
out.write(buf, 0, bytestowrite);
} finally {
end(bytestowrite > 0);
}
totalwritten = bytestowrite;
}
return totalwritten;
}
}

protected void implclosechannel() throws ioexception {
out.close();
open = false;
}
}
很显然,也是属于内存类型的拷贝了,只能算作伪零拷贝实现了。

三。转发到文件服务器上

一般常识为,让最擅长的人来做最擅长的事情,是为高效。使用类如nginx高效的web服务器专门处理文件下载业务,达到零拷贝的目的,也是最佳搭配组合。nginx可以利用header元数据x-accel-redirect来控制文件下载行为,甚是不错。利用java进行业务逻辑判断,若符合规则,则提交给nginx进行处理文件的下载,否则,返回给终端用户权限不够等信息。
用于控制用户是否具有资格进行文件下载业务的控制器:

当然,这个仅仅用于演示,逻辑简单。因为需要和nginx服务器进行配合,构建一个server,其配置文件:

我们在nginx配置文件中,设置/dowloads/目录是不允许直接访问的,必须经由/download/控制器进行转发方可。经测试,中文名不会出现乱码问题,保存的文件也是我们所请求的文件,同名,也不会出现乱码问题。但是,若在后台获取文件名,用于显示/输出,则需要从iso-8859-1解码成gbk编码方可。
但这样做,可能被绑定到某个类型的服务器,但也值得。实际上切换到apache也是很简单的。
ps : apache服务器诶则对应x-sendfile头部属性,因很长时间不再使用apache,这里不再测试。
参考资料:



nieyong 2012-01-19 10:21
]]>
servlet 3.0笔记之servlet的异步特性支持失效怎么办?http://www.blogjava.net/yongboy/archive/2012/01/18/369578.htmlnieyongnieyongwed, 18 jan 2012 02:32:00 gmthttp://www.blogjava.net/yongboy/archive/2012/01/18/369578.htmlhttp://www.blogjava.net/yongboy/comments/369578.htmlhttp://www.blogjava.net/yongboy/archive/2012/01/18/369578.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/369578.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/369578.html满心欢喜的为servlet 添加上异步支持注解(asyncsupported = true),不曾想,其异步特性完全不起作用,仔细检测项目,发现存在一个编码拦截器(filter),虽使用注解,但未标明支持异步,导致被拦截的标注为异步支持的servlet,异步特性皆失效。怎么办,在filter中注解里面添加asyncsupported = true。问题解决。
但转念一想,因历史原因,遗留系统会存在很多的servlet 2.*规范的filter,无法支持异步,怎么办?全部手动修改为注解版本,可能不太现实。还好,doug lea的juc并发包,为我们提供了一种实现思路。
实际步骤:
  1. 准备一个线程池
  2. 把当前请求相关属性包装进一个任务线程中
  3. 获取当前任务线程执行结果(不一定会有返回值)
  4. 阻塞,执行完毕或超时,或被中断异常,可以输出客户端
  5. 整个请求结束
实际上,提交到一个线程池的任务线程,默认会返回一个future对象,利用future对象的get方法阻塞的特性,当前请求需要等待任务线程执行的结束,若指定时间内任务线程顺利完成,则不必等到设定的时间的边界即可自然往下执行。
实际代码:

需要备注说明的是,若future.get()无参数,则意味着需要等待计算完成,然后获取其结果。这样可不用设定等待时间了。更多信息,请参考jdk。


nieyong 2012-01-18 10:32
]]>
servlet 3.0笔记之redis操作示范retwis java版http://www.blogjava.net/yongboy/archive/2011/04/06/347672.htmlnieyongnieyongwed, 06 apr 2011 01:42:00 gmthttp://www.blogjava.net/yongboy/archive/2011/04/06/347672.htmlhttp://www.blogjava.net/yongboy/comments/347672.htmlhttp://www.blogjava.net/yongboy/archive/2011/04/06/347672.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/347672.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/347672.html 最近学习,一个目前风头正劲的key-value nosql,支持list、set、map等格式存储,某些操作竟然是原子级别,比如incr命令,相当惊艳。官方提供的一个(simple clone of twitter)示范,证明了完全可以使用redis替代sql数据库存储数据,真是一大开创性创新,挑战人们的传统思维模式,为之惊叹!

不过那是一个php版本,我下载到本地php但无法部署成功;还好有一个示范,还提供一个在线版,试用之后,再下载源代码,ruby sinatra mvc结构,看的流口水,代码那般简洁。

为体验redis,同时也为对比ruby代码,因此诞生了retwis-java,retwis-rb的山寨版,因此其逻辑和html资源一致,再次表示对原作者的感谢。

retwis-java,基于servlet 3.0 urlrewrite freemarker jedis。示范运行在tomcat 7中,redis为最新的2.22版本,jedis为redis的java客户端操作框架。在servlet 3.0规范中,对url映射处理依然没有进步,因此只能使用urlrewrite框架让部分url看起来友好一些。另外,项目没有使用ioc容器框架,没有使用mvc框架,代码量稍微多些,代码相对耦合一些。若使用struts2 spring 代码量会少一些。

对涉及到的redis存储结构,大致如下:

涉及到的两个对象很简单:

序列化后以二进制数据保存到redis中:

    private byte[] object2bytes(v value) {
        
if (value == null)
            
return null;

        bytearrayoutputstream arrayoutputstream 
= new bytearrayoutputstream();
        objectoutputstream outputstream;
        
try {
            outputstream 
= new objectoutputstream(arrayoutputstream);

            outputstream.writeobject(value);
        } 
catch (ioexception e) {
            e.printstacktrace();
        } 
finally {
            
try {
                arrayoutputstream.close();
            } 
catch (ioexception e) {
                e.printstacktrace();
            }
        }

        
return arrayoutputstream.tobytearray();
    }


    
public void save(string key, v value) {
        jedis.set(getkey(key), object2bytes(value));
    }

获取用户的timeline时,redis的lrange命令提供对list类型数据提供分页操作:

    private list<status> timeline(string targetid, int page) {
        
if (page < 1)
            page 
= 1;

        
int startindex = (page - 1* 10;
        
int endindex = page * 10;

        list
<string> idlist = super.jedis
                .lrange(targetid, startindex, endindex);

        
if (idlist.isempty())
            
return new arraylist<status>(0);

        list
<status> statuslist = new arraylist<status>(idlist.size());
        
for (string id : idlist) {
            status status 
= load(long.valueof(id));

            
if (status == null)
                
continue;

            status.setuser(userservice.load(status.getuid()));

            statuslist.add(status);
        }

        
return statuslist;
    }

很显然,lrange取出了status对象的id,然后我们需要再次根据id获取对应的status对象二进制数据,然后反序列化:

    public status load(long id) {
        
return super.get(getformatid(id));
    }

    
private string getformatid(long id) {
        
return string.format(status_id_format, id);
    }

    
private static final string status_id_format = "status:id:%d";

    
public v get(string key) {
        
return byte2object(jedis.get(getkey(key)));
    }

    @suppresswarnings(
"unchecked")
    
private v byte2object(byte[] bytes) {
        
if (bytes == null || bytes.length == 0)
            
return null;

        
try {
            objectinputstream inputstream;
            inputstream 
= new objectinputstream(new bytearrayinputstream(bytes));
            object obj 
= inputstream.readobject();

            
return (v) obj;
        } 
catch (ioexception e) {
            e.printstacktrace();
        } 
catch (classnotfoundexception e) {
            e.printstacktrace();
        }

        
return null;
    }

以上使用jdk内置的序列化支持;更多序列化,可参考hessian、google protobuf等序列化框架,后者提供业界更为成熟的跨平台、更为高效的序列化方案。更多代码请参见附件。

一些总结和思考:

  1. 不仅仅是缓存,替代sql数据库已完全成为可能,更高效,更经济;虽然只是打开了一扇小的窗户,但说不准以后人们会把大门打开。
  2. 实际环境中,可能最佳方式为sql nosql配合使用,互相弥补不足;还好,redis指令不算多,可速查,简单易记。
  3. java和ruby代码相比,有些重

另:

在线版,请参考 http://retwisrb.danlucraft.com/。那个谁谁,要运行范例,保证redis运行才行。

参考资料:



nieyong 2011-04-06 09:42
]]>
servlet 3.0笔记之servlet单实例以及线程安全小结http://www.blogjava.net/yongboy/archive/2011/03/14/346239.htmlnieyongnieyongmon, 14 mar 2011 05:17:00 gmthttp://www.blogjava.net/yongboy/archive/2011/03/14/346239.htmlhttp://www.blogjava.net/yongboy/comments/346239.htmlhttp://www.blogjava.net/yongboy/archive/2011/03/14/346239.html#feedback3http://www.blogjava.net/yongboy/comments/commentrss/346239.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346239.html众所周知,servlet为单实例多线程,非线程安全的。

若一个servlet对应多个url映射,那么将会生成一个还是多个servlet实例呢?

最好的办法就是动手体验一下

@webservlet({ "/demoservlet1", "/demoservlet2" })
public class demoservlet extends httpservlet {
private static final long serialversionuid = 1l;

protected void doget(httpservletrequest request,
httpservletresponse response) throws servletexception, ioexception {
printwriter out = response.getwriter();
out.println(request.getservletpath() "[" this "]");
out.flush();
out.close();
}
}

输出结果:

/demoservlet1[com.learn.servlet3.thread.demoservlet@1320a41]
/demoservlet2[com.learn.servlet3.thread.demoservlet@9abce9]

输出结果可以看到映射/demoservlet1/demoservlet2对应servlet实例是不同的。

结果证明:servlet将为每一个url映射生成一个实例;一个servlet可能存在多个示例,但每一个实例都会对应不同的url映射。


下面讨论单个servlet、多线程情况下保证数据线程同步的几个方法。

  1. synchronized:代码块,方法
    大家都会使用的方式,不用详细介绍了。
    建议优先选择修饰方法。
  2. volatile
    轻量级的锁,可以保证多线程情况单线程读取所修饰变量时将会强制从共享内存中读取最新值,但赋值操作并非原子性。
    一个具有简单计数功能servlet示范:
    /**
    * 使用volatile作为轻量级锁作为计数器
    *
    * @author yongboy
    * @date 2011-3-12
    * @version 1.0
    */
    @webservlet("/volatilecountdemo")
    public class volatilecountservlet extends httpservlet {
    private static final long serialversionuid = 1l;

    private volatile int num = 0;

    protected void doget(httpservletrequest request,
    httpservletresponse response) throws servletexception, ioexception {
    addone();
    response.getwriter().write("now access num : " getnum());
    }

    /**
    * 读取开销低
    */
    private int getnum() {
    return num;
    }

    /**
    * 其写入为非线程安全的,赋值操作开销高
    */
    private synchronized void addone() {
    num ;
    }
    }
    我们在为volatile修饰属性赋值时,还是加把锁的。
  3. threadlocal
    可以保证每一个线程都可以独享一份变量副本,每个线程可以独立改变副本,不会影响到其它线程。
    这里假设多线程环境一个可能落显无聊的示范,初始化一个计数,然后循环输出:
    @webservlet("/threadlocalservlet")
    public class threadlocalservlet extends httpservlet {
    private static final long serialversionuid = 1l;

    private static threadlocal threadlocal = new threadlocal() {
    @override
    protected integer initialvalue() {
    return 0;
    }
    };

    protected void doget(httpservletrequest request,
    httpservletresponse response) throws servletexception, ioexception {
    response.setheader("connection", "keep-alive");

    printwriter out = response.getwriter();
    out.println("start... " " [" thread.currentthread() "]");
    out.flush();

    for (int i = 0; i < 20; i ) {
    out.println(threadlocal.get());
    out.flush();

    threadlocal.set(threadlocal.get() 1);
    }

    // 手动清理,当然随着当前线程结束,亦会自动清理调
    threadlocal.remove();
    out.println("finish... ");
    out.flush();
    out.close();
    }
    }
    若创建一个对象较为昂贵,但又是非线程安全的,在某种情况下要求仅仅需要在线程中独立变化,不会影响到其它线程。选择使用threadlocal较好一些,嗯,还有,其内部使用到了weakhashmap,弱引用,当前线程结束,意味着创建的对象副本也会被垃圾回收。
    hibernate使用threadlocal创建session;spring亦用于创建对象会使用到一点。
    嗯,请注意这不是解决多线程共享变量的钥匙,甚至你想让某个属性或对象在所有线程中都保持原子性,显然这不是凯发天生赢家一触即发官网的解决方案。
  4. lock
    没什么好说的,现在jdk版本支持显式的加锁,相比synchronized,添加与释放更加灵活,功能更为全面。
    @webservlet("/lockservlet")
    public class lockservlet extends httpservlet {
    private static final long serialversionuid = 1l;

    private static int num = 0;
    private static final lock lock = new reentrantlock();

    protected void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception {
    try{
    lock.lock();
    num ;
    response.getwriter().println(num);
    }finally{
    lock.unlock();
    }
    }
    }
    必须手动释放锁,否则将会一直锁定。
  5. wait/notify
    较老的线程线程同步方案,较之lock,不建议再次使用。
  6. 原子操作
    原子包装类,包括一些基本类型(int, long, double, boolean等)的包装,对象属性的包装等。
    @webservlet("/atomicservlet")
    public class atomicservlet extends httpservlet {
    private static final long serialversionuid = 1l;
    private static final atomicinteger num = new atomicinteger(0);

    protected void doget(httpservletrequest request,
    httpservletresponse response) throws servletexception, ioexception {
    printwriter out = response.getwriter();
    out.println(num.incrementandget());
    out.flush();
    out.close();
    }
    }
    包装类提供了很多的快捷方法,比如上面的incrementandget方法,自身增加1,然后返回结果值,并且还是线程安全的,省缺了我们很多手动、笨拙的编码实现。
    更多原子类,请参见 java.util.concurrent.atomic包。
  7. 一些建议
    尽量不要在servlet中单独启用线程
    使用尽可能使用局部变量
    尽可能避免使用锁
  8. 数据结构小结
    在平常环境下使用的一些数据结构,在多线程并发环境下可选择使用java.util.concurrent里内置替代品。下面有一个小结,仅供参考。
非线程安全工具版juc版本
hashmap collections.synchronizedmap concurrenthashmap
arraylist collections.synchronizedlist copyonwritearraylist
hashset collections.synchronizedset synchronizedset
queue   concurrentlinkedqueue



servlet线程安全有很太多的话题要说,以上仅仅为蜻蜓点水,真正需要学习和实践的还有一段长路要学习。另,话说,这里已和servlet版本无关,是知识积累和分享。



nieyong 2011-03-14 13:17
]]>
servlet 3.0笔记之异步请求重新梳理comet的几种形式http://www.blogjava.net/yongboy/archive/2011/02/27/346195.htmlnieyongnieyongsun, 27 feb 2011 09:17:00 gmthttp://www.blogjava.net/yongboy/archive/2011/02/27/346195.htmlhttp://www.blogjava.net/yongboy/comments/346195.htmlhttp://www.blogjava.net/yongboy/archive/2011/02/27/346195.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346195.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346195.html本来只是想梳理以下servlet 3.0异步请求,不曾想涉及到comet(维基百科上面对有具体的定义和介绍),篇章多了起来,对以往所学习和心得,重新梳理一下。
comet的实现主要流(streaming) 形式和长轮询(long polling)两种形式。流和长轮询区别主要在于,在一次连接中,服务器端推送多次的数据为流,只推送一次为长轮询。
流推送(comet streaming)
comet流推送目前一般采用:
  1. 隐藏iframe(hidden iframe)
    服务器端一般会输出一段javascript脚本

    好处在于各个浏览器都支持iframe,目前在ie、firefox下面效果最好,页面不再显示正在加载中的讨厌的提示(见)
  2. xmlhttprequest
    很可怜,目前支持情况最好的是火狐浏览器、ie8以及safari
    参考文章:

长轮询(ajax with long polling)
客户端会与服务器建立一个持久的连接,直到服务器端有数据发送过来(超时也会发送数据),双方断开连接,客户端处理完推送的数据,会再次发起一个持久的连接,循环往复。其实现形式:
  1. xhr方式长轮询(xmlhttprequest long polling)
    服务器端可以返回原始的数据,也可以返回格式化的json、xml、javascript信息,相比iframe形式流推送,更自由一些。使用形式和传统意义上的ajax get方式大致一样,只不过下一次的轮询需要等到有数据返回处理完方可。
    默认不支持跨域,实践中会打折扣。
  2. javascript标签轮询(script tag long polling)
    可能翻译不太准确,主要使用类似于如下形式:
    使用跨域的js脚本,可能会有安全方面隐患,或许这个间接风险是可控的。
    也可以使用用以规避这个风险。
    进阶阅读:
    代码写的很巧妙,也很简短,思路不错,推荐一看。
    参考文章:

comet浏览器支持汇总
大致总结一下各个浏览器对comet支持大致情况,可能不太科学,仅以自己实践中感知为准。table{border:1px solid #888888;border-collapse:collapse;font-family:arial,helvetica,sans-serif;margin-top:10px;width:100%;}table th {background-color:#cccccc;border:1px solid #888888;padding:5px 15px 5px 5px;text-align:left;vertical-align:baseline;}table td {background-color:#efefef;border:1px solid #aaaaaa;padding:5px 15px 5px 5px;vertical-align:text-top;}
浏览器 流(streaming)长轮询(long polling) 
ie(6-8) htmlfile iframe 支持流形式;
ie8的xdomainrequest支持http streaming
支持脚本加载事件;页面表现完美
firefox(3.5) 支持xmlhttprequest streaming 和隐藏的iframe组件 支持脚本文件async属性;页面表现完美
chrome(>9.0) 不支持xmlhttprequest streaming;使用iframe会一直出现正在加载中图标 支持脚本文件async属性;页面表现完美
safari(5) 浏览器支持xmlhttprequest streaming 支持脚本文件的async属性;页面表现完美
opera(>9.0) 不支持xmlhttprequest streaming,使用iframe的话会一样会出现正在加载中的标志 本机测试脚本文件长轮询时间若长于20秒,则会出现连接中断情况;会出现正在加载中的标志
servlet 3.0 支持
长轮询或流推送,相对于servlet 3.0 异步请求,相差不大,都需要定义一个连接超时时间,但后者可以向客户端推送多次,前者推送一次之后需要立刻断开了与客户端的连接;在超时事件发生后,都可以通知客户端超时已发生。另外在高并发环境下可能需要做多层次的消息路由服务以降低单机的处理极限。
实践中使用comet流推送,也需要在一定的时间后再次重连,或许可以称之为流轮询(streaming poll),只是人们没有提起而已。
发布订阅模型()
comet为发布/订阅模型在前端应用的一种变形。可以很直观的将服务器端视为内容发布者,浏览器端为订阅者。相对于谷歌的pubsubhubbub协议,区别在于comet使用http 1.1 长连接协议,客户端为浏览器,没有公开的可回调的客户端地址。而pubsubhubbub则要求订阅者有一个可以回调的url地址(这个url对于发布者服务器是直达的),一旦有新的可用数据,可以推送到客户端,客户端某个url监听就是。双方在用途、使用环境上不相同,需要区别对待。
话说观察者模式在实际应用中十分广泛,变体也很多。
参考资料:


nieyong 2011-02-27 17:17
]]>
servlet 3.0笔记之异步请求facebook bigpipe简单模型实现http://www.blogjava.net/yongboy/archive/2011/02/22/346196.htmlnieyongnieyongtue, 22 feb 2011 12:47:00 gmthttp://www.blogjava.net/yongboy/archive/2011/02/22/346196.htmlhttp://www.blogjava.net/yongboy/comments/346196.htmlhttp://www.blogjava.net/yongboy/archive/2011/02/22/346196.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346196.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346196.html当前的前端技术明星为facebook,相继抛出了quickling,bigpipe等新鲜概念,引领着前端优化的潮流。果然具有大公司的范儿,适时的回馈给整个开发社群,让全体朝前前进了一小步。超赞!
抽空学习了bigpipe,大概相当于分段刷新(flush),强制输出。虽有点旧瓶装新酒的味道,不过前端已经进入了细节为王的阶段,或许在国内已经有人在用,但缺乏分享沟通,或者还不成熟,缺乏关注,缺少重要数据用以论证等。
,或许可以为需要认知的人们打开一扇窗户。
原文介绍bigpiple:
bigpipe是一个重新设计的基础动态网页服务体系。大体思路是,分解网页成叫做pagelets的小块,然后通过web服务器和浏览器建立管道并管理他们在不同阶段的运行。这是类似于大多数现代微处理器的流水线执行过程:多重指令管线通过不同的处理器执行单元,以达到性能的最佳。虽然bigpipe是对现有的服务网络基础过程的重新设计,但它却不需要改变现有的网络浏览器或服务器,它完全使用php和javascript来实现。
bigpipe在facebook应用中的工作流程:
1. 请求解析:web服务器解析和完整性检查的http请求。
2. 数据获取:web服务器从存储层获取数据。
3. 标记生成:web服务器生成的响应的html标记。
4. 网络传输:响应从web服务器传送到浏览器。
5. css的下载:浏览器下载网页的css的要求。
6. dom树结构和css样式:浏览器构造的dom文档树,然后应用它的css规则。
7. javascript中下载:浏览器下载网页中javascript引用的资源。
8. javascript执行:浏览器的网页执行javascript代码。
其带来效果较为明显处在于:
这种高度并行系统的最终结果是,多个pagelets的不同执行阶段同时进行。例如,浏览器可以正在下载三个pagelets css的资源,同时已经显示另一pagelet内容,与此同时,服务器也在生成新的pagelet。从用户的角度来看,页面是逐步呈现的。最开始的网页内容会更快的显示,这大大减少了用户的对页面延时的感知。如果您要自己亲眼看到区别,你可以尝试以下连接: 。第一个链接是传统模式单一模式显示页面。第二个链接是bigpipe管道模式的页面。如果您的浏览器版本比较老,网速也很慢,浏览器缓存不佳,哪么两页之间的加截时间差别将更加明显。
值得一提的是bigpipe是从微处理器的流水线中得到启发。然而,他们的流水线过程之间存在一些差异。例如,虽然大多数阶段bigpipe只能操作一次pagelet,但有时多个pagelets的css和javascript下载却可以同时运作,这类似于超标量微处理器。bigpipe另一个重要区别是,我们实现了从并行编程引入的“障碍”概念,所有的pagelets要完成一个特定阶段,如多个pagelet显示区,它们都可以进行进一步javascript下载和执行。
启用了bigpipe后,服务器端优先输出页面整体布局,浏览器渲染布局;服务器按照串行方式,一段段按优先级顺序,输出片段内容到浏览器端,浏览器执行js函数,同时可能会同时请求新的js文件(尽可能不要涉及外部js),下载特定样式文件(这个时候可以产生并发连接)。浏览器渲染页面单个组件(pagelet),组件同时加载css、js文件时,不会影响到下一个组件的渲染工作。以往的页面模型在于,浏览器接收所有数据,然后一次性渲染,显示最终结果给客户。
bigpipe涉及到的一些问题,原文没有涉及,来自淘宝的李牧在他的博客中)提出一些疑问:
脚本阻滞:
         我们知道直接在html中引入外部脚本会造成页面阻滞,即使使用无阻脚本下载的一系列方法引入外部js,但因为js单线程,当这些脚本load进来之后运行时也会发生阻滞.因为facebook页面多样,依赖脚本大小不一,这些阻滞会对页面的快速展现造成影响.
        facebook做法是在ondomready之前只在头部输出一个很小的外部脚本,作为bigpipe的支撑.其余所有模块如果依赖外部脚本(比如某一模块需要日历控件支持),都会在domready事件之后加载.这样做即保证了所有页面所有模块都能在domready前快速形成展现,又可以保证无脚本阻滞的快速的domready时间.
        最快可交互时间:
         domready再快也至少是在页面第一个使用bigpipe输出的chunked的http请求完成之后,对于facebook来说,这很可能是2秒之后了.那在这2s期间,如果只是页面展现了而不能交互(点击日历无反应),那方案依然不够完美.
         facebook的做法是,在domready后所依赖脚本已被加载之前,点击行为将会生成一个额外的脚本请求,只将这个点击所依赖的脚步预先load进来.这样保证了最快的可交互时间.中对这个技术进行了详细的描述.
         bigpipe原理简单,实现不复杂,但facebook却用了1年的时间才形成完备的凯发天生赢家一触即发官网的解决方案.生成方案需要时间,而解决随之而来的脚本阻滞,保障最快交互时间等等问题也会消耗大量时间. 
较为全面的了解了bigpipe,下面使用使用java servlet 3.0 异步特性,简单模拟bigpipe实现,没有涉及到页面组件单独请求的js、css文件等,仅仅用以演示其过程(最终效果大打折扣)。
我们目标页面大致结构如下,在 生成结构上修改。 
异步servlet代码:

传统mvc模式在这里貌似去除了,一切在服务器端生成,可借助freemarker或者静态页面辅助,减轻纯手工拼接html代码的麻烦。
生成客户端代码:
在servlet代码中,每输出一段html脚本都会迫使当前线程沉睡一定时间,用户在浏览页面时,按照优先级顺序一段一段的输出,用户体验不错。下面的截图,可以略微感知一下。
话说若使用bigpipe,须分段刷新,则服务器无法获得最终输出内容长度,只能采用chunked压缩编码格式输出内容,能节省网络流量;但不利于seo,按照facebook做法就是,若搜索爬虫访问,就会按照正常方式生成页面。另一方面,bigpipe的应用场景适合计算量大、较为耗时、拥有若干相互独立模块的页面,中小页面不太适合。


nieyong 2011-02-22 20:47
]]>
servlet 3.0笔记之异步请求comet推送长轮询(long polling)篇补遗http://www.blogjava.net/yongboy/archive/2011/02/19/346197.htmlnieyongnieyongsat, 19 feb 2011 02:19:00 gmthttp://www.blogjava.net/yongboy/archive/2011/02/19/346197.htmlhttp://www.blogjava.net/yongboy/comments/346197.htmlhttp://www.blogjava.net/yongboy/archive/2011/02/19/346197.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346197.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346197.html有些东西言犹未尽,这里补遗,使之理解更为全面些。
还多亏了jquery,提供了jsonp请求的方法:
$.getjson('http://192.168.0.99/servlet3/getjsonpnew?callback=?')
指定需要回调的参数名callback,但其值使用一问号替代,作为占位符,jquery会自动贴心的生成一个随机的调用函数名,服务器端的生成结果:
jquery150832738454006945_1297761629067('2011-02-15 17:20:33 921');
我们直接在success函数中直接获取传入的值,无须担心那个看起来怪怪的回调函数名。jsonp的原理,在页面头部区动态产生一个js文件引用(有人称为动态标签引用)。





loading ...



嗯,jquery-jsonp组件提供了更为灵活的jsonp参数配置:
  • 指定回调函数名
  • 提供错误处理函数,兼容opera
  • 在目前大部分浏览器中没有加载进度显示

经测试:
  1. ie
    页面不刷新,效果很满意。jquery-jsonp源代码:
         if ( browser.msie ) {

    script.event = str_onclick;
    script.htmlfor = script.id;
    script[ str_onreadystatechange ] = function() {
    /loaded|complete/.test( script.readystate ) && callback();
    };

    // all others: standard handlers
    }
    使用了ie浏览器onreadystatechange加载事件等特性。
  2. firefox
          script[ str_onerror ] = script[ str_onload ] = callback;

    browser.opera ?

    // opera: onerror is not called, use synchronized script execution
    ( ( scriptafter = $( str_script_tag )[ 0 ] ).text = "jquery('#" script.id "')[0]." str_onerror "()" )

    // firefox: set script as async to avoid blocking scripts (3.6 only)
    : script[ str_async ] = str_async;
    看来我当前浏览器中3.6版本的火狐支持脚本文件async属性,非阻塞加载,再添加上onload,onerror事件支持,页面不刷新显示,也不难办到了。
  3. chrome
    很不错,当前作为主力的chrome浏览器也支持脚本文件的async属性,效果同3.6版本火狐。
  4. opera
    情况很糟糕,在windows opera 11.01以及ubuntu opera 11.00下测试,长轮询时间若长于20秒,则会出现连接中断情况。样例中在opear浏览器下设置长轮询过期时间为20s。
    浏览器会一直显示为正在加载中的状态。
    opera浏览器中脚本加载不支持onerror事件,只能使用阻塞式的脚本加载机制用以处理有可能发生的异常。
    有关阻塞方式实现jsonp,延伸阅读:
  5. safari
    既然同为一个内核的谷歌浏览器也支持脚本文件的async属性,同样的异步加载也是水到渠成的了。
有关浏览器脚本异步加载机制,可进阶阅读:


nieyong 2011-02-19 10:19
]]>servlet 3.0笔记之异步请求comet推送长轮询(long polling)篇http://www.blogjava.net/yongboy/archive/2011/02/17/346198.htmlnieyongnieyongthu, 17 feb 2011 10:03:00 gmthttp://www.blogjava.net/yongboy/archive/2011/02/17/346198.htmlhttp://www.blogjava.net/yongboy/comments/346198.htmlhttp://www.blogjava.net/yongboy/archive/2011/02/17/346198.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346198.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346198.htmlcomet另一种形式为长轮询(long polling),客户端会与服务器建立一个持久的连接,直到服务器端有数据发送过来,服务器端断开,客户端处理完推送的数据,会再次发起一个持久的连接,循环往复。
和流(streaming)区别主要在于,在一次长连接中,服务器端只推送一次,然后断开连接。
其实现形式大概可分文ajax长轮询和javascript轮询两种。
  1. ajax方式请求长轮询

    服务器端可以返回原始的数据,或格式化的json、xml、javascript信息,客户端可拥有更多的处理自由。在形式上和传统意义上的ajax get方式大致一样,只不过下一次的轮询需要等到上一次的轮询数据返回处理完方可进行。
    这里给出客户端的小示范:




    jquery 1.5 with long poll







    基于jquery 1.5,事件链,比以往更简洁明了,尤其是在done方法中又一次调用自身,棒极了。
    服务器端很简单,每1秒输出一次当前系统时间:
    /**
    * 简单模拟每秒输出一次当前系统时间,精细到毫秒
    *
    * @author yongboy
    * @date 2011-2-11
    * @version 1.0
    */
    @webservlet("/getnexttime")
    public class getnexttimeservlet extends httpservlet {
    private static final long serialversionuid = 1l;

    protected void doget(httpservletrequest request,
    httpservletresponse response) throws servletexception, ioexception {
    try {
    thread.sleep(1000l);
    } catch (interruptedexception e) {
    e.printstacktrace();
    }

    response.setcontenttype("text/plain");
    printwriter out = response.getwriter();

    out.print(dateformatutils.format(system.currenttimemillis(),
    "yyyy-mm-dd hh:mm:ss sss"));
    out.flush();

    out.close();
    }
    }
  2. javascript标签轮询(script tag long polling)

    引用的javascript脚本文件是可以跨域的,再加上,更为强大了。
    这里给出一个演示,实现功能类似上面。
    客户端清单:




    jquery 1.5 with jsonp for comet








    服务器端清单:
    /**
    * jsonp形式简单模拟每秒输出一次当前系统时间,精细到毫秒
    * @author yongboy
    * @date 2011-2-11
    * @version 1.0
    */
    @webservlet("/getnexttime2")
    public class getnexttimeservlet2 extends httpservlet {
    private static final long serialversionuid = 1l;

    protected void doget(httpservletrequest request,
    httpservletresponse response) throws servletexception, ioexception {
    try {
    thread.sleep(1000l);
    } catch (interruptedexception e) {
    e.printstacktrace();
    }

    string callback = request.getparameter("callback");

    if(stringutils.isblank(callback)){
    callback = "showresult";
    }

    printwriter out = response.getwriter();

    stringbuilder resultbuilder = new stringbuilder();
    resultbuilder
    .append(callback)
    .append("('")
    .append(dateformatutils.format(system.currenttimemillis(), "yyyy-mm-dd hh:mm:ss sss"))
    .append("');");

    out.print(resultbuilder);
    out.flush();

    out.close();
    }
    }

    每次请求时,都会在html头部生成一个js网络地址(实现跨域):
    http://192.168.0.99:8080/servlet3/getnexttime2?callback=jquery150832738454006945_1297761629067&_=1297761631777
    我们不用指定,jquery会自动生成一个随机的函数名。
    请求上面地址服务器端在有新的数据输出时,生成的回调js函数:
    jquery150832738454006945_1297761629067('2011-02-15 17:20:33 921');
    从下面截图可以看到这一点。

    生成相应内容:


     不过,长连接情况下,浏览器认为当前js文件还没有加载完,会一直显示正在加载中。
上面的jsonp例子太简单,服务器和客户端的连接等待时间不过1秒钟时间左右,若在真实环境下,需要设置一个超时时间。
以往可能会有人告诉你,在客户端需要设置一个超时时间,若指定期限内服务器没有数据返回,则需要通知服务器端程序,断开连接,然后自身再次发起一个新的连接请求。
但在servlet 3.0 异步连接的规范中,我们就不用那么劳心费神,那般麻烦了。掉换个儿,让服务器端通知客户端超时啦,赶紧重新连接啊。
在前面几篇博客中,演示了一个伪真实场景,博客主添加博客,然后主动以流形式推送给终端用户。这里采用jsonp跨域长轮询连接形式。
客户端:


comet jsonp 推送测试










loading ...



服务器端接收长轮询连接请求:
/**
* jsonp获取最新信息
*
* @author yongboy
* @date 2011-2-17
* @version 1.0
*/
@webservlet(urlpatterns = "/getjsonpnew", asyncsupported = true)
public class getnewjsonpblogposts extends httpservlet {
private static final long serialversionuid = 565698895565656l;
private static final log log = logfactory.getlog(getnewjsonpblogposts.class);

protected void doget(httpservletrequest request,
httpservletresponse response) throws servletexception, ioexception {
response.setheader("cache-control", "private");
response.setheader("pragma", "no-cache");
response.setheader("connection", "keep-alive");
response.setheader("proxy-connection", "keep-alive");
response.setcontenttype("text/javascript;charset=utf-8");
response.setcharacterencoding("utf-8");

string timeoutstr = request.getparameter("timeout");

long timeout;

if (stringutils.isnumeric(timeoutstr)) {
timeout = long.parselong(timeoutstr);
} else {
// 设置1分钟
timeout = 1l * 60l * 1000l;
}

log.info("new request ...");
final httpservletresponse finalresponse = response;
final httpservletrequest finalrequest = request;
final asynccontext ac = request.startasync(request, finalresponse);
// 设置成长久链接
ac.settimeout(timeout);
ac.addlistener(new asynclistener() {
public void oncomplete(asyncevent event) throws ioexception {
log.info("oncomplete event!");
newblogjsonplistener.async_ajax_queue.remove(ac);
}

public void ontimeout(asyncevent event) throws ioexception {
// 尝试向客户端发送超时方法调用,客户端会再次请求/blogpush,周而复始
log.info("ontimeout event!");

// 通知客户端再次进行重连
final printwriter writer = finalresponse.getwriter();

string outstring = finalrequest.getparameter("callback")
"({state:0,error:'timeout is now'});";
writer.println(outstring);
writer.flush();
writer.close();

newblogjsonplistener.async_ajax_queue.remove(ac);
}

public void onerror(asyncevent event) throws ioexception {
log.info("onerror event!");
newblogjsonplistener.async_ajax_queue.remove(ac);
}

public void onstartasync(asyncevent event) throws ioexception {
log.info("onstartasync event!");
}
});

newblogjsonplistener.async_ajax_queue.add(ac);
}
}
很显然,在有博客数据到达时,会通知到已注册的客户端。
注意,推送数据之后,需要调用当前的异步上下文complete()函数:
/**
* 监听器单独线程推送到客户端
*
* @author yongboy
* @date 2011-2-17
* @version 1.0
*/
@weblistener
public class newblogjsonplistener implements servletcontextlistener {
private static final log log = logfactory.getlog(newbloglistener.class);
public static final blockingqueue blog_queue = new linkedblockingqueue();
public static final queue async_ajax_queue = new concurrentlinkedqueue();

public void contextdestroyed(servletcontextevent arg0) {
log.info("context is destroyed!");
}

public void contextinitialized(servletcontextevent servletcontextevent) {
log.info("context is initialized!");
// 启动一个线程处理线程队列
new thread(runnable).start();
}

private runnable runnable = new runnable() {
public void run() {
boolean isdone = true;

while (isdone) {
if (!blog_queue.isempty()) {
try {
log.info("async_ajax_queue size : "
async_ajax_queue.size());
micblog blog = blog_queue.take();

if (async_ajax_queue.isempty()) {
continue;
}

string targetjson = buildjsonstring(blog);

for (asynccontext context : async_ajax_queue) {
if (context == null) {
log.info("the current async_ajax_queue is null now !");
continue;
}
log.info(context.tostring());
printwriter out = context.getresponse().getwriter();

if (out == null) {
log.info("the current async_ajax_queue's printwriter is null !");
continue;
}

out.println(context.getrequest().getparameter(
"callback")
"(" targetjson ");");
out.flush();

// 通知,执行完成函数
context.complete();
}
} catch (exception e) {
e.printstacktrace();
isdone = false;
}
}
}
}
};

private static string buildjsonstring(micblog blog) {
map info = new hashmap();
info.put("state", 1);
info.put("content", blog.getcontent());
info.put("date",
dateformatutils.format(blog.getpubdate(), "hh:mm:ss sss"));

jsonobject jsonobject = jsonobject.fromobject(info);

return jsonobject.tostring();
}
}
长连接的超时时间,有人贴切的形容为“心跳频率”,联动着两端,需要根据环境设定具体值。
jsonp方式无论是轮询还是长轮询,都是可以很容易把目标程序融合到第三方网站,当前的个人凯发k8网页登录主页、博客等很多小挂件大多也是采用这种形式进行跨域获取数据的。


nieyong 2011-02-17 18:03
]]>
servlet 3.0笔记之tomcat(7.0.6)稳定版发布http://www.blogjava.net/yongboy/archive/2011/01/26/346199.htmlnieyongnieyongwed, 26 jan 2011 14:42:00 gmthttp://www.blogjava.net/yongboy/archive/2011/01/26/346199.htmlhttp://www.blogjava.net/yongboy/comments/346199.htmlhttp://www.blogjava.net/yongboy/archive/2011/01/26/346199.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346199.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346199.html2011年1月11日tomcat 7.0.6正式发布,很有纪念意义。
这是的第一个稳定版本,可以用在生产环境上面了,用以取代2007年2月发布的tomcat 6。
来自infoq新闻()简要说明了tomcat 7其主要
apache决定不在tomcat中添加对java ee 6 web profile的完整支持,至少在眼下是这样的。因此tomcat 7中只是简单地增加了(java ee 6中引入的)的支持以及。新版本要求使用java se 6或更高版本。
可见此版本,完善的支持了servlet 3.0 规范,以及可能被人忽略的jsp 2.2、el 2.2,这已经够了,不要那么多,简单就好,否则就会变成另一个j2ee应用服务器。
在安全方面也有较为主要的改善:
tomcat 7中的改进也不是全都针对servlet 3.0 api的,其中还有不少重要的安全性改进。现在针对基于脚本的访问、基于web的访问、jmx代理访问和状态页访问有了独立的角色,允许做更具体的访问控制。为了避免跨站请求伪造(csrf)攻击,所有的非幂等性请求(即多次执行不会产生相同结果的操作)都要求生成一个随机数。tomcat 7还针对会话固定攻击(session fixation attack)采取了一些防御措施。会话固定攻击就是将客户端的会话id强制设置为一个明确的已知值。
总之,我们可以在在tomcat 7.0.6中使用servlet 3.0做实际意义上的开发了。

nieyong 2011-01-26 22:42
]]>
servlet 3.0笔记之会话cookie设置相关http://www.blogjava.net/yongboy/archive/2011/01/19/346200.htmlnieyongnieyongwed, 19 jan 2011 13:58:00 gmthttp://www.blogjava.net/yongboy/archive/2011/01/19/346200.htmlhttp://www.blogjava.net/yongboy/comments/346200.htmlhttp://www.blogjava.net/yongboy/archive/2011/01/19/346200.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346200.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346200.html在servlet 3.0中增加对cookie(请注意,这里所说的cookie,仅指和session互动的cookie,即人们常说的会话cookie)较为全面的操作api。最为突出特性:支持直接修改session id的名称(默认为“jsessionid”),支持对cookie设置httponly属性以增强安全,避免一定程度的跨站攻击。
相关较为深入信息,请访问部分。
以前的实现
虽然新的api提供了简答便捷的api操作会话cookie,但新的api之前,我们可以较为生硬的操作响应头部,完成设定工作。看看以前的代码吧:
/**
* 自定义会话cookie属性
* @author yongboy
* @date 2011-1-19
* @version 1.0
*/
@webfilter(dispatchertypes = { dispatchertype.request }, urlpatterns = { "/*" })
public class customcookiefilter implements filter {
private static final log log = logfactory.getlog(customcookiefilter.class);
private static final string custom_session_id = "yongboyid";
private static final string http_only = "httponly";
private static final string set_cookie = "set-cookie";

public void destroy() {
}

public void dofilter(servletrequest req, servletresponse rep,
filterchain chain) throws ioexception, servletexception {
httpservletrequest request = (httpservletrequest) req;
httpservletresponse response = (httpservletresponse) rep;

if (response.containsheader(set_cookie)) {
log.info("haha,we have one new user visit the site ...");

string sessionid = request.getsession().getid();
string cookievalue = custom_session_id "=" sessionid ";path="
request.getcontextpath() ";" http_only;

log.info(set_cookie ":" cookievalue);

response.setheader(set_cookie, cookievalue);
}

chain.dofilter(req, response);
}

public void init(filterconfig fconfig) throws servletexception {
}
}

利用拦截器实现,判断每次请求的响应是否包含set-cookie头部,重写会话cookie,添加需要的属性。虽较为生硬,但灵活性强。

新的规范api

新的规范添加sessioncookieconfig接口,用于操作会话cookie,需要掌握以下主要方法:
  1. setname(string name)
    修改session id的名称,默认为"jsessionid"
  2. setdomain(string domain)
    设置当前cookie所处于的域
  3. setpath(string path)
    设置当前cookie所处于的相对路径
  4. sethttponly(boolean httponly)
    设置是否支持httponly属性
  5. setsecure(boolean secure)
    若使用https安全连接,则需要设置其属性为true
  6. setmaxage(int maxage)
    设置存活时间,单位为秒
如何使用呢,很方便,在servletcontextlistener监听器初始化方法中进行设定即可;下面实例演示如何修改"jsessionid",以及添加支持httponly支持:
/** 全局设置session-cookie相交互部分属性
*
* @author yongboy
* @date 2011-1-19
* @version 1.0
*/
@weblistener
public class sessioncookieinitialization implements servletcontextlistener {
private static final log log = logfactory
.getlog(sessioncookieinitialization.class);

public void contextinitialized(servletcontextevent sce) {
log.info("now init the session cookie");

servletcontext servletcontext = sce.getservletcontext();

sessioncookieconfig sessioncookie = servletcontext
.getsessioncookieconfig();
sessioncookie.setname("yongboyid");
sessioncookie.setpath(servletcontext.getcontextpath());
sessioncookie.sethttponly(true);
sessioncookie.setsecure(false);

log.info("name : " sessioncookie.getname() "\n" "domain:"
sessioncookie.getdomain() "\npath:"
sessioncookie.getpath() "\nage:"
sessioncookie.getmaxage());

log.info("ishttponly : " sessioncookie.ishttponly());
log.info("issecure : " sessioncookie.issecure());
}

public void contextdestroyed(servletcontextevent sce) {
log.info("the context is destroyed !");
}
}
需要通过servletcontext对象获得sessioncookieconfig对象,才能够进一步自定义session cookie的名字等属性。
无论以前的硬编码还是新的api实现,目标都是一致的,所产生头部信息也是完全一致。毫无疑问,后者更为方便快捷,省缺了显示的操作响应元数据。
对当前站点的第一次请求,很容易从响应头信息中看到set-cookie的属性值:



不同浏览器平台上测试

  1. 在safari、ie8、opera 11 一切都很正常
  2. firefox 3.6、chrome 9.0,jsessionid会继续存在:
    yongboyid=601a6c82d535343163b175a4fd5376ea; jsessionid=aa78738ab1ead1f9c649f705ec64d92d; ajstat_ok_times=6; jsessionid=abcpxyjmipbvz6whvo_1s; bayeux_browser=439-1vyje1gmqt8y8giva7pqsu1
  3. 在所有浏览器中,session id等于新设置的yongboyid值(若不相等,问题就严重了!)
  4. 在客户端js无法获得正确的sessioni id了。
tomcat服务器内置支持

在tomcat 6-7,可以不用如上显示设置cookie domain、name、httponly支持,在conf/context.xml文件中配置即可:

...
既然java应用服务器本身支持会话cookie设定,那就没有必要在程序代码中再次进行编码了。这是一个好的实践:不要重复造轮子。
这里给出一段测试session重写的一段脚本:



会被重写的url地址类似于:
http://localhost/servlet3/sessioncookietest;yongboyid=19b94935d50245270060e49c9e69f5b6
嗯,在取消会话cookie之后,可以直接看到修改后的session id名称了,当然这时候httponly属性也没有多大意义了。
有一点别忘记,设置httponly之后,客户端的js将无法获取的到会话id了。

进阶阅读:



    nieyong 2011-01-19 21:58
    ]]>
    servlet 3.0笔记之异步请求相关方法和asynccontext转发等http://www.blogjava.net/yongboy/archive/2011/01/17/346201.htmlnieyongnieyongmon, 17 jan 2011 13:07:00 gmthttp://www.blogjava.net/yongboy/archive/2011/01/17/346201.htmlhttp://www.blogjava.net/yongboy/comments/346201.htmlhttp://www.blogjava.net/yongboy/archive/2011/01/17/346201.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346201.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346201.html在servletrequest中增加的有关异步相关方法分为:
    1. startasync(servletrequest, servletresponse) 传入指定的request和response对象,便于在asynccontext中重复使用(这样被filter、servlet包装过的请求、相应对象才会在异步的环境下继续生效)。
    2. startasync() 若request或者response被包装,将失去预期的效果。
    3. isasyncsupported()isasyncstarted()
      辅助性的方法,用于判断当前请求是否支持异步或者异步已经开始。
    4. getasynccontext()
      需要在异步启动之后才能够访问,否则会报异常。
    在asynccontext中分发的方法有三个,不太容易区分:
    1. asynccontext.dispatch()
      若当前asynccontext由servletrequest.startasync(servletrequest, servletresponse)方法启动,返回的地址可以通过httpservletrequest.getrequesturi()得到。
      否则,分发的地址则是当前url request对象最后一次分发的地址。
      虽有些拗口,两者分发的地址大部分情况下一致;但尽量使用带有参数的异步上下文启动器。
      如本例中请求/asyncdispatch2async?disurl=self,执行dispatch()方法之后,自身会再次分发到自身,包括传递的参数。
    2. asynccontext.dispatch(string path)
      等同于servletrequest.getrequestdispatcher(string)算是一个快捷方法。
      可以转向一个同步或异步的servlet,或者jsp,或其它资源地址等。
    3. asynccontext.dispatch(servletcontext context, string path)
      请求的地址将在给定的上下文里面(servletcontext),有有可能传入的上下文与当前站带你应用的上下文有所区别。
    展示一个较为有趣、但没有多少实际意义的小示范:
    /**
    * 异步上下文的转向分发
    *
    * @author yongboy
    * @date 2011-1-14
    * @version 1.0
    */
    @webservlet(urlpatterns = { "/asyncdispatch2async" }, asyncsupported = true)
    public class asynccontextdispatch2asyncservlet extends httpservlet {
    private static final long serialversionuid = 46172233331022236l;
    private static final log log = logfactory
    .getlog(asynccontextdispatch2asyncservlet.class);

    protected void doget(httpservletrequest request,
    httpservletresponse response) throws servletexception, ioexception {
    response.setheader("cache-control", "private");
    response.setheader("pragma", "no-cache");
    response.setheader("connection", "keep-alive");
    response.setheader("proxy-connection", "keep-alive");
    response.setcontenttype("text/html;charset=utf-8");

    printwriter out = response.getwriter();
    out.println("

    start ...

    ");
    out.flush();

    if (!request.isasyncsupported()) {
    log.info("the servlet is not supported async");
    return;
    }

    request.startasync(request, response);

    if (request.isasyncstarted()) {
    asynccontext asynccontext = request.getasynccontext();
    asynccontext.settimeout(1l * 60l * 1000l);// 60sec

    new counterthread(asynccontext).start();
    } else {
    log.error("the ruquest is not asyncstarted !");
    }
    }

    private static class counterthread extends thread {
    private asynccontext asynccontext;

    public counterthread(asynccontext asynccontext) {
    this.asynccontext = asynccontext;
    }

    @override
    public void run() {
    int interval = 1000 * 20; // 20sec

    try {
    log.info("now sleep 20s, just as do some big task ...");
    thread.sleep(interval);
    log.info("now dispatch to another async servlet");

    servletrequest request = asynccontext.getrequest();

    string disurl = request.getparameter("disurl");

    if (stringutils.isblank(disurl)) {
    disurl = "/demoasynclink";
    }

    if (disurl.endswith(".jsp")) {
    request.setattribute("datestr", dateformatutils.format(
    system.currenttimemillis(), "yyyy-mm-dd hh:mm:ss"));
    }

    log.info("disurl is : " disurl);

    // 将当前异步上下文所持有的request, response分发给servlet容器
    if (stringutils.equals("self", disurl)) {
    // 将分发到自身,即当前异步请求地址
    asynccontext.dispatch();
    } else {
    // 将分发到指定的路径
    asynccontext.dispatch(disurl);
    }
    } catch (interruptedexception e) {
    e.printstacktrace();
    }
    }
    }
    }
    当前请求完成之后,将分发到下一个请求上面,若都是异步的servlet,则很好的组成了异步servlet请求链。
    有趣的地方在于,异步上下文环境可以分发到下一个异步或同步的servlet、jsp、html等资源。若访问类似于如下地址,当前url永远不会断开,又一个永动机,除非网络链接出错或者服务器关闭。
    http://localhost/servlet3/asyncdispatch2async?disurl=self
    一个视图:
    上面的异步servlet总是在不断的请求自我,成为了一个永动机;为disurl传入要转发到的异步或同步的资源地址,组成一个链的模式,相当的简单轻松。


    nieyong 2011-01-17 21:07
    ]]>
    servlet 3.0笔记之超方便的文件上传支持http://www.blogjava.net/yongboy/archive/2011/01/15/346202.htmlnieyongnieyongsat, 15 jan 2011 12:43:00 gmthttp://www.blogjava.net/yongboy/archive/2011/01/15/346202.htmlhttp://www.blogjava.net/yongboy/comments/346202.htmlhttp://www.blogjava.net/yongboy/archive/2011/01/15/346202.html#feedback1http://www.blogjava.net/yongboy/comments/commentrss/346202.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346202.html在以前,处理文件上传是一个很痛苦的事情,大都借助于开源的上传组件,诸如commons fileupload等。现在好了,很方便,便捷到比那些组件都方便至极。以前的html端上传表单不用改变什么,还是一样的multipart/form-data mime类型。
    让servlet支持上传,需要做两件事情
    1. 需要添加multipartconfig注解
    2. 从request对象中获取part文件对象
    但在具体实践中,还是有一些细节处理,诸如设置上传文件的最大值,上传文件的保存路径。
    需要熟悉multipartconfig注解,标注在@webservlet之上,具有以下属性:
    属性名类型是否可选描述
    filesizethresholdint当数据量大于该值时,内容将被写入文件。
    locationstring存放生成的文件地址。
    maxfilesizelong允许上传的文件最大值。默认值为 -1,表示没有限制。
    maxrequestsizelong针对该 multipart/form-data 请求的最大数量,默认值为 -1,表示没有限制。

    一些实践建议:
    1. 若是上传一个文件,仅仅需要设置maxfilesize熟悉即可。
    2. 上传多个文件,可能需要设置maxrequestsize属性,设定一次上传数据的最大量。
    3. 上传过程中无论是单个文件超过maxfilesize值,或者上传总的数据量大于maxrequestsize值都会抛出illegalstateexception异常;
    4. location属性,既是保存路径(在写入的时候,可以忽略路径设定),又是上传过程中临时文件的保存路径,一旦执行part.write方法之后,临时文件将被自动清除。
    5. 但servlet 3.0规范同时也说明,不提供获取上传文件名的方法,尽管我们可以通过part.getheader("content-disposition")方法间接获取得到。
    6. 如何读取multipartconfig注解属性值,api没有提供直接读取的方法,只能手动获取。
      来一个示例吧,上传前台页面:
      后台处理servlet:
      /**
      * 上传文件测试 location为临时文件保存路径
      *
      * @author yongboy
      * @date 2011-1-13
      * @version 1.0
      */
      @multipartconfig(location = "/home/yongboy/tmp/", maxfilesize = 1024 * 1024 * 10)
      @webservlet("/upload")
      public class uploadfileaction extends httpservlet {
      private static final long serialversionuid = 92166165626l;
      private static final log log = logfactory.getlog(uploadfileaction.class);
      // 得到注解信息
      private static final multipartconfig config;

      static {
      config = uploadfileaction.class.getannotation(multipartconfig.class);
      }

      protected void doget(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      request.getrequestdispatcher("/upload.jsp").forward(request, response);
      }

      protected void dopost(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      // 为避免获取文件名称时出现乱码
      request.setcharacterencoding("utf-8");

      part part = null;
      try {
      //
      part = request.getpart("file");
      } catch (illegalstateexception ise) {
      // 上传文件超过注解所标注的maxrequestsize或maxfilesize值
      if (config.maxrequestsize() == -1l) {
      log.info("the part in the request is larger than maxfilesize");
      } else if (config.maxfilesize() == -1l) {
      log.info("the request body is larger than maxrequestsize");
      } else {
      log.info("the request body is larger than maxrequestsize, or any part in the request is larger than maxfilesize");
      }

      forwarderrorpage(request, response, "上传文件过大,请检查输入是否有误!");
      return;
      } catch (ioexception ieo) {
      // 在接收数据时出现问题
      log.error("i/o error occurred during the retrieval of the requested part");
      } catch (exception e) {
      log.error(e.tostring());
      e.printstacktrace();
      }

      if (part == null) {
      forwarderrorpage(request, response, "上传文件出现异常,请检查输入是否有误!");
      return;
      }

      // 得到文件的原始名称,eg :测试文档.pdf
      string filename = uploadutils.getfilename(part);

      log.info("contenttype : " part.getcontenttype());
      log.info("filename : " filename);
      log.info("filesize : " part.getsize());
      log.info("header names : ");
      for (string headername : part.getheadernames()) {
      log.info(headername " : " part.getheader(headername));
      }

      string savename = system.currenttimemillis() "."
      filenameutils.getextension(filename);

      log.info("save the file with new name : " savename);

      // 因在注解中指定了路径,这里可以指定要写入的文件名
      // 在未执行write方法之前,将会在注解指定location路径下生成一临时文件
      part.write(savename);

      request.setattribute("filename", filename);
      request.getrequestdispatcher("/uploadresult.jsp").forward(request,
      response);
      }

      private void forwarderrorpage(httpservletrequest request,
      httpservletresponse response, string errmsg)
      throws servletexception, ioexception {
      request.setattribute("errmsg", errmsg);

      request.getrequestdispatcher("/upload.jsp").forward(request, response);
      }
      }
      获取文件名的函数,很简单:
       /**
      * 如何得到上传的文件名, api没有提供直接的方法,只能从content-disposition属性中获取
      *
      * @param part
      * @return
      */
      protected static string getfilename(part part) {
      if (part == null)
      return null;

      string filename = part.getheader("content-disposition");
      if (stringutils.isblank(filename)) {
      return null;
      }

      return stringutils.substringbetween(filename, "filename=\"", "\"");
      }
      文件上传成功之后,以及日志输出的截图如下:
      截图中可以看到part包含content-disposition属性,可以很容易从值中抽取出文件名。临时生成的上传文件大都以 .tmp为后缀,大致如下:
      让上传出现错误,就可以在保存路径下看到大致如上的临时文件。
      一次上传多个文件的后台servlet示范:
      /**
      * 多文件上传支持
      * @author yongboy
      * @date 2011-1-14
      * @version 1.0
      */
      @multipartconfig(
      location = "/home/yongboy/tmp/",
      maxfilesize = 1024l * 1024l, // 每一个文件的最大值
      maxrequestsize = 1024l * 1024l * 10l // 一次上传最大值,若每次只能上传一个文件,则设置maxrequestsize意义不大
      )
      @webservlet("/uploadfiles")
      public class uploadfilesaction extends httpservlet {
      private static final long serialversionuid = 2304820820384l;
      private static final log log = logfactory.getlog(uploadfilesaction.class);

      protected void doget(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      request.getrequestdispatcher("/uploads.jsp").forward(request, response);
      }

      protected void dopost(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      request.setcharacterencoding("utf-8");

      collection parts = null;
      try {
      parts = request.getparts();
      } catch (illegalstateexception ise) {
      // 可能某个文件大于指定文件容量maxfilesize,或者提交数据大于maxrequestsize
      log.info("maybe the request body is larger than maxrequestsize, or any part in the request is larger than maxfilesize");
      } catch (ioexception ioe) {
      // 在获取某个文件时遇到拉io异常错误
      log.error("an i/o error occurred during the retrieval of the part components of this request");
      } catch (exception e) {
      log.error("the request body is larger than maxrequestsize, or any part in the request is larger than maxfilesize");
      e.printstacktrace();
      }

      if (parts == null || parts.isempty()) {
      doerror(request, response, "上传文件为空!");
      return;
      }

      // 前端具有几个file组件,这里会对应几个part对象
      list filenames = new arraylist();
      for (part part : parts) {
      if (part == null) {
      continue;
      }
      // 这里直接以源文件名保存
      string filename = uploadutils.getfilename(part);

      if (stringutils.isblank(filename)) {
      continue;
      }

      part.write(filename);
      filenames.add(filename);
      }

      request.setattribute("filenames", filenames);
      request.getrequestdispatcher("/uploadsresult.jsp").forward(request,
      response);
      }

      private void doerror(httpservletrequest request,
      httpservletresponse response, string errmsg)
      throws servletexception, ioexception {
      request.setattribute("errmsg", errmsg);

      this.doget(request, response);
      }
      }
      批量上传很简单,但也有风险,任一个文件若超过maxfilesize值,意味着整个上传都会失败。若不限大小,那就不存在以上忧虑了。
      总之,在servlet 3.0 中,无论是上传一个文件,或者多个批量上传都是非常简单,但要处理好异常,避免出错。


      nieyong 2011-01-15 20:43
      ]]>
      servlet 3.0笔记之异步拦截器(async filter)的学习http://www.blogjava.net/yongboy/archive/2011/01/15/346203.htmlnieyongnieyongsat, 15 jan 2011 11:39:00 gmthttp://www.blogjava.net/yongboy/archive/2011/01/15/346203.htmlhttp://www.blogjava.net/yongboy/comments/346203.htmlhttp://www.blogjava.net/yongboy/archive/2011/01/15/346203.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346203.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346203.html异步servlet有时需要一个拦截器,但必须是异步的filter,否则将会报错:
      严重: servlet.service() for servlet [com.learn.servlet3.async.demoasynclinkservlet] in context with path [/servlet3] threw exceptionjava.lang.illegalstateexception: not supported.
      因此异步的filter拦截异步servlet,不要搞错。
      我们需要预先定义这么一个异步连接,每秒输出一个数字字符串,从0到99,诸如下面html字符串:
      2
      最后输出done!
      给出两个访问地址,一个用于被拦截(/demoasynclink),一个用于单独访问(/demoasynclink2),便于对照:
      /**
      * 模拟长连接实现,每秒输出一些信息
      *
      * @author yongboy
      * @date 2011-1-14
      * @version 1.0
      */
      @webservlet(
      urlpatterns = { "/demoasynclink", "/demoasynclink2" },
      asyncsupported = true
      )
      public class demoasynclinkservlet extends httpservlet {
      private static final long serialversionuid = 4617227991063927036l;

      protected void doget(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      response.setheader("cache-control", "private");
      response.setheader("pragma", "no-cache");
      response.setheader("connection", "keep-alive");
      response.setheader("proxy-connection", "keep-alive");
      response.setcontenttype("text/html;charset=utf-8");

      printwriter out = response.getwriter();
      out.println("
      start ...
      ");
      out.flush();

      asynccontext asynccontext = request.startasync(request, response);

      new counterthread(asynccontext).start();
      }

      private static class counterthread extends thread {
      private asynccontext asynccontext;
      public counterthread(asynccontext asynccontext) {
      this.asynccontext = asynccontext;
      }

      @override
      public void run() {
      int num = 0;
      int max = 100;
      int interval = 1000;

      // 必须设置过期时间,否则将会出连接过期,线程无法运行完毕异常
      asynccontext.settimeout((max 1) * interval);
      printwriter out = null;

      try {
      try {
      out = asynccontext.getresponse().getwriter();
      } catch (ioexception e) {
      e.printstacktrace();
      }

      while (true) {
      out.println("
      " (num ) "
      ");
      out.flush();

      if (num >= max) {
      break;
      }

      thread.sleep(interval);
      }
      } catch (interruptedexception e) {
      e.printstacktrace();
      }

      if (out != null) {
      out.println("
      done !
      ");
      out.flush();
      out.close();
      }

      asynccontext.complete();
      }
      }
      }
      若想让httpservletresponse包装器发挥包装的效果,须调用带有参数的startasync(request, response)方法开启异步输出,否则markwapperedresponse将不起作用。因为,若不传递现有的request,response对象,将会调用原生的request和response对象。
      在tomcat7下面,异步连接超时时间为10000单位,若不指定超时时间,递增的数字不会按照预想完整输出到99。
      我们假设需要定义这样一个filter,为每一次的异步输出的内容增加一个特殊标记:

      2
      逻辑很简单,作为示范也不需要多复杂。
      再看看一个异步filter的代码:
      /**
      * 异步拦截器
      *
      * @author yongboy
      * @date 2011-1-14
      * @version 1.0
      */
      @webfilter(
      dispatchertypes = {
      dispatchertype.request,
      dispatchertype.forward,
      dispatchertype.include
      },
      urlpatterns = { "/demoasynclink" },
      asyncsupported = true //支持异步servlet
      )
      public class asyncservletfilter implements filter {
      private log log = logfactory.getlog(asyncservletfilter.class);

      public asyncservletfilter() {
      }

      public void destroy() {
      }

      public void dofilter(servletrequest request, servletresponse response,
      filterchain chain) throws ioexception, servletexception {

      log.info("it was filted now");

      markwapperedresponse wapper = new markwapperedresponse(
      (httpservletresponse) response);

      chain.dofilter(request, wapper);
      }

      public void init(filterconfig fconfig) throws servletexception {
      }
      }
      很简单,添加上asyncsupported = true属性即可。在上面filter中包装了一个httpservletresponse对象,目的在于返回一个定制的printwriter对象,简单重写flush方法(不见得方法多好):
      /**
      * httpservletresponse简单包装器,逻辑简单
      *
      * @author yongboy
      * @date 2011-1-14
      * @version 1.0
      */
      public class markwapperedresponse extends httpservletresponsewrapper {
      private printwriter writer = null;
      private static final string marked_string = "";

      public markwapperedresponse(httpservletresponse resp) throws ioexception {
      super(resp);

      writer = new markprintwriter(super.getoutputstream());
      }

      @override
      public printwriter getwriter() throws unsupportedencodingexception {
      return writer;
      }

      private static class markprintwriter extends printwriter{

      public markprintwriter(outputstream out) {
      super(out);
      }

      @override
      public void flush() {
      super.flush();

      super.println(marked_string);
      }
      }
      }
      在浏览器端请求被包装的/demoasynclink链接,截图以及firebug检测截图如下:
       
      可以在浏览器内同时请求/demoasynclink2前后作为对比一下。


      nieyong 2011-01-15 19:39
      ]]>
      servlet 3.0笔记之异步请求comet流推送(streaming)实现小结http://www.blogjava.net/yongboy/archive/2011/01/14/346204.htmlnieyongnieyongfri, 14 jan 2011 02:26:00 gmthttp://www.blogjava.net/yongboy/archive/2011/01/14/346204.htmlhttp://www.blogjava.net/yongboy/comments/346204.htmlhttp://www.blogjava.net/yongboy/archive/2011/01/14/346204.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346204.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346204.htmlservlet3规范支持异步请求(或者称为长连接,或者反向ajax,或者comet,或者服务器推送技术):无阻塞的输入与输出模型,可以延时的请求和响应功能,还有超时事件通知,看上去一切都是那么完美。
      但终端浏览器支持长连接情况差强人意,对comet的支持大致汇总如下:
      1. ie浏览器最佳实践是使用htmlfile activexobject,以及创建隐藏iframe组件,可以跨越ie6-ie8;虽ie 8支持xdomainrequest支持http streaming,但仅仅是ie 8。
      2. firefox 浏览器相当棒,支持xmlhttprequest streaming 和隐藏的iframe组件。
      3. safari 浏览器支持xmlhttprequest streaming。
      4. chrome有些无奈,算不上支持xmlhttprequest streaming,使用iframe的话会一直出现正在加载中的标志。
      5. opera也不支持xmlhttprequest streaming,使用iframe的话会一直出现正在加载中的标志。
      总之,使用iframe是一个不错的方案,在ie、firefox下表现的很完美,在其它浏览器下只能忍受讨厌的正在加载中。数据交换格式可以采用js脚本调用。
      但无论哪一种方案,都必须认识到,一个持久的连接,当页面内容一直在递增时,会越来越膨胀,会占用用户机器的cpu,尽量隔一段时间断开连接,重新请求。
      http 1.1规范中声明客户端不应该与服务器端建立超过两个 http 连接,因此浏览器内需要借助脚本避免客户重开两个脚本。
      按照目前情形下,需要借助ajax pull   comet push 相结合来打造相当好的用户体验。
      servlet本身,无论2.4或者2.5的版本,可以使用一个循环达到长连接的目标:
      /**
      * 一个典型的长连接实现
      *
      * @author yongboy
      * @date 2011-1-14
      * @version 1.0
      */
      @webservlet("/demolonglink")
      public class demolonglinkservlet extends httpservlet {
      private static final long serialversionuid = 4617227991063927036l;

      protected void doget(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {

      response.setheader("cache-control", "private");
      response.setheader("pragma", "no-cache");
      response.setheader("connection", "keep-alive");
      response.setheader("proxy-connection", "keep-alive");
      response.setcontenttype("text/html;charset=utf-8");

      printwriter out = response.getwriter();
      out.println("
      start ...
      ");
      out.flush();

      int num = 0;
      int max = 100;
      while (true) {
      out.println("
      " (num ) "
      ");
      out.flush();

      if (num >= max) {
      break;
      }

      try {
      thread.sleep(1000);
      } catch (interruptedexception e) {
      e.printstacktrace();
      }
      }

      out.println("
      done !
      ");
      out.flush();
      out.close();
      }
      }
      每一个连接线程都处于一个不断循环之中,不能够有效释放,相当的浪费服务器资源,有可能导致容器内线程池耗尽,将无法应对后续请求。同时少了异步连接的特性,无法直接定义超时时间,更不要说超时事件,超时监听器等企业特性了。
      当然也可以实现异步请求,但可能没有规范那般严格。
      同步请求的模型:
      对比异步请求模型:
      上面两张图借用了,表示感谢。
      在前两篇文章中,使用一个单独线程处理资源,分发到大部分的异步请求中。


      nieyong 2011-01-14 10:26
      ]]>
      servlet 3.0笔记之异步请求comet推送xmlhttprequest示范http://www.blogjava.net/yongboy/archive/2011/01/13/346205.htmlnieyongnieyongthu, 13 jan 2011 02:20:00 gmthttp://www.blogjava.net/yongboy/archive/2011/01/13/346205.htmlhttp://www.blogjava.net/yongboy/comments/346205.htmlhttp://www.blogjava.net/yongboy/archive/2011/01/13/346205.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346205.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346205.html说实话,各个浏览器对xmlhttprequest内置对象请求异步连接(或者称呼为长连接)的支持不一而足,除了firefox,safari,ie8等,其它的要么不支持,要么有缺陷。总之,单纯依靠xhr(xmlhttprequest的简称)来调用长连接,不靠谱。
      xmlhttprequest对象在标准情况下,在请求长连接情况下,readystate = 3时处于一直监听情况,但各大主流浏览器内的支持相关情况不尽相同:
      1. ie(ie6-ie7),只有在请求的内容不再发生变化时以及 readystate = 4 的时候才会获取到返回的responsetext内容,因此不支持长连接。
      2. ie8:以取代activexobject对象,也算是一个进步,但在服务器端有邀请,必须在头部设置 access-control-allow-origin值,若不知道客户端调用js所处的域名,设置成星号,通用即可。
      3. opera:貌似只有在readystate=3时才会触发一次onreadystatechange函数,不支持长连接。
      4. firefox 3.6 和safari 4:默认支持xmlhttprequest streaming。
      5. chrome 4.1版本:只有在请求内容不再发生变化才会达到readystate=2状态,所以支持情况不太好。
      要检测各个浏览器对xmlhttprequest的支持请求,这里有一个好的去处:
        
      以上内容,翻译摘选自: 
      一个客户端订阅页面:
       
      页面代码:


      comet xhr测试
      x-ua-compatible
      " content="ie=8" />
      content-type" content="text/html;charset=utf-8" />
      author" content="yongboy@gmail.com" />
      keywords" content="servlet3, comet, ajax" />
      description" content="" />
      text/css" rel="stylesheet" href="css/main.css" />

      margin: 0; overflow: hidden" onload="">
      showdiv" class="inputstyle">



      当然后台需要一个内容发布的页面:

      后台处理的代码和上篇服务器推送部分较为类似相似:
      /**
      * xhr获取最新信息
      *
      * @author yongboy
      * @date 2011-1-10
      * @version 1.0
      */
      @webservlet(urlpatterns = "/getnew", asyncsupported = true)
      public class getnewblogposts extends httpservlet {
      private static final long serialversionuid = 5656988888865656l;
      private static final log log = logfactory.getlog(getnewblogposts.class);

      protected void doget(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      response.setheader("cache-control", "private");
      response.setheader("pragma", "no-cache");
      response.setheader("connection", "keep-alive");
      response.setheader("proxy-connection", "keep-alive");
      response.setheader("access-control-allow-origin", "*");
      response.setcontenttype("text/html;charset=utf-8");
      response.setcharacterencoding("utf-8");
      printwriter writer = response.getwriter();

      // 若当前输出内容较少情况下,需要产生大约2kb的垃圾数据,诸如下面产生一些空格
      for (int i = 0; i < 10; i ) {
      writer.print(" ");
      }

      writer.println("
      waiting for ......
      ");
      writer.flush();

      final asynccontext ac = request.startasync();
      // 设置成长久链接
      ac.settimeout(10 * 60 * 1000);
      ac.addlistener(new asynclistener() {
      public void oncomplete(asyncevent event) throws ioexception {
      newblogxhrlistener.async_xhr_queue.remove(ac);
      }

      public void ontimeout(asyncevent event) throws ioexception {
      newblogxhrlistener.async_xhr_queue.remove(ac);
      }

      public void onerror(asyncevent event) throws ioexception {
      newblogxhrlistener.async_xhr_queue.remove(ac);
      }

      public void onstartasync(asyncevent event) throws ioexception {
      log.info("now add the asynccontext");
      }
      });

      newblogxhrlistener.async_xhr_queue.add(ac);
      }
      }
      不过多了兼容ie8的代码部分:
      response.setheader("access-control-allow-origin", "*");
      在ie8以及chrome平台下,需要预先生成一些大小为2k的垃圾数据,诸如一些空格。

      单独线程代码和上篇隐藏iframe服务器推送部分相似,这里不再贴出。
      经测试:
      1. 在ubuntu 10.10系统下chrome(版本号8.0.552.224),支持xhr streaming,测试通过。
      2. 2.firefox 3.6 下测试通过。
      3. safari 5.0.3 测试通过。
      4. ie8测试通过。


      nieyong 2011-01-13 10:20
      ]]>
      servlet 3.0笔记之异步请求comet推送iframe示范http://www.blogjava.net/yongboy/archive/2011/01/10/346206.htmlnieyongnieyongmon, 10 jan 2011 02:57:00 gmthttp://www.blogjava.net/yongboy/archive/2011/01/10/346206.htmlhttp://www.blogjava.net/yongboy/comments/346206.htmlhttp://www.blogjava.net/yongboy/archive/2011/01/10/346206.html#feedback5http://www.blogjava.net/yongboy/comments/commentrss/346206.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346206.html
      servlet3规范提出异步请求,绝对是一巨大历史进步。之前各自应用服务器厂商纷纷推出自己的异步请求实现(或者称comet,或者服务器推送支持,或者长连接),诸如tomcat6中的nio连接协议支持,jetty的continuations编程架构,sun、ibm、bea等自不用说,商业版的服务器对comet的支持,自然走在开源应用服务器前面,各自为王,没有一个统一的编程模型,怎一个乱字了得。相关的comet框架也不少,诸如pushlet、dwr、cometd;最近很热html5也不甘寂寞,推出websocket,只是离现实较远。
      总体来说,在java世界,很乱!缺乏规范,没有统一的编程模型,会严重依赖特定服务器,或特定容器。
      好在servlet3具有了异步请求规范,各个应用服务器厂商只需要自行实现即可,这样编写符合规范的异步servlet代码,不用担心移植了。
      现在编写支持comet风格servlet,很简单:
      1. 在注解处标记上 asyncsupported = true;
      2. final asynccontext ac = request.startasync();
      这里设定简单应用环境:一个非常受欢迎博客系统,多人订阅,终端用户仅仅需要访问订阅页面,当后台有新的博客文章提交时,服务器会马上主动推送到客户端,新的内容自动显示在用户的屏幕上。整个过程中,用户仅仅需要打开一次页面(即订阅一次),后台当有新的内容时会主动展示用户浏览器上,不需要刷新什么。下面的示范使用到了iframe,有关comet stream,会在以后展开。有关理论不会在本篇深入讨论,也会在以后讨论。
      这个系统需要一个博文内容功能:
      新的博文后台处理部分代码:
       protected void dopost(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      micblog blog = new micblog();

      blog.setauthor("发布者");
      blog.setid(system.currenttimemillis());
      blog.setcontent(iso2utf8(request.getparameter("content")));
      blog.setpubdate(new date());

      // 放到博文队列里面去
      newbloglistener.blog_queue.add(blog);

      request.setattribute("message", "博文发布成功!");

      request.getrequestdispatcher("/web-inf/pages/write.jsp").forward(
      request, response);
      }

      private static string iso2utf8(string str){
      try {
      return new string(str.getbytes("iso-8859-1"), "utf-8");
      } catch (unsupportedencodingexception e) {
      e.printstacktrace();
      }
      return null;
      }
      当用户需要订阅博客更新时的界面:
      当前页面html代码可以说明客户端的一些情况:


      comet推送测试













      id为“showdiv”的div这里作为一个容器,用于组织显示最新的信息。
      而客户端逻辑,则在comet.js文件中具体展示了如何和服务器交互的一些细节:
      /**
      * 客户端comet js 渲染部分
      * @author yongboy@gmail.com
      * @date 2010-10-18
      * @version 1.0
      */
      string.prototype.template=function(){
      var args=arguments;
      return this.replace(/\{(\d )\}/g, function(m, i){
      return args[i];
      });
      }
      var html = '
      '
      '
      {0}
      '
      '
      last date : {1}
      '
      '
       
      '
      '
      ';

      function showcontent(json) {
      $("#showdiv").prepend(html.template(json.content, json.date));
      }
      var server = 'blogpush';
      var comet = {
      connection : false,
      iframediv : false,

      initialize: function() {
      if (navigator.appversion.indexof("msie") != -1) {
      comet.connection = new activexobject("htmlfile");
      comet.connection.open();
      comet.connection.write("");
      comet.connection.write("");
      writer.flush();

      final asynccontext ac = request.startasync();
      ac.settimeout(10 * 60 * 1000);// 10分钟时间;tomcat7下默认为10000毫秒

      ac.addlistener(new asynclistener() {
      public void oncomplete(asyncevent event) throws ioexception {
      log.info("the event : " event.tostring()
      " is complete now !");
      newbloglistener.async_ajax_queue.remove(ac);
      }

      public void ontimeout(asyncevent event) throws ioexception {
      log.info("the event : " event.tostring()
      " is timeout now !");

      // 尝试向客户端发送超时方法调用,客户端会再次请求/blogpush,周而复始
      log.info("try to notify the client the connection is timeout now ...");
      string alertstr = "";
      writer.println(alertstr);
      writer.flush();
      writer.close();

      newbloglistener.async_ajax_queue.remove(ac);
      }

      public void onerror(asyncevent event) throws ioexception {
      log.info("the event : " event.tostring() " is error now !");
      newbloglistener.async_ajax_queue.remove(ac);
      }

      public void onstartasync(asyncevent event) throws ioexception {
      log.info("the event : " event.tostring()
      " is start async now !");
      }
      });

      newbloglistener.async_ajax_queue.add(ac);
      }
      }
      每一个请求都需要request.startasync(request,response)启动异步处理,得到asynccontext对象,设置超时处理时间(这里设置10分钟时间),注册一个异步监听器。
      异步监听器可以在异步请求于启动、完成、超时、错误发生时得到通知,属于事件传递机制,从而更好对资源处理等。
      在长连接超时(ontimeout)事件中,服务器会主动通知客户端再次进行请求注册。
      若中间客户端非正常关闭,在超时后,服务器端推送数量就减少了无效的连接。在真正应用中,需要寻觅一个较为理想的值,以保证服务器的有效连接数,又不至于浪费多余的连接。
      每一个异步请求会被存放在一个高效并发队列中,在一个线程中统一处理,具体逻辑代码:
      /**
      * 监听器单独线程推送到客户端
      * @author yongboy
      * @date 2011-1-13
      * @version 1.0
      */
      @weblistener
      public class newbloglistener implements servletcontextlistener {
      private static final log log = logfactory.getlog(newbloglistener.class);
      public static final blockingqueue blog_queue = new linkedblockingdeque();
      public static final queue async_ajax_queue = new concurrentlinkedqueue();
      private static final string target_string = "";

      private string getformatcontent(micblog blog) {
      return string.format(target_string, buildjsonstring(blog));
      }

      public void contextdestroyed(servletcontextevent arg0) {
      log.info("context is destroyed!");
      }

      public void contextinitialized(servletcontextevent servletcontextevent) {
      log.info("context is initialized!");
      // 启动一个线程处理线程队列
      new thread(runnable).start();
      }

      private runnable runnable = new runnable() {
      public void run() {
      boolean isdone = true;

      while (isdone) {
      if (!blog_queue.isempty()) {
      try {
      log.info("async_ajax_queue size : "
      async_ajax_queue.size());
      micblog blog = blog_queue.take();

      if (async_ajax_queue.isempty()) {
      continue;
      }

      string targetjson = getformatcontent(blog);

      for (asynccontext context : async_ajax_queue) {
      if (context == null) {
      log.info("the current async_ajax_queue is null now !");
      continue;
      }
      log.info(context.tostring());
      printwriter out = context.getresponse().getwriter();

      if (out == null) {
      log.info("the current async_ajax_queue's printwriter is null !");
      continue;
      }

      out.println(targetjson);
      out.flush();
      }
      } catch (exception e) {
      e.printstacktrace();
      isdone = false;
      }
      }
      }
      }
      };

      private static string buildjsonstring(micblog blog) {
      map info = new hashmap();
      info.put("content", blog.getcontent());
      info.put("date",
      dateformatutils.format(blog.getpubdate(), "hh:mm:ss sss"));

      jsonobject jsonobject = jsonobject.fromobject(info);

      return jsonobject.tostring();
      }
      }
      异步请求上下文asynccontext获取输出对象(response),向客户端传递json格式化序列对象,具体怎么解析、显示,由客户端(见comet.js)决定。
      鉴于servlet为单实例多线程,最佳实践建议是不要在servlet中启动单独的线程,本文放在servletcontextlistener监听器中,以便在web站点启动时中,创建一个独立线程,在有新的博文内容时,遍历推送所有已注册客户端
      整个流程梳理一下:
      1. 客户端请求 blog.html
      2. blog.html的comet.js开始注册启动事件
      3. js产生一个iframe,在iframe中请求/blogpush,注册异步连接,设定超时为10分钟,注册异步监听器
      4. 服务器接收到请求,添加异步连接到队列中
      5. 客户端处于等待状态(长连接一直建立),等待被调用
      6. 后台发布新的博客文章
      7. 博客文章被放入到队列中
      8. 一直在守候的独立线程处理博客文章队列;把博客文章格式化成json对象,一一轮询推送到客户端
      9. 客户端js方法被调用,进行json对象解析,组装html代码,显示在当前页面上
      10. 超时发生时,/blogpush通知客户端已经超时,调用超时(timeout)方法;同时从异步连接队列中删除
      11. 客户端接到通知,对iframe进行操作,再次进行连接请求,重复步骤2
      大致流程图,如下:

      其连接模型,偷懒,借用ibm上一张图片说明:


      nieyong 2011-01-10 10:57
      ]]>
      关于代码片段分享的那些事http://www.blogjava.net/yongboy/archive/2011/01/05/346207.htmlnieyongnieyongwed, 05 jan 2011 10:10:00 gmthttp://www.blogjava.net/yongboy/archive/2011/01/05/346207.htmlhttp://www.blogjava.net/yongboy/comments/346207.htmlhttp://www.blogjava.net/yongboy/archive/2011/01/05/346207.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346207.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346207.html作为程序员,想必有时候必须要分享一些代码,或单独分享一段代码,或粘贴在博客文章中,若使用到了现有的一些博客bsp服务(诸如:csdn,javaeye,blogjava.net)编辑器具有代码粘贴服务,若使用到了blogger.com, wordpress自然有些难处。按照自己有限实践分一下类:1。eclipse平台
           安装java2html插件,官方地址为:
           这里有一篇向导,可以阅读大致了解一下:
          
           缺陷:对java代码格式化有效,生成的代码有些杂乱,并且对html等代码无能为力。
      2。在线工具
         
          个人没有尝试成功,放弃。
        
         严重推荐,注册账号可以使用谷歌openid服务,方便。所粘贴的代码支持输出rss,方便汇总与订阅。整个操作速度都很快。
         虽支持代码格式不算多,但够用。
        
         支持格式很多,也很细。操作简单,不用登陆即可粘贴分享。没有广告在旁边闪烁,可设密码,采用wordpress程序改造而成,在表单提交时速度有些慢。
        
         这个是github项目托管网站提供的附属功能,优点是简洁无比,从下面的截图可以略知一二:

      功能相比以上两个稍微差些,但对代码分享来说,已经够用。
      3。windows live writer
                很赞,但默认情况下对程序代码格式化不支持。可以采用下面这么一个插件:
               
      4。代码高亮js&css框架
      推荐有两个:
      1)。 ,很轻,很方便,可以很方便放入blogger平台里面或者wordpress中,;或者单独使用也是可以的,。推荐使用。
      2)。,也很出名,但安装较为麻烦,凯发k8网页登录官网有详细教程。

      5. postable工具
         有时候需要特殊符号需要修改一下,比如 “<”需要修改成“<”,“>”需要修改成“>”,要是手动修改很麻烦的,推荐两个在线版本:

      暂时就是用到这么多了,若有新的,顺手的,会不定期更新。

      nieyong 2011-01-05 18:10
      ]]>
      servlet 3.0笔记之包含在jar文件中可直接访问的资源文件特性(资源绑定)http://www.blogjava.net/yongboy/archive/2011/01/03/346208.htmlnieyongnieyongmon, 03 jan 2011 04:20:00 gmthttp://www.blogjava.net/yongboy/archive/2011/01/03/346208.htmlhttp://www.blogjava.net/yongboy/comments/346208.htmlhttp://www.blogjava.net/yongboy/archive/2011/01/03/346208.html#feedback1http://www.blogjava.net/yongboy/comments/commentrss/346208.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346208.html
      这次标题够长的,在servlet3协议规范中,包含在jar文件/meta-info/resources/路径下的资源可以直接访问了。这么说一说,可能感觉不到到底有什么好处,以往的jsp或者html页面只能存在站点的目录下,或者在web-inf目录下(只是不能直接访问)。
      规范说,${jar}/meta-inf/resources/被视为根目录,假设home.jsp被放在${jar}/meta-inf/resources/home.jsp,用户可以直接通过 http://域名/home.jsp 访问了。
      呈现一个常见的代码片段:
      /**
      * 简单示范
      * @author yongboy
      * @date 2011-1-16
      * @version 1.0
      */
      @webservlet("/jarhome")
      public class hellojarservlet extends httpservlet {
      private static final long serialversionuid = 6177346686921106540l;

      protected void doget(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      request.setattribute("date", new date());
      request.getrequestdispatcher("/jarpage/jarhome.jsp").forward(request, response);
      }
      }
      而jarhome.jsp文件路径则位于 ${jar}/meta-inf/resources/jarpage/jarhome.jsp
      当然jarhome.jsp文件则没有什么特别之处:
      <%@ page language="java" contenttype="text/html; charset=utf-8"
      pageencoding="utf-8"%>





      welcome to jar home


      j2ee



      now date : <%=((java.util.date)request.getattribute("date")).tostring()%>


      jarhome.jsp文件所引用css/style.css和img/j2ee.png等文件分别位于 ${jar}/meta-inf/resources/css/style.css${jar}/meta-inf/resources/img/j2ee.png目录下。
      把生成的jar文件存放在 web-inf/lib/ 中,下面为一个运行示范图:

      样式和图片等都可以正常访问。
      有时候可能需要使用路径信息等,再看一个示范吧:
      /**
      * 演示jardemo
      *
      * @author yongboy
      * @date 2011-1-16
      * @version 1.0
      */
      @webservlet("/jardemo")
      public class demowebinfpagesservlet extends httpservlet {
      private static final long serialversionuid = -1040850432869481349l;
      private static final log log = logfactory
      .getlog(demowebinfpagesservlet.class);

      @suppresswarnings("deprecation")
      @override
      protected void doget(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      log.info("the /jardemo is accessed now!");

      log.info("getrealpath : " request.getrealpath("/"));
      log.info("servletcontext : getrealpath : "
      getservletcontext().getrealpath("/"));
      log.info("getpathtranslated : " request.getpathtranslated());

      log.info("get jar's resource:");

      inputstream is = getservletcontext().getresourceasstream(
      "/jarfile/demo.txt");
      log.info("the jar/meta-inf/resources/jarfile/demo.txt's content is :\n"
      ioutils.tostring(is));

      request.getrequestdispatcher("/web-inf/pages/notaccess.jsp");
      }
      }
      输入命令行信息为:
      [framework] 2011-01-03 11:45:16,664 - com.learn.servlet3.jardync.demowebinfpagesservlet -798292 [http-8080-exec-6] info  com.learn.servlet3.jardync.demowebinfpagesservlet  - the /jardemo is accessed now!
      [framework] 2011-01-03 11:45:16,664 - com.learn.servlet3.jardync.demowebinfpagesservlet -798292 [http-8080-exec-6] info  com.learn.servlet3.jardync.demowebinfpagesservlet  - getrealpath : /home/yongboy/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/d/
      [framework] 2011-01-03 11:45:16,664 - com.learn.servlet3.jardync.demowebinfpagesservlet -798292 [http-8080-exec-6] info  com.learn.servlet3.jardync.demowebinfpagesservlet  - servletcontext : getrealpath : /home/yongboy/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/d/
      [framework] 2011-01-03 11:45:16,665 - com.learn.servlet3.jardync.demowebinfpagesservlet -798293 [http-8080-exec-6] info  com.learn.servlet3.jardync.demowebinfpagesservlet  - getpathtranslated : null
      [framework] 2011-01-03 11:45:16,665 - com.learn.servlet3.jardync.demowebinfpagesservlet -798293 [http-8080-exec-6] info  com.learn.servlet3.jardync.demowebinfpagesservlet  - get jar's resource:
      [framework] 2011-01-03 11:45:16,665 - com.learn.servlet3.jardync.demowebinfpagesservlet -798293 [http-8080-exec-6] info  com.learn.servlet3.jardync.demowebinfpagesservlet  - the ${jar}/meta-inf/resources/jarfile/demo.txt's content is :
      haha,the demo.s's content!
      haha,haha,haha!
      可以看到getrealpath("/")得到的是项目的根路径(可以参照jar解压后的路径)。而request.getpathtranslated则因为是包含在jar文件中(规范中说包含在远程主机、数据库、jar存档文件中,getpathtranslated都会返回null),则返回null。
      包含在jar中的资源文件,一样可以使用getresourceasstream获取到。
      再来看看jar文件meta-inf/resources目录下文件结构:
      • css/style.css
      • img/j2ee.png
      • jarfile/demo.txt
      • jarpage/jarhome.jsp
      • jsp/h.jsp
      • jsp/helloworld.jsp
      很显然,就是一个小型站点目录结构。
      每一个模块,建立一个web站点应用,使用ant脚本自动打包成jar文件,拷贝到真正站点web-inf/lib下。
      假设一个jar文件包含一个具体的模块,那么模块的部署与装载将是十分方便的。


      nieyong 2011-01-03 12:20
      ]]>
      servlet 3.0笔记之servlet的动态注册http://www.blogjava.net/yongboy/archive/2010/12/30/346209.htmlnieyongnieyongthu, 30 dec 2010 09:27:00 gmthttp://www.blogjava.net/yongboy/archive/2010/12/30/346209.htmlhttp://www.blogjava.net/yongboy/comments/346209.htmlhttp://www.blogjava.net/yongboy/archive/2010/12/30/346209.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346209.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346209.html在servlet3.0中可以动态注册servlet,filter,listener,在servletcontext对应注册api为:
        /**
         * 添加servlet
         */
        public servletregistration.dynamic addservlet(string servletname,
            string classname);

        public servletregistration.dynamic addservlet(string servletname,
            servlet servlet);

        public servletregistration.dynamic addservlet(string servletname,
            classextends servlet> servletclass);

        /**
         * 添加filter
         */
        public filterregistration.dynamic addfilter(string filtername,
            string classname);

        public filterregistration.dynamic addfilter(string filtername, filter filter);

        public filterregistration.dynamic addfilter(string filtername,
            classextends filter> filterclass);

        /**
         * 添加listener
         */
        public void addlistener(string classname);

        public extends eventlistener> void addlistener(t t);

        public void addlistener(classextends eventlistener> listenerclass);
      每个组件注册都提供三个方法,很细心。
      下面谈谈动态注册servlet,但不要希望太高,只能在初始化时进行注册。在运行时为了安全原因,无法完成注册。在初始化情况下的注册servlet组件有两种方法:
      1.实现servletcontextlistener接口,在contextinitialized方法中完成注册.
      2.在jar文件中放入实现servletcontainerinitializer接口的初始化器
      先说在servletcontextlistener监听器中完成注册。
          public void contextinitialized(servletcontextevent sce) {

              servletcontext sc = sce.getservletcontext();

              // register servlet
              servletregistration sr = sc.addservlet("dynamicservlet",
                  "web.servlet.dynamicregistration_war.testservlet");
              sr.setinitparameter("servletinitname", "servletinitvalue");
              sr.addmapping("/*");

              // register filter
              filterregistration fr = sc.addfilter("dynamicfilter",
                  "web.servlet.dynamicregistration_war.testfilter");
              fr.setinitparameter("filterinitname", "filterinitvalue");
              fr.addmappingforservletnames(enumset.of(dispatchertype.request),
                                           true, "dynamicservlet");

              // register servletrequestlistener
              sc.addlistener("web.servlet.dynamicregistration_war.testservletrequestlistener");
          }
      很简单,难度不大。
      再说说在jar文件中的servlet组件注册,需要在jar包含meta-inf/services/javax.servlet.servletcontainerinitializer文件,文件内容为已经实现servletcontainerinitializer接口的类:
      com.learn.servlet3.jardync.customservletcontainerinitializer
      该实现部分代码:
      @handlestypes({ jarwelcomeservlet.class })
      public class customservletcontainerinitializer implements
          servletcontainerinitializer {
        private static final log log = logfactory
            .getlog(customservletcontainerinitializer.class);

        private static final string jar_hello_url = "/jarhello";

        public void onstartup(set> c, servletcontext servletcontext)
            throws servletexception {
          log.info("customservletcontainerinitializer is loaded here...");
         
          log.info("now ready to add servlet : " jarwelcomeservlet.class.getname());
         
          servletregistration.dynamic servlet = servletcontext.addservlet(
              jarwelcomeservlet.class.getsimplename(),
              jarwelcomeservlet.class);
          servlet.addmapping(jar_hello_url);

          log.info("now ready to add filter : " jarwelcomefilter.class.getname());
          filterregistration.dynamic filter = servletcontext.addfilter(
              jarwelcomefilter.class.getsimplename(), jarwelcomefilter.class);

          enumset dispatchertypes = enumset
              .allof(dispatchertype.class);
          dispatchertypes.add(dispatchertype.request);
          dispatchertypes.add(dispatchertype.forward);

          filter.addmappingforurlpatterns(dispatchertypes, true, jar_hello_url);

          log.info("now ready to add listener : " jarwelcomelistener.class.getname());
          servletcontext.addlistener(jarwelcomelistener.class);
        }
      }
      其中@handlestypes注解表示customservletcontainerinitializer 可以处理的类,在onstartup 方法中,可以通过set> c 获取得到。
      jar文件中不但可以包含需要自定义注册的servlet,也可以包含应用注解的servlet,具体怎么做,视具体环境而定。
      把处理某类事物的servlet组件打包成jar文件,有利于部署和传输,功能不要了,直接去除掉jar即可,方便至极!

      nieyong 2010-12-30 17:27
      ]]>
      servletrest对xml配置文件的支持http://www.blogjava.net/yongboy/archive/2010/10/04/333735.htmlnieyongnieyongmon, 04 oct 2010 10:19:00 gmthttp://www.blogjava.net/yongboy/archive/2010/10/04/333735.htmlhttp://www.blogjava.net/yongboy/comments/333735.htmlhttp://www.blogjava.net/yongboy/archive/2010/10/04/333735.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/333735.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/333735.html虽然注解很方便,简单:
      @restsupport("/book/*/chapter/*")
      但耦合性较强,每写一个servlet都要添加上一个注解,想降低耦合或者url经常变动者,可以试试使用xml进行配置:
        <filter>
          <filter-name>restfilterfilter-name>
          <filter-class>com.servlet.rest.restfilterfilter-class>
          <init-param>
            <param-name>scanpackageparam-name>
            <param-value>/servlets.xml,/servlets2.xmlparam-value>
          init-param>    
        filter>
        <filter-mapping>
          <filter-name>restfilterfilter-name>
          <url-pattern>/*url-pattern>
        filter-mapping>

      为scanpackage参数添加需要传入的xml文件即可,多个xml配置文件之间需要使用逗号隔开

      我们再看看servlets.xml配置文件内容:

      xml version="1.0" encoding="utf-8"?>
      <servlets>
      	<servlet>
      		<class>com.yong.test.servlet.xml.welcomeservletclass>
      		<url>/url>
      		<url>/home/url>
      		<url>/welcome/url>
      	servlet>
      	<servlet>
      		<class>com.yong.test.servlet.xml.userhomeactionclass>
      		<url>/user/url>
      	servlet>
      servlets>

      具体到单个servlet配置为

      	<servlet>
      		<class>servlet 类路径class>
      		<url>对应url1url>
                    <url>对应url2url>
      	servlet>

      其实很简单的说,嗯,不要忘记xml一定要放在classpath相应位置。


       

       



      nieyong 2010-10-04 18:19
      ]]>
      介绍一个为servlet增加rest url支持的超小框架,一点都不侵入!http://www.blogjava.net/yongboy/archive/2010/10/01/333609.htmlnieyongnieyongfri, 01 oct 2010 08:06:00 gmthttp://www.blogjava.net/yongboy/archive/2010/10/01/333609.htmlhttp://www.blogjava.net/yongboy/comments/333609.htmlhttp://www.blogjava.net/yongboy/archive/2010/10/01/333609.html#feedback1http://www.blogjava.net/yongboy/comments/commentrss/333609.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/333609.html这个框架()最近刚刚被提交上谷歌代码托管,目标是为servlet增加rest风格url支持,仅仅如此而已,目标非常明确:不做那么多,只做一点点。

      说这个很轻、很微小的框架,一点都不过分,只有10个java文件,只是用注解的话,那就可以减少为7个java文件。

      说这个小东西目标明确,一点不掺假,就是为现有的servlet增加rest风格的url支持,仅此而已。

      我们表示一个具有结构形式的url为:

      /book/head first java/chapter/12 看图说故事

      传统的servlet在url处怎么映射呢 ?

      /book/*

      那么chapter怎么办 ?

      /chapter/*(这里假设/book/*排在较上位置)

      显然上面的链接地址则很难到达 /chapter/*。当然会有兄弟跳出来说,这一切可以交给 /book/*进行处理,嗯,book也一并处理了chapter部分,当然是可以的,带来了责任不单一的问题了,混杂在一起,造成代码管理的混乱。

      那么怎么在servletrest里面怎么做呢 ?

      @restsupport("/book/*/chapter/*")
      其风格完全是以往的servlet映射的风格,只不过支持的参数多了些。
      更重要的是以往的servlet编程经验一点都没有舍弃,都还保留着。在servletrest里没有强迫人们按照新的继承格式创造一个处理类,还是和以往一样,创建一个需要继承 httpservlet 的servlet,重写get、post、delete、put等方法,在类的合适位置添加上注解即可:
      @restsupport("/book/*/chapter/*") 即可。当然这部分可以在xml文件中完成配置,下一篇文章会讲到。
      这里有一个示范:
      @restsupport("/book/*/chapter/*")
      public class chapterservlet extends httpservlet {
              private static final long serialversionuid = -1534235656l;
              protected void doget(httpservletrequest request,
                              httpservletresponse response) throws servletexception, ioexception {
                      // code here ...
              }
              protected void dopost(httpservletrequest request,
                              httpservletresponse response) throws servletexception, ioexception {
                      // code here ...
              }
              protected void doput(httpservletrequest request,
                              httpservletresponse response) throws servletexception, ioexception {
                      // code here ...
              }
              protected void dodelete(httpservletrequest request,
                              httpservletresponse response) throws servletexception, ioexception {
                      // code here ...
              }
      }
      restsupport同时支持多个url @restsupport({"url1","url2"}),除此之外,没有多余功能。

       

      那么怎么在项目中使用呢 ?

      在web.xml 中配置filter:

      
        restfilter 
        class>com.servlet.rest.restfilterclass> 
        
          scanpackage 
          com.yong.test.servlet 
        
      
      
        restfilter 
        /* 
      

      scanpackage需要接收一个需要扫描的包的参数,若有多个包,可以包路径之间有一个分隔符逗号“,”,eg:

      com.yong.test.servlet,com.xiaomin.bookstore

      servletrest 注解需要jdk 1.5支持,servlet 2.*, 3.* 版本,仅仅把servletrest-0.8.jar ()放到项目classpath中,并且不依赖于第三方jar包,除了servlet.jar之外。

      在运行期间可以对servlet的进行动态装载和卸载等操作,servletrest已经封装了相应的接口(必须从全局context中获取,下面代码是从示范jsp代码中摘取):

      		servletfactory servletfactory = (servletfactory)application.getattribute("servletfactory");
      		
      		// 注册新的servlet
      		string mappingurl = "新的servlet映射url";
      		class servletclass = class.forname("要映射的servlet class路径");
      		
      		servletfactory.register(mappingurl, servletclass);
      		
      		// 注销servlet
      		//servletfactory.destory(servletclass);

       

      假如不在jsp中操作,那就需要:

      getservletcontext().getattribute("servletfactory");

      servletrest遵循的原则和原有的servlet容器处理方式一致,一个url对应一个servlet实例原则。

      更多信息请阅读servletrest源代码。



      nieyong 2010-10-01 16:06
      ]]>
      servlet 3.0笔记之体验可插拔特性,以及在实际中可能的应用范围http://www.blogjava.net/yongboy/archive/2010/07/05/346223.htmlnieyongnieyongmon, 05 jul 2010 14:08:00 gmthttp://www.blogjava.net/yongboy/archive/2010/07/05/346223.htmlhttp://www.blogjava.net/yongboy/comments/346223.htmlhttp://www.blogjava.net/yongboy/archive/2010/07/05/346223.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346223.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346223.html
      建立servlet,filter,listener等,没有什么区别。
      使用web fragment,较为麻烦的是页面文件的存放,其它到没有多大区别。但有以下两个方法解决:

      1. 使用ant编写 build.xml 文件,自动打成jar包,假如有html文件的话,可以把html等页面文件部署到主项目的部署设定目录下
              比如,我们设置转向到oneuser.html文件:
             request.getrequestdispatcher("/oneuser.html").forward(request, response);



               就需要在发布时把oneuser.html文件放在主项目的部署根目录下,其它路径,以此类推。
              建议编写ant脚本搞定。


      2. 把页面文件也打进jar包,使用freemarker硬编码实现页面渲染。这种方式有些硬,另一方面移植性好,一个jar包直接包含了servlet 和页面文件,但会带来修改页面文件的麻烦。
      下面为演示如何使用freemarker实现硬编码:

      /**
      * 这次,我们把模板文件放在jar文件包中
      *
      * @author xiaomin
      *
      */
      @webservlet("/some/")
      public class viewsomethingaction extends httpservlet {
          private static final long serialversionuid = 65464645l;
          private static final string default_encoding = "utf-8";
          private static final string template_name = "some";
          private string templatestring;
          private configuration configuration;
          public void init() throws servletexception {
              configuration = new configuration();
              configuration.setdefaultencoding(default_encoding);
              configuration.setencoding(locale.china, default_encoding);
              // 初始化模板
              templatestring = gettemplatestring("pages/something.html");
          }
          protected void doget(httpservletrequest request,
                  httpservletresponse response) throws servletexception, ioexception {
              // 构造要传递到模板的属性值
              map map = new hashmap();       
              map.put("username", "小敏");
              response.setcontenttype("text/html; charset=" default_encoding);
              printresponsesstring(response, map);
          }
          /**
           * 输出请求内容
           * @param response
           * @param map
           * @throws ioexception
           */
          private void printresponsesstring(httpservletresponse response,
                  map map) throws ioexception {
              template template = new template(template_name, new stringreader(
                      templatestring), configuration, default_encoding);
              writer out = response.getwriter();
              try {
                  template.process(map, out);
              } catch (templateexception e) {
                  e.printstacktrace();
              } finally {
                  out.flush();
                  template = null;
              }
          }

          /**
           * 获取jar包内的html模板文件内容
           * @param jarhtmlpath eg: pages/something.html
           * @return
           */
          private string gettemplatestring(string jarhtmlpath) {
              classloader mycl = this.getclass().getclassloader();
              inputstream is = mycl.getresourceasstream(jarhtmlpath);
              if (is == null) {
                  return null;
              } else {
                  try {
                      return templatestring = ioutils.tostring(is, default_encoding);
                  } catch (ioexception e) {
                      e.printstacktrace();
                      return null;
                  }
              }
          }
      }

      运行效果如图:

      注意在eclipse下,可在web fragment项目上点击运行,即可正常运行主项目,见下图:

      当然也可以在主项目上点击运行,依然可以运行。
      关于多个web fragment之间顺序加载问题,可以参阅如下文章:

      获得更多认知。
      小结一下servlet web fragment 可能在以下情况下很受用:
      1. 作为用户拦截、日志记录,实现项目之间的解耦。
      2. 提供rss订阅模块
      3. 后台管理
      4. 项目检测等
      5. 不需要页面的模块
      ......
      最后附加上一个jar文件,里面包含了源代码和要发布的文件:
      本次项目演示,依赖jar:
      commons-io-1.4.jar
      freemarker-2.3.13.jar
      commons-lang-2.3.jar
      本次项目jar文件:

      下次写些什么呢,不如实现更加友好的url,也来一把rest,让url简单一些。

      nieyong 2010-07-05 22:08
      ]]>
      servlet 3.0笔记之使用freemarker替代jsp,更快更轻更高效http://www.blogjava.net/yongboy/archive/2010/07/04/346224.htmlnieyongnieyongsat, 03 jul 2010 18:36:00 gmthttp://www.blogjava.net/yongboy/archive/2010/07/04/346224.htmlhttp://www.blogjava.net/yongboy/comments/346224.htmlhttp://www.blogjava.net/yongboy/archive/2010/07/04/346224.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346224.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346224.html很轻的,servlet freemarker 组合体,没有那么硬~》,不过那是基于servlet 2.× 系列的,今天谈谈如何在servlet 3.0 下使用freemarker进行更快的开发方式。
      servlet 3.0的强大、简单的,摆脱以前的约束,重构类名还得需要到web.xml中手动修改,如今再也没有那么多烦恼,当然这仅仅是一个侧面而已,就已经说明了其强大。
      freemarker强大的模板化能力,据说解析速度超越jsp,让讨厌jsp java混合体编程的人得到一种解脱,身心的。还有一点就是快速的模型填充,不需要随处可见的java代码,任何角落都是。
      总之:servlet 3.0 freemarker, 超级轻的mvc组合,让人愉悦。
      闲话短说,先来一个servlet jsp组合体:

      templatetestaction.java@webservlet("/test")
      public class templatetestaction extends httpservlet {
      private static final long serialversionuid = 88687687987980l;

      protected void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception {
      string id = "1687";

      string title = "jsp测试";
      string content = "这是jsp测试";

      request.setattribute("blog", new userblog(id, title, content, new date()));

      request.getrequestdispatcher("/web-inf/pages/template.jsp").forward(request, response);
      }
      }
      对应jsp:
      template.jsp<%@page import="java.text.simpledateformat"%>
      <%@page import="com.demo.userblog"%>
      <%@ page language="java" contenttype="text/html; charset=utf-8"
      pageencoding="utf-8"%>
      -//w3c//dtd html 4.01 transitional//en" "http://www.w3.org/tr/html4/loose.dtd">


      content-type" content="text/html; charset=utf-8">
      jsp servlet


      <%userblog blog = (userblog)request.getattribute("blog"); %>

      blogjava-凯发k8网页登录


      title : <%=blog.gettitle() %>

      datetime : <%=new simpledateformat("yyyy-mm-dd hh:mm").format(blog.getdate()) %>



      content :

      <%=blog.getcontent() %>





      看看代码,以前大家也都是这些写过来的。
      对比一下servlet freemarker :
      templatetest1action.java@webservlet("/test1")
      public class templatetest1action extends httpservlet {
      private static final long serialversionuid = 6576879808909808l;

      protected void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception {
      string id = "1688";

      string title = "使用freemarker";
      string content = "这是测试";

      request.setattribute("blog", new userblog(id, title, content, new date()));

      request.getrequestdispatcher("/web-inf/pages/template1.html").forward(request, response);
      }
      }

      servlet代码没有发生什么变化,只是这次转向了html文件:
      -//w3c//dtd html 4.01 transitional//en" "http://www.w3.org/tr/html4/loose.dtd">


      content-type" content="text/html; charset=utf-8">
      freemaker模板测试1



      title : ${blog.title}

      datetime : ${blog.date?string("yyyy-mm-dd hh:mm")}


      content :

      ${blog.content}




      请再次对比一下jsp 和 html文件的区别,您会选择写html还是jsp页面呢 ?
      要想让templatetest1action转向html页面生效,您需要配置一个freemarker的控制器,用以解析html页面。
      这里控制器为:templatecontroller.java 文件:
      @webservlet(
      urlpatterns = {"*.html"}, // 需要定义freemarker解析的页面后缀类型
      asyncsupported = false,
      loadonstartup = 0,
      name = "templatecontroller",
      displayname = "templatecontroller",
      initparams = {
      @webinitparam(name = "templatepath", value = "/"),
      @webinitparam(name = "nocache", value = "true"),//定义是否缓存
      @webinitparam(name = "contenttype", value = "text/html; charset=utf-8"),// 定义内容类型
      @webinitparam(name = "template_update_delay", value = "0"), // 开发环境中可设置为0
      @webinitparam(name = "default_encoding", value = "utf-8"),
      @webinitparam(name = "number_format", value = "0.##########")
      }
      )
      public class templatecontroller extends freemarkerservlet {
      private static final long serialversionuid = 8714019900490761087l;
      }
      这里仅仅需要继承freemarkerservlet即可,再加上一些注解即可,内容代码不用写。
      当然也可以省去templatecontroller,直接在web.xml文件中配置:
        
      freemarker
      class>freemarker.ext.servlet.freemarkerservletclass>

      templatepath
      /


      nocache
      true


      contenttype
      text/html; charset=utf-8


      template_update_delay
      0


      default_encoding
      utf-8


      number_format
      0.##########

      2


      freemarker
      *.html

      记得把 freemarker-2.3.13.jar 文件扔进web-inf/lib 目录下。
      项目源代码下载地址:
      接下来一篇将体验一下servlet 3.0 的webfragment功能,支持组件、功能的插拔,使之可以模块化构造一个站点服务,大的跨越,一个变革,必将受益开发者社区。


      nieyong 2010-07-04 02:36
      ]]>
      servlet 3.0笔记之快速上手,快速体验http://www.blogjava.net/yongboy/archive/2010/07/03/346225.htmlnieyongnieyongsat, 03 jul 2010 11:22:00 gmthttp://www.blogjava.net/yongboy/archive/2010/07/03/346225.htmlhttp://www.blogjava.net/yongboy/comments/346225.htmlhttp://www.blogjava.net/yongboy/archive/2010/07/03/346225.html#feedback0http://www.blogjava.net/yongboy/comments/commentrss/346225.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346225.html
      若不选择jdk1.6或者jre 1.6,会有提示错误信息。
      在eclipse 3.6 新建一个“dynamic web project”,主要配置如下:

      然后就是新建一个servlet : homeaction.java 代码如下:
      homeaction.java@webservlet("/home") //最简单的注解方式
      public class homeaction extends httpservlet {
      private static final long serialversionuid = 1l;

      protected void doget(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {

      response.setcontenttype("text/html; charset=utf-8");

      printwriter out = response.getwriter();

      out.write("");
      out.write("");
      out.write("");
      out.write("");
      out.write("
      ");
      out.write("welcome here !");
      out.write("
      ");

      out.write("
      ");
      out.write("");
      out.write("
      ");
      out.write("");

      out.write("
      ");


      out.write("");

      out.write("");

      out.flush();
      out.close();
      }

      protected void dopost(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      doget(request, response);
      }
      }


      一个最简单的servlet注解 : @webservlet(“/home”),简单好记。

      下面看一个较为复杂的servlet注解:

      @webservlet(urlpatterns = {"/hello"}, asyncsupported = false, 
      loadonstartup = -1, name = "helloaction", displayname = "helloaction",
      initparams = {@webinitparam(name = "username", value = "xiaomin")}
      )
      public class helloaction extends httpservlet {
      private static final long serialversionuid = 9191552951446203732l;

      private static string defaultusername = null;

      public void init() {
      defaultusername = this.getinitparameter("username");
      }

      @override
      protected void doget(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {

      string username = request.getparameter("user");

      if (username == null || username.equals("")) {
      username = defaultusername;
      }

      request.setattribute("username", username);

      // 转向jsp进行处理
      request.getrequestdispatcher("/hello.jsp").forward(request, response);
      }

      @override
      protected void dopost(httpservletrequest request,
      httpservletresponse response) throws servletexception, ioexception {
      this.doget(request, response);
      }
      }



      再来学习一下webservlet 的注解配置:



      属性名 类型 描述
      name string 指定 servlet 的 name 属性,等价于 。如果没有显式指定,则该 servlet 的取值即为类的全限定名。
      value string[] 该属性等价于 urlpatterns 属性。两个属性不能同时使用。
      urlpatterns string[] 指定一组 servlet 的 url 匹配模式。等价于 标签。
      loadonstartup int 指定 servlet 的加载顺序,等价于 标签。
      initparams webinitparam[] 指定一组 servlet 初始化参数,等价于 标签。
      asyncsupported boolean 声明 servlet 是否支持异步操作模式,等价于 标签。
      description string 该 servlet 的描述信息,等价于 标签。
      displayname string 该 servlet 的显示名,通常配合工具使用,等价于 标签。

      再新建一个filter:

      accessfilter.java@webfilter(urlpatterns = {"/hello"},
      dispatchertypes = {dispatchertype.request,dispatchertype.include, dispatchertype.forward},
      initparams = {@webinitparam(name = "encoding", value = "utf-8")}
      )
      public class accessfilter implements filter {

      public void dofilter(servletrequest req,
      servletresponse rep, filterchain filterchain)
      throws ioexception, servletexception {
      httpservletrequest request = (httpservletrequest)req;

      system.out.println("datetime : " new date() " ip : " request.getremoteaddr() " url : " request.getrequest);

      filterchain.dofilter(req, rep);
      }

      public void destroy() {
      }

      public void init(filterconfig filterconfig) throws servletexception {
      }
      }


      最简单注解方式 @webfilter(“/hello”),默认为对url的拦截。

      各个配置参数意义如下:



      属性名 类型 描述
      filtername string 指定过滤器的 name 属性,等价于
      value string[] 该属性等价于 urlpatterns 属性。但是两者不应该同时使用。
      urlpatterns string[] 指定一组过滤器的 url 匹配模式。等价于 标签。
      servletnames string[] 指定过滤器将应用于哪些 servlet。取值是 @webservlet 中的 name 属性的取值,或者是 web.xml 中 的取值。
      dispatchertypes dispatchertype 指定过滤器的转发模式。具体取值包括:
      async、error、forward、include、request。
      initparams webinitparam[] 指定一组过滤器初始化参数,等价于 标签。
      asyncsupported boolean 声明过滤器是否支持异步操作模式,等价于 标签。
      description string 该过滤器的描述信息,等价于 标签。
      displayname string 该过滤器的显示名,通常配合工具使用,等价于 标签。



      现在再看看运行效果:





      简单几下就试用了新版本的servlet 3.0 的一些特性,很简单吧,省去了很多配置的繁杂。当然在这很短的时间内,尚有很多东西尚未体验,不着急,慢慢来。有时间,我会一并发送上来。

      项目打包下载地址:



      下一篇将讲如何替代jsp,让开发速度更快一些;最初的servlet javabean jsp 模式,可能已经不太适合现在企业的开发需求。我们需要的是很轻的开发方式,具体是什么,下篇见 :))

      nieyong 2010-07-03 19:22
      ]]>
      servlet 3.0笔记之开发环境搭建http://www.blogjava.net/yongboy/archive/2010/07/02/346226.htmlnieyongnieyongfri, 02 jul 2010 13:41:00 gmthttp://www.blogjava.net/yongboy/archive/2010/07/02/346226.htmlhttp://www.blogjava.net/yongboy/comments/346226.htmlhttp://www.blogjava.net/yongboy/archive/2010/07/02/346226.html#feedback1http://www.blogjava.net/yongboy/comments/commentrss/346226.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/346226.htmltomcat 7 终于羞羞答答出现在众人面前,她让我等了一年多的时间,那个期盼呦。
      为啥呢,因为 tomcat 7 支持servlet 3.0 规范啊。虽然glassfish也早就宣布支持 servlet 3.0,但那个稍大的家伙有些重;只好等待很轻的tomcat 7小姑娘了。等啊等啊,她就到了。
      前几天 eclipse 3.6 也出世了,java ee版本支持servlet 3.0和tomcat 7,很好。
      既然一切都准备好了,那让我们马上开始吧。
      工欲善其事,必先利其器。下面列出进行servlet 3.0 开发的必须环境。

      1. jdk 1.6
      这个没得说,tomcat 7 运行要求,最简单安装jre 1.6即可。
      下载地址:
      2. eclipse 3.6
      嗯,支持servlet 3.0 注解的ide,目前是最受欢迎开发工具。
      凯发k8网页登录官网地址:

      记得要选择
       
      32位下载地址:

      3. tomcat 7
      tomcat 7 下载地址:

      若电脑不是64位,则推荐下载
      32-bit windows zip
      确保以上软件都安装好,正常运行。
      备注:电脑上安装jdk 1.5 或者更老版本的xd,需要注意相关环境设置。
      下一节,我们将做一个简单的示范应用。

      nieyong 2010-07-02 21:41
      ]]>
      很轻的,servlet freemarker 组合体,没有那么硬~http://www.blogjava.net/yongboy/archive/2009/11/10/301860.htmlnieyongnieyongtue, 10 nov 2009 08:52:00 gmthttp://www.blogjava.net/yongboy/archive/2009/11/10/301860.htmlhttp://www.blogjava.net/yongboy/comments/301860.htmlhttp://www.blogjava.net/yongboy/archive/2009/11/10/301860.html#feedback1http://www.blogjava.net/yongboy/comments/commentrss/301860.htmlhttp://www.blogjava.net/yongboy/services/trackbacks/301860.html老调重弹。对ssh经典组合有些腻,不再那么轻,重返到若干年前的原始。

      servlet的轻巧高效,freemarker的强大简便,两者结合将是超轻的组合,即可避免丑陋的java代码和html代码杂揉,又可高效基于模板的站点开发。

      闲话少说,项目需要:

      freemarker-2.3.13.jar

      servlet.jar

      定义两个servlet:

      helloaction.java 对应 /hello,借助freemarker硬编码输出

      public class helloaction extends httpservlet {
          private static final long serialversionuid = -6082007726831320176l;

          private configuration configuration;
          public void init() throws servletexception {
              configuration = new configuration();
              configuration.setservletcontextfortemplateloading(getservletcontext(), "web-inf/pages");
              configuration.setencoding(locale.china, "utf-8");
          }

          @suppresswarnings("unchecked")
          public void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception {
              // 填充数据类型
              map map = new hashmap();
              map.put("username", "小敏");   
              template template = configuration.gettemplate("hello.html");
              response.setcontenttype("text/html; charset=" template.getencoding());
              writer out = response.getwriter();
              try{
                  template.process(map, out);
              }catch (templateexception e) {
                  e.printstacktrace();
              }
          }

          public void destroy() {
              super.destroy();
              if(configuration != null){
                  configuration = null;
              }
          }
      }

      对应模板:




      使用freemarker渲染2


      你好, ${username!} !

       

      hiaction.java 对应 /hi ,借助freemrker servlet的拦截功能,如以往写代码方式,感觉不到freemarker的存在。

      public class hiaction extends httpservlet {
          private static final long serialversionuid = 518767483952153077l;

          public void doget(httpservletrequest request, httpservletresponse response)
                  throws servletexception, ioexception {

              request.setattribute("thename", "小敏");
              request.getrequestdispatcher("/web-inf/pages/hi.html").forward(request, response);
          }
      }

      对应的模板:




      使用freemarker渲染


      hi ${thename!}~


      但需要在web.xml 配置文件中定义如下:


          freemarker
         
              freemarker.ext.servlet.freemarkerservlet
         

         
         
              templatepath
              /
         

         
              nocache
              true
         

         
              contenttype
              text/html; charset=utf-8
             
         

         
         
              template_update_delay
              0
         

         
              default_encoding
              utf-8
         

         
              number_format
              0.##########
         

          1


          freemarker
          *.html

      使用哪一种组合方式,看您喜好了。

      借助于freemarker自身的servlet工具,只是用于拦截servlet中forward转向使用到的html资源文件。

      很简陋,但凑合着能看。

      项目源代码已经打包如下:



      nieyong 2009-11-10 16:52
      ]]>
      网站地图