转载

Servlet和JSP总结

  • BS: Browser Server 浏览器和服务器
    • 特点: 跨平台,功能升级比较方便,加载数据慢,用户体验稍差
  • CS: Client Server 客户端和服务器

    • 特点: 功能升级 需要下载新版本客户端,用户交互界面炫酷,体验度高,需要开发多个平台的版本,开发成本高
  • 总结: 两种架构各有优缺点,以后工作都有可能涉及到

什么是服务器

  • 服务器实际上就是一台高配置的电脑,通常配置内存8g以上,cpu8核以上,硬盘T级别

  • web服务器: 电脑上安装了web服务器软件,提供复杂的数据及文件共享功能

  • 邮件服务器: 电脑上安装了邮件服务器,提供了收发邮件的功能

  • 数据库服务器: 电脑上安装了数据库软件(mysql oracle) 提供了数据的增删改查

  • ftp服务器:电脑上安装了ftp服务软件,提供了文件上传下载功能

什么是web服务器

  • 电脑中得到任何资源(数据或者文件)被远程计算机访问,都必须有一个与之对应的网络通信程序,当有用户来访问时,此程序负责建立网络连接,读取相关资源,并把资源发送给用户,此程序负责底层的网络通讯处理http协议,使用此类型程序,程序猿只需要把精力放在具体的业务逻辑上即可

通过scoket实现web服务器

  • 练习: 请求 http://localhost:8080 返回一个网页
  • 火狐浏览器执行,chrome不支持,windows下不执行可能原因是防火墙
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
public class Socket{
	public static void main(String[] args)throws IOException {
		//创建服务器socket,并指定端口号
		ServerSocket serverSocket=new ServerSocket(8888);
		System.out.println("服务器已经启动");

		//循环接收新的socket
		while(true){
			//得到链接进来的socket对象
			java.net.Socket  socket=serverSocket.accept();

			//构建数据发送通道
			OutputStream outputStream=socket.getOutputStream();

			//得到文件的输入流
			FileInputStream inputStream=new FileInputStream("/home/chenjiabing/文档/a.html");

			//把文件数据读取到,然后写出
			int len=0;
			while((len=inputStream.read())!=-1){
				outputStream.write(len);
			}
			//关闭流
			outputStream.close();
			inputStream.close();
		}
	}
}

市面上常见的web服务器

  • webSphere : 是IBM公司产品,闭源收费

    • 应用场景:IBM的操作系统+DB2+WebSphere
  • Tomcat: apache的产品,属于开源免费应用在中小型网站中

    • web学习阶段使用的服务器
  • weblogic : BEA公司的产品 闭源收费

静态资源和动态资源

  • 静态资源:任何用户 任何时间访问 内容都一样

  • 动态资源: 不同的用户访问显示的内容可能会不一样,通过计算生成的网页

Servlet 介绍

  • 因为web服务器本身只提供了静态资源访问,而具体的业务需求存在着动态资源,servlet就是用来扩展web服务器功能,tomcat属于web容器,而servlet属于存在于容器中的组件,Servlet本身是一个组件规范。

如何创建Servlet

  1. 创建一个Class,继承HttpServlet
  2. 编译
  3. 打包并发布(把servlet添加到tomcat中的webapps目录下)

    • WEB-INF 这个文件夹里面的资源不能直接访问
    • classess (.class文件)
    • lib (存放第三方的jar包)
    • web.xml (部署描述文件)
  4. 运行tomact服务器

创建Servlet第一个程序

  • 创建maven项目

    • 把默认的jar改成war
    • 把package Explorer改成 project Explorer
    • 在工程根目录的第一个文件上右键点击最长的那一个选项(默认工程中没有web.xml文件,这个操作会自动创建web.xml)
  • 创建一个类,继承 HttpServlet

    • 可能你会发现没有这个 HttpServlet 类,因为这里我们还需要一个jar包,我们在项目上右击选择 properties ,然后选择 Targeted Runtime 选择你自己的Tomcat,ok
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServletextends HttpServlet{
	//这个方法可以处理任何的请求,get.post delete put 并且可以在适当的时候调用处理请求类型的各种方法
	@Override
	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		//设置响应的数据类型
		response.setContentType("text/html");
		//设置响应编码格式为UTF-8,否则将会出现中文乱码
		response.setCharacterEncoding("UTF-8");
		//得到输出对象
		PrintWriter writer=response.getWriter();
		//返回数据,这里是向浏览器中写入数据
		writer.write("<h1>Hello World--你好世界</h1>");
		//关闭输出流
		writer.close();
	}
}
  • 接下来在web.xml中配置Servlet的映射地址,在web-app的目录下写上如下内容
<servlet>
	<!-- 自己定义的名字,任意 -->
	<servlet-name>HelloWorld</servlet-name>
	<!-- 指定Servlet的全类名 -->
	<servlet-class>cn.tedu.HelloServlet</servlet-class>
</servlet>
<!-- 指定Servlet的映射关系 -->
<servlet-mapping>
	<!-- 这个是上面定义的Servlet的名字 -->
	<servlet-name>HelloWorld</servlet-name>
	<!-- 指定映射的地址: 这里只需要在浏览器中输入http://localhost:8080/helloWorld即可调用这个Servlet -->
	<url-pattern>/helloWorld</url-pattern>
</servlet-mapping>
  • 在浏览器中输入 http://localhost:8080/helloWorld

错误码

  • 404 找不到访问资源
    • 解决: 检查请求地址,检查项目是否部署成功
  • 500 服务器处理出错
    - 代码执行中有异常,仔细查看异常提示,看看能否找到解决办法

Servlet响应的过程

  1. 浏览器发出请求,会先由浏览器的通讯模块对请求进行打包,打包后把数据传递给tomcat服务器

  2. tomcat由通讯模块接收请求包并且对请求包进行解析,把请求数据封装到Request对象中,并且创建Response对象用于给浏览器返回数据

  3. tomcat通讯模块通过查找web.xml文件和本次请求相对应的Sevlet,通过反射技术创建对象并且调用对象的Service方法

    并把Request和Response传递到方法中

  4. 在service方法中书写各种业务代码,把需要返回的数据交给Respose对象,由Response对象传递给通讯模块,在通讯模块中打包成响应包

  5. 把响应包数据发送给浏览器通讯模块

  6. 浏览器通讯模块解析数据并且展示返回的数据

