Saturday, December 12, 2009

javaweb|对session何时生成的无用讨论

第 16 章 Never End...

第 16 章 Never End...

学习到此为止告一段落

对教程内容和jsp有什么建议的话,可以跟叮咚姐姐联系,随着技术的进步,我们也会同步更新教程的内容。

16.1. 对session何时生成的无用讨论

提出问题:“session是在什么时候生成的?”

现象:

  1. 如果写一个servlet,在里边调用getSession(false),得到的是null。(这里的参数表示当session不存在时是否新建,返回null表示还没为当前用户生成对应的session)。

  2. 如果写一个jsp,在里边打印session,可以看到打印的信息,说明session已经创建了。

对现象的感性认识:jsp会自动创建HttpSession对象,而servlet则不会,只有在servlet中显式调用getSession()方法时才创建session。

对现象的理性说明

  1. 只有执行了getSession()才会生成session。(此处无参数,默认与getSession(true)功能相同)。

  2. jsp转换成servlet后,生成的servlet代码中包含getSession()。

    例子中见tomcat目录下,work\Catalina\localhost\99-01\org\apache\jsp\test1.jsp,其中包含了pageContext.getSession()。

  3. 我们可以在jsp中使用<%@ page session="false"%>禁止生成session的代码,这样即使访问此jsp页面,也不会创建session。

    例子中见tomcat目录下,work\Catalina\localhost\99-01\org\apache\jsp\test2.jsp,这里已经看不到getSession()的代码了。

因为<%@ page session="false"%>不会生成默认的session,在这个jsp页面里就无法直接使用session变量了。下面的代码会导致jsp无法编译的错误。

<%@ page session="false"%> <%     out.println(session); %> <hr />         

访问test3.jsp会出现500服务器内部错误

在使用过<%@ page session="false"%>的页面里只能使用getSession()手工获得session才能使用了。

演示用代码放在99-01目录下,TestServlet映射到/请求路径,test1.jsp会自动创建session,test2.jsp禁用了session,test3.jsp直接使用session会出现错误。

这个问题是典型的无用研究之一,实际工作中会有人使用getSession(false)吗?

16.2. 对手工将jsp转换为servlet的无用讨论

有同学提到了一个问题:让用户自己编写jsp上传到服务器,服务器接收后保存入数据库,在用户访问的时候从数据库中取出对应的jsp,运行后返回响应。

这个效果从理论上讲是可以实现的,我们已经对jsp的运行机制非常熟悉了,第 6.2 节 “jsp与servlet的关系”,我们只需要将jsp转换为对应servlet的java文件,再编译为class,最后使用ClassLoader加载生成的class执行即可。

好消息是tomcat为我们提供了批量转换jsp的工具,使用以下的ant脚本就可以将指定目录下的jsp转换成对应servlet的java代码。(ant的使用方法已经超出了本文的讨论范围,如果有兴趣请自行参阅官方网站。http://ant.apache.org/

<project name="jspc" default="jspc" basedir=".">     <property name="CATALINA_HOME" location="../../"/>      <path id="build.lib">         <pathelement location="WEB-INF/classes"/>         <fileset dir="WEB-INF/lib">             <include name="*.jar"/>         </fileset>     </path>      <target name="jspc">         <taskdef classname="org.apache.jasper.JspC" name="jasper2">             <classpath>                 <pathelement location="${java.home}/../lib/tools.jar"/>                 <fileset dir="${CATALINA_HOME}/server/lib">                     <include name="*.jar"/>                 </fileset>                 <fileset dir="${CATALINA_HOME}/common/lib">                     <include name="*.jar"/>                 </fileset>                 <path refid="build.lib"/>             </classpath>         </taskdef>          <jasper2 verbose="1"             package="org.apache.jsp"             uriroot="."             webXmlFragment="WEB-INF/generated_web.xml"             outputDir="./WEB-INF/src/" />     </target> </project>           

为了顺利完成转换工作,我们需要把一系列jar文件放入classpath中:

  1. WEB-INF/lib/*.jar(星号代表所有)

  2. ${java.home}/../lib/tools.jar(${java.home}表示jdk安装目录)

  3. ${CATALINA_HOME}/server/lib/*.jar(${CATALINA_HOME}表示tomcat的安装目录)

  4. ${CATALINA_HOME}/common/lib/*.jar

简要介绍一下jasper2的配置参数。

  1. verbose="1"表示打印进度信息,如果verbose="0"则不打印信息。

  2. package="org.apache.jsp"表示生成servlet中的包名(package)。

  3. uriroot="."表示会把当前目录下的所有jsp都转换成servlet。

  4. webXmlFragment="WEB-INF/generated_web.xml"会在WEB-INF目录下生成servlet的默认配置。

    比如我们转换的test.jsp将生成如下部分。

    <servlet>     <servlet-name>org.apache.jsp.test_jsp</servlet-name>     <servlet-class>org.apache.jsp.test_jsp</servlet-class> </servlet>  <servlet-mapping>     <servlet-name>org.apache.jsp.test_jsp</servlet-name>     <url-pattern>/test.jsp</url-pattern> </servlet-mapping>                 

    将这部分复制到你项目中的web.xml中就可以通过/test.jsp请求来访问生成的servlet了。

  5. outputDir="./WEB-INF/src/"表示生成的servlet文件放在WEB-INF/src目录下,tomcat会自动生成package对应的目录,比如test.jsp生成的最终路径是WEB-INF/src/org/apache/jsp/test_jsp.java。

接着把生成的servlet编译成class,这次除了common/lib/servlet-api.jar以外我们还需要common/lib/jasper-runtime.jar加入classpath中,编译脚本参考WEB-INF/src/compile.bat。

现在可以删除test.jsp了,web.xml中已经将/test.jsp请求转发至对应的servlet处理,我们甚至不需要修改任何链接。

jspc的主要功能在于预先编译jsp发现其中的语法错误,有些公司也使用这种方式进行加密(毫无意义的做法,把jsp唯一的灵活性都浪费了)。

演示程序在99-02目录下,需要安装ant之后才可能执行run.bat将jsp转换成servlet。

在了解如何手工转换jsp之后,我们可以来讨论在数据库中保存jsp的问题了。

将jsp从数据库中提取出来,将这些数据保存成本地文件,使用jspc转换成servlet再编译为class,最后使用自定义的ClassLoader读取到jvm中执行。

难点在于生成文件要保证互不影响和自定义ClassLoader如何加载管理这些生成的class。

这个问题是典型的无用研究之一,任何想获得灵活模板功能的同学都应该亦然决然的抛弃jsp,jsp这种先解释编译再执行的机制不仅没有帮助我们提升效率,反而大大增加技术难度并降低响应效率。需要自定义模板的同志务必考虑velocity, freemarker此类模板引擎或者groovy一类脚本语言,这时使用jsp无异于给自己带上一副沉重的枷锁。

16.3. 胡乱解释一下session

会话都保存在服务器端。

每个用户打开浏览器就服务器就会给它生成一个sessionId,浏览器或者把这个sessionId放到cookie里,或者每次请求都带在url后边(自动的),然后服务器就拿到这个sessionid,在内存里翻啊翻啊,翻出对应的session来,就这么对应上的。

ServletContext和session就没多大关系了,虽然ServletContext也是在服务器端,每个web应用发布的时候,就要创建这么一块空间放置ServletContext,这个web应用中的所有的servlet, jsp, filter, listener都可以访问这块空间。你可以把他看作是一个全局变量,所有共享数据都放到里边。

No comments: