《JavaWeb从入门到改行》JSP+EL+JSTL大杂烩汤


title: Servlet之JSP
tags: []
notebook: javaWEB

JSP是什么 ?

JSP就是Servlet,全名是"JavaServer Pages" 。因为Servlet不适合设置html响应体,需要大量的response.getWriter().print("<html>"),而和html是静态页面,不能包含动态信息。JSP完美的解决了两者的缺点,在原有html的基础上添加java脚本,构成jsp页面。

JSP的运行机理

当jsp页面第一次被访问时,服务器会通过实现HttpJspPage接口(javax.servlet.jsp包下的接口)把jsp转换成Servlet,也就是java文件(在tomcat的work目录下可以找到jsp转换成.java源代码),下图是jsp转成Servlet的样子。

上图中,JSP页面被翻译成了Servelt ,可以看出JSP页面的主体被转换成了一个_jspService()方法,即实现了HttpJspPage接口。然后再把java编译成.class,再创建该类对象,最后调用它的service()方法完成对客户端的响应(输出内容到客户端) 。 当第二次调用同一jsp时,直接调用service()方法。所以第一次的时间总是很长,常称为"第一次惩罚" 。

JSP与Servlet的分工

jsp

  • 作为请求发起页面,例如显示表单、超链接。
  • 作为请求结束页面,例如显示数据 。

Servlet

  • 作为请求中处理数据的环节。

三大指令+三大java脚本+动作标签

JSP的组成 = html + java脚本 + jsp标签(指令)

3种java脚本

  • <%...%>: Scriptlet,就是java代码片段(常用) 。能放在里面的内容,相当于java中方法的内容
  • <%=...%>:java表达式,用于输出(常用),用于输出一条表达式(或变量)的结果。相当于response.getWriter().print( ... );里面的内容
  • <%!...%>:声明(几乎不用),用来创建类的成员变量和成员方法 。 相当于类体中放的内容JSP标签

三个指令

指令格式: <%@指令名 属性=值 属性=值 ..........%>

page指令(重要)

重要属性:

  • pageEncoding:它指定当前jsp页面的编码 。
  • contentType:它表示添加一个响应头:Content-Type!等同于response.setContentType("text/html;charset=utf-8");
  • import:导包!<%@page import="java.net.*,java.util.*,java.sql.*"%>
  • errorPage:当前页面如果抛出异常,那么要转发到哪一个页面,由errorPage来指定 。可以在web.xml中配置错误页面
  • isErrorPage:它指定当前页面是否为处理错误的页面!当该属性为true时,这个页面会设置状态码为500!而且这个页面可以使用9大内置对象中的exception!
  • isELIgnored:是否忽略el表达式,默认值为false,不忽略,即支持!
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

include指令---->静态包含

与RequestDispatcher的include()方法的功能相似!<%@include%> 它是在jsp编译成java文件时完成的!如果A 页面包含B页面,那么他们共同生成一个java(就是一个servlet)文件,然后再生成一个class!RequestDispatcher的include()是一个方法,包含和被包含的是两个servlet,即两个.class!他们只是把响应的内容在运行时合并了!

<%@include file="b.jsp" %>

需要注意的是,一个被包含的页面不能出现与包含页面相同的代码,比如一些``标签等 。这个指令的作用使一些不变的东西可重用

taglib指令---->导入标签库

两个属性;

  • prefix:指定标签库在本页面中的前缀!由我们自己来起名称!
  • uri: 指定标签库的位置!

用法在导入JSTL标签中会用到

JSP动作标签

动作标签是由tomcat(服务器)来解释执行!它与java代码一样,都是在服务器端执行的!

  • <jsp:forword>:转发!它与RequestDispatcher的forward方法是一样的,一个是在Servlet中使用,一个是在jsp中使用!
  • <jsp:include>:包含:它与RequestDispatcher的include方法是一样的,一个是在Servlet中使用,一个是在jsp中使用!

<jsp:include> <%@include>的区别是: <jsp:include>在编译之间就把包含和被包含页面合并成一个java文件,编译成一个.class。而 <%@include>是包含和被包含页面各自编译,然后包含页面去调用被包含页面的.class 。

  • <jsp:param>:它用来作为forward和include的子标签!用来给转发或包含的页面传递参数!
/**
* 包含页
*/
<h1>a.jsp</h1>
<%--动态包含 --%>
<jsp:include page="b.jsp" >
            <jsp:param value="zhangSan" name="username"/>
            <jsp:param value="123" name="password"/>
</jsp:include>            
/**
* 被包含页
*/
<h1>b.jsp</h1>
<%
	String username = request.getParameter("username");
	String password = request.getParameter("password");
%>
/**
* <h1>a.jsp</h1>不会显示,只显示<h1>b.jsp</h1>。 因为包含动作标签是它与RequestDispatcher的include方法是一样的。既然包含了其他页面,当前页面就算了
*/

JSP九大内置对象和PageContext域对象

