jsp页面与MVC思想

简介:

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,则我们就可以使用{param.username}来取得用户填入的值。 看到这里,大家应该很明确EL表达式只能通过内置对象取值,也就是只读操作,如果想进行写操作的话就让后台代码去完成,毕竟EL表达式仅仅是视图上的输出标签罢了。 **pageContext** 我们可以使用```{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为我们提供了自定义标签的接口

image.png

这是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的任意子路径下。删除一些标签成如下内容:

image.png

我们看到这是一个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()也是这样:
文件存放路径如下:


image.png

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应用程序的处理流程应该如下:


image.png

(注意的几点: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文件。

1.
1.jpg

防止表单重复提交。

一种情况:表单提交成功以后,直接点击浏览器上回退按钮,不刷新页面,然后点击提交按钮再次提交表单。对数据库有操作,重复提交会给我们的数据库添加很多无意义,无效数据,根本原因:因为服务器在处理请求时,不会检查是否为重复提交的请求。

解决方案:使用一个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完成


1.png

session实现验证码:

1.png

1.png

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);
        
        
        
        
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容