##响应数据乱码

-为什么出现乱码,因为输出响应数据默认使用的是iso8859-1 需要把此编码改成utf-8

##发出请求时传递参数

把请求的参数写在请求地址的后面

http://localhost:8080/1712ServletDay02_01Hello/hello?name=xiaoming

通过request获取请求参数

##案例:计算 体质率BMI

页面中 有两个文本输入框 一个用来获取身高,一个用来获取体重 和一个提交按钮

bmi计算公式 bmi = 体重(kg)/身高(m)/身高(m)

根据bmi值判断体重是否正常

bmi<19 体重偏瘦="" bmi="">=19&& bmi<=25 体重正常

bmi>25 该减肥了

步骤:1. 创建页面bmi.html 页面中添加两个文本输入框和一个提交按钮

2. 创建BMIServlet在Service方法中写业务逻辑
3. 在web.xml中配置bmiservlet

地址栏中出现中文 乱码解决方案

因为浏览器默认会对中进行utf-8编码,但是在Servlet里面8.0以前默认是iso8859-1,8.0以后默认是utf-8,如果使用8.0以前版本解决乱码方案有两种:

  1. new String(gender.getBytes("iso8859-1"),"utf-8");
  2. 在server.xml的配置文件中修改
    在server.xml的第65行左右 的<Connector标签中添加以下属性
    <Connector URIEncoding="utf-8"

HTTP协议(了解)

什么是HTTP协议

属于一种网络应用层的协议,规定了浏览器与web服务器之间如何通讯,以及数据包的结构
-tcp/ip协议:属于连接协议,规定了两台设备如何建立连接
-http:应用层协议基于tcp/ip协议
http协议,规定了数据包的内容和结构,规定了请求方式等内容
浏览器->打请求包->服务器->服务器解请求包
服务器->打响应包->浏览器->浏览器解响应包

http://locaohost:8888/1712ServletDay02_01Hello/hello?name=abc

http请求包数据:

  • GET /1712ServletDay02_01Hello/hello?name=abc HTTP/1.1 Host: localhost:8080 主机地址

  • Connection: keep-alive 连接状态

  • Upgrade-Insecure-Requests: 1

  • //浏览器信息

    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36

    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp, / ;q=0.8

    Accept-Encoding: gzip, deflate, sdch

    Accept-Language: zh-CN,zh;q=0.8

响应数据包

  • HTTP/1.1(协议版本号) 200(状态码) OK(状态信息)
  • Server: Apache-Coyote/1.1(服务器信息)
  • Content-Type: text/html;charset=utf-8(响应数据类型及字符集)
  • Content-Length: 21(数据长度)
  • Date: Tue, 03 Apr 2018 07:44:01 GMT(当前时间)

  • http请求包中包含:请求头和请求体

  • http响应包中包含:响应头和响应体

请求方式:GET和POST

  • GET:

    • 请求参数放在请求地址的后面
    • 提交的数据量比较小(大小在2k左右的数据)
    • 不能提交敏感信息因为在地址栏中可能会显示出来,或者某些路由器会保存请求地址中的信息
  • 浏览器什么时候会发出get请求:

    • 在地址栏中发出的请求就是get请求
    • form表单默认的请求方式就是get
    • 点击超链接所发出的请求也是get
  • POST:

    • 会将请求参数放在请求体里面,没有大小限制
    • 敏感信息相对安全
  • 浏览器什么时候会发出post请求

    • 只有当表单的提交方式修改为post的时候

Servlet中service 和doGet/doPost的关系

当Servlet组件被tomcat容器调用执行的时候会先执行service方法,在Service方法中判断请求方式是get就访问doGet 如果是post就访问doPost

获取请求头里面的数据

  • String uri = request.getRequestURI();
  • StringBuffer url = request.getRequestURL();
  • String httpVersion = request.getProtocol();

设置响应头数据

//设置响应数据类型和字符集
response.setContentType("text/html;charset=utf-8");
//设置刷新时间
response.setHeader("refresh", "3;info.html");

乱码

响应数据有中文

  • response.setContentType("text/html;charset=utf-8");

    请求参数有中文

  • get请求:

    1. new String(gender.getBytes("iso8859-1"),"utf-8");

    2. 修改server配置文件 65左右 <Connector URIEncoding=”utf-8”

  • post:

    • 为什么出现乱码:在post表单提交数据的时候使用当前页面的解码格式进行编码,因为Request对象默认使用iso8859-1解码 所以需要使用以下方式解决乱码问题

    • 解决方案:在获取参数之前添加以下代码

      request.setCharacterEncoding("utf-8");

以后写Servlet代码 需要添加以下两行代码

request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");

定时刷新及定时跳转

  • response.setHeader("refresh","2");
  • response.setHeader("refresh","2;home.html");

如何隐藏关闭的工程

  • 在左侧边栏右上角的小三角点击 点击 customView -> filters->closed Project

如何分组显示项目

  • 在左侧边栏右上角的小三角点击 点击Top level Element 选择Working set 然后重新点击小三角 选择select working set 在里面点击new ->Java 给分组起名然后分配工程到此分组里面,也可以不分配之后以拖拽的方式分配项目

重定向

  • 什么是重定向:让浏览器往另外一个地址重新发出请求
  • 实现原理: 重定向命令会给浏览器返回一个302的状态码 和一个location的参数 ,浏览器接收到302状态码后会向location参数的地址发出请求
  • 重定向案例:
    response.sendRedirect(request.getContextPath()+"/FindAllServlet");
  • 得到当前工程根路径的方式:
    • request.getContextPath()

路径的匹配

