分类 Java企业应用开发 下的文章 - 小吴博客
首页
资源下载
关于
搜 索
1
通过 Tampermonkey 实现学习通全自动刷课
1,189 阅读
2
易语言下载与安装
45 阅读
3
易语言的配置
37 阅读
4
Filter 介绍
18 阅读
5
JSP 脚本中的 9 个内置对象
14 阅读
Java
Java面向对象基础
Java企业应用开发
数据结构
Spring Boot
数据库
Mysql数据库原理
网页前端
HTML/CSS/JS
Vue框架
C++
C++程序设计
计算机网络
概述
物理层
数据链路层
网络层
运输层
应用层
网络安全
互联网上的音频/视频服务
无线网络和移动网络
Linux系统管理
实战教程
其他
易语言
登录
/
注册
搜 索
标签搜索
JSP/Servlet 及相关技术详解
下载与配置
小吴
累计撰写
12
篇文章
首页
分类
Java
Java面向对象基础
Java企业应用开发
数据结构
Spring Boot
数据库
Mysql数据库原理
网页前端
HTML/CSS/JS
Vue框架
C++
C++程序设计
计算机网络
概述
物理层
数据链路层
网络层
运输层
应用层
网络安全
互联网上的音频/视频服务
无线网络和移动网络
Linux系统管理
实战教程
其他
易语言
页面
资源下载
关于
用户登录
登录
注册
找到
9
篇与
Java企业应用开发
相关的结果
2023-01-08
Filter 介绍
Filter 可认为是 Servlet 的一种“加强版”,它主要用于对用户请求进行预处理,也可以对 HttpServletResponse 进行后处理,是个典型的处理链。Filter 也可对用户请求生成响应,这一点与 Servlet 相同,但实际上很少会使用 Filter 向用户请求生成响应。使用 Filter 完整的流程是:Filter 对用户请求进行预处理,接着将请求交给 Servlet 进行处理并生成响应,最后 Filter 再对服务器响应进行后处理。 Filter 有如下几个用处。 ➢ 在 HttpServletRequest 到达 Servlet 之前,拦截客户的 HttpServletRequest。 ➢ 根据需要检查 HttpServletRequest,也可以修改 HttpServletRequest 头和数据。 ➢ 在 HttpServletResponse 到达客户端之前,拦截 HttpServletResponse。 ➢ 根据需要检查 HttpServletResponse,也可以修改 HttpServletResponse 头和数据。 Filter 有如下几个种类。 ➢ 用户授权的 Filter:Filter 负责检查用户请求,根据请求过滤用户非法请求。 ➢ 日志 Filter:详细记录某些特殊的用户请求。 ➢ 负责解码的 Filter:包括对非标准编码的请求解码。 ➢ 能改变 XML 内容的 XSLT Filter 等。 ➢ Filter 可负责拦截多个请求或响应;一个请求或响应也可被多个 Filter 拦截。 创建一个 Filter 只需两个步骤。 ① 创建 Filter 处理类。 ② web.xml 文件中配置 Filter。创建 Filter 类 创建 Filter 必须安现 javax.serviet.Filer 接口,在该接口中定义了如下三个方法。 ➢ void init(FilterConfig config):用于完成 Filter 的初始化。 ➢ void destroy():用于 Filter 销毁前,完成某些资源的回收。 ➢ void doFilter(ServletRequcst recucst,ServletResponse response,FilterChain chain):实现过滤功能,该方法就是对每个请求及响应增加的额外处理。 下面介绍一个日志 Filter,这个 Filer 负责拦截所有的用户请求,并将请求的信息记录在日志中。@WebFilter(filterName="log",urlPatterns=) public class LogFilter implements Filter{ // FilterConfig 可用于访问 Filter 的配置信息 private FilterConfig config; // 实现初始化方法 public void init(FilterConfig config){ this.config = config; } // 实现销毁方法 public void destroy(){ this.config = null; } // 执行过滤的核心方法 public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException{ // ---------下面代码用于对用户请求执行预处理-------- // 获取 Servletcontext 对象,用于记录日志 ServletContext context = this.config.getServletContext(); long before = System.currentTimeMillis(); System.out.println("开始过滤..."); // 将请求转换成 HttpServletRequest 请求 HttpServletRequest hrequest = (HttpServletRequest)request; // 输出提示信息 System.out.println("Filter 已经截获到用户的请求的地址:" + hrequest.getServletPath()); // Filter 只是链式处理,请求依然放行到目的地址 chain.doFilter(request,response); // ---------下面代码用于对服务器响应执行后处理--------- long after = System.currentTimeMillis(); // 输出提示信息 System.out.println("过滤结束"); //输出提示信息 System.out.println("请求被定位到" + hrequest.getRequestURI() + " 所花的时间为:" + (after - before)); } } 上面的程序中第一行代码实现了 doFilter()方法,实现该方法就可实现对用户请求进行预处理,也可实现对服务器响应进行后处理——它们的分界线为是否调用了 chain.doFilter(),执行该方法之前,即对用户请求进行预处理;执行该方法之后,即对服务器响应进行后处理。 在上面的请求 Filter 中,仅在日志中记录请求的 URL,对所有的请求都执行 chain.doFilter(request,reponse)方法,当 Filter 对请求过滤后,依然将请求发送到目的地址。如果需要检查权限,可以在 Filter 中根据用户请求的 HttpSession,判断用户权限是否足够。如果权限不够,直接调用重定向即可,无需调用 chain.doFilter(request,reponse) 方法。配置 Filter 前面已经提到,Filter 可以认为是 Servlet 的“增强版”,因此配置 Filter 与配置 Servlet 非常相似,都需要配置如下两个部分。 ➢ 配置 Filter 名。 ➢ 配置 Filter 拦截 URL 模式。 区别在于:Servlet 通常只配置一个 URL,而 Filter 可以同时拦截多个请求的 URL。因此,在配置 Filter 的 URL 模式时通常会使用模式字符串,使得 Filter 可以拦截多个请求。与配置 Servlet 相似的是,配置 Filter 同样有两种方式。 ➢ 在 Filter 类中通过注解进行配置。 ➢ 在 web.xml 文件中通过配置文件进行配置。 上面 Filter 类的代码使用@WebFilter 配置该 Filter 的名字为 log,它会拦截向/*发送的所有的请求。 @WebFilter 修饰一个 Filter 类,用于对 Filter 进行配置,它支持如下表所示的常用属性。属性是否必需说明asyncSupported否指定该 Filter 是否支持异步操作模式。dispatcherTypes否指定该 Filter 仅对那种 dispatcher 模式的请求进行过滤。该属性支持 ASYNC、ERROR、FORWARD、INCLUDE、REQUEST 这5个值的任意组合。默认值为同时过滤 5 种模式的请求displayName否指定该 Filter 的显示名filterName否指定该 Filter 的名称initParams否用于为该 Filter 配置参数servletNames否该属性值可指定多个 Servlet 的名称,用于指定该 Filter 仅对这几个 Servlet 执行过滤urlPatterns/value否这两个属性的作用完全相同。都指定该 Filter 所拦截的 URL 在 web.xml 文件中配置 Filter 与配置 Servlet 非常相似,需要为 Filter 指定它所过滤的 URL,并且也可以为 Filter 配置参数。 在 web.xml 文件中为该 Filter 增加如下配置片段:<!-- 定义 Filter --> <filter> <!-- Filter 的名字,相当于指定@WebFilter 的 filterName 属性 --> <filter-name>log</filter-name> <!-- Filter 的实现类 --> <filter-class>lee.LogFilter</filter-class> </filter> <!-- 定义Filter 拦截的 URL 地址--> <filter-mapping> <!-- Filter 的名字 --> <filter-name>log</filter-name> <!-- FIlter 负责拦截的 URL,相当于指定@WebFilter 的 urlPatterns 属性 --> <url-pattern>/*</url-pattern> </filter-mapping> 上面的代码用于配置该 Filter,从这些代码中可以看出配置 Filter 与配置 Servlet 非常相似,只是配置 Filter 时指定 url-pattern 为/*,即表示该 Filter 会拦截所有用户请求。该 Filter 并未对客户端请求进行额外的处理,仅仅在日志中简要记录请求的信息。 为该 Web 应用提供任意一个 JSP 页面,并通过浏览器来访问该 JSP 页面,即可在 Tomcat 的控制台看到如下图所示信息。 实际上 Filer 和 Serviet 极其相似,区别只是 Fiter 的 doFilter()方法里多了一个 FilterChain 的参数,通过该参数可以控制是否放行用户请求。在实际项目中,Filter 里 doFilter()方法里的代码就是从多个 Servlet 的 service()方法里抽取的通用代码,通过使用 Filter 可以实现更好的代码复用。 假设系统有包含多个 Servlet,这些 Servlet 都需要进行一些的通用处理:比如权限控制、记录日志等,这将导致在这些 Servlet 的 sevice 方法中有部分代码是相同的——为了解决这种代码重复的问题,可以考虑把这些通用处理提取到 Filter 中完成,这样各 Servlet 中剩下的只是特定请求相关的处理代码,而通用处理则交给 Filter 完成。下图显示了 Filter 的用途。 由于 Filter 和 Servlet 如此相似,所以 Filter 和 Servlet 具有完全相同的生命周期行为,且 Filter 也可以通过<init-param.../>元素或@WebFilter 的 initParams 属性来配置初始化参数,获取 Filter 的初始化参数则使用 FilterConfig 的 getInitParameter() 方法。 下面将定义一个较为实用的 Filter,该 Filter 对用户请求进行过滤,Filter 将通过 doFilter 方法来设置 request 编码的字符集,从而避免每个 JSP、Servlet 都需要设置;而且还会验证用户是否登录,如果用户没有登录,系统直接跳转到登录页面。 下面是该 Filter 的源代码。@WebFilter(filterName="athority",urlPatterns=,initParams={ @WebInitParam(name="encoding",value="UTF-8"), @WebInitParam(name="loginPage",value="/login.jsp"), @WebInitParam(name="proLogin",value="/proLogin.jsp"), }) public class AuthorityFilter implements Filter{ // FilterConfig 可用于访问 Filter 的配置信息 private FilterConfig config; // 实现初始化方法 public void init(FilterConfig config){ this.config = config; } // 实现销毁方法 public void destroy(){ this.config = null; } // 执行过滤的核心方法 public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException{ // 获取该 Filter 的配置参数 String encoding = config.getInitParameter("encoding"); String loginPage = config.getInitParameter("loginPage"); String proLogin = config.getInitParameter("proLogin"); // 设置 request 编码用的字符集 request.setCharacterEncoding(encoding); // ① HttpServletRequest requ = (HttpServletRequest)request; HttpSession session = requ.getSession(true); // 获取客户请求的页面 String requestPath = requ.getServletPath(); // 如果 session 范围的 user 为 null,即表明没有登录 // 且用户请求的既不是登录页面,也不是处理登录的页面 if(session.getAttribute("user") == null && !requestPath.endsWith(loginPage) && !requestPath.endsWith(proLogin)){ // forward到登录页面 request.setAttribute("tip","您还没有登录"); request.getRequestDispatcher(loginPage).forward(request,response); } // "放行"请求 else{ chain.doFilter(request,response); } } } 上面 Filter 的 doFilter 方法开始的前三行代码用于获取 Filter 的配置参数,而程序中的粗体字代码则是此 Filter 的核心,①号代码按配置参数设置了 request 编码所用的字符集,接下来的代码判断 session 范围内是否有 user 属性——没有该属性即认为没有登录,如果既没有登录,而且请求地址也不是登录页和处理登录页,系统直接跳转到登录页面。 通过@WebFilter 的 initParams 属性可以为该 Filter 配置初始化参数,它可以接受多个@WebInitParam,每个@WebInitParam 指定一个初始化参数。 在 web.xml 文件中也使用<init-param.../>元素为该 Filter 配置参数,与配置 Servlet 初始化参数完全相同。 如果需要在 web.xml 文件中配置该 Filter,该 Filter 的配置片段如下:<!-- 定义 Filter --> <filter> <!-- Filter 的名字 --> <filter-name>authority</filter-name> <!-- Filter 的实现类 --> <filter-class>lee.AuthorityFilter</filter-class> <!-- 下面三个 init-param 元素配置了三个参数 --> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>loginPage</param-name> <param-value>/login.jsp</param-value> </init-param> <init-param> <param-name>proLogin</param-name> <param-value>/proLogin.jsp</param-value> </init-param> </filter> <!-- 定义 Filter 拦截的 URL 地址 --> <filter-mapping> <!-- Filter 的名字 --> <filter-name>authority</filter-name> <!-- Filter 负责拦截的 URL --> <url-pattern>/*</url-pattern> </filter-mapping> 上面的配置片段中为该 Filter 指定了三个配置参数,指定 loginPage 为/login.jsp,proLogin 为/proLogin.jsp,这表明,如果没有登录该应用,普通用户只能访问/login.jsp 和/proLogin.jsp 页面。只有当用户登录该应用后才可自由访问其他页面。
2023年01月08日
18 阅读
0 评论
2 点赞
2023-01-02
JSP 2 的自定义标签
在 JSP 规范的 1.1 版中增加了自定义标签库规范,自定标签库是一种非常优秀的表现层组件技术。通过使用自定义标签库,可以在简单的标签中封装复杂的功能。 为什么要使用自定义标签呢?主要是为了取代丑陋的 JSP 脚本。在 HTML 页面中插入 JSP 脚本有如下几个坏处: ➢ JSP 脚本非常丑陋,难以阅读。 ➢ JSP 脚本和 HTML 代码混杂,维护成本高。 ➢ HTML 页面中嵌入 JSP 脚本,导致美工人员难以参与开发。 出于以上三个原因,Web 开发需要一种可在页面中使用的标签,这种标签具有和 HTML 标签类似的语法,但又可以完成 JSP 脚本的功能——这种标签就是 JSP 自定义标签。 在 JSP 1.1 规范中开发自定义标签库比较复杂,JSP 2 规范简化了标签库的开发,在 JSP 2 中开发标签库只需如下几个步骤。 ① 开发自定义标签处理类。 ② 建立一个.tld 文件,每个.tld 文件对应一个标签库,每个标签库可包含多个标签 ③ 在 JSP 文件中使用自定义标签。 标签库是非常重要的技术,通常来说,初学者、普通开发人员自己开发标签库的机会很少,但如果希望成为高级程序员,或者希望开发通用框架,就需要大量开发自定义标签了。所有的 MVC 框架,如 Struts2、SpringMVC、JSF 等都提供了丰富的自定义标签。开发自定义标签类 在 JSP 页面使用一个简单的标签时,底层实际上由标签处理类提供支持,从而可以通过简单的标签来封装复杂的功能,从而使团队更好地协作开发(能让美工人员更好地参与 JSP 页面的开发)。 自定义标签类应该继承一个父类:javax.servlet.jsp.tagext.SimpleTagSupport,除此之外,JSP 自定义标签类还有如下要求。 ➢ 如果标签类包含属性,每个属性都有对应的 getter 和 setter 方法。 ➢ 重写 doTag()方法,这个方法负责生成页面内容。 下面开发一个最简单的自定义标签,该标签负责在页面上输出 HelloWorld。public class HelloWorldTag extends SimpleTagSupport{ // 重写doTag()方法,该方法为标签生成页面内容 public void doTag() throws JspException,IOException{ // 获取页面输出流,并输出字符串 getJspContext().getOut().write("Hello World" + new java.util.Date()); } } 上面这个标签处理类非常简单,它继承了 SimpleTagSupport 父类,并重写 doTag()方法,而 doTag()方法则负责输出页面内容。该标签没有属性,因此无须提供 setter 和 getter 方法。建立 TLD 文件 TLD 是 Tag Library Definition 的缩写,即标签库定义,文件的后缀是 tld,每个 TLD 文件对应一个标签库,一个标签库中可包含多个标签。TLD 文件也称为标签库定义文件。 标签库定义文件的根元素是 taglib,它可以包含多个 tag 子元素,每个 tag 子元素都定义一个标签。通常可以到 Web 容器下复制一个标签库定义文件,并在此基础上进行修改即可。例如 Tomcat 8.0,在webapps\examples\WEB-INF\jsp2 路径下包含了一个 jsp2-example-taglib.tld 文件,这就是一个TLD文件的范例。 将该文件复制到 Web 应用的 WEB-INF/路径,或 WEB-INF 的任意子路径下,并对该文件进行简单修改,修改后的 mytaglib.tld 文件代码如下。<?xml version="1.0" encoding="UTF-8"?> <taglib 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-jsptaglibrary_2_1.xsd" version="2.1"> <tlib-version>1.0</tlib-version> <short-name>mytaglib</short-name> <!-- 定义该标签库的 URI --> <uri>mytaglib</uri> <!-- 定义一个标签 --> <tag> <!-- 定义标签名 --> <name>helloWorld</name> <!-- 定义标签处理类 --> <tag-class>lee.HelloWorldTag</tag-class> <!-- 定义标签体为空 --> <body-content>empty</body-content> </tag> </taglib> 上面的标签库定义文件也是一个标准的 XML 文件,该 XML 文件的根元素是 taglib 元素,因此每次编写标签库定义文件时都直接添加该元素即可。 读者可能会发现 Tomcat 8 所带示例的 TLD 文件的根元素与上面 TLD 文件的根元素略有区别,这是由于 Tomcat 8 所带示例的 TLD 文件一直没有更新,它所用的依然是标签库 2.0 规范,而此处介绍的是标签库 2.1 规范。 taglib 下有如下三个子元素。 ➢ tlib-version:指定该标签库实现的版本,这是一个作为标识的内部版本号,对程序没有太大的作用。 ➢ short-name:该标签库的默认短名,该名称通常也没有太大的用处。 ➢ uri:这个属性非常重要,它指定该标签库的 URI,相当于指定该标签库的唯一标识。如上面代码所示,JSP 页面中使用标签库时就是根据该 URI 属性来定位标签库的。 除此之外,taglib 元素下可以包含多个 tag 元素,每个 tag 元素定义一个标签,tag 元素下允许出现如下常用子元素。 ➢ name:该标签的名称,这个子元素很重要,JSP 页面中就是根据该名称来使用此标签的。 ➢ tag-class:指定标签的处理类,毋庸置疑,这个子元素非常重要,它指定了标签由哪个标签处理类来处理。 ➢ body-content:这个子元素也很重要,它指定标签体内容。该子元素的值可以是如下几个。 ⚪ tagdependent:指定标签处理类自己负责处理标签体。 ⚪ empty:指定该标签只能作为空标签使用。 ⚪ scriptless:指定该标签的标签体可以是静态 HTML 元素、表达式语言,但不允许出现 JSP 脚本。 ⚪ JSP:指定该标签的标签体可以使用 JSP 脚本。 ➢ dynamic-attributes:指定该标签是否支持动态属性。只有当定义动态属性标签时才需要该子元素。 因为 JSP 2 规范不再推荐使用 JSP 脚本,所以 JSP 2 自定义标签的标签体中不能包含 JSP 脚本。所以,实际上 body-content 元素的值不可以是 JSP。 定义了上面的标签库定义文件后,将标签库文件放在 Web 应用的 WEB-INF 路径或任意子路径下,Java Web 规范会自动加载该文件,则该文件定义的标签库也将生效。使用标签库 在 JSP 页面中确定指定的标签需要两点。 ➢ 标签库 URI:确定使用哪个标签库。 ➢ 标签名:确定使用哪个标签。 使用标签库分成以下两个步骤。 ① 导入标签库:使用 taglib 编译指令导入标签库,就是将标签库和指定前缀关联起来。 ② 使用标签:在 JSP 页面中使用自定义标签。 taglib 的语法格式如下:<%@ taglib uri="tagliburi" prefix="tagprefix" %> 其中 uri 属性指定标签库的 URI。这个 URI 可以确定一个标签库。而 prefix 属性指定标签库前缀,即所有使用该前缀的标签将由此标签库处理。 使用标签的语法格式如下:<tagPrefix:tagName tagAttribute="tagValue" ...> <tagBody/> </tagPrefix:tagName> 如果该标签没有标签体,则可以使用如下语法格式: <tagPrefix:tagName tagAttribute="tagValue".../> 上面使用标签的语法里都包含了设置属性值,前面介绍的 HelloWorldTag 标签没有任何属性,所以使用该标签只需用<mytag:helloWorld/>即可。其中 mytag 是 taglib 指令为标签库指定的前缀,而 helloWorld 是标签名。 下面是使用 helloWorld 标签的 JSP 页面代码。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!-- 导入标签库,指定 mytag 前缀的标签,由 UPI 为 mytaglib 的标签库处理 --> <%@ taglib uri="mytaglib" prefix="mytag" %> <!DOCTYPE html> <html> <head> <title>自定义标签示范</title> </head> <body bgcolor="#ffffc0"> <h2>下面显示的是自定义标签中的内容</h2> <!-- 使用标签,其中 mytag 是标签前缀,根据 taglib 的编译指令, mytag 前缀将由 UPI 为 mytaglib 的标签库处理 --> <mytag:helloWorld/><br/> </body> </html> 以上页面中第二行代码指定了 URI 为 mytaglib 标签库的前缀为 mytag,倒数第三行代码表明使用 mytag 前缀对应标签库里的 helloWorld 标签。浏览该页面将看到如下图所示的效果。带属性的标签 前面的简单标签既没有属性,也没有标签体,用法、功能都比较简单。实际上还有如下两种常用示签。 ➢ 带属性的标签。 ➢ 带标签体的标签。 正如前面介绍的,带属性标签必须为每个属性提供对应的 setter 和 getter 方法。带属性标签的配置方法与简单标签也略有差别,下面介绍一个带属性标签的示例。public class QueryTag extends SimpleTagSupport { // 定义成员变量来代表标签的属性 private String driver; private String url; private String user; private String pass; private String sql; // 执行数据库访问的对象 private Connection conn = null; private Statement stmt = null; private ResultSet rs = null; private ResultSetMetaData rsmd = null; // 各成员变量的 setter和getter 方法 public void setDriver(String driver) { this.driver = driver; } public void setUrl(String url) { this.url = url; } public void setUser(String user) { this.user = user; } public void setPass(String pass) { this.pass = pass; } public void setSql(String sql) { this.sql = sql; } public String getDriver() { return (this.driver); } public String getUrl() { return (this.url); } public String getUser() { return (this.user); } public String getPass() { return (this.pass); } public String getSql() { return (this.sql); } public void doTag() throws JspException, IOException { try { // 注册驱动 Class.forName(driver); // 获取数据库连接 conn = DriverManager.getConnection(url, user, pass); // 创建 statement 对象 stmt = conn.createStatement(); // 执行查询 rs = stmt.executeQuery(sql); rsmd = rs.getMetaData(); // 获取列数目 int columnCount = rsmd.getColumnCount(); // 获取页面输出流 Writer out = getJspContext().getOut(); // 在页面输出表格 out.write("<table border='1' bgColor='#9999cc' width='400'>"); // 遍历结果集 while (rs.next()) { out.write("<tr>"); // 逐列输出查询到的数据 for (int i = 1; i <= columnCount; i++) { out.write("<td>"); out.write(rs.getString(i)); out.write("</td>"); } out.write("</tr>"); } } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); throw new JspException("自定义标签错误" + cnfe.getMessage()); } catch (SQLException ex) { ex.printStackTrace(); throw new JspException("自定义标签错误" + ex.getMessage()); } finally { // 关闭结果集 try { if (rs != null) rs.close(); if (stmt != null) stmt.close(); if (conn != null) conn.close(); } catch (SQLException sgle) { sgle.printStackTrace(); } } } } 上面这个标签稍微复杂一点,它包含了 5 个属性,如程序中粗体字代码所示(为标签处理类定义员变量即可代表标签的属性),程序需要为这5个属性提供 setter 和 getter 方法。 该标签输出的内容依然由 doTag()方法决定,该方法会根据 SQL 语句查询数据库,并将查询结果显示在当前页面中。 对于有属性的标签,需要为<tag.../>元素增加<attribute.../>子元素,每个 attribute 子元素定义一个标签属性。<attribute.../>子元素通常还需要指定如下几个子元素。 ➢ name:设置属性名,子元素的值是字符串内容。 ➢ required:设置该属性是否为必需属性,该子元素的值是 true 或 false。 ➢ fragment:设置该属性是否支持 JSP 脚本、表达式等动态内容,子元素的值是 true 或 false。 为了配置上面的 QueryTag 标签,需要在 mytaglib.tld 文件中增加如下配置片段。<!-- 定义第二个标签 --> <tag> <!-- 定义标签名 --> <name>query</name> <!-- 定义标签处理类 --> <tag-class>lee.QueryTag</tag-class> <!-- 定义标签体为空 --> <body-content>empty</body-content> <!--配置标签属性:driver --> <attribute> <name>driver</name> <required>true</required> <fragment>true</fragment> </attribute> <!-- 配置标签属性:url --> <attribute> <name>url</name> <required>true</required> <fragment>true</fragment> </attribute> <!-- 配置标签属性:user --> <attribute> <name>user</name> <required>true</required> <fragment>true</fragment> </attribute> <!-- 配置标签属性:pass --> <attribute> <name>pass</name> <required>true</required> <fragment>true</fragment> </attribute> <!-- 配置标签属性:sql --> <attribute> <name>sql</name> <required>true</required> <fragment>true</fragment> </attribute> </tag> 上面代码分别为该标签配置了 driver、url、user、pass 和 sql 五个属性,并指定这 5 个属性都是必需属性,而且属性值支持动态内容。 配置完毕后,就可在页面中使用标签了,先导入标签库,然后使用标签。使用标签的JSP 页面片段如下。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!-- 导入标签库,指定 mytag 前缀的标签,由 UPI 为 mytaglib 的标签库处理 --> <%@ taglib uri="mytaglib" prefix="mytag" %> <!DOCTYPE html> <html> <head> <title>自定义标签示范</title> </head> <body bgcolor="#ffffc0"> <h2>下面显示的是自定义标签中的内容</h2> <!-- 使用标签,其中 mytag 是标签前缀,根据 taglib 的编译指令, mytag 前缀将由 UPI 为 mytaglib 的标签库处理 --> <mytag:query driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/javaee" user="root" pass="root" sql="select * from news_inf"/><br/> </body> </html> 在浏览器浏览该页面,效果如下图所示。 在 JSP 页面中只需要使用简单的标签,即可完成“复杂”的功能:执行数据库查询,并将查询结果在页面上以表格形式显示。这也正是自定义标签库的目的——以简单的标签,隐藏复杂的逻辑。 当然,并不推荐在标签处理类中访问数据库,因为标签库是表现层组件,它不应该包含任何业务逻辑实现代码,更不应该执行数据库访问,它只应该负责显示逻辑。 JSTL是 Sun 提供的一套标签库,这套标签库的功能非常强大。另外,DisplayTag 是 Apache 组织下的一套开源标签库,主要用于生成页面并显示效果。带标签体的标签 带标签体的标签,可以在标签内嵌入其他内容(包括静态的 HTML 内容和动态的 JSP 内容),通常用于完成一些逻辑运算,例如判断和循环等。下面以一个迭代器标签为示例,介绍带标签体标签的开发过程。 一样先定义一个标签处理类,该标签处理类的代码如下。public class IteratorTag extends SimpleTagSupport{ // 标签属性,用于指定需要被迭代的集合 private String collection; // 标签属性,指定迭代集合元素,为集合元素指定的名称 private String item; // collection 和 item 的 setter 和 getter 方法 public void setCollection(String collection) { this.collection = collection; } public void setItem(String item) { this.item = item; } public String getCollection() { return (this.collection); } public String getItem() { return (this.item); } // 标签的处理方法,标签处理类只需要重写doTag()方法 public void doTag() throws JspException,IOException{ // 从 page scope 中获取名为 collection 的集合 Collection itemList = (Collection)getJspContext().getAttribute(collection); // 遍历集合 for(Object s : itemList){ // 将集合的元素设置到 page 范围内 getJspContext().setAttribute(item, s); // 输出标签体 getJspBody().invoke(null); } } } 上面的标签处理类与前面的处理类并没有太大的不同,该处理类包含两个成员变量(代表标签的属性),并为这两个成员量提供了 setter 和 getter 方法。标签处理类的 doTag()方法首先从 page 范围内获取了指定名称的 Collection 对象,然后遍历 Collection 对象的元素,每次遍历都调用了 getJspBody()方法,如程序中最后一行代码所示,该方法返回该标签所包含的标签体:JspFragmet 对象,执行该对象的 invoke()方法,即可输出标签体内容。该标签的作用是:遍历指定集合,每遍历一个集合元素,即输出标签体一次。 因为该标签的标签体不为空,配置该标签时指定 body-content 为 scriptless,该标签的配置代码片段如下。<!-- 定义第三个标签 --> <tag> <!-- 定义标签名 --> <name>iterator</name> <!-- 定义标签处理类 --> <tag-class>lee.IteratorTag</tag-class> <!-- 定义标签体不允许出现 JSP 脚本 --> <body-content>scriptless</body-content> <!-- 配置标签属性:collection --> <attribute> <name>collection</name> <required>true</required> <fragment>true</fragment> </attribute> <!-- 配置标签属性:item --> <attribute> <name>item</name> <required>true</required> <fragment>true</fragment> </attribute> </tag> 上面的配置片段中<body-content.../>指定该标签的标签体可以是静态 HTML 内容,也可以是表达式语言,但不允许出现 JSP 脚本。 为了测试在 JSP 页面中使用该标签的效果,下面先将一个 List 对象设置成 page 范围的属性,然后使用该标签来迭代输出 List 集合的全部元素。 JSP 页面中使用该标签的代码片段如下。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <%@ page import="java.util.*" %> <!-- 导入标签库,指定 mytag 前缀的标签,由 UPI 为 mytaglib 的标签库处理 --> <%@ taglib uri="mytaglib" prefix="mytag" %> <!DOCTYPE html> <html> <head> <title>带标签体的标签-迭代器标签</title> </head> <body> <h2>带标签体的标签-迭代器标签</h2> <% // 创建一个 List 对象 List<String> a = new ArrayList<String>(); a.add("小吴博客"); a.add("xiaowu007.com"); // 将 List 对象放入 page 范围内 pageContext.setAttribute("a", a); %> <table border="1" bgcolor="#aaaadd" width="300"> <!-- 使用迭代器标签,对 a 集合进行迭代 --> <mytag:iterator item="item" collection="a"> <tr> <td>$</td> </tr> </mytag:iterator> </table> </body> </html> 上面的页面代码中<mytag:iterator.../>标签即可实现通过 iterator 标签来遍历指定集合,浏览该页面即可看到如下图所示的界面。 上图显示了使用 iterator 标签遍历集合元素的效果,从 iteratorTag.jsp 页面的代码来看,使用 iterator 标签遍历集合元素比使用 JSP 脚本遍历集合元素要优雅得多,这就是自定义标签的魅力。 实际上 JSTL 标签库提供了一套功能非常强大的标签,例如普通的输出标签,就像刚刚介绍的选代器标签,还有用于分支判断的标签等,JSTL 都有非常完善的实现。 可能有读者感到疑惑:这个 JSP 页面自己先把多个字符串添加到 ArrayList,然后再使用这个 iterator 标签进行迭代输出,好像意义不是很大啊。实际上这个标签的用处非常大,在严格的 MVC 规范下,JSP 页面只负责显示数据——而数据通常由控制器(Servlet)放入 request 范围内,而 JSP 页面就通过 iterator 标签迭代输出 request 范围内的数据。以页面片段作为属性的标签 JSP 2 规范的自定义标签还允许直接将一段“页面片段”作为属性,这种方式给自定义标签提供了更大的灵活性。 以“页面片段”为属性的标签与普通标签区别并不大,只有两个简单的改变。 ➢ 标签处理类中定义类型为 JspFragment 的属性,该属性代表了“页面片段”。 ➢ 使用标签库时,通过<jsp:attribute.../>动作指令为标签的属性指定值。 下面的程序定义了一个标签处理类,该标签处理类中定义了一个 JspFragment 类型的属性,即表明该标签允许使用“页面片段”类型的属性。public class FragmentTag extends SimpleTagSupport{ private JspFragment fragment; // fragment 的 setter 和 getter 方法 public void setFragment(JspFragment fragment){ this.fragment = fragment; } public JspFragment getFragment(){ return this.fragment; } @Override public void doTag() throws JspException,IOException{ JspWriter out = getJspContext().getOut(); out.println("<div style='padding:10px;border:lpx solid black;" + ";border-radius:20px'>"); out.println("<h3>下面是动态传入的 JSP 片段</h3>"); // 调用、输出“页面片段” fragment.invoke(null); out.println("</div>"); } } 上面的程序中定义了 fragment 成员变量,该成员变量代表了使用该标签时的“页面片段”,配置该标签与配置普通标签并无任何区别,增加如下配置片段即可。<tag> <!-- 定义标签名 --> <name>fragment</name> <!-- 定义标签处理类 --> <tag-class>lee.FragmentTag</tag-class> <!-- 指定该标签不支持标签体 --> <body-content>empty</body-content> <!-- 定义标签属性: fragment --> <attribute> <name>fragment</name> <required>true</required> <fragment>true</fragment> </attribute> </tag> 从上面标签库的配置片段来看,这个自定义标签并没有任何特别之处,就是一个普通的带属性标签,该标签的标签体为空。 由于该标签需要一个 fragment 属性,该属性的类型为 JspFragment,因此使用该标签时需要使用<jsp:attribute.../>动作指令来设置属性值,如以下代码片段所示。<h2>下面显示的是自定义标签中的内容</h2> <mytag:fragment> <jsp:attribute name="fragment"> <%-- 使用 jsp:attribute 标签传入 fragment 参数 (该注释不能放在 fragment 内) --%> <!-- 下面是动态的 JSP 页面片段 --> <mytag:helloWorld/> </jsp:attribute> </mytag:fragment> <br/> <mytag:fragment> <jsp:attribute name="fragment"> <!-- 下面是动态的 JSP 页面片段 --> $ </jsp:attribute> </mytag:fragment> 由于程序指定了 fragment 标签的标签体为 empty,因此程序中 fragment 开始标签和 fragment 结束标签之间只能使用<jsp:attribute.../>子元素,不允许出现其他内容,甚至连注释都不允许。 上面的代码片段中<jsp:attribute.../>用于为标签的 fragment 属性赋值,第一个例子使用了另一个简单标签来生成页面片段;第二个例子使用了 JSP 2 的 EL 来生成页面片段;在浏览器中浏览该页面,将看到如下图所示的效果。动态属性的标签 前面介绍带属性标签时,那些标签的属性个数是确定的,属性名也是确定的,绝大部分情况下这种带属性的标签能处理得很好,但在某些特殊情况下,需要传入自定义标签的属性个数是不确定的,属性名也不确定,这就需要借助于动态属性的标签了。 动态属性标签比普通标签多了如下两个额外要求。 ➢ 标签处理类还需要实现 DynamicAttributes 接口。 ➢ 配置标签时通过<dynamic-attributes.../>子元素指定该标签支持动态属性。 下面是一个动态属性标签的处理类。public class DynaAttributesTag extends SimpleTagSupport implements DynamicAttributes{ // 保存每个属性名的集合 private ArrayList<String> keys = new ArrayList<String>(); // 保存每个属性值的集合 private ArrayList<Object> values = new ArrayList<Object>(); @Override public void doTag() throws JspException, IOException { JspWriter out = getJspContext().getOut(); // 此处只是简单地输出每个属性 out.println("<ol>"); for(int i = 0;i < keys.size(); i++ ){ String key = keys.get(i); Object value = values.get(i); out.println("<li>" + key + " = " + value + "</li>"); } out.println("</ol>"); } @Override public void setDynamicAttribute(String uri, String localName, Object value) throws JspException { // 添加属性名 keys.add(localName); // 添加属性值 values.add(value); } } 上面的标签处理类实现了 DynaAttributesTag 接口,就是动态属性标签处理类必须实现的接口,实现该接口必须实现 setDynaAttribute()方法,该方法用于为该标签处理类动态地添加属性名和属性值。标签处理类使用 ArrayList String 类型的 keys 属性来保存标签的所有属性名,使用 ArrayList Object 类型的 values 属性来保存标签的所有属性值。 配置该标签时需要额外地指定<dynamic-attributes.../>子元素,表明该标签是带动态属性的标签。下面是该标签的配置片段。<!-- 定义接受动态属性的标签 --> <tag> <name>dynaAttr</name> <tag-class>lee.DynaAttributesTag</tag-class> <body-content>empty</body-content> <!-- 指定支持动态属性 --> <dynamic-attributes>true</dynamic-attributes> </tag> 上面的配置片段指定该标签支持动态属性。 一旦定义了动态属性的标签,接下来在页面中使用该标签时将十分灵活,完全可以为该标签设置任意的属性,如以下页面代码所示。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!-- 导入标签库,指定 mytag 前缀的标签,由 UPI 为 mytaglib 的标签库处理 --> <%@ taglib uri="mytaglib" prefix="mytag" %> <!DOCTYPE html> <html> <head> <title>自定义标签示范</title> </head> <body bgcolor="#ffffc0"> <h2>下面显示的是自定义标签中的内容</h2> <h4>指定两个属性</h4> <mytag:dynaAttr name="xiaowu" url="xiaowu123.com"/><br/> <h4>指定四个属性</h4> <mytag:dynaAttr 网站="小吴博客" 网址="xiaowu123.com" 当前年份="2023年" 作者="小吴"/><br/> </body> </html> 上面的页面代码中使用<mytag:dynaAttr.../>时十分灵活:可以根据需要动态地传入任意多个属性。不管传入多少个属性,这个标签都可以处理得很好,使用浏览器访问该页面将看到如下图所示的效果。
2023年01月02日
9 阅读
0 评论
2 点赞
2023-01-01
Servlet 介绍
前面已经介绍过,JSP 的本质就是 Servlet,开发者把编写好的 JSP 页面部署在 Web 容器中之后,Web 容器会将 JSP 编译成对应的 Servlet。但直接使用 Servlet 的坏处是:Servlet 的开发效率非常低,特别是当使用 Servlet 生成表现层页面时,页面中所有的 HTML 标签,都需采用 Servlet 的输出流来输出,因此极其烦琐。而且 Servlet 是标准的 Java 类,必须由程序员开发、修改,美工人员难以参与 Servlet 页面的开发。这一系列的问题,都阻碍了 Servlet 作为表现层的使用。 自 MVC 规范出现后,Servlet 的责任开始明确下来,仅仅作为控制器使用,不再需要生成页面标签,也不再作为视图层角色使用。Servlet 的开发 前面介绍的 JSP 的本质就是 Servet,Servlet 通常被称为服务器端小程序,是运行在服务器端的程序,用于处理及响应客户端的请求。 Servlet 是个特殊的 Java 类,这个 Java 类必须继承 HttpServlet。每个 Servlet 可以响应客户端的请求。Servlet 提供不同的方法用于响应客户端请求。 ➢ doGet:用于响应客户端的 GET 请求。 ➢ doPost:用于响应客户端的 POST 请求 ➢ doPut:用于响应客户端的 PUT 请求。 ➢ doDelete:用于响应客户端的 DELETE 请求 事实上,客户端的请求通常只有 GET 和 POST 两种,Servlet 为了响应这两种请求,必须重写 doGet()和 doPost()两个方法。如果 Servlet 为了响应 4 种方式的请求,则需要同时重写上面的 4 个方法。 大部分时候,Servlet 对于所有请求的响应都是完全一样的。此时,可以采用重写一个方法来代替上面的几个方法:只需重写 service() 方法即可响应客户端的所有请求。 另外,HttpServlet 还包含两个方法。 ➢ init(ServletConfig config):创建 Servlet 实例时,调用该方法的初始化 Servlet 资源。 ➢ destroy():销毁 Servlet 实例时,自动调用该方法的回收资源。 通常无须重写 init()和 destroy()两个方法,除非需要在初始化 Servlet 时,完成某些资源初始化的方法,才考虑重写 init 方法。如果需要在销毁 Servlet 之前,先完成某些资源的回收,比如关闭数据库连接等,才需要重写 destroy 方法。 不用为 Servlet 类编写构造器,如果需要对 Servlet 执行初始化操作,应将初始化操作放在 Servlet 的 init()方法中定义。如果重写了 init(ServletConfig config)方法,则应在重写该方法的第一行调用 super.init(config)。该方法将调用 HttpServlet 的 init 方法。 下面提供一个 Servlet 的示例,该 Servlet 将获取表单请求参数,并将请求参数显示给客户端。package lee; import java.io.PrintStream; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; // Servlet 必须继承 HttpServlet 类 @WebServlet(name="firstServletr",urlPatterns=) public class FirstServlet extends HttpServlet{ // 客户端的响应方法,使用该方法可以响应客户端所有类型的请求 public void service(HttpServletRequest request,HttpServletResponse response)throws ServletException,java.io.IOException{ // 设置解码方式 request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charSet=UTF-8"); // 获取 name 的请求参数值 String name = request.getParameter("name"); // 获取 gender 的请求参数值 String gender = request.getParameter("gender"); // 获取 color 的请求参数值 String[] color =request.getParameterValues("color"); // 获取 country的请求参数值 String national = request.getParameter("country"); //获取页面输出流 PrintStream out = new PrintStream(response.getOutputStream()); // 输出 HTML 页面标签 out.println("<html>"); out.println("<head>"); out.println("<title>Servlet 测试</title>"); out.println("</head>"); out.println("<body>"); // 输出请求参数的值:name out.println("您的名字:" + name + "<hr/>"); // 输出请求参数的值:gender out.println("您的性别:" + gender + "<hr/>"); // 输出请求参数的值:color out.println("您喜欢的颜色:"); for(String c : color) { out.println(c + " "); } out.println("<hr/>"); // 输出请求参数的值:national out.println("您来自的国家:" + national + "<hr/>"); out.println("</body>"); out.println("</html>"); } } 上面的 Servlet 类继承了 HttpServet 类,表明它作为一个 Servlet 使用。程序的粗体字代码定义了 service 方法来响应用户请求。对比该 Servlet 和 JSP 脚本中的 9 个内置对象中的 request1.jsp 页面,该 Servlet 和 request1.jsp 页面的效果完全相同,都通过 HttpServltRequest 获取客户端的 form 请求参数,并显示请求参数的值。 Servlet 和 JSP 的区别在于: ➢ Servlet 中没有内置对象,原来 JSP 中的内置对象都必须由程序显式创建。 ➢ 对于静态的 HTML 标签,Servlet 都必须使用页面输出流逐行输出。 这也正是前面介绍的,JSP 是 Servlet 的一种简化,使用 JSP 只需要完成程序员需要输出到客户端的内容,至于 JSP 脚本如何嵌入一个类中,由 JSP 容器完成。而 Servlet 则是个完整的 Java 类,这个类的 service()方法用于生成对客户端的响应。 普通 Servlet 类里的 service()方法的作用,完全等同于 JSP 生成 Servlet 类的_jspService()方法。因此原 JSP 页面的 JSP 脚本、静态 HTML 内容,在普通 Servlet 里都应该转换成 service()方法的代码或输出语句;原 JSP 声明中的内容,对应为在 Servlet 中定义的成员变量或成员方法。 上面 Servlet 类中代码所定义的@WebServlet 属于 Servlet 3.0 的注解,下面会详细介绍Servlet 的配置 编辑好的 Servlet 源文件并不能响应用户请求,还必须将其编译成 class 文件。将编译后的 FirstServlet.class 文件放在 WEB-INF/classes 路径下,如果 Servlet 有包,则还应该将 class 文件放在对应的包路径下(例如,本例的 FirstServlet.class 就放在 WEB-INF/classes/lee 路径下)。 如果需要直接采用 javac 命令来编译 Servlet 类,则必须将 Servlet API接口和类添加到系统的 CLASSPATH 环境变量里。也就是将 Tomcat 8 安装目录下 lib 目录中 servlet-api.jar 和 jsp-api.jar 添加到 CLASSPATH 环境变量中。 为了让 Servlet 能响应用户请求,还必须将 Servlet 配置在 Web 应用中。配置 Servlet 时,需要修改 web.xml文件。 从 Servlet 3.0 开始,配置 Servlet 有两种方式。 ➢ 在 Servlet 类中使用@WebServlet 注解进行配置。 ➢ 通过在 web.xml 文件中进行配置。 上面开发 Servlet 类时使用了@WebServlet 注解修饰该 Servlet 类,使用@WebServlet 时可指定如下表所示的常用属性。属性是否必需说明asyncSupported否指定该 Servlet 是否支持异步操作模式。displayName否指定该 Servlet 的显示名initParams否用于为该 Servlet 配置参数loadOnStartup否用于将该 Servlet 配置成 load-on-startup 的 Servletname否指定该 Servlet 的名称urlPatterns/value否这两个属性的作用完全相同。都指定该 Servlet 处理的 URL 如果打算使用注解来配置 Servlet,有两点需要指出。 ➢ 不要在 web.xml 文件的根元素(<web-app.../>)中指定 metadata-complete="true"。 ➢ 不要在 web.xml 文件中配置该 Servlet。 如果打算使用 web.xml 文件来配置该 Servlet,则需要配置如下两个部分。 ➢ 配置 Servlet 的名字:对应 web.xml 文件中的<servlet/>元素。 ➢ 配置 Servlet 的 URL:对应 web.xml 文件中的<servlet-mapping/>元素。这一步是可选的。但如果没有为 Servlet 配置 URL,则该 Servlet 不能响应用户请求。 接下来的 Servlet、Filter、Listener 等相关配置,都会同时介绍使用 web.xml 配置、使用注解配置两种方式。但实际项目中只要采用任意一种配置方式即可,不需要同时使用两种配置方式 因此,配置一个能响应客户请求的 Servlet,至少需要配置两个元素。关于上面的 FirstServlet 的配置如下。 <!-- 配置 Servlet 的名字 --> <servlet> <!-- 指定 Servlet 的名字,相当于指定@WebServlet 的 name 属性 --> <servlet-name>firstServlet</servlet-name> <!-- 指定 Servlet 的实现类--> <servlet-class>lee.FirstServlet</servlet-class> </servlet> <!-- 配置Servlet的URI --> <servlet-mapping> <!-- 指定 Servlet 的名字--> <servlet-name>firstServlet</servlet-name> <!-- 指定Servlet 映射的 URL 地址,相当于指定@WebServlet 的urlPatterns 属性--> <url-pattern>/aa</url-pattern> </servlet-mapping> 如果在 web.xml 文件中增加了如上所示的粗体字配置片段,则该 Servlet 的 URL 为/aa。如果没有在 web.xml 文件中增加上面的配置片段,那么该 Servlet 类上的@WebServlet 注解就会起作用,该 Servlet 的 URL 为/firstServlet。 将JSP 脚本中的 9 个内置对象中的 form.jsp 复制到本应用中,并对其进行简单修改,将 form 表单元素的 action 修改成 aa,在表单域中输入相应的数据,然后单击“提交”按钮,效果如下图所示。 在这种情况下,Servlet 与 JSP 的作用效果完全相同。JSP/Servlet 的生命周期 JSP 的本质就是 Servlet,开发者编写的 JSP 页面将由 Web 容器编译成对应的 Servlet,当 Servlet 在容器中运行时,其实例的创建及销股等都不是由程序员决定的,而是由 Web 容器进行控制的。 Servlet 实例有两个时机。 ➢ 客户端第一次请求某个 Servlet 时,系统创建该 Servlet 的实例:大部分的 Servlet 都是这种 Servlet。 ➢ Web 应用启动时立即创建 Servlet 实例,即 load-on-startup Servlet。 每个 Servlet 的运行都遵循如下生命周期。 (1) 创建 Servlet 实例。 (2) Web 容器调用 Servlet 的 init 方法,对 Servlet 进行初始化。 (3) Servlet 初始化后,将一直存在于容器中,用于响应客户端请求。如果客户端发送 GET 请求,容器调用 Servlet 的 doGet 方法处理并响应请求;如果客户端发送 POST 请求,容器调用 Servlet 的 doPost 方法处理并响应请求。或者统一使用 service()方法处理来响应用户请求。 (4) Web 容器决定销毁 Servlet 时,先调用 Servlet 的 destroy 方法,通常在关闭 Web 应用之时销毁 Servlet。 Servlet 的生命周期如下图所示。load-on-startup Servlet 上面已经介绍过,创建 Servlet 实例有两个时机:用户请求之时或应用启动之时。应用启动时就创建 Servlet,通常是用于某些后台服务的 Servlet,或者需要拦截很多请求的 Servlet;这种 Servlet 通常作为应用的基础 Servlet 使用,提供重要的后台服务。 配置 load-on-startup 的 Servlet 有两种方式。 ➢ 在 web.xml 文件中通过<servlet.../>元素的<load-on-startup...>子元素进行配置。 ➢ 通过@WebServlet 注解的 loadOnStartup 属性指定。 <load-on-startup../>元素或 loadOnStartup 属性都只接收一个整型值,这个整型值越小,Servlet 就越优先实例化。 下面是一个简单的 Servlet,该 Servlet 不响应用户请求,它仅仅执行计时器功能,每隔一段时间会在控制台打印出当前时间。@WebServlet(loadOnStartup=1) public class TimerServlet extends HttpServlet { public void init(ServletConfig config) throws ServletException { super.init(config); Timer t = new Timer(1000,new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println(new Date()); } }); t.start(); } } 这个 Servlet 没有提供 service()方法,这表明它不能响应用户请求,所以无须为它配置 URL 映射。由于它不能接收用户请求,所以只能在应用启动时实例化。 以上程序中@WebServlet 注解即可将该 Servlet 配置了 load-on-startup Servlet。除此之外,还可以在 web.xml 文件中增加如下配置片段。<servlet> <!-- Servlet 名 --> <servlet-name>timerServlet</servlet-name> <!-- Servlet 的实现类 --> <servlet-class>lee.TimerServlet</servlet-class> <!-- 配置应用启动时,创建 Servlet 实例,相当于指定@WebServlet 的 loadOnStartup 属性--> <load-on-startup>1</load-on-startup> </servlet> 以上配置片段中粗体字代码指定 Web 应用启动时,Web 容器将会实例化该 Servlet,且该 Servlet 不能响应用户请求,将一直作为后台服务执行:每隔 1 秒钟输出一次系统时间。访问 Servlet 的配置参数 配置 Servlet 时,还可以增加额外的配置参数。通过使用配置参数,可以实现提供更好的可移植性,避免将参数以硬编码方式写在程序代码中。 为 Servlet 配置参数有两种方式。 ➢ 通过@WebServlet 的 initParams 属性来指定。 ➢ 通过在 web.xml 文件的<servlet.../>元素中添加<init-param.../>子元素来指定。 第二种方式与为 JSP 配置初始化参数极其相似,因为 JSP 的实质就是 Servlet,而且配置 JSP 的实质就是把 JSP 当 Servlet 使用。 访问 Servlet 配置参数通过 ServletConfig 对象完成,ServletConfig 提供如下方法。 ➢ java.langString getInitParameter(java.lang.String name):用于获取初始化参数。 JSP 的内置对象 config 就是此处的 ServletConfig。 下面的 Servlet 将会连接数据库,并执行 SQL 查询,但程序并未直接给出数据库连接信息,而是将数据库连接信息放在 web.xml 文件中进行管理。@WebServlet(name="testServlet",urlPatterns=, initParams={ @WebInitParam(name="driver",value="com.mysql.jdbc.Driver"), @WebInitParam(name="url",value="jdbc:mysql://localhost:3306/javaee"), @WebInitParam(name="user",value="root"), @WebInitParam(name="pass",value="root")}) public class TestServlet extends HttpServlet{ // 重写 init 方法 public void init(ServletConfig config) throws ServletException{ // 重写该方法,应该首先调用父类的 init 方法 super.init(config); } // 响应客户端请求的方法 public void service (HttpServletRequest request,HttpServletResponse response)throws ServletException,java.io.IOException{ try{ // 获取 ServletConfig 对象 ServletConfig config = getServletConfig(); // 通过 ServletConfig 对象获取配置参数:dirver String driver = config.getInitParameter("driver"); // 通过 Servletconfig 对象获取配置参数:url String url = config.getInitParameter("url"); // 通过 ServletConfig 对象获取配置参数:user String user = config.getInitParameter("user"); // 通过 ServletConfig 对象获取配置参数:pass String pass = config.getInitParameter("pass"); // 注册驱动 Class.forName(driver); // 获取数据库连接 Connection conn = DriverManager.getConnection(url,user,pass); // 创建 Statement 对象 Statement stmt = conn.createStatement(); // 执行查询,获取 ResuletSet 对象 ResultSet rs = stmt.executeQuery("select * from news_inf"); response.setContentType("text/html;charSet=UTF-8"); // 获取页面输出流 PrintStream out= new PrintStream(response.getOutputStream()); // 输出 HTML 标签 out.println("<html>"); out.println("<head>"); out.println("<title>访问 Servlet 初始化参数测试</title>"); out.println("</head>"); out.println("<body>"); out.println("<table bgcolor=\"#9999dd\" border= \"l\"" + "width=\"480\">"); // 遍历结果集 while(rs.next()){ // 输出结果集内容 out.println("<tr>"); out.println("<td>" + rs.getString(1) + "</td>"); out.println("<td>" + rs.getString(2) + "</td>"); } out.println("</table>"); out.println("</body>"); out.println("</html>"); } catch (Exception e){ e.printStackTrace(); } } } ServletConfig 获取配置参数的方法 ServletContext 获取配置参数的方法完全一样,只是 ServletConfig 是取得当前 Servlet 的配置参数,而 ServletContext 是获取整个 Web 应用的配置参数。 以上程序中@WebServlet 中的 initParams 属性用于为该 Servlet 配置参数,initParams 属性值的每个@WebInitParam 配置一个初始化参数,每个@WebInitParam 可指定如下两个属性。 ➢ name: 指定参数名。 ➢ value:指定参数值。 类似地,在 web.xml 文件中为 Servlet 配置参数使用<init-param../>元素,该元素可以接受如下两个子元素。 ➢ param-name:指定配置参数名。 ➢ param-value:指定配置参数值。 下面是该 Servlet 在 web.xml 文件中的配置片段。 <servlet> <!-- 配置 Servlet名 --> <servlet-name>testServlet</servlet-name> <!-- 指定 Servlet 的实现类 --> <servlet-class>lee.TestServlet</servlet-class> <!-- 配置 Servlet 的初始化参数:driver --> <init-param> <param-name>driver</param-name> <param-value>com.mysql.jdbc.Driver</param-value> </init-param> <!-- 配置 Servlet 的初始化参数:url--> <init-param> <param-name>url</param-name> <param-value>jdbc:mysql://localhost:3306/javaee</param-value> </init-param> <!-- 配置 Servlet 的初始化参数:user --> <init-param> <param-name>user</param-name> <param-value>root</param-value> </init-param> <!-- 配置 Servlet 的初始化参数:pass--> <init-param> <param-name>pass</param-name> <param-value>root</param-value> </init-param> </servlet> <servlet-mapping> <!-- 确定 Servlet 名--> <servlet-name>testServlet</servlet-name> <!-- 配置 Servlet 映射的 URL --> <url-pattern>/testServlet</url-pattern> </servlet-mapping> 以上配置片段的代码配置了 4 个配置参数,Servlet 通过这 4 个配置参数就可连接数据库。在浏览器中浏览该 Servlet,可看到数据库查询成功(如果数据库的配置正确)。使用 Servlet 作为控制器 正如前面见到的,使用 Servlet 作为表现层的工作量太大,所有的 HTML 标签都需要使用页面输出流生成。因此,使用 Servlet 作为表现层有如下三个劣势。 ➢ 开发效率低,所有的 HTML 标签都需使用页面输出流完成。 ➢ 不利于团队协作开发,美工人员无法参与 Servlet 界面的开发。 ➢ 程序可维护性差,即使修改一个按钮的标题,都必须重新编辑 Java 代码,并重新编译。 在标准的 MVC 模式中,Servlet 仅作为控制器使用。Java EE 应用架构正是遵循 MVC 模式的,对于遵循 MVC 模式的 Java EE 应用而言,JSP 仅作为表现层(View)技术,其作用有两点。 ➢ 负责收集用户请求参数。 ➢ 将应用的处理结果、状态数据呈现给用户。 Servlet 则仅充当控制器(Controller)角色,它的作用类似于调度员:所有用户请求都发送给 Servlet,Servlet 调用 Model 来处理用户请求,并调用 JSP 来呈现处理结果;或者 Servlet 直接调用 JSP 将应用的状态数据呈现给用户。 Model 通常由 JavaBean 来充当,所有业务逻辑、数据访问逻辑都在 Model 中实现。实际上隐藏在 Model 下的可能还有很多丰富的组件,例如 DAO 组件、领域对象等。 下面介绍一个使用 Servlet 作为控制器的 MVC 应用,该应用演示了一个简单的登录验证。 下面是本应用的登录页面。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>new document</title> </head> <body> <!-- 输出出错提示 --> <span style="color: red;font-weight: bold"> <%if(request.getAttribute("err") != null){ out.println(request.getAttribute("err") + "<br/>"); }%> </span> 请输入用户名和密码: <!-- 登录表单,该表单提交到一个 Servlet --> <form id="login" method="post" action="login"> 用户名:<input type="text" name="username"/><br/> 密   码: <input type="password" name="pass"/><br/> <input type="submit" value="登录"/><br/> </form> </body> </html> 以上页面除了判断语句使用 JSP 脚本输出错误提示之外,该页面其实是一个简单的表单页面,用于收集用户名及密码,并将请求提交到指定 Servlet,该 Servlet 充当控制器角色。 根据严格的 MVC 规范,上面的 login.jsp 页面也不应该被客户端直接访问,客户的请求应该先发送到指定 Servlet,然后由 Servlet 将请求 forward 到该 JSP 页面。 控制器 Servlet 的代码如下。@WebServlet(name="login",urlPatterns=) public class LoginServlet extends HttpServlet{ // 响应客户端请求的方法 public void service(HttpServletRequest request,HttpServletResponse response)throws ServletException,java.io.IOException{ String errMsg = ""; // Servlet 本身并不输出响应到客户端,因此必须将请求转发到视图页面 RequestDispatcher rd; // 获取请求参数 String username = request.getParameter("username"); String pass = request.getParameter("pass"); try{ // Servlet 本身并不执行任何的业务逻辑处理,它调用 JavaBean 处理用户请求 DbDao dd = new DbDao("com.mysql.jdbc.Driver","jdbc:mysql://localhost:3306/liuyan","root","root") ; // 查询结果集 ResultSet rs = dd.query("select pass from user_inf" + " where name = ?",username) ; if (rs.next()){ // 用户名和密码匹配 if (rs.getString("pass").equals(pass)){ // 获取 session 对象 HttpSession session = request.getSession(true); // 设置 session 属性,跟踪用户会话状态 session.setAttribute("namen",username); // 获取转发对象 rd = request.getRequestDispatcher("/welcome.jsp"); // 转发请求 rd.forward(request,response); } else{ // 用户名和密码不匹配时 errMsg += "您的用户名密码不符合,请重新输入"; } } else{ // 用户名不存在时 errMsg += "您的用户名不存在,请先注册"; } } catch (Exception e){ e.printStackTrace(); } // 如果出错,转发到重新登录 if (errMsg != null && !errMsg.equals("")){ rd = request.getRequestDispatcher("/login.jsp"); request.setAttribute("err",errMsg); rd.forward(request,response); } } } 控制器负责接收客户端的请求,它既不直接对客户端输出响应,也不处理用户请求,只调用 JavaBean 来处理用户请求,如程序中代码所示;JavaBean 处理结束后,Servlet 根据处理结果,调用不同的 JSP 页面向浏览器呈现处理结果。 上面 Servlet 使用@WebServlet 注解为该 Servlet 配置了 URL 为/login,因此向/login 发送的请求将会交给该 Servlet 处理。 下面是本应用中 DbDao 的源代码。public class DbDao { private Connection conn; private String driver; private String url; private String username; private String pass; public DbDao() { } public DbDao(String driver, String url, String username, String pass) { this.driver = driver; this.url = url; this.username = username; this.pass = pass; } // 下面是各个成员属性的 setter 和 getter 方法 public void setDriver(String driver) { this.driver = driver; } public void setUrl(String url) { this.url = url; } public void setUsername(String username) { this.username = username; } public void setPass(String pass) { this.pass = pass; } public String getDriver() { return (this.driver); } public String getUrl() { return (this.url); } public String getUsername() { return (this.username); } public String getPass() { return (this.pass); } // 获取数据库连接 public Connection getConnection() throws Exception { if (conn == null) { Class.forName(this.driver); conn = DriverManager.getConnection(url, username, this.pass); } return conn; } // 插入记录 public boolean insert(String sql, Object... args) throws Exception { PreparedStatement pstmt = getConnection().prepareStatement(sql); for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } if (pstmt.executeUpdate() != 1) { return false; } return true; } // 执行查询 public ResultSet query(String sql, Object... args) throws Exception { PreparedStatement pstmt = getConnection().prepareStatement(sql); for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } return pstmt.executeQuery(); } // 执行修改 public void modify(String sql, Object... args) throws Exception { PreparedStatement pstmt = getConnection().prepareStatement(sql); for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } pstmt.executeUpdate(); pstmt.close(); } // 关闭数据库连接的方法 public void closeConn() throws Exception { if (conn != null && !conn.isClosed()) { conn.close(); } } } 上面 DbDao 负责完成查询、插入、修改等操作。从上面这个应用的结构来看,整个应用的流程非常清晰,下面是 MVC 中各个角色的对应组件。 M:Model,即模型,对应 JavaBean。 V:View,即视图,对应 JSP 页面。 C:Controller,即控制器,对应 Servlet。 本应用需要底层数据库的支持,请读者自行创建对应的数据库。
2023年01月01日
9 阅读
0 评论
2 点赞
2022-12-31
JSP 脚本中的 9 个内置对象
JSP 脚本中包含9个内置对象,这 9 个内置对象都是 Servlet API 接口的实例,只是 JSP 规范对它们进行了默认初始化(由 JSP 页面对应 Servlet 的_jspService()方法来创建这些实例)。也就是说,它们已经是对象,可以直接使用。9个内置对象依次如下。 ➢ application:javax.servlet.ServletContext 的实例,该实例代表 JSP 所属的 Web 应用本身,可用于 JSP 页面,或者在 Servlet 之间交换信息。常用的方法有 getAttribute(String attName)、setAttribute(String attName,String attValue)和 getInitParameter(String paramName)等。 ➢ config:javax.servlet.ServletConfig 的实例,该实例代表该 JSP 的配置信息。常用的方法有 getInitParameter(String paramName)和 getInitParameternames()等方法。事实上,JSP 页面通常无须配置,也就不存在配置信息。因此,该对象更多地在 Servlet 中有效。 ➢ exception: java.lang.Throwable 的实例,该实例代表其他页面中的异常和错误。只有当页面是错误处理页面,即编译指令 page 的 isErrorPage 属性为 true 时,该对象才可以使用。常用的方法 getMessage()和 printStackTrace()等。 ➢ out:javax.servlet.jsp.JspWriter 的实例,该实例代表 JSP 页面的输出流,用于输出内容,形成 HTML 页面。 ➢ page:代表该页面本身,通常没有太大用处。也就是 Servlet 中的 this,其类型就是生成的 Servlet 类,能用 page 的地方就可用 this。 ➢ pageContext:javax.servlet.jsp.PageContext 的实例,该对象代表该 JSP 页面上下文,使用该对象可以访问页面中的共享数据。常用的方法有 getServletContext())和 getServletConfig()等。 ➢ request: javax.servlet.http.HttpServletRequest 的实例,该对象封装了一次请求,客户端的请求参数都被封装在该对象里。这是一个常用的对象,获取客户端请求参数必须使用该对象。常用的方法有 getParameter(String paramName)、getParameterValues(String paramName)、setAttribute(String attrName,Object attrValue)、getAttribute(String attrName)和 setCharacterEncoding(String env)等。 ➢ response: javax.servlet.http.HttpServletResponse 的实例,代表服务器对客户端的响应。通常很少使用该对象直接响应,而是使用 out 对象,除非需要生成非字符响应。而 response 对象常用于重定向,常用的方法有 getOutputStream()、sendRedirect(java.lang.String location)等。 ➢ session:javax.servlet.http.HtpSession 的实例,该对象代表一次会话。当客户端浏览器与站点建立连接时,会话开始;当客户端关闭浏览器时,会话结束。常用的方法有:getAttribute(String attrName)、setAttribute(String attrName,Object attrValue)等。 进入 Tomcat 的 work\Catalina\localhost\jspPrinciple\org\apache\jsp 路径下,打开任意一个 JSP 页面对应生成的 Servlet 类文件,看到如下代码片段public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { ··· // 用于响应用户请求的方法 public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { final java.lang.String _jspx_method = request.getMethod(); if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) { response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs only permit GET POST or HEAD"); return; } 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(); ··· } } } 几乎所有的 JSP 页面编译后 Servlet 类都有如上所示的结构,上面 Servlet 类的代码表明:request、response 两个对象是_jspService()方法的形参,当 Tomcat 调用该方法时会初始化这两个对象。而 page、pageContext、application、config、session、out 都是_jspService()方法的局部变量,由该方法完成初始化。 通过上面的代码不难发现 JSP 内置对象的实质:它们要么是_jspService()方法的形参,要么是_jspService()方法的局部变量,所以可以直接在 JSP 脚本(脚本将对应于 Servlet 的_jspService()方法部分)中调用这些对象,无须创建它们。 由于 JSP 内置对象都是在_jspService()方法中完成初始化的,因此只能在 JSP 脚本、JSP 输出表达式中使用这些内置对象。千万不要在 JSP 声明中使用它们!否则,系统将提示找不到这些变量。当编写 JSP 页面时,一定不要仅停留在 JSP 页面本身来看问题,这样可能导致许多误解,导致无法理解 JSP 的运行方式。 细心的读者可能已经发现了:上面的代码中并没有 exception 内置对象,这与前面介绍的正好相符:只有当页面的 page 指令的 isErrorPage 属性为 true 时,才可使用 exception 对象。也就是说,只有异常处理页面对应 Servlet 时才会初始化 exception 对象。application 对象 在介绍 application 对象之前,先简单介绍一些 Web 服务器的实现原理。虽然绝大部分读者都不需要、甚至不曾想过自己开发 Web 服务器,但了解一些 Web 服务器的运行原理,对于更好地掌握 JSP 知识将有很大的帮助。 虽然常把基于 Web 应用称为 B/S (Browser/Server) 架构的应用,但其实 Web 应用一样是 C/S (Client/Server) 结构的应用,只是这种应用的服务器是 Web 服务器,而客户端是浏览器。 现在抛开 Web 应用直接看 Web 服务器和浏览器,对于大部分浏览器而言,它通常负责完成三件事情。 (1)向远程服务器发送请求。 (2)读取远程服务器返回的字符串数据。 (3)负责根据字符串数据渲染出一个丰富多彩的页面 实际上,浏览器是一个非常复杂的网络通信程序,它除了可以向服务器发送请求、读取网络数据之外,最大的技术难点在于将 HTML 文本渲染成页面,建立 HTML 页面的 DOM 模型,支持 JavaScript 脚本程序等。通常浏览器有 Internet Explorer、FireFox、Opera、Safari 等,至于其他如 MyIE、傲游等浏览器可能只是对它们进行了简单的包装。 Web 服务器则负责接收客户端请求,每当接收到客户端连接请求之后,Web 服务器应该使用单独的线程为该客户端提供服务:接收请求数据、送回响应数据。下图显示了 Web 服务器的运行机制。 如上图所示的应用架构总是先由客户端发送请求,服务器接收到请求后送回响应的数据,所以也将这种架构称做“请求/响应”架构。根据如上图所示的机制进行归纳,对于每次客户端请求而言,Web 服务器大致需要完成如下几个步骤。 ① 启动单独的线程。 ② 使用 I/O 流读取用户请求的二进制流数据。 ③ 从请求数据中解析参数。 ④ 处理用户请求。 ⑤ 生成响应数据 ⑥ 使用 IO 流向客户端发送请求数据。 最新版的 Tomcat 已经不需要对每个用户请求都启用单独的线程、使用普通 I/O 读取用户请求的数据,最新的 Tomcat 使用的是异步 IO,具有更高的性能。 在上面 6 个步骤中,第 1、2 和 6 步是通用的,可以由 Web 服务器来完成,但第 3、4 和 5 步则存在差异:因为不同请求里包含的请求参数不同,处理用户请求的方式也不同,所生成的响应自然也不同。那么 Web 服务器到底如何执行第 3、4 和 5 步呢? 实际上,Web 服务器会调用 Servlet 的_jspService()方法来完成第 3、4 和 5 步,编写 JSP 页面时,页面里的静态内容、JSP 脚本都会转换成_jspService()方法的执行代码,这些执行代码负责完成解析参数、处理请求、生成响应等业务功能,而 Web 服务器则负责完成多线程、网络通信等底层功能。 Web 服务器在执行了第 3 步解析到用户的请求参数之后,将需要通过这些请求参数来创建 HttpServletRequest、HttpServletResponse 等对象,作为调用_jspService()方法的参数,实际上一个 Web 服务器必须为 Servlet API 中绝大部分接口提供实现类。 从上面介绍可以看出,Web 应用里的 JSP 页面、Servlet 等程序都将由 Web 服务器来调用,JSP、Servlet 之间通常不会相互调用,这就产生了一个问题:JSP、Servlet 之间如何交换数据? 为了解决这个问题,几乎所有 Web 服务器(包括 Java、ASP、PHP、Ruby 等)都会提供4个类似 Map 的结构,分别是 application、session、request、page,并允许 JSP、Servlet 将数据放入这 4 个 Map 的结构中,并允许从这 4 个 Map 结构中取出数据。这4个 Map 结构的区别是范围不同。 ➢ application:对于整个 Web 应用有效,一旦 JSP、Servlet 将数据放入 application 中,该数据将可以被该应用下其他所有的 JSP、Servlet 访问。 ➢ session:仅对一次会话有效,一旦 JSP、Servlet 将数据放入 session 中,该数据将可以被本次会话的其他所有的 JSP、Servlet 访问。 ➢ request:仅对本次请求有效,一旦 JSP、Servlet 将数据放入 request 中,该数据将可以被该次请求的其他 JSP、Servlet 访问。 ➢ page: 仅对当前页面有效,一旦 JSP、Servlet 将数据放入 page 中,该数据只可以被当前页面的 JSP 脚本、声明部分访问。 就像现实生活中有两个人,他们的钱需要相互交换,但他们两个人又不能相互接触,那么只能让 A 把钱存入银行,而 B 从银行去取钱。因此可以把 application、session、request 和 page 理解为类似银行的角色。 把数据放入 application、session、request 或 page 之后,就相当于扩大了该数据的作用范围,所以认为 application、session、request 和 page 中的数据分别处于 application、session、request 和 page 范围之内。 JSP 中的 application、session、request 和 pageContext 4 个内置对象分别用于操作 application、session、request 和 page 范围中的数据。 application 对象代表 Web 应用本身,因此使用 application 来操作 Web 应用相关数据。application 对象通常有如下两个作用。 ➢ 在整个 Web 应用的多个 JSP、Servlet 之间共享数据。 ➢ 访问 Web 应用的配置参数。 1.让多个 JSP、Servlet 共享数据 application 通过 setAttribute(String attrName,Object value)方法将一个值设置成 application 的 atrName 属性,该属性的值对整个 Web 应用有效,因此该 Web 应用的每个 JSP 页面或 Servlet 都可以访问该属性访问属性的方法为 getAttribute(StringattrName)。 看下面的页面,该页面仅仅声明了一个整型变量,每次刷新该页面时,该变量值加 1,然后将该变量的值放入 application 内。下面是页面的代码。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%> <!DOCTYPE html> <html> <head> <title> application 测试 </title> </head> <body> <!-- JSP 声明 --> <%! int i; %> <!-- 将 i 值自加后放入 application 的变量内 --> <% application.setAttribute("counter",String.valueOf(++i)); %> <!-- 输出 i 值 --> <%=i%> </body> </html> 以上页面的代码实现了每次刷新该页面时,变量i都先自加,并被设置为 application 的 counter 属性的值,即每次 application 中的 counter 属性值都会加1。 再看下面的 JSP 页面,该页面可以直接访问到 application 的 counter 属性值。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%> <!DOCTYPE html> <html> <head> <title> application 测试 </title> </head> <body> <%=application.getAttribute("counter")%> </body> </html> 以上页面的代码直接输出 application 的 counter 属性值。虽然这个页面和 put-aptication.jsp 没有任何关系,但它一样可以访问到 application 的属性,因为 application 的属性对于整个 Web 应用的 JSP、Servlet 都是共享的。 在浏览器的地址栏中访间第一个 put-application.jsp 页面,经多次刷新后,看到如下图所示的页面。 访问 get-application.jsp 页面,也可看到类似于上图所示的效果,因为 get-application.jsp 页面可以访问 application 的 counter 属性值。 application 不仅可以用于两个 JSP 页面之间共享数据,还可以用于 Servlet 和 JSP 之间共享数据。可以把 application 理解成一个 Map 对象,任何 JSP、Servlet 都可以把某个变量放入 application 中保存,并为之指定一个属性名;而该应用里的其他 JSP、Servlet 就可以根据该属性名来得到这个变量。 下面的 Servlet 代码示范了如何在 Servlet 中访问 application 里的变量。package lee; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletContext; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(name="get-application",urlPatterns=) public class GetApplication extends HttpServlet{ public void service(HttpServletRequest request,HttpServletResponse response)throws IOException{ response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<html><head><title>"); out.println("测试 application"); out.println("</title></head><body>"); ServletContext sc = getServletConfig().getServletContext(); out.print("application 中当前的 counter 值为:"); out.println(sc.getAttribute("counter")); out.println("</body></html>"); } } 由于在 Serviet 中并没有 application 内置对象,所以上面程序显式获取了该 Web 应用的 ServletContext 实例,每个 Web 应用只有一个 ServletContext 实例,在 JSP 页面中可通过 application 内置对象访问该实例,Servlet 中则必须通过代码获取。程序访问、输出了application 中的 counter 变量。 该 Servlet 类同样需要编译成 class 文件才可使用,实际上该 Servlet 还使用了@WebServlet 注解进行部署,关于 Servlet 的用法请参看下一篇。编译 Servlet 时可能由于没有添加环境出现异常,只要将 Tomcat 8 的 lib 路径下的 jsp-api.jar、servlet-api.jar 两个文件添加到 CLASSPATH 环境变量中即可。 将 Servlet 部署在 Web 应用中,在浏览器中访问 Servlet,出现如下图所示的页面。 最后要指出的是:虽然使用 application(即 ServletContext 实例)可以方便多个JSP、Servlet 共享数据,但不要仅为了 JSP、Servlet 共享数据就将数据放入 application 中!由于 application 代表整个 Web 应用,所以通常只应该把 Web 应用的状态数据放入 application 里。 2.获得 Web 应用配置参数 application 还有一个重要用处:可用于获得 Web 应用的配置参数。看如下 JSP 页面,该页面访问数据库,但访问数据库所使用的驱动、URL、用户名及密码都在 web.xml 中给出。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <%@ page import="java.sql.*" %> <!DOCTYPE html> <html> <head> <title>application 测试</title> </head> <body> <% // 从配置参数中获取驱动 String driver = application.getInitParameter("driver"); // 从配置参数中获取数据库url String url = application.getInitParameter("url"); // 从配置参数中获取用户名 String user= application.getInitParameter("user"); // 从配置参数中获取密码 String pass = application.getInitParameter("pass"); // 注册驱动 Class.forName(driver); // 获取数据库连接 Connection conn = DriverManager.getConnection(url,user,pass); // 创建 Statement 对象 Statement stmt = conn.createStatement (); // 执行查询 ResultSet rs = stmt.executeQuery("select * from news_inf"); %> <table bgcolor="#9999dd" borders="1" width="480"> <% // 遍历结果集 while(rs.next()){ %> <tr> <td><%=rs.getString(1)%></td> <td><%=rs.getString(2)%></td> </tr> <% } %> </table> </body> </html> 上面的程序中的代码使用 application 的 getInitParameter(String paramName)来获取 Web 应用的配置参数,这些配置参数应该在 web.xml 文件中使用 context-param 元素配置,每个<context-param.../>元素配置一个参数,该元素下有如下两个子元素。 ➢ param-name:配置 Web 参数名。 ➢ param-value:配置 Web 参数值。 web.xml 文件中使用<context-param.../>元素配置的参数对整个 Web 应用有效,所以也被称为 Web 应用的配置参数。与整个 Web 应用有关的数据,应该通过 application 对象来操作。 为了给 Web 应用配置参数,应在 web.xml 文件中增加如下片段。 <!-- 配置第一个参数:driver --> <context-param> <param-name>driver</param-name> <param-value>com.mysql.jdbc.Driver</param-value> </context-param> <!-- 配置第二个参数:url --> <context-param> <param-name>url</param-name> <param-value>jdbc:mysql://localhost:3306/javaee</param-value> </context-param> <!-- 配置第三个参数:user --> <context-param> <param-name>user</param-name> <param-value>root</param-value> </context-param> <!-- 配置第四个参数:pass --> <context-param> <param-name>pass</param-name> <param-value>32147</param-value> </context-param> 在浏览器中浏览 getWebParam.jsp 页面时,可看到数据库连接、数据查询完全成功。可见,使用 application 可以访间 Web 应用的配置参数。 通过这种方式,可以将一些配置信息放在 web.xml 文件中配置,避免使用硬编码方式写在代码中,从而更好地提高程序的移植性。config 对象 config 对象代表当前 JSP 配置信息,但 JSP 页面通常无须配置,因此也就不存在配置信息,所以 JSP 页面比较少用该对象。但在 Servlet 中则用处相对较人,因为 Servet 需要在 web.xml 文件中进行配置,可以指定配置参数。关于 Servlet 的使用将在下一篇介绍。 看如下 JSP 页面代码,该 JSP 代码使用了 config 的一个方法 getServletName()。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>测试 config 内置对象</title> </head> <body> <!-- 直接输出 config 的 getServletName 的值 --> <%=config.getServletName()%> </body> </html> 上面的代码中直接输出了 config 的 getServletName()方法的返回值,所有的 JSP 页面都有相同的名字:jsp,所以代码输出为 jsp。 实际上,也可以在 web.xml 文件中配置 JSP(只是比较少用),这样就可以为 JSP 页面指定配置信息,并可为 JSP 页面另外设置一个 URL。 config 对象是 ServletConfig 的实例,该接口用于获取配置参数的方法是 getInitParameter(StringparamName)。下面的代码示范了如何在页面中使用 config 获取 JSP 配置参数。、<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>测试 config 内置对象</title> </head> <body> <!-- 输出该 JSP 名为 name 的配置参数 --> name 配置参数的值:<%=config.getInitParameter("name")%><br/> <!-- 输出该 JSP 名为 age 的配置参数 --> age 配置参数的值:<%=config.getInitParameter("age") %> </body> </html> 上面的代码中输出了 config 的 getInitParameter()方法返回值,它们分别获取 name、age 两个配置参数的值。 配置 JSP 也是在 web.xml 文件中进行的,JSP 被当成 Servlet 配置,为 Servlet 配置参数使用 init-param 元素,该元素可以接受 param-name 和 param-value 两个子元素,分别指定参数名和参数值。 在 web.xml 文件中增加如下配置片段,即可将 JSP 页面配置在 Web 应用中。<servlet> <!-- 指定 Servlet 名字 --> <servlet-name>config</servlet-name> <!-- 指定将哪个 JSP 页面配置成 Servlet --> <jsp-file>/configTest2.jsp</jsp-file> <!-- 配置名为 name 的参数,值为xiaowu007.com --> <init-param> <param-name>name</param-name> <param-value>xiaowu123.com</param-value> </init-param> <!-- 配置名为 age 的参数,值为 30 --> <init-param> <param-name>age</param-name> <param-value>30</param-value> </init-param> </servlet> <servlet-mapping> <!-- 指定将 config Servlet 配置到/config 路径 --> <servlet-name>config</servlet-name> <url-pattern>/config</url-pattern> </servlet-mapping> 上面的配置文件片段中前半部分为该 Servlet(其实是 JSP)配置了两个参数:name 和 age。上面的配置片段把 configTest2.jsp 页面配置成名为 config 的 Servlet,并将该 Servlet 映射到/config 处,这就允许通过/config 来访问该页面。在浏览器中访问/config 看到如下图所示界面。 从上图中可以看出,通过 config 可以访问到 web.xml 文件中的配置参数。实际上,也可以直接访问 configTest2.jsp 页面,在浏览器中访问该页面将看到如下图所示界面。 对比上面两张图不难看出,如果希望 JSP 页面可以获取 web.xml 配置文件中的配置信息,则必须通过为该 JSP 配置的路径来访问该页面,因为只有这样访问 JSP 页面才会让配置参数起作用。exception 对象 exception 对象是 Throwable 的实例,代表 JSP 脚本中产生的错误和异常,是 JSP 页面异常机制的一部分。 在 JSP 脚本中无须处理异常,即使该异常是 checked 异常。事实上,JSP 脚本包含的所有可能出现的异常都可交给错误处理页面处理。 看如右图所示的异常处理结构,这是典型的异常捕捉处理块。在 JSP 页面中,普通的 JSP 脚本只执行第一个部分——代码处理段,而异常处理页面负责第二个部分——异常处理段。在异常处理段中,可以看到有个异常对象,该对象就是内置对象 exception。 exception 对象仅在异常处理页面中才有效,通过前面的异常处理结构,读者可以非常清晰地看出这点。 打开普通 JSP 页面所生成的 Servlet 类,将可以发现如下代码片段public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { ··· try { // 所有的 JSP 脚本、静态 HTML 都会转换成此部分代码 response.setContentType("text/html; charset=UTF-8"); ··· out.write("</body>\r\n"); out.write("</html>"); } catch (java.lang.Throwable t) { ··· //处理该异常 if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { //释放资源 _jspxFactory.releasePageContext(_jspx_page_context); } } 从上面代码中可以看出,JSP 脚本和静态 HTML 部分都将转换成_jspService()方法里的执行性代码——这就是 JSP 脚本无须处理异常的原因:因为这些脚本已经处于 try 块中。一旦 try 块捕提到 JSP 脚本的异常,并且_jspx_page_context 不为 null,就会由该对象来处理该异常。 _jspx_page_context 对异常的处理也非常简单:如果该页面的 page 指令指定了 errorPage 属性,则将请求 forward 到 errorPage 属性指定的页面,否则使用系统页面来输出异常信息。 由于只有 JSP 脚本、输出表达式才会对应于_jspService()方法里的代码,所以这两个部分的代码无须处理 checked 异常。但 JSP 的声明部分依然需要处理 checked 异常,JSP 的异常处理机制对 JSP 声明不起作用。 在 JSP 的异常处理机制中,一个异常处理页面可以处理多个 JSP 页面脚本部分的异常。异常处理页面通过 page 指令的 errorPage 属性确定。 下面的页面再次测试了 JSP 脚本的异常机制。<!-- 通过 errorPage 属性指定异常处理页面 --> <%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="error.jsp" %> <!DOCTYPE html> <html> <head> <title> JSP 脚本的异常机制 </title> </head> <body> <% int a = 6; int c = a / 0; %> </body> </html> 以上页面的代码将抛出一个 ArithmeticEception,则 JSP 异常机制将会转发到 error.jsp 页面,error.jsp 页面代码如下。<%@ page contentType="text/html; charset=UTF-8" language="java" isErrorPage="true" %> <!DOCTYPE html> <html> <head> <title> 异常处理页面 </title> </head> <body> 异常类型是:<%=exception.getClass() %><br/> 异常信息是:<%=exception.getMessage() %> </body> </html> 以上页面 page 指令的 isErrorPage 属性被设为 true,则可以通过 exception 对象来访问上一个页面所出现的异常。在浏览器中请求 throwEx.jsp 页面,将看到如下图所示的界面。 打开 error.jsp 页面生成的 Serviet 类,在_jspService()方法中发现如下代码片段: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; // 初始化 exception 对象 java.lang.Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request); if (exception != null) { response.setStatus(javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } ··· } 从以上代码片段中可以看出,当 JSP 页面 page 指令的 isErrorPage 为 true 时,该页面就会提供 exception 内置对象。 应将异常处理页面中 page 指令的 isErrorPage 属性设置为 true。只有当 isErrorPage 属性设置为 true 时才可访问 exception 内置对象。out 对象 out 对象代表一个页面输出流,通常用于在页面上输出变量值及常量。一般在使用输出表达式的地方,都可以使用 out 对象来达到同样效果。 看下面的 JSP 页面使用 out 来执行输出。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <%@ page import="java.sql.*" %> <!DOCTYPE html> <html> <head> <title> out 测试</title> </head> <body> <% // 注册数据库驱动 Class.forName("com.mysql.jabc.Driver"); // 获取数据库连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/javaee","root","root"); // 创建 Statement 对象 Statement stmt = conn.createStatement(); // 执行查询,获取 ResultSet 对象 ResultSet rs = stmt.executeQuery("select * from news_inf"); %> <table bgcolor="#9999dd" border="1" width="400"> <% // 遍历结果集 while(rs.next()) { // 输出表格行 out.println("<tr>"); // 输出表格列 out.println("<td>"); // 输出结果集的第二列的值 out.println(rs.getString(1)); // 关闭表格列 out.println("</td>"); // 开始表格列 out.println("<td>"); // 输出结果集的第三列的值 out.println(rs.getString(2)); // 关闭表格列 out.println("</td>"); // 关闭表格行 out.println("</tr>"); } %> <table> </body> </html> 从 Java 的语法上看,上面的程序更容易理解,out 是个页面输出流,负责输出页面表格及所有内容,但使用 out 则需要编写更多代码。 所有使用 out 的地方,都可使用输出表达式来代替,而且使用输出表达式更加简洁。<%=...%>表达式的本质就是 out.println(...);。通过 out 对象的介绍,读者可以更好地理解输出表达式的原理。pageContext 对象 这个对象代表页面上下文,该对象主要用于访问 JSP 之间的共享数据。使用 pageContext 可以访问 page、request、session、application 范围的变量。 pageContext 是 PageContext 类的实例,它提供了如下两个方法来访问 page、request、sessionapplication 范围的变量。 ➢ getAttribute(String name):取得 page 范围内的 name 属性。 ➢ getAtribute(String name,int scope):取得指定范围内的 name 属性,其中 scope 可以是如下 4 个值。 ⚪ PageContext.PAGE_SCOPE:对应于 page 范围。 ⚪ PageContext.REQUEST_SCOPE:对应于 request 范围 ⚪ PageContext.SESSION_SCOPE:对应于 session 范围。 ⚪ PageContext.APPLICATION_SCOPE:对应于 application 范围。 与 getAttribute()方法相对应,PageContext 也提供了两个对应的 setAttribute()方法,用于将指定变量放入 page、request、session、application 范围内。 下面的 JSP 页面示范了使用 pageContext 来操作 page、request、session、application 范围内的变量。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>pageContext 测试</title> </head> <body> <% // 使用 pageContext 设置属性,该属性默认在 page 范围内 pageContext.setAttribute("page","hello"); // 使用 request 设置属性,该属性默认在 request 范围内 request.setAttribute("request","hello"); // 使用 pageContext 将属性设置在 request 范围中 pageContext.setAttribute("request2","hello",pageContext.REQUEST_SCOPE); // 使用 session 将属性设置在 session 范围中 session.setAttribute("session","hello"); // 使用pageContext 将属性设置在 session 范围中 pageContext.setAttribute("session2","hel1o",pageContext.SESSION_SCOPE); // 使用 application 将属性设置在 application 范围中 application.setAttribute("app","hello"); // 使用 pageContext 将属性设置在 application 范围中 pageContext.setAttribute("app2","hello",pageContext.APPLICATION_SCOPE); // 下面获取各属性所在的范围: out.println("page变量所在范围:" + pageContext.getAttributesScope("page") + "<br/>"); out.println("request变量所在范围:" + pageContext.getAttributesScope("request") + "<br/>"); out.println("request2变量所在范围:" + pageContext.getAttributesScope("request2") + "<br/>"); out.println("session变量所在范围:"+ pageContext.getAttributesScope("session") + "<br/>"); out.println("session2变量所在范围:" + pageContext.getAttributesScope("session2") + "<br/>"); out.println("app变量所在范围:" + pageContext.getAttributesScope("app") + "<br/>"); out.println("app2变量所在范围:" + pageContext.getAttributesScope("app2") + "<br/>"); %> </body> </html> 以上页面的代码使用 pageContext 将各变量分别放入 page、request、session、application 范围内,程序的代码还使用 pageContext 获取各变量所在的范围。 浏览以上页面,可以看到如下图所示的效果。 上图中显示了使用 pageContext 获取各属性所在的范围,其中这些范围获取的都是整型变量,这些整型变量分别对应如下 4 个生存范围。 1:对应 page 生存范围。 2:对应 request 生存范围 3:对应 session 生存范围。 4:对应 application 生存范围。 不仅如此,pageContext 还可用于获取其他内置对象,pageContext 对象包含如下方法。 ➢ ServletRequest getRequest():获取 request 对象。 ➢ ServletResponse getResponse():获取 response 对象 ➢ ServletConfig getServletConfig():获取 config 对象。 ➢ ServletContext getServletContext():获取 application 对象。 ➢ HttpSession getSession():获取 session 对象。 因此一旦在 JSP、Servlet 编程中获取了 pageContext 对象,就可以通过它提供的上面方法来获取其他内置对象。request 对象 request 对象是 JSP 中重要的对象,每个 request 对象封装着一次用户请求,并且所有的请求参数都被封装在 request 对象中,因此 request 对象是获取请求参数的重要途径。 除此之外,request 可代表本次请求范围,所以还可用于操作 request 范围的属性。 1.获取请求头/请求参数 Web 应用是请求/响应架构的应用,浏览器发送请求时通常总会附带一些请求头,还可能包含一些请求参数发送给服务器,服务器端负责解析请求头/请求参数的就是 JSP 或 Servlet,而 JSP 和 Servlet 取得请求参数的途径就是 request。request 是 HttpServletRequest 接口的实例,它提供了如下几个方法来获取请求参数。 ➢ String getParameter(String paramName):获取 paramName 请求参数的值。 ➢ Map getParameterMap():获取所有请求参数名和参数值所组成的 Map 对象。 ➢ Enumeration getParameterNames():获取所有请求参数名所组成的 Enumeration 对象。 ➢ String[] getParameterValues(String name):paramName 请求参数的值,当该请求参数有多个值时,该方法将返回多个值所组成的数组。 HttpServletRequest 提供了如下方法来访问请求头。 ➢ String getHeader(String name): 根据指定请求头的值。 ➢ java.util.Enumeration getHeaderNames():获取所有请求头的名称。 ➢ java.util.Enumeration getHeaders(String name):获取指定请求头的多个值。 ➢ int getIntHeader(String name):获取指定请求头的值,并将该值转为整数值。 对于开发人员来说,请求头和请求参数都是由用户发送到服务器的数据,区别在于请求头通常由浏览器自动添加,因此一次请求总是包含若干请求头;而请求参数则通常需要开发人员控制添加,让客户端发送请求参数通常分两种情况。 ➢ GET 方式的请求:直接在浏览器地址栏输入访问地址所发送的请求或提交表单发送请求时,该表单对应的 form 元素没有设置 method 属性,或设置 method 属性为 get,这几种请求都是 GET 方式的请求。GET 方式的请求会将请求参数的名和值转换成字符串,并附加在原URL之后,因此可以在地址栏中看到请求参数名和值。且 GET 请求传送的数据量较小,一般不能大于 2KB。 ➢ POST 方式的请求:这种方式通常使用提交表单(由 form HTML 元素表示)的方式来发送,目需要设置 form 元素的 method 属性为 post。POST 方式传送的数据量较大,通常认为 POST 请求参数的大小不受限制,但往往取决于服务器的限制,POST 请求传输的数据量总比 GET 传输的数据量大。而且 POST 方式发送的请求参数以及对应的值放在 HTML HEADER 中传输,用户不能在地址栏里看到请求参数值,安全性相对较高。 对比上面两种请求方式,不难发现,通常应该采用 POST 方式发送请求。 几乎每个网站都会大量使用表单,表单用于收集用户信息,一旦用户提交请求,表单的信息将会提交给对应的处理程序,如果为 form 元素设置 mehed 属性为 post,则表示发送 POST 请求。 下面是表单页面的代码<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>收集参数的表单页</title> </head> <body> <form id="form1" method="post" action="request1.jsp"> 用户名:<br/> <input type="text" name="name"><hr/> 性别:<br/> 男:<input type="radio" name="gender" value="男"> 女:<input type="radio" name="gender" value="女"><hr/> 喜欢的颜色:<br/> 红:<input type="checkbox" name="color" value="红"> 绿:<input type="checkbox" name="color" value="绿"> 蓝:<input type="checkbox" name="color" value="蓝"><hr/> 来自的国家:<br/> <select name="country"> <option value="中国">中国</option> <option value="美国">美国</option> <option value="俄罗斯">俄罗斯</option> </select><hr/> <input type="submit" value="提交"> <input type="reset" value="重置"> </form> </body> </html> 这个页面没有动态的 JSP 部分,它只是包含一个收集请求参数的表单,且设置了该表单的 action 为 request1.jsp,这表明提交该表单时,请求将发送到 request1.jsp 页面;还设置了 method 为 post,这表明提交表单将发送 POST 请求。 除此之外,表单里还包含 1 个文本框、2 个单选框、3 个复选框及 1 个下拉列表框,另外包括“提交”和“重置”两个按钮。页面的执行效果如下图所示。 在该页面中输入相应信息后,单击“提交”按钮,表单域所代表的请求参数将通过 request 对象的 getParameter()方法来取得。 并不是每个表单域都会生成请求参数,而是有 name 属性的表单域才生成请求参数。关于表单域和请求参数的关系遵循如下 4 点。 ➢ 每个有 name 属性的表单域对应一个请求参数。 ➢ 如果有多个表单域有相同的 name 属性,则多个表单域只生成一个请求参数,只是该参数有多个值。 ➢ 表单域的 name 属性指定请求参数名,value 指定请求参数值。 ➢ 如果某个表单域设置了 disabled="disabled"属性,则该表单域不再生成请求参数。 上面的表单页向 request1.jsp 页面发送请求,request1.jsp 页面的代码如下<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <%@ page import="java.util.*" %> <!DOCTYPE html> <html> <head> <title>获取请求头/请求参数</title> </head> <body> <% // 获取所有请求头的名称 Enumeration<String> headerNames = request.getHeaderNames(); while(headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); // 获取每个请求、及其对应的值 out.println(headerName + "-->" + request.getHeader(headerName) + "<br/>"); } out.println("<hr/>"); // 设置解码方式,对于简体中文,使用 GBK 解码 request.setCharacterEncoding("GBK"); // ① // 下面依次获取表单域的值 String name = request.getParameter("name"); String gender = request.getParameter("gender"); // 如果某个请求参数有多个值,将使用该方法获取多个值 String[] color = request.getParameterValues("color"); String national = request.getParameter("country"); %> <!-- 下面依次输出表单域的值 --> 您的名字:<%=name%><hr/> 您的性别: <%=gender%><hr/> <!-- 输出复选框获取的数组值 --> 您喜欢的颜色:<%for(String c : color) %><hr/> 您来自的国家:<%=national%><hr/> </body> </html> 上述页面代码中示范了如何获取请求头、请求参数,在获取表单域对应的请求参数值之前,先设置 request 编码的字符集 (如①号代码所示)——如果 POST 请求的请求参数里包含西欧字符,则必须在获取请求参数之前先调用 setCharacterEncoding()方法设置编码的字符集。 如果发送请求的表单页采用 GBK 字符集,该表单页发送的请求也将采用 GBK 字符集,所以本页面需要先执行如下方法。 ➢ setCharacterEncoding("GBK"):设置 request 编码所用的字符集。 在表单提交页的各个输入域内输入对应的值,然后单击“提交”按钮,request1.jsp 就会出现如下图所示的效果。 如果需要传递的参数是普通字符串,而且仅需传递少量参数,可以选择使用 GET 方式发送请求参数,GET 方式发送的请求参数被附加到地址栏的 URL 之后,地址栏的 URL 将变成如下形式:url?paraml=valuel¶m2=value2&···paramN=valueN URL 和参数之间以“?”分隔,而多个参数之间以“&”分隔。 下面的 JSP 页面示范了如何通过 request 来获取 GET 请求参数值。<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>获取 GET 请求参数</title> </head> <body> <% // 获取 name 请求参数的值 String name = request.getParameter("name"); // 获取 gender 请求参数的值 String gender = request.getParameter("gander"); %> <!-- 输出 name 变量值 --> 您的名字:<%=name%><hr/> <!-- 输出 gender 变量值 --> 您的性别: <%=gender%><hr/> </body> </html> 上面的页面中获取了 GET 方式的请求参数,从这些代码不难看出:request 获取 POST 请求参数的代码和获取 GET 请求参数代码完全一样。向该页面发送请求时直接在地址栏里增加一些 GET 方式的请求参数,执行效果如下图所示。 细心的读者可能发现上面两个请求参数值都由英文字符组成,如果请求参数值里包含非西欧字符,那么是不是应该先调用 setCharacterEncoding()来设置 request 编码的字符集呢?读者可以试一下。答案是不行,如果 GET 方式的请求值里包含了非西欧字符,则获取这些参数比较复杂。 下面的页面示范了如何获取 GET 请求里的中文字符。<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>获取 包含非西欧字符的 GET 请求参数</title> </head> <body> <% // 获取请求里包含的查询字符串 String rawQueryStr = request.getQueryString(); out.println("原始查询字符串为:" + rawQueryStr + "<hr/>"); // 使用 URLDecoder 解码字符串 String queryStr = java.net.URLDecoder.decode(rawQueryStr,"UTF-8"); out.println("解码后的查询字符串为:" + queryStr + "<hr/>"); // 以&符号分解查询字符串 String[] paramPairs = queryStr.split("&"); for(String paramPair : paramPairs) { out.println("每个请求参数名、值对为: "+ paramPair + "<br/>"); // 以=来分解请求参数名和值 String[] nameValue = paramPair.split("="); out.println(nameValue[0] + "参数的值是: " + nameValue[1] + "<hr/>"); } %> </body> </html> 上面的程序中的代码就是获取 GET 请求里中文参数值的关键代码,为了获取 GET 请求里的中文参数值,必须借助于 java.net.URLDecoder 类。 上面页面代码使用了 UTF-8 字符集进行解码,到底应该用哪种字符集来解码,这取决于浏览器。对于简体中文的环境来说,一般要么是 UTF-8 字符集,要么是 GBK 字符集。 读者可以编写一个表单,并让表单以 GET 方式提交请求到 request3.jsp 页面,将可看到如下图所示的效果。 如果读者不想这样做,还可以在获取请求参数值之后对请求参数值重新编码。也就是先将其转换成字节数组,再将字节数组重新解码成字符串。例如,可通过如下代码来取得 name 请求参数的参数值。// 获取原始的请求参数值 String rawName = request.getParameter("name"); // 将请求参数值使用 ISO-8859-1 字符串分解成字节数组 byte[] rawBytes = rawName.getBytes("ISO-8859-1"); // 将字节数组查询解码成字符串 String name = new String(rawBytes,"UTF-8"); 通过上面代码片段也可处理 GET 请求里的中文请求参数值。 2.操作 request 范围的属性 HtpServietRequest 还包含如下两个方法,用于设置和获取 request 范围的属性。 ➢ setAtribute(String attName,Objcct attValue):将 attValue 设置成 request 范围的属性。 ➢ Object getAttribute(String attName):获取 request 范围的属性。 当 forward 用户请求时,请求的参数和请求属性都不会丢失。看下一个 JSP 页面,这个 JSP 页面是个简单的表单页,用于提交用户请求。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>取钱的表单页</title> </head> <body> <!-- 取钱的表单 --> <form method="post" action="first.jsp"> 取钱:<input type="text" name="balance"> <input type="submit" value="提交"> </form> </body> </html> 该页面向 first.jsp 页面请求后,balance 参数将被提交到first.jsp 页面,下面是 first.jsp 页面的实现代码。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <%@ page import="java.util.*"%> <!DOCTYPE html> <html> <head> <title>request 处理</title> </head> <body> <% // 获取请求的钱数 String bal = request.getParameter("balance"); // 将钱数的字符串转换成双精度浮点数 double qian = Double.parseDouble(bal); // 对取出的钱进行判断 if (qian < 500) { out.println("给你" + qian + "块"); out.println("账户减少" + qian); } else { // 创建了一个 List 对象 List<String> info = new ArrayList<String>(); info.add("1111111"); info.add("2222222"); info.add("3333333"); // 将 info 对象放入 request 范围内 request.setAttribute("info",info); %> <!-- 实现转发 --> <jsp:forward page="second.jsp"/> <%}%> </body> </html> first.jsp 页面首先获取请求的取钱数,然后对请求的钱数进行判断。如果请求的钱数小于 500,则允许直接取钱;否则将请求转发到 second.jsp。转发之前,创建了一个 List 对象,并将该对象设置成 request 范围的 info 属性。 接下来在 second.jsp 页面中,不仅获取了请求的 balance 参数,而且还会获取 request 范围的 info 属性。second.jsp 页面的代码如下。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <%@ page import="java.util.*"%> <!DOCTYPE html> <html> <head> <title>request 处理</title> </head> <body> <% // 取出请求参数 String bal = request.getParameter("balance"); double qian = Double.parseDouble(bal); // 取出 request 范围内的 info 属性 List<String> info = (List<String>)request.getAttribute("info"); for (String tmp : info) { out.println(tmp + "<br/>"); } out.println("取钱" + qian + "块"); out.println("账户减少" + qian); %> </body> </html> 如果页面请求的钱数大于 500,请求将被转发到 second.jsp 页面处理,而且在 second.jsp 页面中可以获取到 balance 请求参数值,也可获取到 request 范围的 info 属性,这表明:forward 用户请求时,请求参数和 request 范围的属性都不会丢失,即 forward 后还是原来的请求,并未再次向服务器发送请求。 如果请求取钱的钱数为 654,则页面的执行效果如下图所示。 3.执行 forward 或 include request 还有一个功能就是执行 forward 和 include,也就是代替 JSP 所提供的 forward 和 inchude 动作指令。前面需要 forward 时都是通过 JSP 提供的动作指令进行的,实际上 request 对象也可以执行 forward。 HttpServletReques 类提供了一个 getRequestDispatcher(String path)方法,其中 path 就是希望 forward 或者 include 的目标路径,该方法返回 RequestDispatcher,该对象提供了如下两个方法。 ➢ forward(ServletRequest request,ServletResponse response):执行 forward。 ➢ include(ServletRequest request,ServletResponse response): 执行 include。 如下代码行可以将 a.jsp 页面 include 到本页面中:getRequestDispatcher("/a.jsp").include(request,response); 如下代码行则可以将请求 forward 到 a.jsp 页面:getRequestDispatcher("/a.jsp").forward(request,response); 使用 request 的 getRequestDispatcher(String path)方法时,该 path 字符串必须以斜线开头。response 对象 response 代表服务器对客户端的响应。大部分时候,程序无须使用 response 来响应客户端请求,因为有个更简单的响应对象——out,它代表页面输出流,直接使用 out 生成响应更简单。 但 out 是 JspWriter 的实例,JspWriter 是 Writer 的子类,Writer 是字符流,无法输出非字符内容。假如需要在 JSP 页面中动态生成一幅位图、或者输出一个 PDF 文档,使用 out 作为响应对象将无法完成,此时必须使用 response 作为响应输出。 除此之外,还可以使用 response 来重定向请求,以及用于向客户端增加 Cookie。 1.response 响应生成非字符响应 对于需要生成非字符响应的情况,就应该使用 response 来响应客户端请求。下面的 JSP 页面将在客户端生成一张图片。response 是 HttpServletResponse 接口的实例,该接口提供了一个 getOutputStream()方法,该方法返回响应输出字节流。<%-- 通过 contentType 属性指定响应数据是图片 --%> <%@ page contentType="image/png" language="java" %> <%@ page import="java.awt.image.*,javax.imageio.*,java.io.*,java.awt.*"%> <% // 创建 BufferedImage 对象 BufferedImage image = new BufferedImage(340,160,BufferedImage.TYPE_INT_RGB); // 以 Image 对象获取 Graphics 对象 Graphics g = image.getGraphics(); // 使用 Graphics 画图,所画的图像将会出现在 image 对象中 g.fillRect(0,0,400,400); // 设置颜色:红 g.setColor(new Color(255,0,0)); // 画出一段弧 g.fillArc(20,20,100,100,30,120); // 设置颜色:绿 g.setColor(new Color(0,255,0)); // 画出一段弧 g.fillArc(20,20,100,100,150,120); // 设置颜色:蓝 g.setColor(new Color(0,0,255)); //画出一段弧 g.fillArc(20,20,100,100,270,120); // 设置颜色:黑 g.setColor(new Color(0,0,0)); g.setFont(new Font("Arial Black",Font.PLAIN,16)); //画出三个字符串 g.drawString("red:climb",200,60); g.drawString("green:swim",200,100); g.drawString("blue:jump",200,140); g.dispose(); //将图像输出到页面的响应 ImageIO.write(image,"png",response.getOutputStream()); %> 以上页面的代码先设置了服务器响应数据是 image/png,这表明服务器响应是一张 PNG 图片。接着创建了一个 BufferedImage 对象(代表图像),并获取该 BufferedImage 的 Graphics 对象(代表画笔),然后通过 Graphics 向 BufferedImage 中绘制图形,最后一行代码将直接将 BufferedImage 作为响应发送给客户端。 请直接在浏览器中请求该页面,将看到浏览器显示一张图片,效果如下图所示。 也可以在其他页面中使用 img 标签来显示这个图片页面,代码如下:<img src="img.jsp"> 使用这种临时生成图片的方式就可以非常容易地实现网页上的图形验证码功能。不仅如此,使用 response 生成非字符响应还可以直接生成 PDF 文件、Excel 文件,这些文件可直接作为报表使用。 2.重定向 重定向是 response 的另外一个用处,与 forward 不同的是,重定向会丢失所有的请求参数和 request 范围的属性,因为重定向将生成第二次请求,与前一次请求不在同一个 request 范围内,所以发送一次请求的请求参数和 request 范围的属性全部丢失。 HttpServletResponse 提供了一个 sehdRedirect(String path)方法,该方法用于重定向到 path 资源,即重新向 path 资源发送请求。 下面的 JSP 页面将使用 response 执行重定向。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <% // 生成页面响应 out.println("===="); // 重定向到 request2.jsp 页面 response.sendRedirect("request2.jsp"); %> 以上页面 response.sendRedirect 用于执行重定向,向该页面发送请求时,请求会被重定向到 request2.jsp 页面。例如,在地址栏中输入 http://localhost:8080/jspObject/doRedirect.jsp?name=xiaowu&gender=男,然后按回车键,将看到如下图所示的效果。 注意地址栏的改变,执行重定向动作时,地址栏的 URL 会变成重定向的目标 URL。 重定向会丢失所有的请求参数,使用重定向的效果,与在地址栏里重新输入新地址再按回车键的效果完全一样,即发送了第二次请求。 从表面上来看,forward 动作和 rdirec 动作有些相似:它们都可将请传递到另一个页面。但实际上 fonward 和 redirect 之间存在较大的差异, forward 和 rediret 的差异如下表所示。转发(forward)重定向(redirect)执行 forward 后依然是上一次请求执行 redirect 后生成第二次请求forward 的目标页面可以访问原请求的请求参数,因为依然是同一次请求,所有原请求的请求参数、request 范围的属性全部存在redirect 的目标页面不能访问原请求的请求参数,因为是第二次请求了,所有原请求的请求参数、request 范围的属性全部丢失地址栏里请求的 URL 不会改变地址栏改为重定向的目标 URL。相当于在浏览器地址栏里输入新的 URL 后按回车键 3.增加Cookie Cookie 通常用于网站记录客户的某些信息,比如客户的用户名及客户的喜好等。一旦用户下次登录,网站可以获取到客户的相关信息,根据这些客户信息,网站可以对客户提供更友好的服务。Cookie 与 session 的不同之处在于: session 会随浏览器的关闭而失效,但 Cookie 会一直存放在客户端机器上,除非超出 Cookie 的生命期限。 由于安全性的原因,使用 Cookie 客户端浏览器必须支持 Cookie 才行。客户端浏览器完全可以设置禁用 Cookie。 增加 Cookie 也是使用 response 内置对象完成的,response 对象提供了如下方法。 ➢ void addCookie(Cookie cookie):增加 Cookie。 正如在上面的方法中见到的,在增加 Cookie 之前,必须先创建 Cookie 对象。增加 Cookie 请按如下步骤进行。 ① 创建 Cookie 实例,Cookie 的构造器为 Cookie(String name,String value)。 ② 设置 Cookie 的生命期限,即该 Cookie 在多长时间内有效。 ③ 向客户端写 Cookie。 看如下 JSP 页面,该页面可以用于向客户端写一个 username 的 Cookie。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>z增加 Cookie</title> </head> <body> <% // 获取请求参数 String name = request.getParameter("username"); // 以获取到的请求参数为值,创建一个 Cookie 对象 Cookie c = new Cookie("username",name); // 设置 Cookie 对象的生存期限 c.setMaxAge(24* 3600); // 向客户端增加 Cookie 对象 response.addCookie(c); %> </body> </html> 如果浏览器没有阻止 Cookie,在地址栏输入 http://localhost:8080/jspObject/addCookie.jsp?name=xiaowu,执行该页面后,网站就会向客户端机器写入一个名为 usemame 的 Cookie,该 Cookie 将在客户端硬盘上一直存在,直到超出该 Cookie 的生存期限(本 Cookie 设置为 24 小时)。 访问客户端 Cookie 使用 request 对象,request 对象提供了 getCookies()方法,该方法将返回客户端机器上所有 Cookie 组成的数组,遍历该数组的每个元素,找到希望访问的 Cookie 即可。 下面是访问 Cookie 的 JSP 页面的代码。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>读取 Cookie</title> </head> <body> <% // 获取本站在客户端上保留的所有 Cookie Cookie[] cookies = request.getCookies() ; // 遍历客户端上的每个 Cookie for (Cookie c : cookies) { // 如果 Cookie 的名为 username,表明该 Cookie 是需要访问的 Cookie if(c.getName().equals ("username")) { out.println(c.getValue()) ; } } %> </body> </html> 上面的代码就是通过 request 读取 Cookie 数组,并搜寻指定 Cookie 的关键代码,访问该页面即可读出刚才写在客户端的 Cookie。 使用 Cookie 对象必须设置其生存期限,否则 Cookie 将会随浏览器的关闭而自动消失。 默认情况下,Cookie 值不允许出现中文字符,如果需要值为中文内容的 Cookie 怎么办呢?同样可以借助于 java.net.URLEncoder 先对中文字符进行编码,将编码后的结果设为 Cookie 值。当程序要读取 Cookie 时,则应该先读取,然后使用 java.net.URLDecoder 对其进行解码。 如下代码片段示范了如何存入值为中文的 Cookie。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <% // 以编码后的字符串为值,创建一个 Cookie 对象 Cookie c = new Cookie("cnName",java.net.URLEncoder.encode("孙悟空","gbk")); //设置 Cookie 对象的生存期限 c.setMaxAge(24 * 3600); // 向客户端增加 Cookie 对象 response.addCookie(c); // 获取本站在客户端上保留的所有 Cookie Cookie[] cookies = request.getCookies(); // 遍历客户端上的每个 Cookie for (Cookie cookie : cookies) { // 如果 Cookie 的名为 username,表明该 Cookie 是需要访问的 Cookie if(cookie.getName().equals("cnName")) { // 使用 java.util.URLDecoder 对 cookie 值进行解码 out.println(java.net.URLDecoder.decode(cookie.getValue ())); } } %> 上面的程序中 java.net.URLDecoder 是存入值为中文的 Cookie 的关键:存入之前先用 java.net.URLEncoder 进行编码;读取时需要对读取的 Cookie 值用 java.net.URLDecoder 进行解码。session 对象 session 对象也是一个非常常用的对象,这个对象代表一次用户会话。一次用户会话的含义是:从客户端浏览器连接服务器开始,到客户端浏览器与服务器断开为止,这个过程就是一次会话。 session 通常用于跟踪用户的会话信息,如判断用户是否登录系统,或者在购物车应用中,用于跟踪用户购买的商品等。 session 范围内的属性可以在多个页面的跳转之间共享。一旦关闭浏览器,即 session 结束,session 范围内的属性将全部丢失。 session 对象是 HttpSession 的实例,HttpSession 有如下两个常用的方法。 ➢ setAttribute(String attName,Obiect attValue):设置 session 范围内 attName 属性的值为 attValue。 ➢ getAttribute(String attName):返回 session 范围内 attName 属性的值。 下面的示例演示了一个购物车应用,以下是陈列商品的 JSP 页面代码。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>选择物品购买</title> </head> <body> <form method="post" action="processBuy.jsp"> 书籍: <input type="checkbox" name="item" value="book"/><br/> 电脑: <input type="checkbox" name="item" value="computer"/><br/> 汽车: <input type="checkbox" name="item" value="car"/><br/> <input type="submit" value="购买"/> </form> </body> </html> 这个页面几乎没有动态的 JSP 部分,全部是静态的 HTML 内容。该页面包含一个表单,表单里包含三个复选按钮,用于选择想购买的物品,表单由 processBuy.jsp 页面处理,其页面的代码如下。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <%@ page import="java.util.*" %> <% // 取出 session 范围的 itemMap 属性 Map<String,Integer> itemMap = (Map<String,Integer>)session.getAttribute("itemMap"); //如果 Map 对象为空,则初始化 Map 对象 if (itemMap == null) { itemMap = new HashMap<String,Integer>(); itemMap.put("书籍",0); itemMap.put("电脑",0); itemMap.put("汽车",0); } // 获取上一个页面的请求参数 String[] buys = request.getParameterValues("item"); // 遍历数组的各元素 for (String item : buys) { // 如果 item 为 book,表示选择购买书籍 if(item.equals("book")) { int num1 = itemMap.get("书籍").intValue(); //将书籍 key 对应的数量加1 itemMap.put("书籍",num1 + 1); } // 如果 item 为 computer,表示选择购买电脑 else if (item.equals("computer")) { int num2 = itemMap.get("电脑").intValue(); // 将电脑 key 对应的数量加 1 itemMap.put("电脑",num2 + 1); } // 如果 item 为 car,表示选择购买汽车 else if (item.equals("car")) { int num3 = itemMap.get("汽车").intValue(); // 将汽车 key 对应的数量加 1 itemMap.put("汽车",num3 + 1); } } // 将 itemMap 对象放到设置成 session 范围的 itemMap 属性 session.setAttribute("itemMap",itemMap); %> <!DOCTYPE html> <html> <head> <title>new document</title> </head> <body> 您所购买的物品: <br/> 书籍:<%=itemMap.get("书籍")%>本<br/> 电脑:<%=itemMap.get("电脑")%>台<br/> 汽车:<%=itemMap.get("汽车")%>辆 <p><a href="shop.jsp">再次购买</a></p> </body> </html> 以上页面的代码使用 session 来保证 itemMap 对象在一次会话中有效,这使得该购物车系统可以反复购买,只要浏览器不关闭,购买的物品信息就不会丢失,下图显示的是多次购买后的效果。 考虑 session 本身的目的,通常只应该把与用户会话状态相关的信息放入 session 范围内。不要仅仅为了两个页面之间交换信息,就将该信息放入 session 范围内。如果仅仅为了两个页面交换信息,可以将该信息放入 request 范围内,然后 forward 请求即可。 关于 session 还有一点需要指出,sssion 机制通常用于保存客户端的状态信息,这些状态信息需要保存到 Web 服务器的硬盘上,所以要求 session 里的属性值必须是可序列化的,否则将会引发不可序列化的异常。 session 的属性值可以是任何可序列化的 Java 对象。
2022年12月31日
14 阅读
0 评论
3 点赞
2022-12-31
JSP 的 7 个动作指令
动作指令与编译指令不同,编译指令是通知 Servlet 引擎的处理消息,而动作指令只是运行时的动作。编译指令在将 JSP 编译成 Servlet 时起作用;而处理指令通常可替换成 JSP 脚本,它只是 JSP 脚本的标准化写法。 JSP 动作指令主要有如下7个。 jsp:forward:执行页面转向,将请求的处理转发到下一个页面。 jsp:param:用于传递参数,必须与其他支持参数的标签一起使用。 jsp:include:用于动态引入一个 JSP 页面。 jsp:plugin:用于下载 JavaBean 或 Applet 到客户端执行。 jsp:useBean:创建一个 JavaBean 的实例。 jsp:setProperty: 设置 JavaBean 实例的属性值。 jsp:getProperty:输出 JavaBean 实例的属性值。forward 指令 forward 指令用于将页面响应转发到另外的页面。既可以转发到静态的 HTML 页面,也可以转发到动态的 JSP 页面,或者转发到容器中的 Servlet。 JSP 的 forward 指令的格式如下。 对于 JSP 1.0,使用如下语法:<jsp:foeward page=""/> 对于 JSP 1.1 以上规范,可使用如下语法:<jsp:foeward page=""> </jsp:foeward> 第二种语法用于在转发时增加额外的请求参数。增加的请求参数的值可以通过 HttpServletRequest 类的 getParameter()方法获取。 下面示例页面使用了 forward 动作指令来转发用户请求。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%> <!DOCTYPE html> <html> <head> <title> forward的原始页 </title> </head> <body> <h3>forward的原始页</h3> <jsp:forward page="forward-result.jsp"> <jsp:param name="age" value="29"/> </jsp:forward> </body> </html> 这个 JSP 页面非常简单,它包含了简单的 title 信息,页面中也包含了简单的文本内容,页面的代码则将客户端请求转发到 forward-result.jsp 页面,转发请求时增加了一个请求参数:参数名为age,参数值为 29。 在 forward-result.jsp 页面中,使用 request 内置对象(request 内置对象是 HttpServletRequest 的实例,关于 request 的详细信息参看下一篇)来获取增加的请求参数值。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%> <!DOCTYPE html> <html> <head> <title> forward结果页 </title> </head> <body> <!-- 使用request内置对象获取age参数的值 --> <%=request.getParameter("age")%> </body> </html> forward-result.jsp 页面中的第 9 行代码设置了 title 信息,并输出了 age 请求参数的值,在浏览器中访问 jsp-forward.jsp 页面的执行效果如下图所示。 从上图中可以看出,执行 forward 指令时,用户请求的地址依然没有发生改变,但页面内容却完全变为被 forward 目标页的内容。 执行 forward 指令转发请求时,客户端的请求参数不会丢失。看下面表单提交页面的例子,该页面没有任何动态的内容,只是一个静态的表单页,作用是将请求参数提交到 jsp-forward.jsp 页面。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%> <!DOCTYPE html> <html> <head> <title> 提交 </title> </head> <body> <!-- 表单提交页面 --> <form id="login" method="post" action="jsp-forward.jsp"> <input type="text" name="username"> <input type="submit" value="login"> </form> </body> </html> 修改 forward-result.jsp 页,增加输出表单参数的代码,也就是在 forward-result.jsp 页面上增加如下代码:<!-- 输出 username 请求参数的值 --> <%=request.getParameter("username")%> 在表单提交页面中的文本框中输入任意字符串后提交该表单,即可看到如下图所示的执行效果。 从上图中可看到,forward-result.jsp 页面中不仅可以输出 forward 指令增加的请求参数,还可以看到表单里 username 表单域对应的请求参数,这表明执行 forward 时不会丢失请求参数。 从表面上看,<jsp:forward.../>指令给人一种感觉:它是将用户请求“转发”到了另一个新页面,但实际上,<jsp:forward.../>并没有重新向新页面发送请求,它只是完全采用了新页面来对用户生成响应——请求依然是一次请求,所以请求参数、请求属性都不会丢失。include 指令 include 指令是一个动态 include 指令,也用于包含某个页面,它不会导入被 include 页面的编译指令,仅仅将被导入页面的 body 内容插入本页面。 下面是 include 动作指令的语法格式:<jsp:include page="" flush="true"/> 或者<jsp:include page="" flush="true"> <jsp:param name="parameterName" value="patameterValue"/> </jsp:include> flush 属性用于指定输出缓存是否转移到被导入文件中。如果指定为 true,则包含在被导入文件中;如果指定为 false,则包含在原文件中。对于 JSP 1.1 以前版本,只能设置为 false。 对于第二种语法格式,则可在被导入页面中加入额外的请求参数。 下面的页面使用了动态导入语法来导入指定 JSP 页面。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%> <!DOCTYPE html> <html> <head> <title> jsp-include 测试 </title> </head> <body> <!-- 使用动态 include 指令导入页面 --> <jsp:include page="scriptlet.jsp" /> </body> </html> 以上页面中<jsp:include.../>使用了动态导入语法来导入了 scriptlet.jsp。表面上看,该页面的执行效果与使用静态 include 导入的页面并没有什么不同。但查看 jsp-include.jsp 页面生成 Servlet 的源代码,可以看到如下片段:// 使用页面输出流,生成 HTML 标签内容 out.write("</head>\r\n"); out.write("<body>\r\n"); out.write("<!-- 使用动态 include 指令导入页面 -->\r\n"); org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "scriptlet.jsp", out, false); out.write("\r\n"); out.write("</body>\r\n"); 以上代码片段中显示了动态导入的关键:动态导入只是使用一个 include 方法来插入目标页面的内容,而不是将目标页面完全融入本页面中。 归纳起来,静态导入和动态导入有如下三点区别: ➢ 静态导入是将被导入页面的代码完全融入,两个页面融合成一个整体 Servlet;而动态导入则在 Servlet 中使用 include 方法来引入被导入页面的内容。 ➢ 静态导入时被导入页面的编译指令会起作用;而动态导入时被导入页面的编译指令则失去作用,只是插入被导入页面的 body 内容。 ➢ 动态包含还可以增加额外的参数。 除此之外,执行 include 动态指令时,还可增加额外的请求参数,如下面 JSP 页面所示。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%> <!DOCTYPE html> <html> <head> <title> jsp-include 测试 </title> </head> <body> <jsp:include page="forward-result.jsp"> <jsp:param name="age" value="32"/> </jsp:include> </body> </html> 在上面的 JSP 页面中的代码同样使用<jsp:include.../>指令包含页面,而且在 jsp:include 指令中还使用 param 指令传入参数,该参数可以在 forward-result.jsp 页面中使用 request 对象获取。 forward-result.jsp 前面已经给出,此处不再赘述。页面执行的效果如下图所示。 实际上,forward 动作指令和 include 动作指令十分相似 (它们的语法就很相似),它们都采用方法来引入目标页面,通过查看 JSP 页面所生成 Servlet 代码可以得出:forward 指令使用_jspx_page_context 的 forward()方法来引入目标页面,而 include 指令则使用通过 JspRuntimeLibrary 的 include()方法来引入目标页面。区别在于:执行 forward 时,被 forward 的页面将完全代替原有页面;而执行 include 时,被 include 的页面只是插入原有页面。简而言之,forward 拿目标页面代替原有页面,而 include 则拿目标页面插入原有页面。useBean、setProperty、getProperty 指令 这三个指令都是与 JavaBean 相关的指令,其中 useBean 指令用于在 JSP 页面中初始化一个 Java 实例;setProperty 指令用于为 JavaBean 实例的属性设置值;getProperty 指令用于输出 JavaBean 实例的属性。 如果多个 JSP 页面中需要重复使用某段代码,则可以把这段代码定义成 Java 类的方法,然后让多个 JSP 页面调用该方法即可,这样可以达到较好的代码复用。 useBean的语法格式如下:<jsp:useBean id="name" class="classname" scope="page | request | session | application"/> 其中,id 属性是 JavaBean 的实例名,class 属性确定 JavaBean 的实现类。scope 属性用于指定 JavaBean 实例的作用范围,该范围有以下 4 个值。 ➢ page:该 JavaBean 实例仅在该页面有效。 ➢ request:该 JavaBean 实例在本次请求有效。 ➢ session:该 JavaBean 实例在本次 session 内有效。 ➢ application:该 JavaBean 实例在本应用内一直有效。 后面有关于这4个作用范围的详细介绍。 setProperty 指令的语法格式如下:<jsp:setProperty name="BeanName" property="propertyName" value="value"/> 其中,name 属性确定需要设定 JavaBean 的实例名;property 属性确定需要设置的属性名;value属性则确定需要设置的属性值。 getProperty的语法格式如下:<jsp:getProperty name="BeanName" property="propertyName"/> 其中,name 属性确定需要输出的 JavaBean 的实例名;property 属性确定需要输出的属性名。 下面的 JSP 页面示范了如何使用这三个动作指令来操作 JavaBean。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%> <!DOCTYPE html> <html> <head> <title> jsp-include 测试 </title> </head> <body> <!-- 创建 lee.Person 的实例,该实例的实例名为p1 --> <jsp:useBean id="p1" class="lee.Person" scope="page"/> <!-- 设置p1的 name 属性值 --> <jsp:setProperty name="pl" property="name" value="crazyit.org"/> <!-- 设置 p1 的 age 属性值 --> <jsp:setProperty name="p1" property="age" value="23"/> <!-- 输出 p1 的 name 属性值--> <jsp:getProperty name="p1" property="name"/><br/> <!-- 输出 P1 的 age 属性值 --> <jsp:getProperty name="p1" property="age"/> </body> </html> 以上页面代码示范了使用useBean、setProperty 和 getProperty 来操作 JavaBean 的方法。 对于上面的 JSP 页面中的 setProperty 和 getProperty 标签而言,它们都要求根据属性名来操作 JavaBean 的属性。实际上 setProperty 和 getProperty 要求的属性名,, Java 类中定义的属性有一定的差别,例如 setProperty 和 getProperty 需要使用 name 属性,但 JavaBean 中是否真正定义了 name 属性并不重要,重要的是在 JavaBean 中提供了 setName()和 getName()方法即可。事实上,当页面使用 setProperty 和 getProperty 标签时,系统底层就是调用 setName()和 getName()方法来操作 Person 实例的属性的。 下面是 Person 类的源代码。package lee; public class Person { private String name; private int age; //无参数的构造器 public Person() { } //初始化全部属性的构造器 public Person(String name , int age) { this.name = name; this.age = age; } //name 的 setter 和 getter 方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } //age 的 setter 和 getter 方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } } 上面的 Person.java 只是源文件,此处将该文件放在 Web 应用的 WEB-INF/src 路径下,实际上 Java 源文件对 Web 应用不起作用,所以此处会使用 Ant 来编译它,并将编译得到的二进制文件放入 WEB-INF/classes 路径下。而且,为 Web 应用提供了新的 class 文件后,必须重启该 Web 应用,让他可以重新加载这些新的 class 文件。 该页面的执行效果如下图所示。. 对于上面三个标签完全可以不使用,将 beanTest.jsp 修改成如下代码,其内部的执行是完全一样的。<%@page import="lee.Person"%> <%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%> <!DOCTYPE html> <html> <head> <title> jsp-include 测试 </title> </head> <body> <% // 实例化 JavaBean 实例,实现类为 lee.Person,该实例的实例名为p1 Person p1 = new Person(); //将 p1 放置到 page 范围中 pageContext.setAttribute("p1",p1); // 设置 pl 的 name 属性值 p1.setName("wawa"); //设置 p1 的 age 属性值 p1.setAge(23); %> <!--输出 p1 的 name 属性值 --> <%=p1.getName()%><br/> <!--输出 pl 的 age 属性值 --> <%=p1.getAge()%> </body> </html> 使用 useBean 标签时,除在页面脚本中创建了 JavaBean 实例之外,该标签还会将该 JavaBean 实例放入指定 scope 中,所以通常还需要在脚本中将该 JavaBean 放入指定 scope 中,如下面的代码片段所示:// 将 p1 放入 page 的生存范围中 pageContext.setAttribute("p1",p1); // 将 p1 放入 request 的生存范围中 request.setAttribute("pl",p1) // 将 p1 放入 session 的生存范围中 session.setAttribute("p1",p1) // 将 p1 放入 application 的生存范围中 application.setAttribute("pl",p1);plugin 指令 plugin 指令主要用于下载服务器端的 JavaBean 或 Applet 到客户端执行。由于程序在客户端执行,因此客户端必须安装虚拟机。 实际由于现在很少使用 Applet,而且就算要使用 Applet,也完全可以使用支持 Applet 的 HTML 标签,所以jsp:plugin 标签的使用场景并不多。因此为了节省篇幅起见,后面不再详细介绍 plugin 指令的用法。param 指令 param 指令用于设置参数值,这个指令本身不能单独使用,因为单独的 param 指令没有实际意义。param 指令可以与以下三个指令结合使用。 ➢ jsp:include ➢ jsp:forward ➢ jsp:plugin 当与 include 指令结合使用时,param 指令用于将参数值传入被导入的页面;当与 forward 指令结合使用时,param 指令用于将参数值传入被转向的页面;当与 plugin 指令结合使用时,则用于将参数传入页面中的 JavaBean 实例或 Applet 实例。 param 指令的语法格式如下:<jsp:param name="paramName" value="paramValue"/> 关于 param 的具体使用,请参考前面的示例。
2022年12月31日
10 阅读
0 评论
3 点赞
2022-12-25
JSP 的 3 个编译指令
JSP 的编译指令是通知 JSP 引擎的消息,它不直接生成输出。编译指令都有默认值,因此开发人员无须为每个指令设置值。 常见的编译指令有如下三个。 ➢ page:该指令是针对当前页面的指令。 ➢ include:用于指定包含另一个页面。 ➢ taglib:用于定义和访问自定义标签。 使用编译指令的语法格式如下:<%@ 编译指令名 属性名="属性值"···%> 下面主要介绍 page 和 include 指令,关于 taglib 指令,将在自定义标签库处详细讲解。page 指令 page 指令通常位于 JSP 页面的顶端,一个 JSP 页面可以使用多条 page 指令。page 指令的语法格式如下:<%@page [language="Java"] [extends="package.class"] [import="package.class | package.*,..."] [session="true | false"] [buffer="none | 8KB | size Kb"] [autoFlush="true | false"] [isThreadSafe="true | false"] [info="text"] [errorPage="relativeURL"] [contentType="mimeType[;charset=characterSet]" | "text/html;charSet=ISO-8859-1"] [pageEncoding="ISO-8859-1"] [isErrorPage="true | false"] %> 下面依次介绍 page 指令各属性的意义。 ➢ language:声明当前 JSP 页面使用的脚本语言的种类,因为页面是 JSP 页面,该属性的值通常都是 java,该属性的默认值也是 java,所以通常无须设置。 ➢ extends:指定 JSP 页面编译所产生的 Java 类所承的父类,或所实现的接口。 ➢ import: 用来导入包。下面几个包是默认自动导入的,不需要显式导入。默认导入的包有:java.lang.、javax.servlet.、javax.servlet.jsp.、javax.servlet.http.。 ➢ session:设定这个 JSP 页面是否需要 HTTP Session。 ➢ buffer:指定输出缓冲区的大小。输出缓冲区的 JSP 内部对象:out用于缓存 JSP 页面对客户浏览器的输出,默认值为 8KB,可以设置为 none,也可以设置为其他的值,单位为 KB。 ➢ autoFlush:当输出缓冲区即将溢出时,是否需要强制输出缓冲区的内容。设置为 true 时为正常输出;如果设置为 false,则会在 buffer 溢出时产生一个异常。 ➢ info:设置该 JSP 程序的信息,也可以看做其说明,可以通过 Servlet.getServletInfo()方法获取该值。如果在 JSP 页面中,可直接调用 getServletInfo()方法获取该值,因为 JSP 页面的实质就是 Servlet。 ➢ errorPage:指定错误处理页面。如果本页面产生了异常或者错误,而该 JSP 页面没有对应的处理代码,则会自动调用该属性所指定的JSP页面。 因为 JSP 内建了异常机制支持,所以 JSP 可以不处理异常,即使是 checked 异常。 ➢ isErrorPage:设置本 JSP 页面是否为错误处理程序。如果该页面本身已是错误处理页面,则通常无须指定 errorPage 属性。 ➢ contentType:用于设定生成网页的文件格式和编码字符集,即 MIME 类型和页面字符集类型,默认的 MIME 类型是 text/html;默认的字符集类型为 ISO-8859-1。 ➢ pageEncoding:指定生成网页的编码字符集。 从JSP 的 4 种基本语法中执行数据库操作的 JSP 页面中可以看出,在basicSyntax\connDb.jsp 页面的头部,使用了两条 page 指令:<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <%@ page import="java.sql.*" %> 其中第二条指令用于导入本页面中使用的类,如果没有通过 page 指令的 import 属性导入这些类,则需在脚本中使用全限定类名——即必须带包名。可见,此处的 import 属性类似于 Java 程序中的 import 关键字的作用。 如果删除第二条 page 指令,则执行效果如下图所示。 看下面的 JSP 页面,该页面使用 page 指令的 info 属性指定了 JSP 页面的描述信息,又使用 getServletInfo()方法输出该描述信息。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!--指定info信息--> <%@ page info="this is a jsp"%> <!DOCTYPE html> <html> <head> <title> 测试page指令的info属性 </title> </head> <body> <!-- 输出info信息 --> <%=getServletInfo() %> </body> <html> 以上页面的第 3 行代码设置了 info 属性,用于指定该 JSP 页面的描述信息;第 11 行代码使用了getServletInfo()方法来访问该描述信息。 在浏览器中执行该页面,将看到如下图所示的效果。 errorPage 属性的实质是 JSP 的异常处理机制,JSP 脚本不要求强制处理异常,即使该异常是checked异常。如果 JSP 页面在运行中抛出未处理的异常,系统将自动跳转到 errorPage 属性指定的页面;如果 errorPage 没有指定错误页面,系统则直接把异常信息呈现给客户端浏览器——这是所有的开发者都不愿意见到的场景。 看下面的 JSP 页面,该页面设置了 page 指令的 errorPage 属性,该属性指定了当本页面发生异常时的异常处理页面。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="error.jsp" %> <!DOCTYPE html> <html> <head> <title> new document </title> </head> <body> <% // 下面代码将出现运行时异常 int a = 6; int b = 0; int c = a / b; %> </body> <html> 以上页面的 errorPage 属性指定 errorTest.jsp 页面(上面的页面)的错误处理页面是 error.jsp。下面是 error.jsp 页面,该页面本身是错误处理页面,因此将 isErrorPage 设置成true。<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true"%> <!DOCTYPE html> <html> <head> <title> 错误提示页面 </title> </head> <body> <!-- 提醒客户端系统出现异常 --> 系统出现异常<br/> </body> </html> 上面页面的 isErrorPage 属性指定error.jsp 页面是一个错误处理页面。在浏览器中浏览 errorTest.jsp 页面的效果如下图所示。 如果将前一个页面中 page 指令的 errorPage 属性删除,再次通过浏览器浏览该页面,执行效果如下图所示。 可见,使用 errorPage 属性控制异常处理的效果在表现形式上要好得多。 关于 JSP 异常,在介绍 exception 内置对象时还会有更进一步的解释。include 指令 使用 include 指令,可以将一个外部文件嵌入到当前 JSP 文件中,同时解析这个页面中的 JSP 语句(如果有的话)。这是个静态的 inude 语句,它会把目标页面的其他编译指令也包含进来,但动态 include 则不会。 include 既可以包含静态的文本,也可以包含动态的 JSP 页面。静态的 include 编译指令会将被包含的页面加入本页面,融合成一个页面,因此被包含页面甚至不需要是一个完整的页面。 include 编译指令的语法如下:<%@include file="relativeURISpec"%> 如果被嵌入的文件经常需要改变,建议使用<jsp:include>操作指令,因为它是动态的 include 语句。 下面的页面是使用静态导入的示例代码。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title> 静态include测试 </title> </head> <body> <!-- 使用include编译指定导入页面 --> <%@include file="scriptlet.jsp"%> </body> </html> 以上页面中第 9 行代码使用静态导入的语法将 scriptlet.jsp 页面导入本页,该页面的执行效果与 scriptlet.jsp 的执行效果相同。 scriptlet.jsp 为JSP 的 4 种基本语法中的小脚本测试页 查看 Tomcat 的 work\Catalina\localhost\directive\org\apache\jsp 路径下的 staticInclude_jsp.java文件,从 staticInclude.jsp 编译后的源代码可看到,staticInclude.jsp 页面已经完全将 scriptlet.jsp 的代码融入到本页面中。下面是 staticInclude_jsp.java 文件的片段:out.write("<table bgcolor=\"#9999dd\" border=\"1\" width=\"300px\">\r\n"); out.write("<!-- Java脚本,这些脚本会对HTML的标签产生作用 -->\r\n"); for(int i = 0; i < 10; i++){ out.write("\r\n"); out.write(" <!-- 上面的循环将控制<tr>标签循环 -->\r\n"); out.write(" <tr>\r\n"); out.write(" <td>循环值:</td>\r\n"); out.write(" <td>"); out.print(i); out.write("</td>\r\n"); out.write(" </tr>\r\n"); } 上面这些页面代码并不是由 staticInclude.jsp 页面所生成的,而是由 scriptlet.jsp 页面生成的。也就是说,scriptlet.jsp 页面的内容被完全融入 staticInclude.jsp 页面所生成的 Servlet 中,这就是静态包含意义:包含页面在编译时将完全包含了被包含页面的代码。 需要指出的是,静态包含还会将被包含页面的编译指令也包含进来,如果两个页面的编译指令冲突,那么页面就会出错。
2022年12月25日
7 阅读
0 评论
3 点赞
2022-12-25
JSP 的 4 种基本语法
前面已经讲过,编写 JSP 页面非常简单:在静态 HTML 页面中“镶”动态 Java 脚本即可。现在开始学习的内容是:JSP 页面的 4 种基本语法——也就是 JSP 允许在静态 HTML 页面中“镶嵌”的成分。掌握这4种语法之后,读者即可按如下步骤开发JSP页面。 ① 编写一个静态 HTML 页面。 ② 用合适的语法向静态 HTML 页面中“镶嵌”4 种基本语法的一种或多种,这样即可为静态 HTML 页面增加动态内容。JSP 注释 JSP 注释用于标注在程序开发过程中的开发提示,它不会输出到客户端 JSP注释的格式如下:<%-- 注释内容 --%> 与 JSP 注释形成对比的是 HTML 注释,HTML 注释的格式是:<!-- 注释内容 --> 看下面的 JSP 页面。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <html> <head> <title> 注释示例 </title> </head> <body> 注释示例 <!-- 增加 JSP 注释 --> <%-- JSP 注释部分 --%> <!-- 增加 HTML 注释 --> <!-- HTML 注释部分 --> </body> </html> 上面的页面中第 9 行代码是 JSP 注释,其他注释都是 HTML 注释。在浏览器中浏览该页面,看页面源代码,页面的源代码如下:<html> <head> <title> 注释示例 </title> </head> <body> 注释示例 <!-- 增加 JSP 注释 --> <!-- 增加 HTML 注释 --> <!-- HTML 注释部分 --> </body> </html> 在上面的源代码中可看到,HTML 的注释可以通过源代码查看到,但 JSP 的注释是无法通过源代码查看到的。这表明 JSP 注释不会被发送到客户端。JSP 声明 JSP 声明用于声明变量和方法。在 JSP 声明中声明方法看起来很特别,似乎不需要定义类就可以直接定义方法,方法似乎可以脱离类独立存在。实际上,JSP 声明将会转换成对应 Servlet 的成员变量或成员方法,因此 JSP 声明依然符合 Java 语法。 JSP 声明的语法格式如下:<%! 声明部分 %> 看下面使用JSP声明的示例页面。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title>Document</title> </head> <!-- 下面是 JSP 声明部分 --> <%! // 声明一个整型变量 public int count; // 声明一个方法 public String info(){ return "hello"; } %> <body> <% out.println(count++); %> <br/> <% // 输出 info() 方法的返回值 out.println(info()); %> </body> </html> 在浏览器中测试该页面时,可以看到正常输出了 count 值,每刷新一次,count 值将加 1,同时也可以看到正常输出了 info()方法的返回值。 上面的代码中声明了一个整型变量和一个普通方法,表面上看起来这个变量和方法不属于任何类,似乎可以独立存在,但这只是一个假象。打开 Tomcat 的 work\Catalina\localhost\basicSyntax\org\apache\jsp 目录下的 declare_jsp.java 文件,看到如下代码片段:public final class declare_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { // 声明一个整型变量 public int count; // 声明一个方法 public String info(){ return "hello"; } ··· } 上面的代码与 JSP 页面的声明部分完全对应,这表明 JSP 页面的声明部分将转换成对应Servlet的成员变量或成员方法。 由于 JSP 声明语法定义的变量和方法对应于 Servlet 类的成员变量和方法,所以 JSP 声明部分定义的变量和方法可以使用 private、public 等访问控制符修饰,也可使用 static 修饰,将其变成类属性和类方法。但不能使用 abstract 修饰声明部分的方法,因为抽象方法将导致 JSP 对应 Servlet 变成抽象类,从而导致无法实例化。 打开多个浏览器,甚至可以在不同的机器上打开浏览器来刷新该页面,将发现所有客户端访问的 count 值是连续的,即所有客户端共享了同一个 count 变量。这是因为:JSP 页面会编译成一个 Servlet 类,每个 Servlet 在容器中只有一个实例;在 JSP 中声明的变量是成员变量,成员变量只在创建实例时初始化,该变量的值将一直保存,直到实例销毁。 值得注意的是,info()的值也可正常输出。因为 JSP 声明的方法其实是在 JSP 编译中生成的 Servlet 的实例方法——Java 里的方法是不能独立存在的,即使在 JSP 页面中也不行。 JSP 声明中独立存在的方法,只是一种假象。输出 JSP 表达式 JSP 提供了一种输出表达式值的简单方法,输出表达式值的语法格式如下:<%=表达式%> 看下面的 JSP 页面,该页面使用输出表达式的方式输出变量和方法返回值。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title> 输出表达式 </title> </head> <%! public int count; public String info(){ return "hello"; } %> <body> <!-- 使用表达式输出变量值 --> <%=count++%> <br/> <!-- 使用表达式输出方法返回值 --> <%=info()%> </body> </html> 上面的页面中 body 标签内的代码使用输出表达式的语法代替了原来的 out.println 输出语句,该页面的执行效果与前一个页面的执行效果没有区别。由此可见,输出表达式将转换成 Servlet 里的输出语句。 输出表达式语法后不能有分号。JSP 脚本 以前 JSP 脚本的应用非常广泛,因此 JSP 脚本里可以包含任何可执行的的 Java 代码。通常来说,所有可执行性 Java 代码都可通过 JSP 脚本嵌入 HTML 页面。看下面使用 JSP 脚本的示例程序。<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title> 小脚本测试 </title> </head> <body> <table bgcolor="#9999dd" border="1" width="300px"> <!-- Java脚本,这些脚本会对HTML的标签产生作用 --> <% for(int i = 0; i < 10; i++){ %> <!-- 上面的循环将控制<tr>标签循环 --> <tr> <td>循环值:</td> <td><%=i%></td> </tr> <% } %> </table> </body> </html> 上面的页面中 for 循环语句就是使用 JSP 脚本的代码,这些代码可以控制页面中静态内容。上面例子程序将<tr.../>标签循环 10 次,即生成一个 10 行的表格,并在表格中输出表达式值。 在浏览器中浏览该页面,将看到如下图所示的效果。 接下来打开 Tomcat 的 work\Catalina\localhost\basicSyntax\org\apache\jsp 路径下的 scriptlet_jsp.java 文件,将看到如下代码片段:public final class scriptlet_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { ··· public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { ··· out.write("\r\n"); out.write("<!DOCTYPE html>\r\n"); out.write("<html>\r\n"); out.write("<head>\r\n"); out.write(" <title> 小脚本测试 </title>\r\n"); out.write("</head>\r\n"); out.write("<body>\r\n"); out.write("<table bgcolor=\"#9999dd\" border=\"1\" width=\"300px\">\r\n"); out.write("<!-- Java脚本,这些脚本会对HTML的标签产生作用 -->\r\n"); for(int i = 0; i < 10; i++){ out.write("\r\n"); out.write(" <!-- 上面的循环将控制<tr>标签循环 -->\r\n"); out.write(" <tr>\r\n"); out.write(" <td>循环值:</td>\r\n"); out.write(" <td>"); out.print(i); out.write("</td>\r\n"); out.write(" </tr>\r\n"); } out.write("\r\n"); out.write("</table>\r\n"); out.write("</body>\r\n"); out.write("</html>"); ··· } } 上面的代码片段中 for 循环完全对应于 scriptlet.jsp 页面中的小脚本部分。由上面代码片段可以看,JSP脚本将转换成 Servlet 里_jspService 方法的可执行性代码。这意味着在 JSP 小脚本部分也可以声变量,但在 JSP 脚本部分声明的变量是局部变量,但不能使用 private、public 等访问控制符修饰,也不可使用 static 修饰。 实际上不仅 JSP 小脚本部分会转换成_jspService 方法里的可执行性代码,JSP 页面里的所有静态内容都将由_jspService 方法里输出语句来输出,这就是 JSP 脚本可以控制 JSP 页面中静态内容的原因。由于 JSP 脚本将转换成_jspService 方法里的可执行性代码,而 Java 语法不允许在方法里定义方法,所以 JSP 脚本里不能定义方法。 因为 JSP 脚本中可以放置任何可执行性语句,所以可以充分利用 Java 语言的功能,例如连接数据库和执行数据库操作。看下面的 JSP 页面执行数据库查询。<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <%@ page import="java.sql.*" %> <!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title> 小脚本测试 </title> </head> <body> <% // 注册数据库驱动 Class.forName("com.mysql.jdbc.Driver"); // 获取数据库连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3308/javaee","root","root"); // 创建Statement Statement stmt = conn.createStatement(); // 执行查询 ResultSet rs = stmt.executeQuery("select * from news_inf"); %> <table bgcolor="#9999dd" border="1" width="300"> <% // 遍历结果集 while(rs.next()) {%> <tr> <!-- 输出结果集 --> <td><%=rs.getString(1)%></td> <td><%=rs.getString(2)%></td> </tr> <%}%> <table> </body> </html> 上面程序中的脚本执行了连接数据库,执行 SQL 查询,并使用输出表达式语法来输出查询结果。在浏览器中浏览该页面,将看到如下图所示的效果。 上面的页面执行 SQL 查询需要使用 MySQL 驱动程序,所以读者应该将 MySQL 驱动的 JAR 文件放在 Tomcat 的 lib 路径下(所有 Web 应用都可使用 MySQL 驱动),或者将 MySQL 驱动复制到该 Web 应用的 WEB-INF/lib 路径下(只有该 Web 应用可使用 MySOL 驱动)。除此之外,由于本 JSP 需要查询 javaee 数据库下的 news_inf 数据表,所以不要忘记了将 data.sql 导入数据库。
2022年12月25日
9 阅读
0 评论
2 点赞
1
2