内置对象 介绍 是哪个类的实例(对象)
out jsp的输出流,用来向客户端响应 javax.servlet.jsp.JspWriter
page 当前jsp对象!当前JSP页面的"this " javax.servlet.jsp.HttpJspPage
pageContext 页面上下文对象 ,四大域对象之一 javax.servlet.jsp.PageContext
exception 只有在错误页面中可以使用这个对象 java.lang.Throwable
config 就是Servlet中的ServletConfig 类的对象 javax.servlet.ServletConfig
request 就是HttpServletRequest类的对象 javax.servlet.http.HttpServletRequest
response 就是HttpServletResponse类的对象 javax.servlet.http.HttpServletResponse
application 就是ServletContext类的对象 javax.servlet.ServletContext
session 就是HttpSession类的对象 javax.servlet.http.HttpSession

什么是内置对象

在JSP中通常会用到上述的九个对象,为了避免在JSP中出现繁琐定义对象的语句,索性直接先定义好上述的九个对象,并且各自给对象起了名字,当我们用的时候,无需再定义对象,如HttpServletRequest request = new HttpServletRequest(),直接用request对象就可以了。而且JSP的本质就是Servlet ,我们写的JSP页面都会被翻译成Servlet去执行,可以这么说,JSP和Servlet中的对象是通用的。所以在Servlet中域对象中存储的值,在JSP中直接就可以获得。这是非常方便的。
ServletConfig、HttpServletRequest、HttpServletResponse 、ServletContext、HttpSession 的学习 请点击这儿学习 。

PageContext域对象与pageContext内置对象

PageContext是javaweb四大域对象之一,又称为page域,而且只有在JSP中有,Servlet没有 . PageContext作为内置对象,同时也是域对象之一,能够存取数据。而且PageContext一个顶九个,非常重要 。

  • 在一个jsp页面中共享数据!这个域是在当前jsp页面和当前jsp页面中使用的标签之间共享数据!
abstract  java.lang.Object getAttribute(java.lang.String name) 
          Returns the object associated with the name in the page scope or null if not found. 
abstract  void setAttribute(java.lang.String name, java.lang.Object value) 
          Register the name and value specified with page scope semantics.           
  • (page域特有)这个域对象可以代理其他域,能够向其他域中存取东西pageContext.setAttribute("xxx", "XXX", PageContext.SESSION_SCOPE)
abstract  java.lang.Object getAttribute(java.lang.String name, int scope) 
         Return the object associated with the name in the specified scope or null if not found. 
abstract  void setAttribute(java.lang.String name, java.lang.Object value, int scope) 
          Register the name and value specified with appropriate scope semantics. 
  • (page域特有)全域查找(重要),在四大域中都能查找。 从小到大查找,小域优先大域。
abstract  java.lang.Object findAttribute(java.lang.String name) 
          Searches for the named attribute in page, request, session (if valid), and application scope(s) in order and returns the value associated or null. 
  • (pageContext内置对象特有)一个顶九个,能够获取其他的8个内置对象,也就是说,PageContext一个内置对象就可以当九个内置对象用。
abstract  JspWriter getOut() 
          The current value of the out object (a JspWriter). 
abstract  java.lang.Exception getException() 
          The current value of the exception object (an Exception). 
abstract  java.lang.Object getPage() 
          The current value of the page object (In a Servlet environment, this is an instance of javax.servlet.Servlet). 
abstract  ServletRequest getRequest() 
          The current value of the request object (a ServletRequest). 
abstract  ServletResponse getResponse() 
          The current value of the response object (a ServletResponse). 
abstract  ServletConfig getServletConfig() 
          The ServletConfig instance. 
abstract  ServletContext getServletContext() 
          The ServletContext instance. 
abstract  HttpSession getSession() 
          The current value of the session object (an HttpSession). 

javaweb四大域对象与jsp九大内置对象

点击这儿

javaBean解析

什么是javabean ?

JavaBean是一种规范,也就是对类的要求。要求如下:

  • 必须要为成员提供get/set方法(也就是读方法和写方法)(两者只提供一个也是可以的)。
    • 对于有get/set方法的成员变量称之为属性
    • 属性名是由set/get方法决定的,不是由成员名字决定的。 比如 String name ; public void setUserName(){....}。属性名是userName,而不是name .
    • 有get/set方法的成员,但是没有成员也是可以的。这也是有属性
  • 必须要有默认构造器(没参的)
  • boolean类型的属性,它的读方法可以是is开头,也可以是get开头!
public class Person {
	private String name;  //成员
	private int age; 
	private boolean bool;	  //boolean类型成员
	public boolean isBool() {   //读方法
		return bool;
	}
	public void setBool(boolean bool) {
		this.bool = bool;
	}
	public String getId() {  // 就算没有成员id,也是有id属性的
		return "fdsafdafdas";
	}
	public String getUserName() {
		return name;
	}
	public void setName(String username) {  //就算成员名字是name,但是属性名字还是userName 。
		this.name = name;
	}
	public int getAge() {  //读方法
		return age;
	}
	public void setAge(int age) { //写方法
		this.age = age;
	}
	public Person() {  //必须有默认的无参的构造函数
		super();
		// TODO Auto-generated constructor stub
	}
	public String toString() {
		return "Person [name=" + name + ", age=" + age + ", gender=" + gender
				+ "]";
	}
	public Person(String name, int age, String gender) {
		super();
		this.name = name;
		this.age = age;
		this.gender = gender;
	}
}

操作javaBean的方法(一): 内省(一般不用)

内省,自我反省。 底层依赖的是反射 ,通过反射来操作javabean 。