两种匹配方式:

  • 精确匹配:web.xml中的url parttern要和请求地址一致
  • 模糊匹配: 通过添加* 的方式让多个请求地址对应一个Servlet

    1. /* : *代表一个或多个未知,此地址会对应所有的动态资源地址(servlet地址) /abc /bcd /aaa /a/b/c /x/y

      http://localhost:8080/appname/(内容任意)

    2. /user/* :此匹配地址必须要求请求地址中必须是

      http://localhost:8080/appname/user/(内容任意)

    3. 后缀匹配 *.do (*.action)

      http://localhost:8080/appname/xxxx.do

浏览器输入一个地址查找资源的过程是怎样的?

1. 在当前应用的web.xml中查找是否有与之匹配的动态资源路径(Servlet)
2. 如果匹配到则执行相对应的Servlet
3. 如果没有匹配到会使用默认的Servlet查找是否有同名的静态资源
4. 如果有则返回资源文件
5. 如果没有则页面会显示404(找不到资源)
总结:先找动态 然后找静态 都找不到就404

复制工程时注意事项

  • 如果复制工程,需要在工程上右键 properties->web settings 修改里面的名称,此时的名称为复制之前工程的名称,修改成新工程的名字

Servlet的生命周期

  • 什么是生命周期: 什么时候实例化 什么时候初始化 什么时候调用方法 什么时候销毁
  • 实例化: 两种情况:

    1. 默认什么时候请求 什么时候实例化
    2. web容器启动的时候实例化 需要在Web.xml中进行配置 <load-on-startup>1</load-on-startup> 数值越小 优先级越高
  • 初始化: 当请求地址在web.xml中匹配到相应的Servlet的时候 web容器会通过反射实例化Servlet对象 并且调用有参数的init方法 在有参数的方法中调用了无参的init()如果需要写初始化代码 重写无参的init();

  • 方法调用: service doget dopost web容器先实例化Servlet然后初始化Servlet 然后web容器调用service方法在Service方法中判断请求方式然后调用doget或doPost方法
  • 销毁: 当工程从web容器(Tomcat)中卸载的时候执行

执行步骤:

  • 实例化->初始化->方法调用->销毁

JSP

  • 什么是JSP: Java Server Page java服务器页面
    是sun公司提供的一套动态页面规范
    1. 虽然直接使用Servlet也可以生成动态页面,但是操作过于繁琐(out.pringtln()),也不利于页面的维护,所以sun公司才提出了jsp规范
    2. jsp实际上就是一个以.jsp结尾的文件,可以在此文件中写html(css/javaScript)也可以写Java代码片段,容器会将此文件转换成Servlet来执行
      总结:jsp文件的本质就是Servlet
  • 如何创建jsp文件
    1. 创建一个file 名称为first.jsp
    2. 在jsp文件中可以写以下内容

      Html(包括css,JavaScript)

      直接写即可和操作html文件一样

      Java代码

  • 两种写法:
    1. <% java代码 %> 转换成servlet时会直接照搬过去
    2. <%=java表达式 %> 只能输出一行 等效out.println(java表达式)

      隐含对象

  • 什么是隐含对象
    • 在jsp中不用创建可以直接使用的对象称为隐含对象(比如:out,request,response,servletConfig…)
  • 为什么可以直接用隐含对象
    • 因为把jsp转成servlet的时候会自动生成创建这些对象的代码

      指令

  • 什么是指令:告诉容器,将jsp转成servlet的时候所做的一些额外操作 比如 import contentType pageEncoding等
    pageEncoding指令
    有些容器在读取磁盘中的jsp文件时默认的解码是iso-8859-1(tomcat默认是utf-8),但是通常jsp文本保存时选择的编码字符集是utf-8,为了保证编码和解码字符集一致所以在页面中通过pageEncoding属性设置解码字符集
    weblogic bea 收费 默认是iso-8859-1

jsp是如何执行的

  1. 容器会将jsp文件转成一个servlet
    • html(css,js)—>在_jspService中 通过out.write
    • <% %> —-> 直接原样照搬到_jspService中
    • <%= %>—-> 在_jspService中 使用 out.print()
  2. 容器和调用其它servlet一样调用此Servlet

练习: 自己写一个jsp 显示当前时间

格式为 2018年10月20日 11点23分44秒

练习: 显示用户表中的所有用户信息

###cellpadding 内容距td边框的距离

###cellspacing td边框距table边框的距离

Servlet(显示 业务逻辑)

Dao数据访问

JSP(显示 业务逻辑)

Dao数据访问

##三层架构

JSP(显示)

Servlet(业务逻辑)

Dao数据访问

案例:查询所有用户

  1. Servlet: 执行查询数据的代码放在Servlet里面
  2. JSP:控制显示的代码
    ##转发
    一个web组件将未完成的工作交给另外一个web组件
    web组件(Servlet和jsp)
    通常情况下是在Servlet里面获取数据,然后把数据交给Jsp显示
    浏览器发请求->Servlet -> Jsp
    以前请求发送到Servlet或jsp 现在分层之后 先把请求发送到Servlet,在Servlet里面获取数据 然后把数据转发给Jsp显示
    浏览器发请求->Servlet
    浏览器发请求->Jsp
    ##如何实现转发
  3. 把数据绑定到request对象上 可以绑定多个数据
    request.setAttribute(“users”, users);
  4. 得到转发器 并调用forward方法
    RequestDispatcher dispatcher =
    request.getRequestDispatcher("userlist3.jsp");
    dispatcher.forward(request, response);
    注意:转发实际上就是web容器找到相对应的组件并且执行了组件的_jspService方法
    ##转发的特点
    1. 转发的目的地有限制只能是应用内部的资源
    2. 转发后浏览器的地址栏不变
      ###转发和重定向的区别:
      Servlet和JSP总结
  5. 浏览器地址栏有没有变化?
    -转发没有
    -重定向有变化
  6. 能否共享Request和Response对象
    -转发:可以共享,因为转发只有一次请求web容器只创建了一对Request和Response对象两个组件使用的是相同的
    -重定向:不可以共享:因为两次请求,web容器创建了两对Request和Response 每个组件使用的是自己的Request和Response
  7. 访问地址有何区别
    -转发:只能访问工程内部的资源
    -重定向:可以访问任意地址

include 指令

  • 引入一个jsp页面,实现页面复用
  • <jsp:include page="file.jsp">

路径相关问题

  • 转发,重定向,表单提交,超链接
    • request.getDispatcher("")
    • response.sendRedirect("")
    • <form action="">
    • <a href=""></a>

相对路径

  • 不以 / 开头的路径就是相对路径,此路径相对于当前组件的位置

  • 如果想要找到上一级的资源需要加上 ../

    • 访问上上级的a.jsp : ../../a.jsp

绝对路径

  • 假设工程名为web1
    • 获取工程名 : request.getContextPath()
  • / 开头的路径是绝对路径
  • 转发从工程名之后写
    • request.getDispatcher("/jsp/a.jsp") 直接省略前面的工程名
  • 其他(重定向,超链接,表单提交)从工程名开始写
    • <a href="<%=request.getContextPath() %>/jsp/a.jsp">
    • <form action="<%=request.getContextPath() %>/jsp/helloServlet">
    • response.sendRedirect("<%=request.getContextPath() %>/jsp/a.jsp")

总结

  • 以后工作中更多的使用的是绝对路径,可维护性和扩展性更好,相对路径可能会出现一个地方更改多个地方出现错误的情况

状态管理(数据管理)

什么是状态管理

  • 将浏览器和服务器之间的多次交互建立关系,此时需要数据建立关系,数据保存和修改称为状态管理。状态即是数据

Cookie

  • 把少量数据保存在浏览器(客户端)的一种技术
  • cookie默认是保存在内存中,浏览器关闭则清除,如果设置了时间为0则立即清除,如果设置时间为正整数,则保存在磁盘中,时间到后自动删除

  • 工作原理:

    • 浏览器访问服务器时,服务器会将一些数据以 setCookie 的形式把数据存放到响应的消息头中,然后浏览器再次访问服务器时,会将Cookie数据放在请求的消息头中,这样服务器就能够得到之前请求时保存的一些数据,这样多次请求就能建立联系
  • 服务器如何添加cookie:

    //创建一个cookie对象
    Cookie cookie= new Cookie("name", "xiaoming");
    //设置cookie的过期时间,如果设置为0表示立即清除,如果没有设置那么浏览器关闭之后就会清除
    cookie.setMaxAge(100);
    //添加到响应头中,并且返回给浏览器
    response.addCookie(cookie);
    
    • 此时在浏览器中就可以查看到这个cookie的值了(name属性对应的值)
  • cookie时间:

    • 如果Cookie没有设置时间,时间为负整数,cookie保存在内存中,如果浏览器关闭,则数据清除
    • 如果cookie时间设置为0,是立即清除cookie的意思
    • 如果设置成为大于0 的整数,此时的cookie会保存到磁盘中,当时间到了之后会自动删除
    • cookie.setMaxAge(100); 单位是秒
  • 获取cookie的值

    // 获取Cookie,返回的是一个数组
    	Cookie[] cookies = request.getCookies();
    	//如果Cookies存在,读取
    	if (cookies != null) {
    		for(Cookie cookie : cookies){
    			System.out.println(cookie.getName()+" : " + cookie.getValue());
    		}
    	}else {
    		System.out.println("其中没有cookie");
    	}
    
  • cookie的路径

    • 如果不设置路径,默认会以当前组件的路径为准,只有当访问地址为当前组件地址或者组件地址的子地址时才会带上cookie

      • 假设我们添加cookie的servlet为 http://localhost:8080/web1/cookie/setCookieServlet ,那么我们的获取添加的cookie的servlet地址只有是 http://localhost:8080/web1/cookie 这个地址的子地址(后代),比如 http://localhost:8080/web1/cookie/user/getCookieServlet
    • 为cookie设置路径

      • cookie.setPath(“/“);
    • 举例:

      • 如果设置path为 /a
        • /a/servlet1 : yes
        • /b/servlet2 : no
  • cookie的编码问题

    • cookie只能保存英文,不能保存中文,如果需要保存中文,那么需要编码

    • 中文编码

      String name="小明";
      //对中文进行url编码
      name=URLEncoder.encode(name,"utf-8");
      //创建一个cookie对象
      Cookie cookie= new Cookie("name", name);
      //设置cookie的过期时间,如果设置为0表示立即清除,如果没有设置那么浏览器关闭之后就会清除
      cookie.setMaxAge(100);
      
      System.out.println(cookie.getPath());
      //添加到响应头中,并且返回给浏览器
      response.addCookie(cookie);
      
    • 将获取的中文cookie解码输出

      // 获取Cookie,返回的是一个数组
      Cookie[] cookies = request.getCookies();
      //如果Cookies存在,读取
      if (cookies != null) {
      	for(Cookie cookie : cookies){
      		String value=cookie.getValue();
      		//把cookie的值取出,然后url解码
      		value=URLDecoder.decode(value,"utf-8");
      		System.out.println(cookie.getName()+" : " + value);
      	}
      }else {
      	System.out.println("其中没有cookie");
      }
      
  • cookie的限制

    • cookie可以被用户禁止
    • cookie不安全 ,对于敏感信息一定要加密
    • cookie的数据大小有限制,大约4k左右
    • cookie总数也有限制,大约200个左右

使用cookie记录客户端访问次数

@Override
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		Cookie[] cookies = request.getCookies();
		Map<String, String> map = getValues(cookies);   //获取map
		String stringCount = map.get("count"); // 获取指定的value
		if (stringCount == null) {// 第一次请求
			stringCount = "1"; // 设置访问次数为1
		} else {// 不是第一次请求
			// 请求次数+1
			stringCount = "" + (Integer.parseInt(stringCount) + 1);
		}
		Cookie cookie = new Cookie("count", stringCount); // 把运行次数放置到cookie中
		response.addCookie(cookie); // 添加cookie,如果前面已经存在了,那么相当于更新cookie的值
		System.out.println(stringCount);
	}

	/**
	 * 将cookie数组中的键值对存放到Map中,这样就能判断出这个cookie中是否含有指定的key
	 */
	public Map<String, String> getValues(Cookie[] cookies){
		Map<String, String> map = new HashMap<String, String>();
		if (cookies != null) {
			for (Cookie cookie : cookies) {
				map.put(cookie.getName(), cookie.getValue());
			}
		}
		return map;
	}

