简介:
JSP****全名为Java Server Pages,中文名叫java<u>服务器</u>页面,其根本是一个简化的<u>Servlet</u>设计,它[1] 是由<u>Sun Microsystems</u>公司倡导、许多公司参与一起建立的一种<u>动态网页</u>技术标准。JSP技术有点类似ASP技术,它是在传统的<u>网页</u>HTML(<u>标准通用标记语言</u>的子集)文件(.htm,.<u>html</u>)中插入Java<u>程序段</u>(Scriptlet)和JSP标记(tag),从而形成JSP文件,后缀名为(*.jsp)。
为什么用JSP,因为普通的Servlet来响应HTML网页很麻烦!
必须在WEB.XML中注册映射,对于JSP,这些则由WEB容器自动完成。在Tomcat安装目录下的conf子目录下有一个全局的web.xml,打开看会发现有:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
九大对象
首先打开index_jsp.java,找到service()方法,可以看到前几行,Tomcat自动翻译jsp文件时,自动声明的几个类型的变量。
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
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;
由于这里的8个变量已经由Tomcat自动在转化jsp页面为Servlet的时候帮我们声明定义了,所以我们在jsp页面中使用这个些变量不需要在声明,直接使用,所以我们把这些变量叫隐含变量,对应的类型就是隐含对象。
包含这个8个以外还有一个总共有9个这样的变量:request,response,pageContext,session,application,config,out,page,exception
除以下三个不常用或不用外,其它的大量的用。
exception:
exception:此对象不能在一般JSP文件中直接使用,特殊情况下才能使用,只能在使用了“<%@ page isErrorPage="true "%>”的JSP文件中使用。
Page:
Page:代表了正在运行的由JSP文件产生的对应的Servlet类对象,不使用。
config:
Config:对象提供一些配置信息。获取jsp在web.xml文件中配置的初始化参数,基本不用,
<1>.request常用的方法:
1.request.getContextPath(); 获取项目的根目录 如:'/项目名'.
JSP常用语法:
1)JSP模版元素:
就是JSP中的<html>静态的标签,他对JSP的显示是非常必要的,但是对于JSP的编程人员来说,就不怎么关心这些部分内容,他主要有网页的美工来完成,它遵循的是html语法规则!
2)JSP表达式:
语法:<%="变量/常量"%>,字符串用" "号定界,无需分号结束,需要向浏览器输出内容时,可以使用JSP表达式,类似于Servlet中resposne.getWriter().write("哈哈");
3)JSP脚本片断:
语法:<%一行呀多行Java代码,以分号结束%>
生成的源码,都位于_jspService()方法中,多个JSP脚本片段可以相互访问,可以和html部分相互壳套,你中有我,我中有你。
4)JSP声明,我们在jsp页面写的java代码会出现在自动生成的servlet类的service方法中,在一个方法里怎么定义另一个方法呢?
语法:<%! 声明实例变量或普通方法%>
5)JSP****注释:
语法:<%-- JSP特有的注释 --%>
JSP****引擎不会将其翻译成Servlet源码,即忽略,这种注释浏览器端看不到
其它注释都会被翻译进Sercvlet源码,这种注释浏览器端看得到,并且可以在里边写jsp脚本代码。
6)四个域对象(pageContext,request,session,application)有几个非常常用的方法。
setAttribute(name, o)****:设置属性
getAttribute(name)****:根据属性名字,获取属性的值
getAttributeNames()****:获取所有属性名字,以枚举类型返回
removeAttribute(name)****:移除指定名字的属性
测试上面对象在不同领域的作用:
1.同一个页页中,给这四个对象的属性赋值,然后再取值,可以全取。
SetGetAttribute.jsp:
<body>
<%
pageContext.setAttribute("pageContext","pageContext");
request.setAttribute("request","request");
session.setAttribute("session","session");
application.setAttribute("application","application");
%>
<a href="SetGetAttribute2.jsp">到下一面看看获取的东西</a><br/>
<%=pageContext.getAttribute("pageContext") %><br/><br/>
<%=request.getAttribute("request") %><br/><br/>
<%=session.getAttribute("session") %><br/><br/>
<%=application.getAttribute("application") %>
</body>
2.跳转到另一页: SetGetAttribute2.jsp,可取后两个,前两个为null:
<%=pageContext.getAttribute("pageContext") %><br/><br/>
<%=request.getAttribute("request") %><br/><br/>
<%=session.getAttribute("session") %><br/><br/>
<%=application.getAttribute("application") %>
3,Servlet中获取jsp设置的属性值。
注:pageContext在servlet中不能用。
首先定义一个servlet类:
public class GetJspAttribute extends HttpServlet{
doGet(){
resp.getWriter().println(req.getAttribute("request"));
resp.getWriter().println(req.getSession().getAttribute("session"));
resp.getWriter().println(getServletContext().getAttribute("application")); //ServletContext就是jsp中的appliction
}; //页面用GET方法请求
}
其次,页请求:
SetGetAttribute.jsp:加入如下html代码
<a href="/myweb/GetJspAttribute">转到servlet获取</a>
从测试中总结四个域对象的作用域范围:
>>PageContext****:只能应用在当前面页的一次请求中
>>request****:只要在同一个请求中,不论该请求经过N个动态资源,只能是转发
** >>session****:只要在一次新会话中(浏览器不关),不论该请求经过N个动态资源,不论转发还是重定向,例如:购物车**
** >>application****:只要在当前web应用中,不论该请求经过N个动态资源,不论转发还是重定向,不论多个新会话,例如:在线访问计数器,QQ群聊室**
JSP指令用于提供整个JSP页面的相关信息以及用于JSP页面与容器之间的通信。
jsp指令有三种:
1page指令:用于设定整个JSP页面的属性和相关功能,page指令共有11个属性:**
1.1 contentType****属性和pageEncoding****属性:
contentType****属性指定JSP****页面的MIME(是一个网络标准,作用就是告诉浏览器这是一个什么文件,你应该用什么应用才能正确打开)和编码格式
<%@page contentType="text/html;charset=UTF-8"%>
pageEncoding****属性用来指定JSP****文件的编码格式(JSP文件保存时选择的编码格式)**
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
1.2 import:在JSP中引入Java的包和类,多个包之间以逗号隔开
1.3 session:指定当前页面是否能获得当前用户的session对象,缺省是true,如果指定为false,那么在该页面中无法使用session,使用的话会提示500错误。
<%@page session="true"%>,****后面还要讲的。
1.4 errorPage:如果当前页面发生异常,网页会重定向到errorPage所指定的页面进行处理
<%@ page errorPage="error.jsp" isErrorPage="false" %>当前页面发生异常,并且没有对异常进行捕获的时候,跳转到error.jsp页面,不指定errorPage的话,画面上直接显示异常的相关信息,这样对使用的用户不是很友好
1.5 isErrorPage:
允许指定的JSP页面为错误处理页面,配合exception,可以获取错误信息输出到jsp页面上
1.6 isELIgnored属性
用来标示是否支持EL表达式<%@page isELIgnored="true"%>不支持EL表达式,EL表达式会被当做普通的html文本
<%@page isELIgnored="false"%> 支持EL表达式。
2 include指令
静态包含:表示在JSP编译时插入一个包含文件或者代码的文件,是一种静态包含,静态包含(统一编译):
<%@ include file="included.jsp"%>
file属性的值是相对地址。多个jsp文件会合并起来生成一个jsp_servlet文件。
动态包含:(<jsp: include page="included.jsp"/>)多个jsp文件各自生成一个jsp_servlet文件。动态包含总是检查被包含页面的变化,静态包含不一定检查被包含页面的变化。
动态包含可带参数,静态包含不能带参数.如:
<jsp: include page="included.jsp">
<jsp:param value="内容" name="pageTitle"/>
</jsp:include>
在被引用的页面可以使用下面的方式获取参数并展示:
<%=request.getParameter("pageTitle")%>
扩展一下jsp标签,除了 <jsp: include以外,还有<jsp:forward page="path"></jsp:forward> 就是转发。
3 taglib指令
声明JSP文件使用了标签库(JSP标准标签库,第三方标签库,自定义标签库)
<%@ taglib prefix="c"uri="http://java.sun.com/jsp/jstl/core"%>声明使用JSTL的核心标签库taglib指令让用户能够自定义标签,后面会详细讲
jsp中的EL
EL全名为Expression Language EL语法很简单,它最大的特点就是使用上很方便。接下来介绍EL主要的语法结构:
${sessionScope.user.sex}
所有EL都是以${为起始、以}为结尾的。上述EL范例的意思是:从Session的范围中,取得
用户的性别。假若依照之前JSP Scriptlet的写法如下:
User user = (User)session.getAttribute("user");
String sex = user.getSex( );
两者相比较之下,可以发现EL的语法比传统JSP Scriptlet 更为方便、简洁。
EL 提供 . 和 [ ] 两种运算符来导航数据。下列两者所代表的意思是一样的:
${sessionScope.user.sex}等于${sessionScope.user["sex"]}
.和 [ ] 也可以同时混合使用,如下:
${sessionScope.shoppingCart[0].price}
回传结果为shoppingCart中第一项物品的价格。
不过,以下情况,两者会有差异:
(1)当要存取的属性名称中包含一些特殊字符,如. 或 – 等并非字母或数字的符号,就一定要使用 [ ],例如:
${user.My-Name }
上述是不正确的方式,应当改为:
${user["My-Name"] }
(2)我们来考虑下列情况:
${sessionScope.user[data]}
此时,data是一个变量,假若data的值为"sex"时,那上述的例子等于
${sessionScope.user.sex};
假若data 的值为"name"时,它就等于
${sessionScope.user.name}。
因此,如果要动态取值时,就可以用上述的方法来做,但. 无法做到动态取值。
(3)数组元素的获取用[]
<body>
<%
String[] arr={"Java Web开发典型模块大全","Java范例完全自学手册","JSP项目开发全程实录"}; //定义一维数组
request.setAttribute("book",arr); //将数组保存到request对象中
%>
<%
String[] arr1=(String[])request.getAttribute("book"); //获取保存到request范围内的变量
//通过循环和EL输出一维数组的内容
for(int i=0;i<arr1.length;i++){
request.setAttribute("requestI",i);
%>
${requestI}:${book[requestI]}<br> <!-- 输出数组中第i个元素 -->
<%} %>
上段代码中,必须将循环变量i保存到request范围内的空间里,否则将不能正确访问数组,因为EL是获取域空间的数据,不能直接获取其它值。
EL变量
EL存取变量数据的方法很简单,例如:${username}。它的意思是取出某一范围中名称为username的变量。
因为我们并没有指定哪一个范围的username,所以它的默认值会先从Page 范围找,假如找不到,再依序到Request、Session、Application范围。
假如途中找到username,就直接回传,不再继续找下去,但是假如全部的范围都没有找到时,就回传null,当然EL表达式还会做出优化,页面上显示空白,而不是打印输出NULL。
**属性范围(jstl名称) EL中的名称**
Page PageScope
Request RequestScope
Session SessionScope
Application ApplicationScope
**我们也可以指定要取出哪一个范围的变量:**
范例 说明
${pageScope.username} 取出Page范围的username变量
${requestScope.username} 取出Request范围的username变量
${sessionScope.username} 取出Session范围的username变量
${applicationScope.username} 取出Application范围的username变量
其中,pageScope*、requestScope、sessionScope和applicationScope都是EL 的隐含对象,由它们的名称可以很容易猜出它们所代表的意思,例如:${sessionScope.username}是取出Session范围的username 变量。这种写法是不是比之前JSP 的写法:
String username = (String) session.getAttribute("username");****容易、简洁许多
**EL自动转变类型:**
EL 除了提供方便存取变量的语法之外,它另外一个方便的功能就是:自动转变类型,我们来看下面这个范例:
${param.count + 20}
假若窗体传来count的值为10时,那么上面的结果为30。
之前在JSP 之中不能这样做,原因是从窗体所传来的值,它们的类型一律是String,所以当你接收之后,必须再将它转为其他类型,如:int、float 等等,然后才能执行一些数学运算,
下面是之前的做法:String str_count = request.getParameter("count");
int count = Integer.parseInt(str_count);
count = count + 20;
所以,注意不要和java的语法(当字符串和数字用“+”链接时会把数字转换为字符串)搞混淆喽。
不过有一点要注意的是如果你要用EL输出一个常量的话,字符串要加双引号,不然的话EL会默认把你认为的常量当做一个变量来处理,这时如果这个变量在4个声明范围不存在的话会输出空,如果存在则输出该变量的值。
属性(Attribute)与范围(Scope)
与范围有关的EL隐含对象包含以下四个:
pageScope、requestScope、sessionScope 和applicationScope,它们基本上就和JSP的pageContext、request、session和application一样,所以笔者在这里只稍略说明。
不过必须注意的是,这四个隐含对象只能用来取得范围属性值,即JSP中的getAttribute(String name),却不能取得其他相关信息,
例如:JSP中的request对象除可以存取属性之外,还可以取得用户的请求参数或表头信息等等。但是在EL中,它就只能单纯用来取得对应范围的属性值,
例如:我们要在session 中储存一个属性,它的名称为username,在JSP 中使用session.getAttribute("username") 来取得username 的值,
但是在EL中,则是使用${sessionScope.username}来取得其值的。
EL的 cookie
所谓的cookie是一个小小的文本文件,它是以key、value的方式将Session Tracking的内容记录在这个文本文件内,这个文本文件通常存在于浏览器的暂存区内。
JSTL并没有提供设定cookie的动作,因为这个动作通常都是后端开发者必须去做的事情,而不是交给前端的开发者。
假若我们在cookie 中设定一个名称为userCountry的值,那么可以使用${cookie.userCountry}来取得它。
initParam
就像其他属性一样,我们可以自行设定web站台的环境参数(Context),当我们想取得这些参数initParam就像其他属性一样,
我们可以自行设定web 站台的环境参数(Context),当我们想取得这些参数
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2eexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"version="2.4">
<context-param>
<param-name>userid</param-name>
<param-value>mike</param-value>
</context-param>
</web-app>
那么我们就可以直接使用${initParam.userid}来取得名称为userid,其值为mike 的参数。
下面是之前的做法:String userid = (String)application.getInitParameter("userid");
param和paramValues
在取得用户参数时通常使用一下方法,如表单,?aaa=xxx:
request.getParameter(String name)
request.getParameterValues(String name)
在EL中则可以使用param和paramValues两者来取得数据。
${param.name}
${paramValues.name}
这里param的功能和request.getParameter(String name)相同,而paramValues和
request.getParameterValues(String name)****相同。如果用户填了一个表格,表格名称为username,则我们就可以使用{pageContext}```来取得其他有关用户要求或页面的详细信息。下表列出了几个比较常用的部分
Expression 说明
${pageContext.request.queryString} 取得请求的参数字符串
${pageContext.request.requestURL} 取得请求的URL,但不包括请求之参数字符串,即servlet的HTTP地址。
${pageContext.request.contextPath} 服务的webapplication的名称
${pageContext.request.method} 取得HTTP的方法(GET、POST)
${pageContext.request.protocol} 取得使用的协议(HTTP/1.1、HTTP/1.0)
${pageContext.request.remoteUser} 取得用户名称
${pageContext.request.remoteAddr} 取得用户的IP地址
${pageContext.session.new} 判断session是否为新的,所谓新的session,表示刚由server产生而client尚未使用
${pageContext.session.id} 取得session的ID
${pageContext.servletContext.serverInfo} 取得主机端的服务信息
EL算术运算
<****body****>**
**<****h2****>****表达式语言**** - ****算术运算符</h2>**
**<****hr****>**
**<****table ****border****="1"**** bgcolor****="aaaadd">**
**<****tr****>**
**<****td****><****b****>****表达式语言****</****b****></****td****>**
**<****td****><****b****>****计算结果****</****b****></****td****>**
**</****tr****>**
**<!-- ****直接输出常量 -->**
**<****tr****>**
**<****td****>\****${1}</td>**
**<****td****>****${1}</td>**
**</****tr****>**
**<!-- ****计算加法 -->**
**<****tr****>**
**<td****>\****${1.2 + 2.3}</td>**
**<****td****>****${1.2 + 2.3}</td>**
**</****tr****>**
**<!-- ****计算加法 -->**
**<****tr****>**
**<****td****>\****${1.2E4 + 1.4}</td>**
**<****td****>****${1.2E4 + 1.4}</td>**
**</****tr****>**
**<!-- ****计算减法 -->**
**<****tr****>**
**<****td****>\****${-4 - 2}</td>**
**<****td****>****${-4 - 2}</td>**
**</****tr****>**
**<!-- ****计算乘法 -->**
**<****tr****>**
**<****td****>\****${21 * 2}</td>**
**<****td****>****${21 * 2}</td>**
**</****tr****>**
**<!-- ****计算除法 -->**
**<****tr****>**
**<****td****>\****${3/4}</td>**
**<****td****>****${3/4}</td>**
**</****tr****>**
**<!-- ****计算除法 -->**
**<****tr****>**
**<****td****>\****${3 div 4}</td>**
**<****td****>****${3 div 4}</td>**
**</****tr****>**
**<!-- ****计算除法 -->**
**<****tr****>**
**<****td****>\****${3/0}</td>**
**<****td****>****${3/0}</td>**
**</****tr****>**
**<!-- ****计算求余 -->**
**<****tr****>**
**<****td****>\****${10%4}</td>**
**<****td****>****${10%4}</td>**
**</****tr****>**
**<!-- ****计算求余 -->**
**<****tr****>**
**<****td****>\****${10 mod 4}</td>**
<td>${10 mod 4}</td>**
</tr>
<!-- 计算三目运算符 -->
<tr>
<td>\${(1==2) ? 3 : 4}</td>
<td>${(1==2) ? 3 : 4}</td>
</tr>
</table>
</body>
注意:在使用EL关系运算符时,不能够写成:
${param.password1} = = ${param.password2}
或者
${ ${param.password1 } = = ${ param.password2 } }
而应写成
${ param.password1 = = param.password2 }
Empty 运算符
Empty 运算符主要用来判断值是否为空(NULL,空字符串,空集合)。
${empty 变量名},若null,则返回显示true
自定义标签。
前面我们在jsp页面中,我们对集合或数组的显示,都是通过java的循环遍历来做的!
这个样子做,太麻烦,而且不利于前端美工和后端java程序员的分离,程序修改和维护起来都非常麻烦,我们希望的,java代码和前端页面完全分离!这个时候,EL标签就不够用了,比如循环遍历,if分支等等EL是做不到的!
首先我们想到的,能不能将循环,if分支也做成一个像EL的标签:
JDK为我们提供了自定义标签的接口

这是Java中标签规范的继承体系,实现Tag接口的我们叫做传统式标签库开发,这种开发模式略显发复杂,基本已经被SimpleTag式的简单式开发标签库给取代了。Java中提供了一个默认的实现类SimpleTagSupport来实现自定义标签,我们只要继承此类即可。
接下来我们就看自定义标签的开发步骤:
①编写标签功能的java类(标签处理器)
②编写标签库描述文件(.tld)
③中jsp页面中导入和使用自定义的标签
------首先我们先从自定义标签处理类开始,正如上文所说,这个类只有继承了SimpleTagSupport这个类可以省去省去重写SimpleTag接口中的一些方法。我们说个doTag()这个方法很重要,这个方法类似于java中的main方法一样,当jsp页面加载到我们定义的标签的时候就会过来调用这个方法。
public classMyTag extends SimpleTagSupport{
@Override
public void doTag() throwsJspException,IOException{
getJspContext().getOut().write("hello tag");
}
}
这是一个简单的标签处理类,具体的细节暂时不用关心,只需要知道,它负责向jsp页面输出字符串即可。
-----下面我们看看第二步,创建*tld文件。这个文件我们没有必要重新写一遍,到Tomcat服务器上的webapps/examples/WEB-INF/jsp2中复制一个过来,
修改名字存放到我们的项目中WEB-INF的任意子路径下。删除一些标签成如下内容:

我们看到这是一个XML文件,根元素为taglib,而taglib主要有以下几个子元素:
description //描述信息
tlib-version //指定标签库的版本号,基本不用我们操心
short-name //指定标签库的短名字,也是没什么用
uri //这是一个重要的子元素,是该标签库的唯一标识
tag //看名字就知道,这是定义标签的子元素,很重要
对于taglib这个根元素,我们主要关心他下面的uri和tag两个子元素,一个标签库可以由多个标签,也就是可以有多个tag标签。关于tag标签,主要有以下几个子元素:
1. name //该标签的唯一标识,很重要
2. tag-class //指定了处理该标签的类,也就是使用该标签谁给我返回结果
3. body-content //标签体,后面详说,很重要
4. 还可以有attribute //属性,后面介绍,很重要
-----使用标签库也是有两个步骤,首先导入标签库,然后引用标签。我们使用taglib编译指令导入标签库,具体格式如下:
<%@ taglib uri="tld文件中指定的唯一标识" prefix="指定标签前缀"%>
<body>
<指定标签前缀:tag--name/>
</body>
为标签添加属性
思路:1.标签类中,定义属性字段,并且是成员私有变量。2.属性操作,一般要定义一控制类,如TagController.java。3.tld文件中加<attribute></attribute>标签。
4.显示,jsp页中显示之。
例:
1.MyTag.java
会接收到控制类req.setAttribute()的map变量,再注入到标签中,当jsp文件调用时,会显示其数据。
@SuppressWarnings("unchecked")
public class MyTag extends SimpleTagSupport {
private String map; //标签属性名,该字段必须有set,get方法,这样规定的,没有为什么
public String getMap() {
return map;
}
public void setMap(String map) {
this.map = map;
}
@Override
public void doTag() throws JspException, IOException {
//getJspContext().getOut().println("Hello tag,我是自定义标签!");
PageContext pageContext =(PageContext) getJspContext();
HttpServletRequest hreq= (HttpServletRequest) pageContext.getRequest();
Map<String,Integer> maps = (Map<String, Integer>) hreq.getAttribute("map");
for(String key:maps.keySet()) {
pageContext.getOut().println("<tr>");
pageContext.getOut().println("<td>");
pageContext.getOut().println(key);
pageContext.getOut().println("</td>");
pageContext.getOut().println("<td>");
pageContext.getOut().println(maps.get(key));
pageContext.getOut().println("</td>");
pageContext.getOut().println("</tr>");
}
}
}
2.TagController.java
@WebServlet(urlPatterns = {"*.tag"})
public class TagController extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
String mn = req.getServletPath(); // 获取请求的Servlet地址如:/query.udo
mn = mn.substring(1); // 去掉'/',mn=query.udo
mn = mn.substring(0, mn.length() - 4); // mn=query
try {
Method method = this.getClass().getDeclaredMethod(mn, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 浏览器上(index.tag),访问到这个方法里来,该方法是该问数据库,用自定义标签显示之
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@SuppressWarnings("unused")
private void index(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Map<String,Integer> map = new HashMap<>();
map.put("张三",20);
map.put("李四",21);
map.put("小白", 22);
req.setAttribute("map", map);
req.getRequestDispatcher("/tag/FirstTag.jsp").forward(req, resp);
}
}
mytag.tld
<?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">
<description>A tag library exercising SimpleTag handlers.</description>
<tlib-version>1.0</tlib-version>
<!--标签的名字,不重要 -->
<short-name>mytag</short-name>
<!-- 标签的IP值,非常重要的,格式照URL写,它符串自定义 -->
<uri>http://mytag.com/core</uri>
<tag>
<description>Outputs Hello, World</description>
<!-- 标签唯一标识,很重要 ,应用时 <mytagfirst:mapdisplay map="map"/> -->
<name>mapdisplay</name>
<tag-class>cn.ybzy.mvcproject.tag.MyTag</tag-class>
<body-content>empty</body-content> <!-- empty单标签 -->
<attribute>
<name>map</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
FirstTag.jsp
<%@ taglib uri="http://mytag.com/core" prefix="mytagfirst" %>
<body>
<table border="1">
<mytagfirst:mapdisplay map="map111"/> <!--map是MyTag.java中定义private String map;它不是Map -->
</table>
</body>
测试运行: http://localhost:8080/mvcproject/index.tag 控制类中有转送到jsp页面的代码
显示 :
李四 21
张三 20
小白 22
自定义带标签体的标签
例:在上例的基础上改一下。
<mytag:hello map="map">
<tr>
<td>${name}</td>
<td>${age}</td>
</tr>
</mytag:hello>
---------1.mytag.tld文件改动不多。
上面定义的标签没有标签体,而此处应用开式为有标签体的标签,至于td元素中的内容,这是一个EL表达式,这里的意思就是在当前页面寻找共享数据名为name和age的数据,找到就获取其值,否则为“”。
tld文件中的改动不多,就是将body-content的值改动成scriptless,这表示标签体可以是静态的html,但是不能是jsp脚本。而我们之前一直是empty,它指定该标签是没有标签体的。
--------2.MyTag.java
for(String key:maps.keySet()) {
pageContext.getOut().println("<tr>");
pageContext.getOut().println("<td>");
pageContext.getOut().println(key);
pageContext.getOut().println("</td>");
pageContext.getOut().println("<td>");
pageContext.getOut().println(maps.get(key));
pageContext.getOut().println("</td>");
pageContext.getOut().println("</tr>");
}
//改为:
for(String key:maps.keySet()) {
pageContext.setAttribute("name", key);
pageContext.setAttribute("age", maps.get(key));
getJspBody().invoke(null); //立即写入
}
3.TagController.java不改。
4.FirstTag.jsp
有标签体的:body-content scriptless body-content
<table border="1">
<mytagfirst:mapdisplay map="map111">
<tr>
<td>${name }</td>
<td>${age }</td>
</tr>
</mytagfirst:mapdisplay>
</table>
jsp中的路径:
首先,建议的原则:尽量写绝对路径,不写相对路径.
所谓绝对路径就是要加上"应用名称的路径":http://localhost/mvcproject/ 加了"web应用名字"就是我们理解的绝对路径 ,没加,就是我们理解的相对路径,其实绝对路径和相对路径的本质在于"/"代表"根目录"时的实际位置不同理解:
在浏览器看来,根目录指的是服务器的根目录 http://localhost/
在 web 应用看来,根目录指的是自己的根目录 http://localhost/mvcproject/
***I在我们的项目中,什么时候"/"被认为是服务器根目录:http://localhost/
request获取路径的各种方法(包括HttpServletRequest等等的request形式)
与其路径的方式。
System.out.println(req.getContextPath());
System.out.println(req.getRequestURI());
System.out.println(req.getServletPath());
System.out.println(req.getSession().getServletContext().getContextPath());
chain.doFilter(req, resp);
结果:
/mvcproject
/mvcproject/main.jsp
/main.jsp
/mvcproject
<1>.jsp里的都是:
超链接:<a href="<%=resquest.getContextPath()/a.jsp">超链接</a>
<2>form 表单:<form action="<%=resquest.getContextPath()%>/a.jsp">
<3>Servlet里:
重定向:response.sendRedirect(resquest.getContextPath()+"/a.jsp");
注: 为了不出错,在上面的三种情况下,一定要写绝对路径:在前面加:resquest.getContextPath(),为什么不直接加项目名呢,因为项目名会有改的可能性。
***II什么时候"/"被认为是web应用自己的根目录:http://localhost/project/
<1>Servlet里的转发:
request.getRequestDispatcher("/a.jsp")
<2>web.xml里的servlet-mapping中的url映射:
<servlet-mapping>
<servlet-name>UserController</servlet-name>
<url-pattern>/a.jsp</url-pattern>
</servlet-mapping>
<3>自定义标签中的 / 也是这样。
测试:以超链接为例讲解,其它表单action,重定向resp.sendRedirect()也是这样:
文件存放路径如下:

1.全是jsp页面的情况下,相对路径不会出错:
a.jsp在根目录下,它要链到到b.jsp或c.jsp,写法如下:
<a href="./path/c.jsp">c.jsp</a>
./代表当前路径。
../代表上层路径
c.jsp要链接到上级目录的a.jsp
<a href="../a.jsp">a.jsp</a>
b.jsp 链接到c.jsp
同一目录下,可写成两种方式
<a href="c.jsp">c.jsp</a>或<a href="./c.jsp">c.jsp</a>
2.a.jsp转送到servlet的b.udo再转送到b.jsp再在b.jsp中转到c.jsp
a.jsp:
<a href="<%=request.getContextPath()%>/b.udo">由Servelt--b.udo转到b.jsp</a>
//用到request.getContextPath()的好处是,项目名改了不暴错。
<a href="/mvcproject/b.udo">由Servelt--b.udo转到b.jsp</a><br/>
Servlet:UserController.java
@WebServlet(urlPatterns={"*.udo"}) //"*.udo","*.sec"等用逗号隔开多个控制类,本例中一个。
public class UserController extends HttpServlet{
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String mn = req.getServletPath(); //获取请求的Servlet地址如:/query.udo
mn=mn.substring(1); //去掉'/',mn=query.udo
mn=mn.substring(0,mn.length()-4); //mn=query
Method method=this.getClass().getDeclaredMethod(mn,HttpServletRequest.class,HttpServletResponse.class);
method.invoke(this,req,resp);
}catch(Exception e) {
e.printStackTrace();
}
}
private void b(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("/path/b.jsp").forward(req,resp); //上面说了,这里的/代表/mvcproject,第二个/表示虚拟路径
}
}
b.jsp,如果按1,的代码中那样写,href="c.jsp",会出错。
这是b.jsp
<a href="/mvcproject/path/c.jsp">转到c.jsp</a>
MVC
要编写大型的web工程的话,
我们现有的编写模式会造成web应用的可扩展性较差,而且一但出现问题不能准确的定位出问题出在哪里。Java是一门应用设计模式比较广泛的语言。目前主流提出的23种设计模式均可在Java语言编写的程序中所应用。目前主流在Java Web应用中应用的最广泛的设计模式便是MVC模式,目前的主流Web框架大多也是基于MVC设计模式所编写的。
MVC模式在Java Web应用中的具体应用
所谓MVC,即Model-View-Controller。分三层
Model层:
Model指模型部分,一般在应用中Model层包括业务处理层Service和数据访问层Dao。数据访问层主要是对数据库的一些操作的封装。业务处理层主要是用作将从Controller层获取的数据和数据库的数据进行桥接。除此以外,对复杂业务逻辑进行处理,比如事务处理。
Controller层:
Controller指控制部分,一般是对View层提交的请求为其设置对应的Servlet进行特定功能的处理,这里的进行特定功能的处理一般是编写在Model中的业务处理层中的。Controller一般只是在Web应用中充当一个中介者的作用。
View层:
View指视图部分,这一部分的内容是展示给用户实际进行交互的,通常使用JSP和HTML进行构建(个人比较喜欢以HTML嵌入JSP的方式来构建网页)。
综上来说,一个小型完整的基于MVC设计模式的Web应用程序的处理流程应该如下:

(注意的几点:MVC模型中没有跨层操作,各层之间不应该有很强的耦合(面向接口编程),必须具有相对独立性,修改一个层的代码,不应该会影响到其他层代码跟着修改!)
一个应用小例子:
项目名:mvcproject
包:cn.ybzy.mvcproject. controller
.service
.dao
. model (如 javaBean:User)
.utils 工具类(如JdbcUtils.java:getConn,ConnClose等等)
.test 测试(Junit工具)
开发顺序:数据库DAO层----(先要有Bean-User 只少一个空构造函数)---->service层---视图层/控制层或两层交叉进行 ,若事务层有改动则又会下传到DAO层修改。
####数据库 mvcproject (utf8 utf8_general_ci innodb)
1.创建库mvcproject表users代码:
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id int(11) NOT NULL,
username varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
password varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
phone_no varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
address varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
reg_date datetime DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.连接数据库,用到连接池。见 'mvcproject---......utils---JdbcUtils.java'
连接池用到c3p0包,本人已有c3p0-0.9.5.2.jar包
c3p0依赖包:commons-dbutils-1.3.jar
commons-lang-2.4.jar
数据库驱动包:mysql-connector-java-5.1.46-bin 对应mysql5.x
mysql-connector-java-8.0.16-bin对应mysql8.x
本人包的来源:1.本人的私服 localhost:8081/nexus中下载。
2.钟洪发老师的课件,课件中还有c3p0.xxx.bin.zip包(已放在项目的同级文件夹下),该包的
doc--index.html中可找到配置c3p0-config.xml的文件代码。
所有包复制到‘项目'的lib下。
配置c3p0-config.xml ,该文件放在src根目录下,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<c3p0-config>
<named-config name="mysql">
<!-- 连接mysql数据库的基本必须的信息的配置,注意mysql8x才用到.cj. -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://xiongshaowen.com:3306/mvcproject</property>
<property name="user">root</property>
<property name="password">xiong</property>
<!-- 若数据库中的连接数量不足的时候,向数据库申请的连接数量 -->
<property name="acquireIncrement">5</property>
<!-- 初始化数据库连接池时连接的数量 -->
<property name="initialPoolSize">10</property>
<!-- 数据库连接池中的最小的数据库连接数 -->
<property name="minPoolSize">5</property>
<!-- 数据库连接池中的最大的数据库连接数 -->
<property name="maxPoolSize">100</property>
<!-- C3P0数据库连接池可以维护的Statement数量 -->
<property name="maxStatements">2</property>
<!-- 每个连接同时可以使用Statement的数量 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
3.测试连接成功否:
src--test:jdbcUtilsTest.java
junit5方法(手工创建的web项目,STSIDE自带这个功能,若是创建maven web工程,要在pom.xml中导入junit包),右击方法名---run---Junit test
问题:
获取数据库表时,时间出现异常,可用下面图片的红色部分代码加到c3p0-config.xml文件。

防止表单重复提交。
一种情况:表单提交成功以后,直接点击浏览器上回退按钮,不刷新页面,然后点击提交按钮再次提交表单。对数据库有操作,重复提交会给我们的数据库添加很多无意义,无效数据,根本原因:因为服务器在处理请求时,不会检查是否为重复提交的请求。
解决方案:使用一个token的机制
- token就是令牌的意思
-服务器在处理请求之前先来检查浏览器的token - token由服务器来创建,并交给浏览器,浏览器在向服务器发送请求时需要带着这个token
- 服务器处理请求前检查token是否正确,如果正确,则正常处理,否则返回一个错误页面**
-服务器所创建的token只能使用一次 - token一般使用一个唯一的标识
-在jsp页面,获取uuid作为token -
UUID:32位字符串,通常作为对象或者表的唯一标识,根据机器码和时间戳(从1970年1月1日开始到现在)生成,UUID含义是通用唯一识别码 (Universally Unique Identifier),javaJDK提供了UUID.randomUUID().toString()是一个自动生成主键的方法。它生成的值能保证对在同一时空中的所有机器都是唯一的,是由一个十六位的数字组成,表现出来的形式。
-UUID的唯一缺陷在于生成的结果串会比较长。
例:上面是jsp页面,下面是servlet,可以看的懂吧,不会这差劲吧!!!!!!
image.png
例子:网盘---可运行的web项目--mvc基础入门项目---javaCookieLoginMVC--mvcproject
/repeatsubmit/add.jsp
<%@page import="java.util.UUID"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
String uuid=UUID.randomUUID().toString();
session.setAttribute("uuid", uuid);
%>
<form action="<%=request.getContextPath()%>/add.rdo">
<input type="hidden" name="token" value="<%=uuid %>">
用户名:<input type="text" name="username"/>
<br><br>
密 码:<input type="text" name="password"/>
<br><br>
<input type="submit" value="提交表单"/>
</form>
</body>
</html>
RepeatSubmitController.java
import org.apache.catalina.manager.util.SessionUtils;
@WebServlet(urlPatterns = {"*.rdo"})
public class RepeatSubmitController extends HttpServlet{
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String token=req.getParameter("token");
HttpSession session =req.getSession();
String sessioUuid = (String)session.getAttribute("uuid");
session.removeAttribute("uuid");
if(token.equals(sessioUuid)) {
System.out.println("合法的请求,是第一次提交表单");//是第一次就进行下面的添加操作
User user = new User();
String xUsername = req.getParameter("username");
user.setUsername(xUsername);
user.setPassword(req.getParameter("password"));
user.setAddress(req.getParameter("address"));
user.setPhoneNo(req.getParameter("phoneNo"));
user.setRegDate(new Date()); // java.util.Date()
int rows = userService.save(user); // 返回一个数 1
if(rows<0) {
resp.sendRedirect(req.getContextPath() + "/error.jsp");
}
}else {
System.out.println("重复提交了表单!");
resp.sendRedirect(req.getContextPath() + "/repeatsubmit/error.jsp");
}
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
测试:打开浏览器输入:localhost:8080/mvcproject/repeatsubmit/add.jsp
输入参数admin admin 提交,再又回退到登陆或注册,删除页页再提交,造成重复提交,会产生垃圾数据到数据库。
}
有了上面的代码就不会重复提交了,把令牌相关代码,去掉,再测试,会产生很多垃圾,
不过,项目中已经有跳转,转送功能的程序不会产生这样的代码,因为一注册成功,就会跳到main.jsp,不成功转到error.jsp,这样不会造成很多垃圾数据到库中。
第二种情况:在提交表单时,如果网速较差,可能会导致点击提交按钮多次,这种情况也会导致表单重复提交。
解决方法:点击提交按钮之后,使按钮不可用。通过js完成

session实现验证码:


1.CaptchaUtils .java 画验证码图片工具类
package cn.ybzy.mvcproject.utils;
//画验证码图片工具类
public class CaptchaUtils {
/**
* 验证码的宽
*/
private int width;
/**
* 验证码的高
*/
private int height;
/**
* 验证码的字符个数
*/
private int num;
/**
* 验证码的字典,
*/
private String code;
/**
* 取随机数据的对象
*/
private static final Random ran = new Random();
/**
* 单列模式,只可实例化一个对象,实例多个等同于一个的。
*/
private static CaptchaUtils captcha;
private CaptchaUtils() { //私有空构造方法,外部不能创建对象,默认,字典 个数
code="0123456789abcdefghkij"; //字典
num=4; //验证码有六个字符
}
public static CaptchaUtils getInstance() {
if(captcha==null)
captcha = new CaptchaUtils();
return captcha;
}
/**
* 重新设置验证码的高,宽,个数,字典
* @param width
* @param height
* @param num
* @param code
*/
public void set(int width,int height,int num,String code) {
this.width = width;
this.height =height;
this.setNum(num);
this.setCode(code);
}
public void set(int width,int height) {
this.width = width;
this.height =height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
/**
* 生成字符
* @return
*/
public String generateCheckcode() {
StringBuffer cc=new StringBuffer(); //大量字符拼接,用到它,而不用String
for(int i= 0;i<num;i++) {
cc.append(code.charAt(ran.nextInt(code.length())));
}
return cc.toString();
}
public BufferedImage generateCheckImg(String checkcode) {
//创建一个图片对象
BufferedImage image =new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//获取图片对象的画笔
Graphics2D graphic = image.createGraphics();
graphic.setColor(Color.WHITE); //羰色为白色
graphic.fillRect(0, 0, width, height); //画一个白色背影的框
graphic.setColor(Color.black);
graphic.drawRect(0,0, width-1, height-1); //画一个框框
Font font = new Font("宋体",Font.BOLD+Font.ITALIC,(int)(height*0.8));//字体
graphic.setFont(font); //把字体放到画笔中
//画字符
for(int i=0;i<num;i++) {
graphic.setColor(new Color(ran.nextInt(155),ran.nextInt(255),ran.nextInt(255))); //设画笔色
graphic.drawString(String.valueOf(checkcode.charAt(i)), i*(width/num)+4, (int)(height*0.8));
}
//加一些点
for(int i=0;i<(width+height);i++) {
graphic.setColor(new Color(ran.nextInt(255),ran.nextInt(255),ran.nextInt(255))); //设画笔色
graphic.drawOval(ran.nextInt(width), ran.nextInt(height), 1, 1);
}
//加一些线
for(int i=0;i<2;i++) {
graphic.setColor(new Color(ran.nextInt(255),ran.nextInt(255),ran.nextInt(255))); //设画笔色
graphic.drawLine(0, ran.nextInt(height), width, ran.nextInt(height));
}
return image;
}
}
2.UserController.java 画验证码图片,并保存验证码进session空间里,等下让login()方法调用,判断用户输入与session的验证码是否一样。
private void drawCheckCode(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("image/jpg"); //设置传送文件格式,为图片
int width = 100;
int height=30;
//具体画验证码
CaptchaUtils captcha=CaptchaUtils.getInstance();
captcha.set(width, height);
String cc=captcha.generateCheckcode(); //拿到验证码
HttpSession session= req.getSession();
session.setAttribute("checkCode", cc); //验证码放session里了,等下要用到
OutputStream out = resp.getOutputStream();
ImageIO.write(captcha.generateCheckImg(cc),"jpg",out);
}