如定义上述JavaBean(Person类),其成员是私有的。当然可以通过反射去访问Person类的私有成员,但是有危险。 一般都是通过get/set方法来操作私有成员 。 内省的目标就是得到JavaBean属性的读、写方法(get/set)。 这样就能通过set/get方法操作javabean. 通过内省操作javabean的方式是这样的:

  1. 通过内省类Introspector的getBeanInfo方法返回BeanInfo 对象
  2. 通过接口BeanInfo的getMethodDescriptors() 方法得到所有属性描述符对象PropertyDescriptor
  3. 通过类PropertyDescriptor的getReadMethod() 和getWriteMethod()方法,也就是get/set方法

具体API的方法如下:(javaSE6.0 API)

static BeanInfo getBeanInfo(Class<?> beanClass) 
          在 Java Bean 上进行内省,了解其所有属性、公开的方法和事件。 
 PropertyDescriptor[] getPropertyDescriptors() 
          获得 beans PropertyDescriptor。  
 Method getReadMethod() 
          获得应该用于读取属性值的方法。 
 Method getWriteMethod() 
          获得应该用于写入属性值的方法。 

操作javaBean的方法(二): 使用Commons-beanutils 工具(方便简单,常用)

使用内省的方法固然能行,但是太过繁琐 。 commons-beanutils这个工具, 它底层使用了内省,对内省进行了大量的简化! 所以要导入这个工具

  • commons-beanutils-1.8.3.jar 下载
  • commons-logging-1.1.1.jar 下载

下面的代码完美演示了Commons-beanutils 工具对javabean的操作。 (javabean 用上述的Person类)

/**
* 使用BeanUtils工具来操作User类(javabean)
*/
import org.apache.commons.beanutils.BeanUtils;
public void fun1() throws Exception {
	/**
    *    反射
	*/
	String className = "包.Person";
	Class clazz = Class.forName(className);
	Object bean = clazz.newInstance();
	/**
    *   利用setProperty设置 属性
	*/	
	BeanUtils.setProperty(bean, "name", "张三");
	BeanUtils.setProperty(bean, "age", "23");   //会自动将字符串转换成整形 。
	BeanUtils.setProperty(bean, "gender", "男");   //就算是Person中没有gender这个属性,一样不会报错
	/**
    *   利用getProperty获取 属性值
	*/		
	String age = BeanUtils.getProperty(bean, "age");
	System.out.println(age); //输入单个属性
	System.out.println(bean);  //调用Person中的toString()方法整体输出
	}
/**
*把map中的属性直接封装到一个bean中
*把map的数据封装到一个javabean中!要求map的key与bean的属性名相同!
*/
public void fun2() throws Exception {
	/**
    * 创建Map  
	*/
	Map<String,String> map = new HashMap<String,String>();
	map.put("username", "zhangSan");
	map.put("age", "123");
	/**
    * 新建bean对象
	*/		
	Person person = new Person();
	/**
    *  map中的属性直接封装到bean中
	*/
	BeanUtils.populate(person, map);		
	System.out.println(person);
}
/**
* 把map中的数据封装到Person中的第二种形式。 更加简化了代码
*/
/**
* 编写CommonUtils类 
*/
public class CommonUtils {
	/**
	 * 生成不重复的32位长的大写字符串
	 */
	public static String uuid() {
		return UUID.randomUUID().toString().replace("-", "").toUpperCase();
	}
	
	/**
	 * 把map转换成指定类型的javaBean对象
	 */
	public static <T> T toBean(Map map, Class<T> clazz) {
		try {
			/*
			 * 1. 创建指定类型的javabean对象
			 */
			T bean = clazz.newInstance();
			/*
			 * 2. 把数据封装到javabean中
			 */
			BeanUtils.populate(bean, map);
			/*
			 * 返回javabean对象
			 */
			return bean;
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}
}
/**
* 把map中的数据封装到Person中
*/
public void fun3() {
		Map<String,String> map = new HashMap<String,String>();
		map.put("username", "zhangSan");
		map.put("password", "123");
        /**
        * 一句代码完成封装
        */
		User user = CommonUtils.toBean(map, User.class);
		System.out.println(user);
	}

jsp中通过标签操作javaBean (过时的东西)

<jsp:useBean> <jsp:setProperty><jsp:getProperty>,这三个标签在如今的model II 年代已经过时了 。 想学的自行百度

EL表达式初窥门径

EL是什么? EL的作用是什么

JSP2.0要求把html和css分离、要把html和javascript分离、要把Java脚本替换成标签 。 而El表达式就是要替换掉java脚本中的输出脚本<%=...%>,也就是说EL表达式只能做输出用 。 使用EL标签的好处就是非java人员也可以使用,EL全程是“Expression Language ”

EL的语法

如果要输出的结果是一个对象中的属性,可以利用[].来访问该属性,就相当于调用get方法: 格式为: ${对象.属性}或者${对象["属性"]}。当属性不是有效的java变量名称时,只能用${对象["属性"]}这种形式。