使用cookie保存登录的用户名和信息

  • 需求:
    • 当用户选择了记住用户名和密码的选项,那么当用户登录成功的时候,接下来每次请求登录页面的时候浏览器会自动显示之前成功登录的用户名和密码
  • 实现:

    • 一个单选按钮,选择是否记录登录信息,jsp页面
    • 在表单提交给servlet之后,验证用户是否登录成功,如果登录成功了并且还选择了记住用户名和密码,那么就将此时的用户名和密码信息添加到cookie中
    • 在每次跳转到登录页面的时候都需要经过一个Servlet,这个Servlet的作用是获取cookie的值,并且存放在request域中,这样在login.jsp页面中就可以使用这个值

    • login.jsp

      <form action="/Servlet01/RememberLoginServlet" method="post">
      				<table cellpadding="0" cellspacing="0" border="0"
      					class="form_table">
      					<tr>
      						<td valign="middle" align="right">username:</td>
      						<td valign="middle" align="left"><input type="text"
      							class="inputgri" name="username" value="<%=request.getAttribute("username") %>" /></td>
      					</tr>
      					<tr>
      						<td valign="middle" align="right">password:</td>
      						<td valign="middle" align="left"><input type="password"
      							class="inputgri" name="password" value="<%=request.getAttribute("password") %>" /></td>
      					</tr>
      
      					<tr>
      						<td></td>
      						<td valign="middle" align="left">
      							<input type="checkbox" name="isRemember">记住用户名和密码一周
      						</td>
      					</tr>
      
      
      				</table>
      				<p>
      					<input type="submit" class="button" value="Submit »" />
      				</p>
      			</form>
      
    • 验证用户,保存信息到cookie中的servlet

      @Override
      protected void doPost(HttpServletRequest request,
      		HttpServletResponse response) throws ServletException, IOException {
      	request.setCharacterEncoding("UTF-8"); // 设置中文格式
      	//设置响应字符集必须写在获取输出对象的前面
      	response.setContentType("text/html;charset=utf-8");
      	PrintWriter writer=response.getWriter();
      	// 获取用户名和密码
      	String username = request.getParameter("username");
      	String password = request.getParameter("password");
      	String isRemeber = request.getParameter("isRemember");
      
      	// 登录流程
      	Connection connection = null;
      	PreparedStatement statement = null;
      	ResultSet resultSet = null;
      	try {
      		connection = DBUtils.getConn(); // 获取连接
      		String sql = "select count(*) c from user where username=? and password=?";
      		statement = connection.prepareStatement(sql);   //创建预编译对象
      		//设置占位符的值
      		statement.setString(1, username);
      		statement.setString(2, password);
      		resultSet = statement.executeQuery(); // 执行查询语句
      		//遍历查询结果集,如果count>0 表示登录成功,如果=0表示用户名或密码错误
      		while (resultSet.next()) {
      			int count = resultSet.getInt("c"); // 获取总数
      			if (count > 0) {
      				System.out.println("登录成功");
      
      				// 判断是否记住密码
      				//如果设置了记住密码,那么就将此时的用户名和密码保存在cookie中
      				if (isRemeber != null) {
      					//将username和password添加到cookie中
      					Cookie cookie=new Cookie("loginInfo", username+","+password);
      					cookie.setMaxAge(7*24*3600);  //设置时间为一周,单位为秒
      					response.addCookie(cookie);
      				}
      				//跳转到首页
      				request.getRequestDispatcher("home.jsp").forward(request, response);
      			} else {
      				System.out.println("登录失败,用户名或密码错误");
      				response.sendRedirect("/Servlet01/ShowLoginCookieServlet");  //重定向到登录界面
      			}
      		}
      	} catch (Exception e) {
      		e.printStackTrace();
      		writer.write("服务器出错.....");
      	} finally {
      		DBUtils.close(connection, statement, resultSet); // 关闭资源
      	}
      }
      
    • 获取cookie值,存放到request域中,便于在login.jsp页面中访问到信息

      @Override
      protected void doGet(HttpServletRequest request,
      		HttpServletResponse response) throws ServletException, IOException {
      	Cookie[] cookies = request.getCookies();
      	Map<String, String> map = getValues(cookies);
      	String loginInfo = map.get("loginInfo"); // 获取cookie的值
      	String username = "";
      	String password = "";
      	if (loginInfo != null) {
      		username = loginInfo.split(",")[0]; // 分割字符串,获取信息
      		password = loginInfo.split(",")[1];
      	}
      	request.setAttribute("username", username);
      	request.setAttribute("password", password);
      	request.getRequestDispatcher("login.jsp").forward(request, response);
      }
      
      /**
       * 将cookie数组中的键值对存放到Map中,这样就能判断出这个cookie中是否含有指定的key
       */
      public Map<String, String> getValues(Cookie[] cookies){
      	Map<String, String> map = new HashMap<String, String>();
      	if (cookies != null) {
      		for (Cookie cookie : cookies) {
      			map.put(cookie.getName(), cookie.getValue());
      		}
      	}
      	return map;
      }
      

