最近学习Servlet和jsp,学习过程中有一些疑问和想法,上网找答案,下面是一些总结:
一、jsp和servlet的区别
在学习到jsp的时候,我发现jsp实际的概念和自己原来的理解有很大差异,我一直简单的以为jsp就是在html标签里签入了一些java和js代码而已,原来jsp其实本身就对应一个servlet。上网百度jsp和servlet的区别。利用servlet本身就可以完成网页的制作,利用它可以实现页面间的传值,页面的显示(利用response.write输出html标签)等操作。但是servlet把业务逻辑的处理和页面的显示代码混杂在一起,不易维护。于是Sun公司开发jsp用于独立显示页面,然后把业务逻辑层的信息放到JavaBean里。完全可以在jsp里嵌入java代码完成servlet的功能,jsp本身就对应一个servlet。所以学习servlet有利于更好的理解jsp,学习了jsp一定要学习JavaBean,单纯的学习jsp和单纯学习servlet一样没有什么意义。
在学习servlet的过程中我发现,servlet可以完成跳转页面的功能,和struct里面的Action比较类似。于是百度servlet和Action的区别。网上这方面的资料很少,可能是我的这个问题本身问的就不对。我自己理解为struct包中的Action有一个dispatchAction子类。在java web开发中,原始的配置action都是使用一个action类实现一个功能,如果工程项目够大,action类数目会很多,就增加了该项目后期的维护难度。DispatchAction继承自Action类,它是一个抽象类,封装了一些基础方法,来解决使用一个Action处理多个操作的能力,这就是DispatchAction最大的用途,它可以帮助我们用一个Action类,封装一套类似的操作方法,节省了类的数目,同时也减轻了后期维护的困难。
DispatchAction中主要包括以下几个方法:protected ActionForward dispatchMethod protected java.lang.reflect.Method getMethod protected java.lang.String getMethodNameDispatchAction,在配置上与标准的Action稍有不同,就是要在Action配置文件中多一个parameter属性,这个属性将指导DispatchAction找到对应的方法,例如这样配置:<action path="/saveSubscription" type="org.apache.struts.actions.DispatchAction" name="subscriptionForm" scope="request" input="/subscription.jsp" parameter="method"/>parameter的属性值是可以任意起的,只要你记得在传参数的时候统一就可以了。比如我写了一个类似这样的Action,它继承自DispatchAction类,包含了三个操作方法,有Add(),Update(),Delete()。当我想要调用这个Action的Update操作时,提交的URL应该类似这样的:
saveSubscription.do?method=Update 就是这么简单,不过非常方便我们程序员了,开发中我感觉的确省了好多代码,至少以前的三个类文件变成了现在一个类了,而且在后期维护的时候感觉也是方便很多。 需要注意的是:在调用DispatchAction的时候method参数是不能为空的,如果空,DispatchAction会调用unspecified方法并抛出异常。但是可以不传递method这个参数,这样dispatchAction就会调用defaultforward方法。所以我感觉dispatchAction相当于servlet的功能(这种理解有待考证)。
二、servlet和Action的区别 servlet的线程安全的问题
在查找servlet和Action的区别的时候,搜索的资料大都是关于servlet的线程安全的问题,于是就顺便看了看。
http://terryjs.javaeye.com/blog/739167这个帖子讲了在大量用户访问同一个servlet的时候有可能会出现不同步的现象,每访问一次servlet,OS就会分配一个线程,虽然servlet只被实例化一次(这里也是相对而言,请看注释1),但是可能会出现数据不一致的情况,这个帖子还列举了一个实例以及如何设计线程安全的Servlet。
http://www.javaeye.com/topic/225749这个帖子从jvm的内存机制出发说明了为什么会出现数据不同步的现象。讨论了java的内存机制就是堆栈和帧的使用。
通过这两个帖子我理解到,java类被实例化后,类的实例和实例变量是放在堆里,而方法里的局部变量和形参以及一些引用,是放在各自的栈里,堆是共享的,而栈是不共享的。所以要设计线程安全的servlet就要尽可能少的使用共享资源,而是变量局部化。每一个方法里的变量只有被执行到的时候才会被创建,所以每一个线程都有各自的栈来存放这些局部变量。因此不存在共享的问题。而struts里的Action也和servlet类似也是线程不安全的。也要注意尽量不要使用实例变量。如果使用实例变量,但是实例变量是final型或者在方法里没有再赋值也是线程安全的。
【注释1】:Servlet的单例不由class来决定,而是由您在web.xml里配置的servlet-name来决定。也就是说每一个servlet-name只有1个单例。当你有3个不同的serlvet-name指定了相同的1个servlet-class时,容器会产生3个不同的servlet object。
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getOutputStream().println(this.hashCode());
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
在5秒内打开N个浏览器窗口,返回的都是相同的Hashcode。只有在context reload以后,Hashcode才发生变化。因为原来的servlet object销毁了,容器又新建了一个。
虽然这段代码我没运行过,但我在自己定义的servlet里输出hashcode看过,如果在配置文件里不同的serlvet-name指定了相同的1个servlet-class,当访问不同名字的servlet的时候(url-pattern肯定要不一样才行)虽然他们都实例化了同一个类,但是产生的hashcode是不同的。说明Servlet的单例不由class来决定,而是由您在web.xml里配置的servlet-name来决定,也就是说每一个servlet-name只有1个单例。但是当我在同一个页面刷新页面时,hashcode的前几位数字是发生变化的,后面几位保持不变。我对hashcode没多少理解,只是猜想可能hashcode的前几位是表示不同的线程号,后几位是类的实例号。