本章主要是JSP和Servlet,很多细节以前真没注意过。
1、JSP同Servlet本质是一样的。JSP最终必须编译成Servlet才能运行。
2、早期的J2EE应用,都是JSP为主组成的。但随着业务逻辑不断复杂,JSP充当过多角色就显得不合适了。如今J2EE应用中,JSP已经变成单一的表现层计数,不再包含逻辑组件和持久层逻辑。
3、构建Web应用,一个构建的应用目录应该如下:
<xxxx> - Web应用名称、可以改变
| - <a.jsp> - 这里可以存放任意多个JSP文件。
| - WEB-INF
| | - classes 存放编译好的jsp的class字节码文件
| | - lib 存放库
| | - web.xml 存放web.xml配置
一个基础的jsp页面如下:
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <html> <head> <title>First jsp page</title> </head> <body> First JSP Page !!! </body>
一个基础的web.xml只需要包含如下内容(空的web-app结点):
<?xml version="1.0" encoding="utf-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" metadata-complete="true"> </web-app>
4、web.xml文件称为配置描述符,描述了整个J2EE Web项目的配置。在Servlet 2.5及之前,web.xml都是必须的配置,且必须在WEB-INF下。
5、Servlet 3以后,这已经不再是必须的了,因为web.xml中的配置都可以通过注解的方式在Servlet中实现。
6、Servlet 3之后的另外一个关于web.xml的改变是:新增了metadata-complete属性。如果为true,则该web应用将不会使用上述注解中配置的web.xml。如果为false,注解才会生效。
7、对于Java Web服务器而言,WEB-INF是一个特殊的文件夹。不会被任何客户访问到。
8、如何通过web.xml配置首页:
<?xml version="1.0" encoding="utf-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" metadata-complete="true"> <welcome-file-list> <welcome-file>index.jsp</welcome-file> <welcome-file>a.jsp</welcome-file> </welcome-file-list> </web-app>
9、除了每个Web应用外,每个Web容器(Java Web服务器)也会包含一个web.xml。存储一些共用配置,Tomcat的在conf下,Jetty的在etc下。
10、JSP页面可以看成如下两部分:
(1) 静态部分(HTML标签组成)
(2) 动态部分(受Java程序控制的内容),以脚本的方式插入到.jsp文件中。
10、最简单的页面:
注意<%和%>内的Java代码和Java语法要求一样,必须有分号!
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <html> <head> <title>First jsp page</title> </head> <body> <% out.println(new java.util.Date()); %> </body>
11、JSP页面由系统编译成Servlet,然后由Servlet负责相应用户请求。当我们浏览上面这个页面,能显示出来后,可以在Tomcat_HOME/Catalinna/localhost/jspPrinciple/org/apache/jsp目录下找到编译好的Java文件,就是一个JSPServlet,名为xx_jsp.java,xx是你的jsp文件名,内容如下:
这其实也是一个Servlet的子类(只不过是Tomcat实现的),所有正常Servlet的init, destroy, service方法都加上了前缀jsp_
/* * Generated by the Jasper component of Apache Tomcat * Version: Apache Tomcat/7.0.27 * Generated at: 2012-06-07 13:39:33 UTC * Note: The last modified time of this file was set to * the last modified time of the source file after * generation to assist with modification tracking. */ package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; public final class d_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private javax.el.ExpressionFactory _el_expressionfactory; private org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public void _jspInit() { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); } public void _jspDestroy() { } public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null; final javax.servlet.ServletContext application; final javax.servlet.ServletConfig config; javax.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; javax.servlet.jsp.JspWriter _jspx_out = null; javax.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html; charset=UTF-8"); pageContext = _jspxFactory.getPageContext(this, request, response, "", true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write("\n"); out.write("<html>\n"); out.write("<head>\n"); out.write("\t<title>First jsp page</title>\n"); out.write("</head>\n"); out.write("<body>\n"); out.write(" "); out.println(new java.util.Date()); out.write("\n"); out.write("\n"); out.write("</body>\n"); } catch (java.lang.Throwable t) { if (!(t instanceof javax.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { out.clearBuffer(); } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } }
12、由于JSP第一次访问需要编译成Servlet,速度很慢。
13、JSP的注释(只在JSP文件可见,客户端不可见):
<%-- JSP注释 --%>
HTML的注释(在客户端也可见)
<!-- HTML注释 -->
14、JSP中可以声明成员变量、甚至函数,但注意声明时候要使用<%! %>,多的一个叹号表示声明。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <html> <head> <title>First jsp page</title> </head> <body> <%! public int count; public String info() { return "hello "+count++; } %> <% for(int i=1;i<=6;i++) { //out.println(String.format("<h%d>First jsp page.</h%d>\n", i, i)); out.println(String.format("<h%d>%s</h%d>\n", i, info(), i)); } %> </body>
15、上面声明之后,我们可以看到在编译成的Java文件中,info()和count都变成了成员变量,如下:
public final class a_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { public int count; public String info() { return "hello "+count++; } private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory(); // ...... }
16、上述jsp页面在刷新,或者新开窗口,可以发现count是连续递增的!这是因为,一个Servlet在Tomcat容器中只会实例化一份。一旦声明一个变量,是所有Request共享的。这也是大量J2EE Web应用中存在线程不安全问题的根本原因!
17、JSP表达式,通过等号的方式,可以直接输出变量、函数返回值。和PHP什么的很类似:
<%=count%> <%=info()%>
18、在JSP中,Java代码与HTML可以混搭。交叉使用,以达到循环输出的目的,如下述表格的输出。
<table bgcolor="#9999dd" border="1" width="300px"> <tr> <% for(int i=0;i<10;i++) { %> <tr> <td>循环</td><td><%=i%></td> </tr> <% } %>
要特别指出的是:这种做法严重加强了表现层和逻辑层之间的耦合,是非常幼稚的,我觉得稍微有点工程基础的人都不应该把这种设计思路拿到实际项目中去。
更好的方法应该是使用模板语言。
19、JSP中Java代码的部分和Java无异,例如我们可以在JSP中连接数据,并将结果打印出来。
注意需要将MySQL的Connector的jar包拷贝到webapp/xxx/WEB-INF/lib下,或者拷贝到Tomcat的lib目录下。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <%@ page import="java.sql.*" %> <html> <head> <title>JSP MySQL Test</title> </head> <body> <% Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/weibo", "liheyuan", "123456"); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from wb"); %> <table bgcolor="#9999dd" border="1" width="300"> <% while(rs.next()) { %> <tr> <td><%=rs.getInt(1)%></td> <td><%=rs.getString(2)%></td> <% } %> </tr> </table> </body> </html>
20、JSP有三个编译指令page、include、taglib,在JSP文件编译成Servlet时发挥作用。
21、page指令:可以控制页面编码、缓存、错误页、页面编码、import包等等。
一个页面可以包含多条page指令。contentType、language不解释了,如下:
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <html> <head> ......
当页面执行发生错误时,会调用ErrorPage指定的错误处理页面。而后者,需要声明子isErrorPage="true"。
如下:
a.jsp
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="error.jsp" %> <html> <head> <title>First jsp page</title> </head> <body> <% int a = 0; int b = 0; out.println(a/b); %> </body>
error.jsp
<%@ page contentType="text/html; charset=UTF-8" language="java" isErrorPage="true" %> <html> <head> <title>出错了!</title> </head> <body> 系统异常!! </body>
当执行上面的a.jsp时,会由于除0而自动跳转到其指定的errorPage: error.jsp。
后者error.jsp需要将isErrorPage设置为true。
22、include指令将解析被嵌入页面的代码,一起编译到当前页面,融合成一个新的页面。这叫做静态include(好怪异啊)。。。
该编译指令也会将被包含页面的编译指令也包含进来,因此要注意冲突可能导致错误。
<%@ include file="a.jsp" %>
23、taglib主要是标签库。
24、除了上面的3个编译指令,JSP还支持7个动作指令,处理指令不是在编译时起作用,通常可以替换成相应的jsp脚本。7个指令如下:
jsp:forward 页面转向
jsp:param 传递参数
jsp:include 动态引入页面
jsp:plugin 用于下载JavaBean或者Applet到客户端
jsp:useBean 创建JavaBean
jsp:setProperty 设置JavaBean实例(对象)的属性值
jsp:getProperty 读取JavaBean实例(对象)的属性值
25、jsp forward可以转发到静态HTML(只能是容器内的,不能是http外站),或者动态JSP页面,或者Servlet。注意这种转发是转发上下文和参数,浏览器中的URL是没有变化的。
如果无序参数,如下即可:
<jsp:forward page="a.jsp" />
如果需要传递参数,如下:
<jsp:forward page="e.jsp" > <jsp:param name="name" value="lhy" /> </jsp:forward>
如上面提到的,forward和字面含义不同,对用户来说,服务器端即使经过了forward,也是一次http请求。不是303那种。
26、include指令,这是动态指令,不同于前面提到的静态编译的include。
<jsp:include page="xx.jsp" flush="false"/"true" />
page是要包含的页面,flush如果为true,输出缓存移到被导入的页面中,如果为false,原页面中。
可以看一下生成的Servlet代码:
org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "e.jsp", out, false);
同前面的编译incude不同,这里只是用了一条JSP语句,而编译include指令是把目标页面每条语句都解析,编译成JSP文件写入到当前文件。
动态include适用于被包含页面经常改动的情况,因为此时无需重新编译。
另外,动态include可以附加参数:
<jsp:include page="e.jsp" > <jsp:param name="name" value="lhy" /> </jsp:include>
27、useBean动作,在JSP页面中初始化一个JavaBean。
如下,其中id是UserBean的一个实例(变量名),class决定了这个Bean的具体未知。
userBean除了创建这样一个实例,还会把这个对象放到对应作用域中,scope是该Bean存活的作用域,可以有request\page\session。
<jsp:useBean id="auser" class="pack1.UserBean" scope="request"></jsp:useBean>
setProperty动作设置上述生成的Bean的某个属性。
<jsp:setProperty name="auser" property="name" value="lhy"/>
getProperty读取上述生成的Bean的某个属性。
<jsp:getProperty name="auser" property="name" />
28、上述三个关于Bean的标签,完全可以用Java代码代替:
pack1.User user = new pack1.User(); pageContext.setAttribute("auser", user); user.setName("lhy"); out.println(user.getName());
29、plugin指令用于加载Applet,现在这个已经很古董了。
30、param前面已经用到了,包含在<jsp:include>、<jsp:forward>和<jsp:plugin>之内,提供参数。
31、