Session

  • 服务端为了保存状态(数据)创建的一个特殊的对象,session数据会保存在服务器

  • 工作原理

    • 当浏览器第一次向服务器请求,服务器创建一个session对象,然后把session对象的唯一标识sessionid以cookie的形式返回给浏览器,服务器通过sessionid找到上次保存的session对象,这样的话多次请求只需要把数据保存在session对象中,

如何创建Session对象

  • HttpSession session=request.getSession(boolean flag)
    • 参数为false,如果不存在这个session,那么就返回一个null
    • 参数为true,会通过cookie中的sessionid获取之前保存的session对象,如果有则返回,如果没有则创建一个新的session,这个是默认的形式 (缺省值为true)

添加删除数据

  • setAttribute(key,value)
  • getAtttribute(key,value)
  • removeAttribute(key)

session超时

  • session默认时间是在服务器保存30分钟

  • 如何修改session存活时间

    • 修改配置文件

      • 在servers中的web.xml中查找如下
        <session-config>
             		<session-timeout>30</session-timeout>
         		</session-config>
        
    • 通过代码设置时间

      • session.setMaxInactiveInterval(int mils); 单位为秒

删除session

  • session.invalidate()

删除session中的数据

  • session.removeAttribute(key)

实现自动登录(Session)

  • 这个在学到过滤器的时候再讲

Base64加密

  • 什么是Base64: 将任意二进制数据转换成字符串(由64个基础字符组成a-z A-Z 0-9 + /),可以将需要加密的字符串转换成二进制数据后再转换成Base64的字符串,也可以把任意文件的二进制数据转换成可见的字符串
String pw="admin";
BASE64Encoder encoder=new BASE64Encoder();
String newPW=encoder.encode(pw.getBytes("utf-8"));
System.out.println(newPW);

//解密
BASE64Decoder decoder=new BASE64Decoder();
String oldPw=new String(decoder.decodeBuffer(newPW),"utf-8");
System.out.println(oldPw);