  • 操作list和数组: ${list[0]}
  • 操作bean属性 : ${person.name}、${person[‘name’]} ,称为javaBean导航
  • 操作Map的值:${map.key}、${map[‘key’]}

EL的11大内置对象

域相关的内置对象

内置对象 相关功能
pageScope 能够输出各自域总的数据
requestScope
sessionScope
applicationScope
如果是`${xxx}`这种形式,就是全域查找名为xxx的属性,如果不存在,输出空字符串,而不是null。四个域都存在,按照小的来。千万不要当成是输出xxx字符串。
/**
* Class Address
*/
public class Address {
	private String city;
	private String street;
	public String getCity() {
		return city;
	}
	public void setCity(String city) {
		this.city = city;
	}
	public String getStreet() {
		return street;
	}
	public void setStreet(String street) {
		this.street = street;
	}
	@Override
	public String toString() {
		return "Address [city=" + city + ", street=" + street + "]";
	}	
}
/**
* Class Person
*/
public class Employee {
	private String name;
	private double salary;
	private Address address;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	public Address getAddress() {
		return address;
	}
	public void setAddress(Address address) {
		this.address = address;
	}
	@Override
	public String toString() {
		return "Employee [name=" + name + ", salary=" + salary + ", address="
				+ address + "]";
	}
}
/**
* jsp中输出Person的地址信息
*/
<%
	Address address = new Address();
	address.setCity("昆明");
	address.setStreet("昆明理工大学");
	
	Person p = new Person();
	p.setName("李小四");
	p.setSalary(123456);
	p.setAddress(address);
    <!--request域存数据-->
	request.setAttribute("person", p);
%>
    <!--使用EL表达式输出-->
    ${requestScope.emp.address.street }

其他的内置对象

  • param: Map<String, String[]>类型,用来获取请求参数。参数的名字就是键的名字。类似于${request.getparameter()},不同的是,即使参数不存在,返回空字符串,而不是null。 用法为${param.name}
  • paramValues:Map<String, String[]>类型,也是用来获取参数,当一个参数名对应多个参数值的时候使用。多个参数值以字符串数组的形式存在。所以用法大多数是paramValues.hobby[0]
  • header : Map<String,String>类型,用来获取请求头 ,如获取主机名:${header.Host}
  • headerValues: Map<String,String>类型,用来获取多值请求头 ,如获取accept-language标头的第一个值${headerValue["accept-language"][0]}
  • initParam :Map<String,String>类型。它对应web.xml文件中的参数 。 如获取XXXX,${initParam.aaa}
<context-param>
  	<param-name>aaa</param-name>
  	<param-value>XXX</param-value>
  </context-param>
  • cookie : Map<String,Cookie>类型,其中key是Cookie的名字,而值是Cookie对象本身。 例如要获取cookie名字为uname的名字和值${cookie.unam.name}${cookie.unam.value}
  • pageContext: PageContext类型 。在前文中学过,pageContext这个内置对象是一个顶九个,可以获得其他8个内置对象,所以输出的时候也可以用来输出其他内置对象的东西。

${pageContext.request.contextPath},先获得request对象,然后在调用request的contextpath方法获取到的结果是 :/项目名。 如获取session的ID${pageContext.session.id }

注意的是:项目名字可能会更改,那么代码中含有项目名的路径就需要更改,所以,代码有项目名的路径,一般都要用${pageContext.request.contextPath}这种方法来获取项目名,如超链接中: <a href="${pageContext.request.contextPath }/文件名/XX.jsp">点击这里</a>。 表单中:<form action="${pageContext.request.contextPath }/文件名/XXX" method="post">

EL运算符

EL作为输出的表达式,当然用可以进行计算,如${1+3} 。常见的运算符几乎和常见的运算一样,不在累赘 。

官方的EL函数库与自定义EL函数

什么是EL函数库? 怎么使用 ?

EL函数库是由第三方对EL的扩展,JSTL的函数库最是出名。EL函数库里定义了一些有返回值的静态方法,然后通过EL来调用它们,这些函数库里面的函数是定义好的,可以说就是官方的函数,当然我们可以自己定义函数库。官方的函数包含了很多对字符串的操作方法,以及对集合对象的操作。

JSP页面导入函数库

因为是第三方的函数库,所以在JSP页面中要使用函数库中的函数,需要使用taglib指令导入函数库
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>,其中 prefix和uri的值其实就是 如果用的MyEclipse开发工具的话,我们没有必须要导入这个包,因为在发布到服务器上时候会在tomcat目录下的web-inf下lib自动存在关于jsf的相关包,所以不需要人为的导入什么jar包。我们做的只是在jsp页面添加这个Page指令而已 。在这几个关于jsf的包中,打开jstl-版本号.jar-->META-INF下的fn.tld。 在这里面能够发现fnhttp://java.sun.com/jsp/jstl/functions。 这就是这两个参数的由来。

函数库中函数 与 EL调用这些函数

函数库中的函数如下:

  • String toUpperCase(String input):把参数转换成大写
  • String toLowerCase(String input):把参数转换成小写
  • int indexOf(String input, String substring):从大串,输出小串的位置!
  • boolean contains(String input, String substring):查看大串中是否包含小串
  • boolean containsIgnoreCase(String input, String substring):忽略大小写的,是否包含
  • boolean startsWith(String input, String substring):是否以小串为前缀
  • boolean endsWith(String input, String substring):是否以小串为后缀
  • String substring(String input, int beginIndex, int endIndex):截取子串
  • String substringAfter(String input, String substring):获取大串中,小串所在位置后面的字符串
  • substringBefore(String input, String substring):获取大串中,小串所在位置前面的字符串
  • String escapeXml(String input):把input中“<”、">"、"&"、"'"、""",进行转义
  • String trim(String input):去除前后空格
  • String replace(String input, String substringBefore, String substringAfter):替换
  • String[] split(String input, String delimiters):分割字符串,得到字符串数组
  • int length(Object obj):可以获取字符串、数组、各种集合的长度!
  • String join(String array[], String separator):联合字符串数组!

用EL表达式调用这些函数的案例:

<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
…
String[] strs = {"a", "b","c"};
List list = new ArrayList();
list.add("a");
pageContext.setAttribute("arr", strs);
pageContext.setAttribute("list", list);
%>
${fn:length(arr) }<br/><!--3-->
${fn:length(list) }<br/><!--1-->
${fn:toLowerCase("Hello") }<br/> <!-- hello -->
${fn:toUpperCase("Hello") }<br/> <!-- HELLO -->
${fn:contains("abc", "a")}<br/><!-- true -->
${fn:containsIgnoreCase("abc", "Ab")}<br/><!-- true -->
${fn:contains(arr, "a")}<br/><!-- true -->
${fn:containsIgnoreCase(list, "A")}<br/><!-- true -->
${fn:endsWith("Hello.java", ".java")}<br/><!-- true -->
${fn:startsWith("Hello.java", "Hell")}<br/><!-- true -->
${fn:indexOf("Hello-World", "-")}<br/><!-- 5 -->
${fn:join(arr, ";")}<br/><!-- a;b;c -->
${fn:replace("Hello-World", "-", "+")}<br/><!-- Hello+World -->
${fn:join(fn:split("a;b;c;", ";"), "-")}<br/><!-- a-b-c -->

${fn:substring("0123456789", 6, 9)}<br/><!-- 678 -->
${fn:substring("0123456789", 5, -1)}<br/><!-- 56789 -->
${fn:substringAfter("Hello-World", "-")}<br/><!-- World -->
${fn:substringBefore("Hello-World", "-")}<br/><!-- Hello -->
${fn:trim("     a b c     ")}<br/><!-- a b c -->
${fn:escapeXml("<html></html>")}<br/> <!-- <html></html> -->

自定义EL函数库

自定义EL函数库的步骤

  1. 写一个类,写一个有返回值的静态方法
  2. 编写xxx.tld文件(案例以kmust.tld文件为例)。把xxx.tld文件放在/WEB-INF目录下
  3. 在页面中添加taglib指令,导入自定义标签库

第一步:写一个有返回值的静态方法的类

/**
* Class MyFunctions.java
* 这个类中写有返回值的静态方法,也就是我们自定义的函数
*/
package cn.kmust.fn;
public class KmustFunctions {
	public static String func1() {
         return "这是我自己定义的函数" ;
	}
}

第二步: 编写kmust.tld文件。把kmust.tld文件放在/WEB-INF目录下

<?xml version="1.0" encoding="UTF-8" ?>

<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
  version="2.0">
    
  <tlib-version>1.0</tlib-version>
  <short-name>it</short-name>
  <uri>http://www.kmust.cn/el/functions</uri>
  
  <function>
    <name>func1</name>
    <function-class>cn.kmust.fn.MyFunction</function-class>
    <function-signature>java.lang.String func1()</function-signature>
  </function>
  
</taglib>

第三步 : 在页面中添加taglib指令,导入自定义标签库

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="it" uri="/WEB-INF/tlds/kmust.tld" %>
<html>
  <body>
<h1>${it:func1() } </h1>
  </body>
</html>

JSTL标签库

什么是JSTL标签库

JSTL是Apache对EL表达式的扩展,依赖EL 。JSTL是标签语言。JSP在2.0以后开始放弃java脚本,EL表达式代替<%=...%>解决了输出的问题。而JSTL标签将要代替<% ...%> 解决另外的Java脚本问题。

导入标签库

同EL函数库的导入相似,如果使用MyEclipse开发工具,则不需要人为导入jar包,因为项目发布到Tomcat时,MyEclipse会在lib目录下存放jstl的jar包。我们只需要在JSP页面中使用taglib指令导入标签库即可。
jstl一共有四个标签库,分别是:

  • core: 核心标签库 (重点)。 因为前缀是c,所以又叫做c标签库
  • fmt: 格式化标签库 (里面有两个重要标签)。因为前缀是fmt,所以又叫做c标签库
  • SQL: 数据库标签库 (过时了)
  • XML: xml标签库 (过时了)

以导入core核心标签库为列: <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>

core标签库常用标签

  1. out标签<c:out>:输出

    • value属性:可以是字符串常量,也可以是EL表达式
    • default属性:当输出的内容为null时,会输出defaulst指定的值
    • escapeXml属性: 默认为true,表示转义
  2. set标签<c:set>:设置(可以创建域的属性)

    • var属性:变量名
    • value属性: 变量值,可以是EL表达式
    • scope属性: 域,如果不写,默认为page域。 可选的值: page、request、session、application
<c:set var="code" value="<script>alert('hello');</script>" scope="request"/> // 在request域中添加name为a,值为弹窗hello的数据
<c:out value="${code }" /> //全域查找,输出nme为a的值。 并且把<script>标签给转义了。所有直接输出字符串,而不是弹窗
  1. remove标签<c:remove>:删除域变量
    • var属性: 变量名
    • scope属性 : 如果不给出scope这个属性,表示删除所有域中的该名称的变量;如果指定了域,只删除该域的变量
  2. URL标签<c:url>:指定路径
    • var属性: 指定变量名,一旦添加了这个属性,那么URL标签就不会再输出到页面,而是把生成的URL保存到域中
    • value属性: 指定的路径。 会在路径前面自动添加项目名<c:url value="/index.jsp"> = ${pageContext.request.contextpath}/index.jsp=/项目名字/index.jsp。 所以超链接或者表单中的路径,可以使用前两种都可以。
    • scope属性 : 与var属性一起使用把URL保存到域中。
<a href="<c:url value='/index.jsp'/>">点击这里回到主页</a>
<c:url value='/index.jsp'>
  <c:param name="name" value="张三"/> //加了参数,如果参数包含中文,则自动URL编码 ,输出:/项目名/index.jsp?username=A%E%G%D%G%D%G%D%
</c:url>
  1. if标签<c:if>:对应java中的if语句
    • <c:if test="布尔类型">...</c:if>,当test为值时,执行标签体内容!
<c:if test="${empty param.name }"> //参数的name不为空
	输出hello  
</c:if>
  1. choose标签<c:choose>:对应Java中的if/else
    <c:choose>
      <c:when test="">...</c:when>
      <c:when test="">...</c:when>
      <c:when test="">...</c:when>
       ... 
      <c:otherwise> ...</c:otherwise>
    </c:choose>
    等同与
    if(...) {
    } else if( ....) {
    } else if( ....) {
    } else if( ....) {
    } ...
    else { ...}
  1. forEach标签<c:forEach>:用来循环遍历数组、集合。可以用计数方式来循环
    • var:循环变量
    • begin:设置循环变量从几开始。
    • end:设置循环变量到几结束。
    • step:设置步长!等同与java中的i++,或i+=2。step默认为1
<c:forEach var="i" begin="1" end="10" step="2">//for循环
	${i }<br/>
</c:forEach>

用来输出数组、集合

  • items:指定要循环谁,它可以是一个数组或一个集合
  • var:把数组或集合中的每个元素赋值给var指定的变量。
<%
	String[] strs = {"one", "two"};
	request.setAttribute("strs", strs);
%>
<c:forEach items="${strs }" var="str">//输出数组和集合
 ${str }<br/>
</c:forEach>

循环状态

  • count:循环元素的个数
  • index:循环元素的下标
  • first:是否为第一个元素
  • last:是否为最后一个元素
  • current:当前元素
<%
	ArrayList<String> list = new ArrayList<String>();
	list.add("一");
	list.add("二");
	list.add("三");
	
	pageContext.setAttribute("list", list);
%>
<c:forEach items="${list }" var="ele" varStatus="vs">    //循环状态
	${vs.index} ${vs.count } ${vs.first } ${vs.last } ${vs.current }<br/>
</c:forEach>

fmt标签库常用标签

fmt标签库是用来格式化输出的,通常需要格式化的有时间和数字

  1. 对时间的格式化 :<fmt : formatDate value="" pattern="" />
    • value: 指定一个Date类型的变量
    • pattern: 用来指定输出的模板 ,例如yyyy-MM-dd HH:mm:ss
<%
	Date date = new Date();
	request.setAttribute("date", date);
	
%>
<fmt:formatDate value="${requestScope.date }" pattern="yyyy-MM-dd HH:mm:ss"/>   // 按照给定的格式输出时间
  1. 对数字的格式化:
<%
	request.setAttribute("num1", 3.1415926);
%>
<fmt:formatNumber value="${requestScope.num1 }" pattern="0.000"/><br/> //按照0.000保留小数点后面的位数,四舍五入,不足补0
<fmt:formatNumber value="${requestScope.num1 }" pattern="#.###"/>  //按照#.###保留小数点后面的位数,四舍五入,不足不补0 

自定义标签

自定义标签步骤:

  1. 标签处理类
    需要实现SimpleTag接口(javaSE下),其接口下的方法:
 void doTag()   //每次执行标签时都会调用这个房
          Called by the container to invoke this tag. 
 JspTag getParent()  //返回父标签
          Returns the parent of this tag, for collaboration purposes. 
 void setJspBody(JspFragment jspBody)  //设置 标签体 
          Provides the body of this tag as a JspFragment object, able to be invoked zero or more times by the tag handler. 
 void setJspContext(JspContext pc)  //设置jsp上下文对象,儿子就是PageContext,一般都是用pageContext
          Called by the container to provide this tag handler with the JspContext for this invocation. 
 void setParent(JspTag parent) 
          Sets the parent of this tag, for collaboration purposes.          
public class MyTag1 implements SimpleTag {
	private PageContext pageContext;
	private JspFragment body;
	/**
	 * 所有的setXxx()方法都会在doTag()方法之前被tomcat调用!
	 * 所在doTag()中就可以使用tomcat传递过来的对象了。
	 */
	public void doTag() throws JspException, IOException {
		pageContext.getOut().print("Hello Tag!");
	}
	public JspTag getParent() {
		return null;
	}
	public void setJspBody(JspFragment body) {
		this.body = body;
	}
	public void setJspContext(JspContext context) {	
		this.pageContext = (PageContext) context;
	}
	public void setParent(JspTag arg0) {}
}

标签处理类的这些方法都是由Tomcat调用:过程如下;

  • 当容器(Tomcat)第一次执行到某个标签时,会创建标签处理类的实例
  • 然后调用setJspContext(JspXontext)方法,把当前JSP页面的pageContext对象传递给这个方法
  • 如果当前标签有父标签。那么使用父标签的标签处理类对象调用setParent(JspTag)方法
  • 如果标签有标签体,那么把标签体转换成JSPFragment对象,然后调用setJSPBody()方法
  • 每次执行标签时,都调用doTage()方法,它是标签处理方法
    实现SimpleTag接口过于繁琐,有专门的一个类SimpleTagSupport可以继承,只需要重写doTag()方法就可以了,因为这个类帮我们创建写好其他的方法。可以通过getXX()的方法获取其他方法。具体的方法如下;
 void doTag() 
          Default processing of the tag does nothing. 
static JspTag findAncestorWithClass(JspTag from, Class<?> klass) 
          Find the instance of a given class type that is closest to a given instance. 
protected  JspFragment getJspBody() 
          Returns the body passed in by the container via setJspBody. 
protected  JspContext getJspContext() 
          Returns the page context passed in by the container via setJspContext. 
 JspTag getParent() 
          Returns the parent of this tag, for collaboration purposes. 
 void setJspBody(JspFragment jspBody) 
          Stores the provided JspFragment. 
 void setJspContext(JspContext pc) 
          Stores the provided JSP context in the private jspContext field. 
 void setParent(JspTag parent) 
          Sets the parent of this tag, for collaboration purposes. 
  1. 编写tld文件,放在WEN-INF下
<tag>
  	<name></name> 指定当前标签的名称
  	<tag-class></tag-class> 指定当前标签的标签处理类!
  	<body-content></body-content> 指定标签体的类型
  </tag>

标签体的内容有如下四种;

内容 说明
empty 无标签体(常用)
scriptless 可以是EL表达式或者其他的标签或者字符串(常用)
JSP (不使用)
tagdependent (不使用)
  1. 页面中使用<%@taglib %>来导入tld文件
    <%@ taglib prefix= uri= %>

自定义标签进阶 1 : 不再执行标签下面的内容

可以设置,如果执行这个标签,后面的标签都会不执行。实现这一功能的方法是在标签处理类中的doTag()方法中使用SkippageException来结束! Tomcat会调用标签处理类的doTag()方法,然后Tomcat会得到SkipPageException,它会跳过页面其他内容

public void doTag() throws JspException, IOException {
		
		throw new SkipPageException();//抛出这个异常后,在本标签后面的内容,将看不到!
	}

自定义标签进阶 2 :标签属性

添加属性的步骤

  • 为标签处理类添加属性,属性需要一个set()方法,这个set()方法会在doTag()方法之前被Tomcat执行,所以doTag()中就可以使用属性了。
  • 在tld文件中对属性进行配置
<attribute>
  		<name>test</name> 指定属性名
  		<required>true</required> 指定属性是否为必需的
  		<rtexprvalue>true</rtexprvalue> 指定属性是否可以使用EL
</attribute>

自定义标签小案例 :

/**
* Class MyTag1
* 继承SimpleTagSupport类,重写doTag()方法。没有标签体
*/
package cn.kmust.tag;
public class MyTag1 extends SimpleTagSupport {
	public void doTag() throws JspException, IOException {
	   //因为这个SimpleTagSupport中早就为我们写好了出doTag()之外的其他方法,所以通过this.getXXX()即可获得其他方法的返回对象。
		this.getJspContext().getOut().print("Hello one !");  //通过getJspContext获得pageContext,然后getOut获得输出对象,通过print像页面输出 。
	}
}
/**
* Class MyTag2
* 有标签体
*/
package cn.kmust.tag;
public class MyTag3 extends SimpleTagSupport {
	public void doTag() throws JspException, IOException {
		Writer out = this.getJspContext().getOut();//获取当前jsp页面的输出流
		this.getJspBody().invoke(out);//执行标签体内容,把结果写到指定的流中,即页面上。
		//需要说明的是,invoke()的参数可以写成null, 如果是null的话,表示使用的就是当前页面的out !this.getJspBody().invoke(null);
	}
}
/**
* Class MyTag3
* 抛出SkipPageException异常,如果执行这个标签,则后面的标签都不会再执行了
*/
package cn.kmust.tag;
public class MyTag3 extends SimpleTagSupport {
	public void doTag() throws JspException, IOException {
		this.getJspContext().getOut().print("只能看到我,下面什么都没有!");
		throw new SkipPageException();//抛出这个异常后,在本标签后面的内容,将看不到!
	}
}

/**
* Class  MyTag4
* 带有一个属性
*/
package cn.kmust.tag;
public class MyTag4 extends SimpleTagSupport {
	private boolean test; //定义这个属性
	/*
	 * 这个方法会由tomcat来调用,并且在doTag()之前
	 */
	public void setTest(boolean test) {
		this.test = test;
	}
	public void doTag() throws JspException, IOException {
		if(test) {
			/*
			 * 执行标签体
			 */
			this.getJspBody().invoke(null);//如果传递的输出流为null,表示使用的就是当前页面的out!
		}
	}
}
/**
* tld文件的配置 。 名字是kmust-tag.tld  。 位置: WEN-INF/tlds
*/
<?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>kmust</short-name>
  <uri>http://www.kmust.cn/tags/it-1.0</uri> //这是uri是随便起的

  <tag>
  	<name>myTag1</name>
  	<tag-class>cn.kmust.tag.MyTag2</tag-class>  //标签处理类的位置
  	<body-content>empty</body-content>  //没有标签体
  </tag> 
  
  <tag>
  	<name>myTag2</name>
  	<tag-class>cn.kmust.tag.MyTag3</tag-class>
  	<body-content>scriptless</body-content>  //有标签体
  </tag>
  
  <tag>
  	<name>myTag3</name>
  	<tag-class>cn.kmust.tag.MyTag4</tag-class>
  	<body-content>empty</body-content>
  </tag>
  
  <tag>
  	<name>myTag4</name>
  	<tag-class>cn.kmust.tag.MyTag5</tag-class>
  	<body-content>scriptless</body-content>
  	<attribute> //属性
  		<name>test</name>
  		<required>true</required>
  		<rtexprvalue>true</rtexprvalue>
  	</attribute>
  </tag>
</taglib>
/**
* jsp页面中利用自定义的标签
*/
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="it" uri="/WEB-INF/tlds/kmust-tag.tld" %>   //导入自定义的tld文件的路径
<html>
<body>
    <it:myTag4 test="${empty param.xxx }">  //test属性的值是 “参数” ,标签处理类中的定义是 :如果参数不为空,就执行标签体中的内容
        <it:myTag4/>   //标签体中也是一个自定义的标签,这个标签的意思是 : 执行我,后面的标签就不执行了
    </it:myTag4>

    <it:myTag1/>  //输出hello one! 

<%
     request.setAttribute("xxx", "zhangSan");
%>
    <it:myTag2>
       ${xxx }   //标签体中的这个EL表达式会到标签处理类中执行。
    </it:myTag3>
    <it:myTag2>
       我是张三的大哥  //标签体中的内容也可以是字符串
    </it:myTag3>

  </body>
</html>

补充知识1: 在web.xml中配置错误页面

 <error-page>
  	<error-code>404</error-code>
  	<location>/error404.jsp</location>
  </error-page>
  <error-page>
  	<error-code>500</error-code>
  	<location>/error500.jsp</location>
  </error-page>
  <error-page>
  	<exception-type>java.lang.RuntimeException</exception-type>
  	<location>/error.jsp</location>
  </error-page> 
当出现404时,会跳转到error404.jsp页面;
当出现RuntimeException异常时,会跳转到error.jsp页面;
当出现非RuntimeException的异常时,会跳转到error500.jsp页面。

补充知识 2 :javaweb四大域对象与JSP九大内置对象

PageContext(page域)、ServletRequest(request域)、HttpSession(session域)、ServletContext(application域)。page域、request域、session域、application域这几个词表明的是域的范围。 。Sevlet只能使用后三个域。JSP能使用所用的域。 简单来说, 域对象简单说就是能在Servlet之间(page域使用在JSP页面中)传递参数,因为要降低耦合度,所以我们创建的每个Servlet之间都是不能相互交流的,可以说,域对象是串联起多个Servlet的线,可以为多个Servlet之间的交流传递数据,这就显得比较重要。域对象内部有个Map,用来存取数据。

所有的域对象都有如下的方法:

java void setAttribute(java.lang.String name, java.lang.Object o) //保存值 
              Stores an attribute in this request. java.lang.
Object getAttribute(java.lang.String name) //获取值 
              Returns the value of the named attribute as an Object, or null if no attribute of the given name exists. 
void removeAttribute(java.lang.String name) //移除值 
              Removes an attribute from this request.` 

这四个域的范围不同:

  • PageContext : 这个范围是最小的,在JSP中只能在同一个页面中传递传递参数。
  • HttpServletRequest:一个请求创建一个request对象,所以在同一个请求中可以共享request,例如一个请求从AServlet转发到BServlet,那么AServlet和BServlet可以共享request域中的数据;
  • HttpSession:一个会话创建一个HttpSession对象,同一会话中的多个请求中可以共享session中的数据;浏览器不关,不会死亡,在一个浏览器不关闭的状态下,再打开相同的浏览器,还是不会死亡,但是打开不同的浏览器就不可以访问 。
  • ServletContext:范围最大的一个域 ,一个应用只创建一个ServletContext对象,所以在ServletContext中的数据可以在整个应用中共享,只要不启动服务器,那么ServletContext中的数据就可以共享;服务器不关,就不会死亡

需要注意的是,PageContext能够代理其他三个域,即,PageContext实现其他三个域的存取操作

JSP的九大内置与上述的四大域对象可以说没有太大的关系。内置对象是JSP已经定义好的对象,这些对象中有的是Servlet中类的实例化对象,有点的io中类的实例化对象等等 。都是jSp中可能常用到的类 。
需要注意的是,pageContext这个内置对象一个顶九个,可以获取其他八个内置对象。也就是说,一个pageContext内置对象就可以实现其他八个内置对象的功能 。

原文地址:https://www.cnblogs.com/zyuqiang/p/6850516.html