比较cookie和Session

  • cookie: 优点不占用服务器资源,缺点:大小有限制4k 数量限制200左右 内容有限制只能存放字符串,cookie不够安全而且有些浏览器可以模拟cookie数据

  • Session: 优点 :安全(因为数据保存在服务器)大小无限制,保存数据类型丰富, 缺点 : 占用资源,浏览器关闭后Session则失效,因为session的是状态是存储在cookie中的seessionid决定的。

自动登录加强版

  • 需要自己创建一个cookie

过滤器

什么是过滤器

  • Servlet规范中定义的是一种特殊组件,用来拦截web容器调用Servlet/jsp组件的过程
  • 好处: 可以在不改动Servlet的情况下增加业务功能,可以起到代码复用的作用,因为一个过滤器可以对应拦截多个Servlet

如何创建一个过滤器

  • new - > Filter

    • 这样就创建一个过滤器,其中的类实现了Filter这个接口

      public class MyFilterimplements Filter{
      	public MyFilter(){
      	}
      
      	public void destroy(){
      		System.out.println("过滤器被销毁");
      	}
      
      	public void doFilter(ServletRequest request, ServletResponse response,
      			FilterChain chain) throws IOException, ServletException {
      		System.out.println("过滤器执行");
      		chain.doFilter(request, response);
      	}
      
      	public void init(FilterConfig fConfig)throws ServletException {
      		System.out.println("过滤器初始化");
      	}
      
      }
      
    • 这个类中同样有init和destroy方法,但是实现代码逻辑实在doFilter()这个方法中

    • 在web.xml中配置这个过滤器
      <!-- 配置Filter 的name和class -->
      	<filter>
        	<filter-name>MyFilter</filter-name>
        	<filter-class>cn.filter.MyFilter</filter-class>
      	</filter>
      	<!-- 配置Filter的过滤的url,其中的name是前面定义好的 -->
      	<filter-mapping>
        	<filter-name>MyFilter</filter-name>
        	<!-- /* 拦截所有的Servlet,但是也拦截器了这个路径下的jsp ,如果设置成/MyServlet,那么只拦截这一个Servlet-->
        	<url-pattern>/*</url-pattern>
      </filter-mapping>
      

过滤器的生命周期

  • 创建: 无参构造方法,当web容器启动时,会自动创建过滤器
  • 初始化: init()方法 当过滤器创建后会自动调用
  • 销毁: destroy()方法 当应用程序从web容器中卸载时
  • doFilter(): 当调用被拦截器的Servlet或者jsp的时候执行,在此方法中执行doFilter方法相当于执行Servlet里面的Service方法,因为过滤器里面的Request和Response对象和Servlet中的是同一对象,所以在Servlet里面做的任何事都可以在过滤器中实现

案例: 实现评论功能

  • 如果出现了敏感字符禁止访问

  • 步骤

    1. 准备一个Comment.jsp页面,页面中有一个文本框和一个提交按钮
    2. 如果出现敏感信息禁止提交,并跳转到原页面重新填写评论
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {
		HttpServletRequest req=(HttpServletRequest)request;

		//继续执行下面的Filter和Servlet,没有这个方法,那么将不会执行
		chain.doFilter(request, response);
	}

如果配置多个过滤器拦截器同一个请求地址

  • 此时多个过滤器都会响应,哪个先执行,取决于在web.xml中哪个过滤器先配置,先配置的先执行

<init-param> 设置初始化值

  • 配置Filter 的初始化值,在web.xml中定义
  • 其中的

    <filter>
        <filter-name>CommentFiletr</filter-name>
        <filter-class>cn.filter.CommentFiletr</filter-class>
        <init-param>
        	<param-name>word</param-name>
        	<param-value>美女,我操</param-value>
        </init-param>
    </filter>
    
  • 获取其中的值

    • 在init中初始化FilterConfig对象
    • 在doFilter中使用FilterConfig对象获取初始化值即可
      public class CommentFiletrimplements Filter{
      	private FilterConfig config;  //定义成员变量FilterConfig对象
      
          public CommentFiletr(){
          }
      
      	public void destroy(){
      	}
      
      	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {
      		String word=this.config.getInitParameter("word");   //获取初始化值
      
      		chain.doFilter(request, response);
      	}
      
      	//初始化方法
      	public void init(FilterConfig fConfig)throws ServletException {
      		this.config=fConfig;
      	}
      
      }
      

ServletContext(上下文)

  • 定义: web服务器启动的时候会为每一个应用创建一个符合ServletContext接口的对象

  • 特点:

    • 唯一性: 整个工程中只有一个
    • 持久性: 只要容器不关闭,整个ServletContext对象就会存在于内存中
  • 应用场景:

    1. 负责传递数据(共享数据),任何一个组件往ServletContext对象中保存数据都可以给整个工程的所有Servlet访问
    2. 可以在web.xml中获取全局的初始化数据

如何配置参数

  • 在web.xml中配置即可
  • 如果想要配置多个,那么可以定义多个 <context-param> 即可
<context-param>
  	<param-name>name</param-name>
  	<param-value>陈加兵</param-value>
</context-param>
  • 在组件中获取ServletContext(在任何组件中都可以获取)

    • Servlet 中获取

      protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
      	ServletContext context=this.getServletContext(); //获取对象
      	String name=context.getInitParameter("name");  //获取name属性的值
      }
      
    • Filter 中获取

      //初始化方法
      public void init(FilterConfig fConfig)throws ServletException {
      	ServletContext context=fConfig.getServletContext();
      	String name=context.getInitParameter("name");
      }
      

添加和获取数据

  • getAttribute(key)
  • setAttribute(key,value)

Request,Session,ServletContex,PageContext作用范围

  • ServletContext > Session > Request > PageContext

  • 如何选择传递数据的域:符合需求的域中,选择范围最小的

监听器 Listener

什么是监听器

  • Servelt规范中定义的一个特殊组件,用来监听容器内部各组件的事件
  • 组件会有两大类事件
    • 生命周期相关事件(比如session的创建的和销毁)
    • 绑定数据相关事件

如何创建监听器Listener

  • new --- > Listener ---> 类名 ---> next -- > 选择对应的Listener 具体的选项如下图

Servlet和JSP总结

  • 选项中有三大域的监听器,ServletContext,Session,Request
    • 其中Liftcycle 是 生名周期金监听器 其中可以监听三大域的创建和销毁

      -Change to Attributes 是三大域绑定删除数据监听器

  • 创建成功之后会在web.xml中自动为我们填上监听器的配置
<listener>
    <listener-class>cn.listener.MyListener</listener-class>
</listener>

统计在线人数

  • 当开始一个会话将会就表示在线人数+1,因此需要监听Session的生命周期
  • 因为我们是统计在线人数,因此我们需要在jsp页面中显示出人数,我们需要将在线人数这个变量存放在ServletContext才能实现共享,这样只有当web容器关闭才会清空其中的在线人数

  • 如果存放在session中,那么当浏览器关闭就会清空session中的数据,或者到了指定的时间也会清空,因此我们不能存放在Session

  • 创建一个监听Session的监听器

public class MyListenerimplements HttpSessionListener{

	//Session创建时调用的方法
	public void sessionCreated(HttpSessionEvent sessionEvent){
		System.out.println("会话开始");
		// 取出当前在线人数
		ServletContext context = sessionEvent.getSession().getServletContext(); // 得到ServletContext
		Integer count=(Integer) context.getAttribute("count");   //获取当前的在线人数
		// 如果count是第一次,那么此时的count就是null,因为这里还没有设置这个上下文参数
		if (count == null) {
			count = 1;
		} else {
			count++;
		}
		// 将在线人数保存回去
		context.setAttribute("count",count);
	}

	//Session销毁时调用的方法
	public void sessionDestroyed(HttpSessionEvent event){
		System.out.println("会话结束");
		ServletContext context = event.getSession().getServletContext();
		Integer count=(Integer) context.getAttribute("count");   //获取当前的在线人数
		count--; // 直接人数-1
		// 将此时的人数保存回去
		context.setAttribute("count",count);
	}

}

缓存数据练习

  1. 创建一ServletContext生命周期监听器,在ServletContext创建的的方法中读取数据库中的数据并将数据保存在ServletContext中,因为ServletContext在容器创建的时候就会创建,因此在web容器开启的时候就会读取数据库中的信息

  2. 我们在Servlet中直接读取ServletContext中的数据即可,不同在请求Servlet的时候从数据库中读取,提高Servlet的响应效率

好处

  • 在我们使用同一种数据的时候,并且数据常用,我们可以在web容器启动的时候就加载出来,不用每次用到该数据就请求一次读取数据库一次,提高了效率

代码

public class CacheListenerimplements ServletContextListener{

	//ServletContext销毁的时候调用
    public void contextDestroyed(ServletContextEvent event){
    	System.out.println("销毁");
    }

    //ServletContext初始化的时候调用
    public void contextInitialized(ServletContextEvent event){
    	ServletContext context=event.getServletContext();  //获取ServletContext对象
    	EmpDao empDao=new EmpDao();  //创建Dao对象,用于读取数据库中的信息
    	List<Emp> emps=empDao.findAll();  //获取所有的数据
    	context.setAttribute("emps", emps);  //添加到ServletContext中
    }
}

各组件执行顺序

  • MyServlet、MyListener(监听ServletContext),MyFilter

  • web容器启动 – > MyListener(监听) — > MyFilter实例化 – > 请求 —> MyFilter(doFilter) —> 执行MyServlet

Servlet线程安全问题

为什么

  • 为什么会有线程安全问题:因为每一个请求 服务器都会开启一条新的线程来执行,这样的话如果请求量比较大出现高并发访问就会出现多条线程同时执行,如果多一条线程执行的过程中,有需要去修改同一份数据,则有可能出现线程安全问题,即一条数据没有处理完,另外一条数据把数据取走

解决方案

  • 通过同步代码块,将可能会出现线程安全的代码包裹起来,这样就可以解决线程安全问题

实例

  • 假设我们的线程不安全的Servlet如下
public class ThreadSafeServletextends HttpServlet{
	private int count = 0;

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

			System.out.println(Thread.currentThread().getName() + ":开始执行" + count);
			try {
				Thread.sleep(5000);   //线程睡眠5s
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			count++;

			System.out.println(Thread.currentThread().getName() + ":执行完毕" + count);

	}

}
  • 我们在浏览器多次请求这个servlet,那么我们可以看到输出的每一个开始执行的count的值都是0,但是我们后面都count++了,从此可以看出线程不安全,那么我们添加一个同步代码块来确保线程安全
public class ThreadSafeServletextends HttpServlet{
	private int count = 0;

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		//同步代码块
		synchronized (this) {
			System.out.println(Thread.currentThread().getName() + ":开始执行" + count);
			try {
				Thread.sleep(5000);   //线程睡眠5s
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			count++;

			System.out.println(Thread.currentThread().getName() + ":执行完毕" + count);
		}

	}

}

JSP扩展

什么是JSP

  • java Server page java服务器页面
  • jsp文件部署到web容器时会自动转成Servlet组件,添加到容器中

如何写jsp

  • java代码的写法

    • <% %> 写java代码,任意java代码都行,转化成Servlet的时候直接是写在service方法体中
    • <%! %> 声明变量或者方法,转换成Servlet的时候直接作为其成员变量或者成员方法
    • <%= %> java表达式,返回的是一个值
  • 指令

    • 高速容器,将jsp转成Servlet的时候所做的一些额外操作
    • <%@ page> : import导包 contentType pageEncoding

      • session的默认值为true,如果值为false,则在java代码中不能使用session隐式对象
      • errorPage :指定jsp里面出现异常时显示的页面
      • isErrorPage : 设置当前页面为错误异常页面,默认为false,设置为true之后页面中才可以使用exception获取异常信息

      tablib : 引入标签库

JSP中隐式对象

什么是隐式对象

  • 在JSP中可以不用创建,可以直接使用的对象

为什么可以直接使用

  • 因为将JSP转成Servlet类的时候会自动创建的对象

有哪些 (九大隐式对象) 面试中常考

application
session
request
  1. pageContext : 用于在同一个jsp中共享数据,常用方法有setAttribute(),getAttribute(),removeAttribute()

    <%
    pageContext.setAttribute("name", "陈加兵");
    %>
    
    <h1><%=pageContext.getAttribute("name") %></h1>
    
  2. response : 类型HttpServletResponse,用于处理响应数据和重定向,因为有out,更多使用的是out

  3. out : 类型为JSPWriter,用于输出数据

  4. page : page就是jsp本身,因为jsp最终会转成Servlet,page相当于this

  5. exception : 异常对象,用于获取异常信息,只有当page指令里面添加了 isErrorPage=true 的时候才能使用

  6. config : 类型为ServletConfig,用于获取配置文件中初始化参数

JSP的注释

<!--注释内容-->
<%-- 注释内容 --%>

JSP如何执行的

  1. 将JSP转成Servlet

  2. 调用Servlet

JSP标签和EL表达式

什么是jsp标签

  • 是sun公司提供的一套类似于html标签的内容,用于替换jsp中出现的java代码
  • 因为在jsp中写java代码不利于维护,代码的可读性也很差,以后工作时显示相关的内容很可能交给前端工程师或者美工,所以在jsp中尽量不要出现java代码,所以才产生了jsp标签

什么是EL表达式

  • 一套简单的运算规则,用于从域对象中取值,然后给jsp中标签的属性赋值

EL表达式的使用( ${} )

  1. 访问Bean对象中的属性(属性必须有get方法)
    • ${对象名.属性名} ,假设一个对象user,访问其中的name属性,我们可以使用 ${user.name} ,这个相当于调用了 user.getName() 方法

EL表达式执行过程

  1. ${user.name} 会先从pageContext域中查找如果有则用,如果没有会到request域中查找,如果没有再到session域中查找,如果没有再到ServletContext中查找

    • 如果找不到直接输出空字符串 "" ,如果没有获取到对象调用对象的方法不会报空指针异常,仍然输出空字符串
  2. 指定域获取 ${requestScope.user.name} 相当于 request.getAttribute("user").getName()

    • pageScope
    • requestScope
    • sessionScope
    • applicationScope
  3. ${user['name']} 个人不推荐使用

使用EL表达式获取请求参数(使用不多,一般都是在Servlet获取)

  • 直接使用 ${param.请求参数名} 获取指定的请求参数

    • ${param.name} 相当于 request.getParameter("name")
  • ${paramValues.参数名[index]} 获取多个同名参数

    • 相当于 request.getParameterValues("参数名")[index]

EL表达式的简单运算

  • 运算结果可以直接给标签的属性赋值

算术运算符

  • 可以直接使用加减乘除 ${1+2},${5/2},${5*3}
  • 注意+ 只能做求和运算,不能字符串拼接

逻辑运算符

  • ${true and false}=false , ${true and true}=true , ${true or false}=true

关系运算符

  • 使用 == , != , >= , < , > , <= , && , || 如: ${age<30}
  • 可以直接在EL表达式比较大小,返回的也是false和true,可以用来判断,如下:

    ${1<2}=false , ${(10*10)>200}=true , ${age>11&&age<20}

  • empty 判断是否为空(空字符串或者值为null)

    • 判断字符串为null或者为空字符串
    • 判断数组,值为null和不为null但是数组里面没有数据都会返回true
    • 判断对象为null
    • ${empty str} 判断字符串是否为空
    • ${empty user} 判断对象user是否为空

jstl

  • java standard tab lib (java标准标签库)

  • jstl是Apache开发的一套jsp标签

如何使用

  1. 导入jstljar包,使用maven,在pom.xml中添加依赖

    <dependency>
    		<groupId>javax.servlet</groupId>
    		<artifactId>jstl</artifactId>
    		<version>1.2</version>
    </dependency>
    
  2. 通过tablib指定引入标签库

    • <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> , uri:是标签库的命名空间,代表标签库的唯一标识,prefix :别名或前缀
  3. 几个核心标签

    • if标签, <c:if test=""> test中填写的是判断条件,使用EL表达式

      • var : test中的判断结果,如果test中的判断为真,那么此时var的变量值为true
      • scope : 将var中的变量存放到指定的域中,便于直接访问
        <%
        	request.setAttribute("age", 22);
        %>
        <c:if test="${requestScope.age>20 }">
        	<h1><c:out value="年龄大于20"></c:out></h1>
        </c:if>
        
        <c:if test="${requestScope.age>10 }" var="result" scope="session">
        	<h2>此时的判断结果为 : ${sessionScope.result }</h2>
        </c:if>
        
    • choose标签 (相当于switch case)

      • 需要和 when,otherwise结合使用
        <%
        User user=new User();
        user.setUsername("libai");
        user.setPassword("admin");
        request.setAttribute("user", user);
        %>
        
        <c:choose>
        	<c:when test="${user.username=='libai' && user.password=='admin' }">
        		<h1>登录成功</h1>
        	</c:when>
        
        	<!-- 其他的任何类型的判断,只要不是when中的,都在这里执行,相当于else -->
        	<c:otherwise>
        		<h1>登录失败</h1>
        	</c:otherwise>
        </c:choose>
        
    • forEach标签 相当于java中的forEach,由于遍历集合或者数组

      • items : 需要遍历的集合或者数组
      • var :遍历的对象的变量名称,遍历时会把当前遍历的对象绑定在PageContext域中,需要获取遍历对象的内容时直接使用EL表达式从域中获取出来
      • begin : 开始的索引
      • end : 结束的索引
      • step : 指定步长,默认的步长为1
      • varStatus : 遍历的状态,如果需要得到遍历对象的下标调用 index ,如果想要得到遍历对象是集合中的第几个调用 count

        <table width="500" border="1">
        	<tr>
        		<th>用户名</th>
        		<th>密码</th>
        		<th>性别</th>
        		<th>级别</th>
        		<th>下标</th>
        		<th>第几个</th>
        	</tr>
        	<c:forEach var="user" items="${requestScope.users }" begin="0"  end="19"  step="1" varStatus="s">
        		<c:if test="${s.index%2==0 }"> <tr id="row1"> </c:if>
        		<c:if test="${s.index%2!=0 }"> <tr id="row2"> </c:if>
        			<td>${user.username }</td>
        			<td>${user.password }</td>
        			<td>${user.gender }</td>
        			<td>${user.level }</td>
        			<!-- 下标 -->
        			<td>${s.index }</td>
        			<!-- 第几个 -->
        			<td>${s.count }</td>
        		</tr>
        	</c:forEach>
        </table>
        

自定义标签

  • 简单标签技术
  • 复杂标签技术:支持标签内部写java代码
原文  https://chenjiabing666.github.io/2018/04/24/Servlet和JSP总结/
正文到此结束
Loading...