C/S和B/S是软件发展过程中出现的两种软件架构方式。
C/S结构即客户端/服务器(Client/Server),例如QQ;
需要编写服务器端程序,以及客户端程序,例如我们安装的就是QQ的客户端程序;
缺点:软件更新时需要同时更新客户端和服务器端两端,比较麻烦;
优点:安全性比较好。
B/S结构即浏览器/服务器(Browser/Server);
优点:只需要编写服务器端程序,不需要安装专门的客户端软件,只需要浏览器就行;
缺点:安全性较差。
Web(World Wide Web) 称为万维网,简单理解就是网站,它用来表示Internet主机上供外界访问的资源。
Internet上供外界访问的资源分为:
供人们浏览的数据始终是不变;
浏览器能直接看懂
例如:HTML、CSS、JavaScript、各种图片
供人们浏览的数据是由程序产生的,不同时间点访问WEB页面看到的内容各不相同。
客户端请求的页面如果是静态网页,那么服务器会直接把静态网页的内容响应给客户端。如果客户端请求的是动态网页,服务器需要先把动态网页转换成静态网页,然后再把转换后的静态网页响应给客户端。
注:从广义上讲,用户看到的都是静态网页。
例如:Servlet、JSP、ASP、PHP,但是在Java里面只涉及JSP/Servlet
在Java中,动态web资源开发技术统称为JavaWeb
Web服务器是运行及发布Web应用的容器,只有将开发的Web项目放置到该容器中,才能使网络中的所有用户通过浏览器进行访问。
开源:
三者的用法从代码角度完全相同,只有在开启、关闭服务器软件时对应的命
令稍有区别。掌握一个即掌握所有
收费:
提供相应的服务与支持,软件大,耗资源
Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,免费开源、并支持Servlet 和JSP 规范。目前Tomcat最新版本为9.0。
Tomcat 技术先进、性能稳定,深受Java 爱好者喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。
官网:Apache Tomcat® - Welcome!
Tomcat8下载地址:Apache Tomcat® - Apache Tomcat 8 Software Downloads
根据自己电脑情况,选择32位版本或64位版本下载。为了统一,一定要下载压缩版本。
将Tomcat解压到一个没有特殊符号的目录中,纯英文路径就可以。
文件夹 | 说明 | 备注 |
---|---|---|
bin | 该目录下存放的是二进制可执行文件 | startup.bat启动Tomcat、shutdown.bat停止Tomcat |
conf | 这是一个非常重要的目录,这个目录下有两个最为重要的文件server.xml和web.xml | server.xml:配置整个服务器信息。例如修改端口号,编码格式等。web.xml:项目部署描述符文件,这个文件中注册了很多MIME类型,即文档类型。 |
lib | Tomcat的类库,里面存放Tomcat运行所需要的jar文件。 | |
logs | 存放日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,异常也会记录在日志文件中。 | |
temp | Tomcat的临时文件,这个目录下的东西在停止Tomcat后删除。 | |
webapps | 存放web项目的目录,其中每个文件夹都是一个项目;其中ROOT是一个特殊的项目,在地址栏中没有给出项目目录时,对应的就是ROOT项目。 | |
work | 运行时生成的文件,最终运行的文件都在这里。 | 当客户端用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下。 |
进入Tomcat安装目录bin下,双击startup.bat启动程序,直到控制台出现xxxms,表示启动完成。
打开浏览器,在地址栏输入http://localhost:8080
,出现如下界面表示启动成功。
image-20210905234021264
双击shutdown.bat即可关闭Tomcat启动窗口。
注意:一定不要点击控制台的关闭按钮,可能导致停止失败。
静态网站:
在webapps目录下创建一个目录hello(命名必须不包含中文和空格),这个目录称之为项目目录;
在项目目录下创建一个html文件,例如index.html;
启动tomcat;
打开浏览器访问:http://localhost:8080/hello/index.html
动态网站:
在webapps目录下创建一个项目目录hello1;
在项目目录下创建如下内容:
WEB-INF目录;
在WEB-INF目录下创建web.xml文件(到ROOT项目下的WEB-INF复制即可);
创建classes,用于存放.class文件;
创建lib,用于存放jar文件。
创建动态页面index.jsp和WEB-INF同级目录;
打开浏览器访问:http://localhost:8080/hello1/index.jsp
index.jsp内容如下
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Indextitle>
head>
<body>
<%
String name = "张三";
out.print("<p>" + name + "p>");
%>
<p>Hello JavaWebp>
body>
html>
关于访问路径:协议://主机名:端口号/项目名/资源
注意:WEB-INF目录中的资源无法直接访问。
启动Tomcat控制台闪退,一般是由于没有配置JAVA_HOME
。
之前在配置JDK的环境变量时,配置JAVA_HOME
就是为了此处准备的。
JAVA_HOME
的值是JDK中bin的上一级目录。
HTTP(hypertext transport protocol),即超文本传输协议。这个协议详细规定了浏览器和万维网服务器之间互相通信的规则。
HTTP就是一个通信规则,通信规则规定了客户端发送给服务器的内容格式,也规定了服务器发送给客户端的内容格式。我们要学习的就是这个两个格式。客户端发送给服务器的格式叫“请求协议”;服务器发送给客户端的格式叫“响应协议”。
HTTP协议的特点:
支持客户端(浏览器)/服务器模式。
简单快速:客户端只向服务器发送请求方法和路径,服务器即可响应数据,因而通信速度很快。请求方法常用的有GET、POST等。
灵活:HTTP允许传输任意类型的数据,传输的数据类型由Content-Type标识。
无连接:无连接指的是每次TCP连接只处理一个或多个请求,服务器处理完客户的请求后,即断开连接。采用这种方式可以节省传输时间。
无状态:HTTP协议是无状态协议。
客户端发送给服务器的格式
格式:
请求首行
请求头信息
空行
请求体
分类:
服务器发送给客户端的格式
格式:
响应首行
响应头信息
空行
响应体
常见响应码
此处以Idea 2020.3举例
注意:Idea2020无法直接新建JavaWeb项目,只能通过新建普通Java项目的方式间接新建JavaWeb项目。
选择项目位置和普通Java项目相同,此处略过。
项目根目录->右键->Add Framework Support
选择JavaEE版本
勾选左侧的Web Application
完成之后,可以看到项目下新建了web目录,并包含如下内容。
添加相关依赖File->Project Structure
执行上述操作之后,Tomcat相关Jar包就添加到了项目中,不添加后续很多开发无法进行。
此处指的是将Idea中开发的Web项目在Tomcat中部署。
修改index.jsp的代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Indextitle>
head>
<body>
<%
String name = "张三";
out.print("<p>" + name + "p>");
%>
<p>Hello JavaWebp>
body>
html>
单击运行按钮,运行项目,默认会在浏览器中打开index.jsp
在以后的开发中,多数时候都是重复上述步骤进行JavaWeb项目的开发。
Add as Library
;项目完成后,有时候需要打成war方便部署。war包可以直接放入Tomcat的webapps目录中,启动Tomcat后自动解压,即可访问。
执行上述操作,后在项目根目录下生成out目录,内部包含的war包就是我们需要的war包。
将该war包拷贝到tomcat的webapps目录下,双击运行startup.bat,tomcat会自动解压该war包并发布项目,发布之后我们就可以访问。
Server Applet(服务器小程序),是由服务器端调用和执行的、按照Servlet自身规范编写的Java类。
JavaWeb的三大组件(Servlet、Filter、Lisener)之一,是动态资源的一种。
作用:
后续我们学习Servlet也是集中在这三点。
package com.qfedu.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;
public class TestServlet implements Servlet {
public void init(ServletConfig config) throws ServletException{
}
public void service(ServletRequest request,ServletResponse response) throws ServletException,IOException{
System.out.println("My First Servlet!");
}
public void destroy(){
}
public ServletConfig getServletConfig(){
return null;
}
public String getServletInfo(){
return null;
}
}
在web.xml中配置Servlet
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>aServletservlet-name>
<servlet-class>com.qfedu.servlet.AServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>aServletservlet-name>
<url-pattern>/aServleturl-pattern>
servlet-mapping>
web-app>
url-pattern配置的内容就是浏览器地址栏输入的URL中项目名称后资源的内容
在Tomcat中部署该项目,访问http://localhost:8080/项目名/testServlet,控制台及浏览器有输出证明Servlet部署及访问成功。
500错误:服务器内部出现了错误
修改TestServlet代码如下:
package com.qfedu.servlet;
import javax.servlet.*;
import java.io.IOException;
public class AServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("My First Servlet!");
String str = null;
System.out.println(str.length());
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
运行JavaWeb项目,访问TestServlet,页面显示如下:
Servlet是单例的,一个类型的Servlet只有一个实例对象,那么就有可能会出现一个Servlet同时处理多个请求,Servlet不是线程安全的,不应该在Servlet中创建成员变量,因为可能会存在一个线程对这个成员变量进行写操作,另一个线程对这个成员变量进行读操作。
javax.servlet.Servlet
代码
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
Servlet中的大多数方法不由我们来调用,而是由Tomcat来调用,并且Servlet的对象也不由我们来创建,由Tomcat来创建!
生命周期方法
生命周期方法方法演示
package com.qfedu.servlet;
import javax.servlet.*;
import java.io.IOException;
public class BServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init...");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service...");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("destroy...");
}
}
在web.xml中配置Servlet
<servlet>
<servlet-name>bServletservlet-name>
<servlet-class>com.qfedu.servlet.BServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>bServletservlet-name>
<url-pattern>/bServleturl-pattern>
servlet-mapping>
启动tomcat->访问BServlet->关闭tomcat
观察控制台的打印。
关于ServletConfig
修改BServlet代码
package com.qfedu.servlet;
import javax.servlet.*;
import java.io.IOException;
public class BServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init...");
System.out.println(servletConfig.getInitParameter("name"));
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service...");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("destroy...");
}
}
修改web.xml关于BServlet的配置
<servlet>
<servlet-name>bServletservlet-name>
<servlet-class>com.qfedu.servlet.BServletservlet-class>
<init-param>
<param-name>nameparam-name>
<param-value>zhangsanparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>bServletservlet-name>
<url-pattern>/bServleturl-pattern>
servlet-mapping>
GenericServlet使编写Servlet变得更容易。它提供生命周期方法init和destroy的简单实现,要编写一般的Servlet,只需重写抽象service方法即可。
代码如下:
package com.qfedu.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class CServlet extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("CServlet....");
}
}
web.xml配置CServlet
<servlet>
<servlet-name>cServletservlet-name>
<servlet-class>com.qfedu.servlet.CServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>cServletservlet-name>
<url-pattern>/cServleturl-pattern>
servlet-mapping>
HttpServlet类是GenericServlet的子类,它提供了对HTTP请求的特殊支持,所以通常我们都会通过继承HttpServlet来完成自定义的Servlet。
在HttpServlet的service(HttpServletRequest,HttpServletResponse)
方法会去判断当前请求是GET还是POST,如果是GET请求,那么会去调用本类的doGet()方法,如果是POST请求会去调用doPost()方法,这说明我们在子类中去覆盖doGet()或doPost()方法即可。
package com.qfedu.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class DServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doGet.......");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doPost........");
}
}
web.xml配置DServlet
<servlet>
<servlet-name>dServletservlet-name>
<servlet-class>com.qfedu.servlet.DServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>dServletservlet-name>
<url-pattern>/dServleturl-pattern>
servlet-mapping>
Servlet2.5及之前使用该方式
<servlet>
<servlet-name>aServletservlet-name>
<servlet-class>com.qfedu.servlet.AServletservlet-class>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>aServletservlet-name>
<url-pattern>/aServleturl-pattern>
servlet-mapping>
url-pattern定义匹配规则,取值说明:
/具体的名称
只有url路径是具体的名称的时候才会触发Servlet*.xxx
只要是以xxx结尾的就匹配触发Servlet/*
匹配所有请求,包含服务器的所有资源/
匹配所有请求,包含服务器的所有资源,不包括jspload-on-startup
Servlet3.0新增特性,推荐使用
@WebServlet常用属性
name:Serlvet名字,可选
value::配置url路径,可以配置多个
urlPatterns:配置url路径 ,和value作用一样,不能同时和value使用
l- oadOnStartup:配置Servlet的创建的时机, 如果是0或者正数,启动程序时创建,如果是负数,则访问时创建。 数子越小优先级越高。
package com.qfedu.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "EServlet", value = "/eServlet", loadOnStartup = 1)
public class EServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doGet...");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doPost...");
}
}
HTTP Status 404资源找不到
-. 第一种情况:地址书写错误;
-. 第二种情况:地址没有问题,把IDEA项目中out目录删除,然后重新运行。
Serlvet地址配置重复,多个Servlet的url-pattern使用相同的值
Serlvet地址配置错误,比如没有写/。
reponse类型为javax.servlet.http.HttpServletResponse
,客户端发出每个请求时,服务器都会创建一个response对象,并传入给Servlet.service()
方法。response对象是用来对客户端进行响应的,这说明在service()方法中使用response对象可以完成对客户端的响应工作。
主要功能
关于设置响应正文
response是响应对象,向客户端输出响应正文(响应体)可以使用response的响应流,常用方法:
PrintWriter out = response.getWriter()
:获取字符流—16ServletOutputStream out = response.getOutputStream()
:获取字节流—8如果响应正文内容为字符,那么使用response.getWriter(),如果响应内容是字节,例如下载时,那么可以使用response.getOutputStream();
注意:在一个请求中,不能同时使用这两个流,也就是说,要么你使用repsonse.getWriter()
,要么使用response.getOutputStream()
,但不能同时使用这两个流。不然会抛出IllegalStateException
异常。
例:在页面打印文字,字体颜色为红色。
package com.qfedu.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "FServlet", value = "/FServlet")
public class FServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.println("Hello World
");
out.println("你好世界
");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
问题分析:
response.getWriter()
时默认字符编码为ISO-8859-1
,不支持中文;response.setCharacterEncoding(“utf-8”)
来设置,这样可以保证输出给客户端的字符都是使用的UTF-8编码;解决方案:
response.setContentType("text/html;charset=utf-8")
;response.setCharacterEncoding(“utf-8”)
保证输出给客户端的字符都是使用UTF-8编码的。上述案例修改
package com.qfedu.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "FServlet", value = "/FServlet")
public class FServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应头
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("Hello World
");
out.println("你好世界
");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
现象:当访问http://baidu.com时,发现地址栏变成https://www.baidu.com,这就是重定向了
。
概念:重定向是服务器通知浏览器去访问另一个地址,即再发出另一个请求。
实现方式1:
response.setStatus(302);
response.setHeader("Location", "/项目名/bServlet");
实现方式2:
response.sendRedirect("/项目名/bServlet");
package com.qfedu.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "GServlet", value = "/GServlet")
public class GServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("GServlet............................");
//实现方式1
//response.setStatus(302);
//response.setHeader("Location", "/JavaWebTest_war_exploded/bServlet");
//实现方式2
response.sendRedirect("/JavaWebTest_war_exploded/bServlet");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
关于重定向的总结
response.getWriter()或response.getOutputStream()
输出数据request是Servlet.service()
方法的一个参数,类型为javax.servlet.http.HttpServletRequest
。在客户端发出每个请求时,服务器都会创建一个request对象,并把请求数据封装到request中,然后在调用Servlet.service()
方法时传递给service()
方法,这说明在service()
方法中可以通过request对象来获取请求数据。
主要功能
String getMethod()
:返回请求方法,例如:GET String getRemoteAddr()
:返回当前客户端的IP地址 String getRemoteHost()
:返回当前客户端的主机名,但这个方法的实现还是获取IP地址 String getServerName()
:返回主机名,例如:localhost int getServerPort()
:返回服务器端口号,例如:8080
package com.qfedu.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "HServlet", value = "/HServlet")
public class HServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("请求方法:" + request.getMethod());
System.out.println("客户端IP:" + request.getRemoteAddr());
System.out.println("客户端主机名:" + request.getRemoteHost());
System.out.println("主机名:" + request.getServerName());
System.out.println("服务器端口号:" + request.getServerPort());
System.out.println("客户端端口号:" + request.getRemotePort());
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
客户端传递参数方式有两种(GET和POST)
GET请求和POST请求的区别
request获取请求参数的API
String getParameter(String name)
:通过指定名称获取参数值(必须掌握)String[] getParameterValues(String name)
:当多个参数名称相同时,可以使用方法来获取Enumeration getParameterNames()
:获取所有参数的名字Map getParameterMap()
:获取所有参数封装到Map中,其中key为参数名,value为参数值,因为一个参数名称可能有多个值,所以参数值是String[],而不是StringServlet代码如下:
package com.qfedu.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.util.Arrays;
@WebServlet(name = "IServlet", value = "/IServlet")
public class IServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数
String username = request.getParameter("username");
System.out.println(username);
//当多个参数名称相同时,可以使用方法来获取
String[] hobbies = request.getParameterValues("hobby");
System.out.println(Arrays.toString(hobbies));
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
Get方式提交:
http://localhost:8080/JavaWebTest_war_exploded/IServlet?username=zs&hobby=睡觉&hobby=game
Post方式提交:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<form action="/JavaWebTest_war_exploded/IServlet" method="post">
用户名<input type="text" name="username" /><br/>
爱好<input type="checkbox" name="hobby" value="睡觉1">睡觉1
<input type="checkbox" name="hobby" value="睡觉2">睡觉2
<input type="checkbox" name="hobby" value="睡觉3">睡觉3
<button type="submit">提交button>
form>
body>
html>
Get请求乱码
POST请求乱码
修改IServlet代码如下:
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.util.Arrays;
@WebServlet(name = "IServlet", value = "/IServlet")
public class IServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置请求编码
request.setCharacterEncoding("utf-8");
//获取请求参数
String username = request.getParameter("username");
System.out.println(username);
//当多个参数名称相同时,可以使用方法来获取
String[] hobbies = request.getParameterValues("hobby");
System.out.println(Arrays.toString(hobbies));
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
转发的作用在服务器端,将请求发送给服务器上的其他资源,以共同完成一次请求的处理,多个Servlet或JSP共同来处理一个请求。
实现步骤:
RequestDispatcher rd = request.getRequestDispatcher("/BServlet");
rd.forward(request, response);
@WebServlet(name = "JServlet", value = "/JServlet")
public class JServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//在request域中存放数据
request.setAttribute("name", "zs");
//转发
//创建调度器
RequestDispatcher dispatcher = request.getRequestDispatcher("/KServlet");
//转发
dispatcher.forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
@WebServlet(name = "KServlet", value = "/KServlet")
public class KServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//从request域中获取数据
String name = (String)request.getAttribute("name");
System.out.println(name);
//从request域中删除数据
request.removeAttribute("name");
//转发
request.getRequestDispatcher("/LServlet").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
@WebServlet(name = "LServlet", value = "/LServlet")
public class LServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//从request域中获取数据
String name = (String)request.getAttribute("name");
//由于KServlet删除了域对象中的内容,导致name输出为null
System.out.println("LServlet:" + name);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
请求转发和重定向的区别
超链接、表单、重定向都是客户端路径(假设当前主机是http://localhost:8080,应用名称Test
)
http://www.baidu.com
/
开头的相对路径
(http://localhost:8080)
/
开头,一定是/Test/...
/
开头的相对路径
/
开头的相对路径,后面是当前应用的名称,再是访问路径。这里说的服务器路径,就是以/
开头的相对路径
(http://localhost:8080/Test)
使用Servlet完成登录功能,不需要连接数据库,需求如下:
登录页代码如下:
DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>登录title>
head>
<body>
<form action="http://localhost:8080/JavaWebTest/LoginServlet" method="post">
<fieldset style="width: 300px;">
<legend>用户登录legend>
<p>
<label>账号label>
<input type="text" name="username" placeholder="请输入用户名" />
p>
<p>
<label>密码label>
<input type="password" name="password" placeholder="请输入密码" />
p>
<p>
<button type="submit">登录button>
<button type="reset">重置button>
p>
fieldset>
form>
body>
html>
登录成功页
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>successtitle>
head>
<body>
<p>登录成功p>
body>
html>
登录失败页
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>errortitle>
head>
<body>
<p>登录失败p>
body>
html>
Servlet代码
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
if(username.equals("admin") && password.equals("admin")) {
request.getRequestDispatcher("/success.html").forward(request, response);
} else {
request.getRequestDispatcher("/error.html").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
一个项目只有一个ServletContext对象,使用它可以给多个Servlet传递数据,与天地同寿,这个对象在Tomcat启动时就创建,在Tomcat关闭时才会消失。
特点:
唯一性: 一个应用对应一个ServletContext。
生命周期: 只要容器不关闭或者应用不卸载,ServletContext就一直存在。
GenericServlet提供了getServletContext()
方法;
HttpServletRequest提供了getServletContext()
方法;
HttpSession提供了getServletContext()
方法。
//获取项目在服务器上的真实路径
String realpath=servletContext.getRealPath("/");
上下文路径指的是应用名称。
//获取项目的上下文路径
//上下文路径(应用程序名称)
System.out.println(servletContext.getContextPath());
System.out.println(request.getContextPath());
案例
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "MServlet", value = "/MServlet")
public class MServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取servletContext
ServletContext servletContext = this.getServletContext();
//获取项目真实路径
System.out.println(servletContext.getRealPath("/"));
//获取项目上下文路径
System.out.println(servletContext.getContextPath());
System.out.println(request.getContextPath());
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
ServletContext拥有作用域,可以存储数据到全局容器中
servletContext.setAttribute("name",value);
servletContext.getAttribute("name");
servletContext.removeAttribute("name");
案例,统计网站访问人数
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "NServlet", value = "/NServlet")
public class NServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext sctx = this.getServletContext();
Integer count = (Integer) sctx.getAttribute("count");
if(count == null) {
count = 1; //自动装箱
} else {
count++;
}
sctx.setAttribute("count", count);
PrintWriter out = response.getWriter();
out.println(""
+ count + "");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
JSP(Java Server Pages)是JavaWeb服务器端的动态资源。它与HTML页面的作用是相同的,显示数据和获取数据。
JSP=HTML+Java脚本+JSP动作标签(包含EL表达式)
本质上就是Java代码片段
分类:
<%...%>
:Java语句<%=…%>
:Java表达式out.print(…);<%!...%>
:Java定义类成员内置对象(无需创建就可以使用的对象):
<%=…%>
与out.print()功能是相同的,它们都是向客户端输出<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>jsptesttitle>
head>
<body>
<h1>JSP演示h1>
<%
// Java语句
String s1 = "hello jsp";
// 不会输出到客户端,而是在服务器端的控制台打印
System.out.println(s1);
%>
输出变量:<%=s1 %><br/>
输出int类型常量:<%=100 %><br/>
输出String类型常量:<%="你好" %><br/>
使用HTML直接输出常量<span>100span>
body>
html>
在一个JSP中多个<%...%>
可以一起使用
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>jsp表格title>
head>
<body>
<h1>表格h1>
<table border="1" width="50%">
<tr>
<th>序号th>
<th>用户名th>
<th>密码th>
tr>
<%
for(int i = 0; i < 5; i++) {
%>
<tr>
<td><%=i+1 %>td>
<td>user<%=i %>td>
<td><%=100+1 %>td>
tr>
<%
}
%>
table>
body>
html>
JSP是特殊的Servlet(查看编译后的JSP源码)类,当JSP页面首次被访问时,容器(Tomcat)会先把JSP编译成Servlet,然后再去执行Servlet。所以JSP其实就是一个Servlet。
JSP生成的Servlet存放在tomcat的work目录下,它是JSP的“真身”。我们打开看看其中的内容,了解一下JSP的“真身”。
你会发现,在JSP中的静态信息(例如等)在“真身”中都是使用out.write()完成打印!这些静态信息都是作为字符串输出给了客户端。
<%-- ... --%>
, 在JSP编译成.java
时会被忽略的,即JSP注释。 可以在JSP页面中使用html注释:,但这个注释在JSP编译成的.java中是存在的,它不会被忽略,而且会被发送到客户端浏览器。
Cookie翻译成中文是小甜点,小饼干的意思。在HTTP中它表示服务器送给客户端浏览器的小甜点。
Cookie是在浏览器访问Web服务器的某个资源时,由Web服务器在HTTP响应消息头中附带传送给浏览器的一小段数据。一旦Web浏览器保存了某个Cookie,那么它在以后每次访问该Web服务器时,都应在HTTP请求头中将这个Cookie回传给Web服务器。一个Cookie主要由标识该信息的名称(name)和值(value)组成。
Cookie大小上限为4KB;
一个服务器最多在客户端浏览器上保存20个Cookie;
一个浏览器最多保存300个Cookie;
上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能!
注意,不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。
//创建Cookie
Cookie ck=new Cookie("name", "zs");
ck.setMaxAge(-1);//内存存储,取值有三种:>0有效期,单位秒;=0浏览器关闭;<0内存存储,默认-1
response.addCookie(ck);//添加到response对象中,响应时发送给客户端
//获取所有的Cookie
Cookie[] cks=request.getCookies();
//遍历Cookie
for(Cookie ck:cks){
//检索出自己的Cookie
if(ck.getName().equals("name")) {
//记录Cookie的值
code=ck.getValue();
break;
}
}
只需要保证Cookie的名和路径一致即可修改
//修改Cookie
Cookie ck=new Cookie("name", "ls");
ck.setMaxAge(-1);//内存存储,取值有三种:>0有效期,单位秒;=0失效;<0内存存储
response.addCookie(ck);//让浏览器添加Cookie
Cookie不只有name和value,Cookie还是生命。所谓生命就是Cookie在客户端的有效时间,可以通过setMaxAge(int)
来设置Cookie的有效时间。
cookie.setMaxAge(-1)
:cookie的maxAge属性的默认值就是-1,表示只在浏览器内存中存活。一旦关闭浏览器窗口,那么cookie就会消失。cookie.setMaxAge(60*60)
:表示cookie对象可存活1小时。当生命大于0时,浏览器会把Cookie保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie也会存活1小时;cookie.setMaxAge(0)
:cookie生命等于0是一个特殊的值,它表示cookie被作废!也就是说,如果原来浏览器已经保存了这个Cookie,那么可以通过Cookie的setMaxAge(0)来删除这个Cookie。无论是在浏览器内存中,还是在客户端硬盘上都会删除这个Cookie。案例
//设置Cookie
@WebServlet(name = "AServlet", value = "/AServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置Cookie
Cookie cookie = new Cookie("name", "zs");
//设置Cookie的超时时间 -1表示一旦关闭浏览器窗口,那么cookie就会消失
cookie.setMaxAge(-1);
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
//获取Cookie
@WebServlet(name = "BServlet", value = "/BServlet")
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie[] cks = request.getCookies();
for (Cookie ck : cks) {
if(ck.getName().equals("name")) {
//获取Cookie的值
System.out.println(ck.getValue());
break;
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
关于会话:
javax.servlet.http.HttpSession
接口表示一个会话,是Java Web提供的。
Session是服务器端对象,保存在服务器端。
HttpSession request.getSesssion()
:如果当前会话已经有了session对象那么直接返回,如果当前会话还不存在会话,那么创建session并返回; JSP中得到session对象:session是JSP内置对象之一,不用创建就可以直接使用。
一个会话创建一个HttpSession对象,同一会话中的多个请求中可以共享session中的数据
目前为止已经学习了三个域对象,分别是request、session、servletContext,他们都有共同的方法:
如果用户需要在会话范围之内共享数据,应该将数据保存在session中。
案例,演示session中会话的多次请求中共享数据
@WebServlet(name = "CServlet", value = "/CServlet")
public class CServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取Session
HttpSession session = request.getSession();
//在Session域中存放数据
session.setAttribute("name", "zs");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
@WebServlet(name = "DServlet", value = "/DServlet")
public class DServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取Session
HttpSession session = request.getSession();
//从Session域中获取数据
String name = (String)session.getAttribute("name");
System.out.println(name);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
login.jsp:提供登录表单,提交表单请求到LoginServlet
LoginServlet:获取请求参数,校验用户是否登录成功
success.jsp:登录成功页面,显示欢迎信息,关闭浏览器后,直接访问登录页会提示登录
LoginServlet代码如下
@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
if(username.equals("admin") && password.equals("admin")) {
//登录成功
//保存数据到Session
HttpSession session = request.getSession();
session.setAttribute("username", username);
request.getRequestDispatcher("/success.jsp").forward(request, response);
} else {
//登录失败信息
String msg = "用户名或密码错误";
request.setAttribute("msg", msg);
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
login.jsp代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Logintitle>
head>
<body>
<%
String msg = (String)request.getAttribute("msg");
if(msg != null) {
%>
<p style="color: red;"><%=msg %>p>
<%
}
%>
<form action="<%=request.getContextPath() %>/LoginServlet" method="post">
<fieldset style="width: 300px;">
<legend>用户登录legend>
<p>
<label>账号label>
<input type="text" name="username" placeholder="请输入用户名" />
p>
<p>
<label>密码label>
<input type="password" name="password" placeholder="请输入密码" />
p>
<p>
<button type="submit">登录button>
<button type="reset">重置button>
p>
fieldset>
form>
body>
html>
success.jsp代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>successtitle>
head>
<body>
<%
String username = (String) session.getAttribute("username");
if(username != null) {
%>
<p>欢迎<%=username %>p>
<%
} else {
%>
<p>请先登录p>
<%
}
%>
body>
html>
Session底层依赖Cookie
String getId()
:获取sessionId;
int getMaxInactiveInterval()
:获取session可以的最大不活动时间(秒),默认为30分钟。当session在30分钟内没有使用,那么Tomcat会在session池中移除这个session;
void setMaxInactiveInterval(int interval)
:设置session允许的最大不活动时间(秒),如果设置为1秒,那么只要session在1秒内不被使用,那么session就会被移除;
long getCreationTime()
:返回session的创建时间,返回值为当前时间的毫秒值;
long getLastAccessedTime()
:返回session的最后活动时间,返回值为当前时间的毫秒值;
void invalidate()
:让session失效!调用这个方法会被session失效,当session失效后,客l 户端再次请求,服务器会给客户端创建一个新的session,并在响应中给客户端新session的sessionId;
boolean isNew()
:查看session是否为新。当客户端第一次请求时,服务器为客户端创建session,但这时服务器还没有响应客户端,也就是还没有把sessionId响应给客户端时,这时session的状态为新。
修改LoginServlet,体会Session超时时间及Session失效
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
if(username.equals("admin") && password.equals("admin")) {
//登录成功
//保存数据到Session
HttpSession session = request.getSession();
session.setAttribute("username", username);
//设置Session超时时间为1s
session.setMaxInactiveInterval(1);
//延时2000ms,模拟Session超时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//设置Session失效
//session.invalidate();
request.getRequestDispatcher("/success.jsp").forward(request, response);
} else {
//登录失败信息
String msg = "用户名或密码错误";
request.setAttribute("msg", msg);
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
修改登录案例,增加退出功能
用于退出登录的Servlet
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "LogoutServlet", value = "/LogoutServlet")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置Session失效
request.getSession().invalidate();
//跳转到登录页
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
修改success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>successtitle>
head>
<body>
<%
String username = (String) session.getAttribute("username");
if(username != null) {
%>
<p>欢迎<%=username %>p>
<p><a href="<%=request.getContextPath()%>/LogoutServlet">退出a>p>
<%
} else {
%>
<p>请先登录p>
<%
}
%>
body>
html>
在我们注册时,如果没有验证码的话,我们可以使用URLConnection来写一段代码发出注册请求。甚至可以使用while(true)来注册!那么服务器就废了!
验证码可以去识别发出请求的是人还是程序!当然,如果聪明的程序可以去分析验证码图片!但分析图片也不是一件容易的事,因为一般验证码图片都会带有干扰线,人都看不清,那么程序一定分析不出来。
在项目中导入ValidateCode.jar
import cn.dsna.util.images.ValidateCode;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyTest {
public static void main(String[] args) throws IOException {
//生成验证码
ValidateCode vc=new ValidateCode(200, 30, 4, 10);
//获取验证码对应的文字
String code=vc.getCode();
System.out.println(code);
// 保存图片
FileOutputStream out = new FileOutputStream("D:/code_img.jpg");
vc.write(out);
}
}
运行完成后可以在D盘下看到验证码图片。
增加生成验证码的Servlet
import cn.dsna.util.images.ValidateCode;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "CodeServlet", value = "/CodeServlet")
public class CodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//生成验证码
ValidateCode vc = new ValidateCode(150, 30, 4, 5);
String sysCode = vc.getCode();
System.out.println(sysCode);
//在Session中存储验证码
request.getSession().setAttribute("sysCode", sysCode);
//将验证码响应给用户
vc.write(response.getOutputStream());
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
修改登录页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Logintitle>
head>
<body>
<%
String msg = (String)request.getAttribute("msg");
if(msg != null) {
%>
<p style="color: red;"><%=msg %>p>
<%
}
%>
<form action="<%=request.getContextPath() %>/LoginServlet" method="post">
<fieldset style="width: 300px;">
<legend>用户登录legend>
<p>
<label>账号label>
<input type="text" name="username" placeholder="请输入用户名" />
p>
<p>
<label>密码label>
<input type="password" name="password" placeholder="请输入密码" />
p>
<p>
<label>验证码label>
<input type="text" name="code" placeholder="请输入验证码" />
<img src="<%=request.getContextPath()%>/CodeServlet" alt="验证码">
p>
<p>
<button type="submit">登录button>
<button type="reset">重置button>
p>
fieldset>
form>
body>
html>
修改LoginServlet
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
String code = request.getParameter("code");
//获取Session中的Code
HttpSession session = request.getSession();
String sysCode = (String)session.getAttribute("sysCode");
if(sysCode.equalsIgnoreCase(code)) {
if(username.equals("admin") && password.equals("admin")) {
session.setAttribute("username", username);
request.getRequestDispatcher("/success.jsp").forward(request, response);
} else {
//登录失败信息
String msg = "用户名或密码错误";
request.setAttribute("msg", msg);
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
} else {
String msg = "验证码输入错误";
request.setAttribute("msg", msg);
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
在项目中引入jquery,修改登录页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录title>
<script src="<%=request.getContextPath() %>/js/jquery-3.4.1.min.js">script>
<script>
$(function () {
$("#codeImg").click(function () {
var date = new Date();
$("#codeImg").prop("src", "<%=request.getContextPath() %>/CodeServlet?timeStamp=" + date.getTime());
});
});
script>
head>
<body>
<%
String errorMsg = (String)request.getAttribute("errorMsg");
if(errorMsg != null) {
%>
<p style="color: red;"><%=errorMsg %>p>
<%
}
%>
<form action="<%=request.getContextPath() %>/LoginServlet" method="post">
<fieldset style="width: 300px;">
<legend>用户登录legend>
<p>
<label>账号label>
<input type="text" name="username" placeholder="请输入用户名" />
p>
<p>
<label>密码label>
<input type="password" name="password" placeholder="请输入密码" />
p>
<p>
<label>验证码label>
<input type="text" name="code" placeholder="请输入验证码" />
<img id="codeImg" src="<%=request.getContextPath() %>/CodeServlet" alt="验证码" />
p>
<p>
<button type="submit">登录button>
<button type="reset">重置button>
p>
fieldset>
form>
body>
html>
JSP指令用来设置与整个JSP页面相关的属性。
语法格式:指令格式:<%@指令名 attr1="" attr2="" %>
。
一般都会把JSP指令放到JSP文件的最上方,但这不是必须的。
常用指令:
page
:定义页面的依赖属性,比如脚本语言、error页面、缓存需求等等;
include
:包含其他文件;
taglib
:引入标签库的定义,可以是自定义标签。
page指令是最为常用的指定,也是属性最多的属性,page指令没有必须属性,都是可选属性,例如<%@page %>
,没有给出任何属性也是可以的。
关于pageEncoding和contentType:
import属性:对应java代码中的import语句,用来导入包。
学习jstl标签使用,后面再讲
JSP内置对象:在JSP中无需创建就可以使用的9个对象。
九大内置对象如下:
out(JspWriter)
:等同与response.getWriter(),用来向客户端发送文本数据config(ServletConfig)
:对应”真身”中的ServletConfigpage(当前JSP的真身类型)
:当前JSP页面的“this”,即当前对象pageContext(PageContext)
:页面上下文对象,它是最后一个没讲的域对象exception(Throwable)
:只有在错误页面中可以使用这个对象request(HttpServletRequest)
:即HttpServletRequest类的对象(注意)response(HttpServletResponse)
:即HttpServletResponse类的对象(注意)application(ServletContext)
:即ServletContext类的对象(注意)session(HttpSession)
:即HttpSession类的对象,不是每个JSP页面中都可以使用,如果在某个JSP页面中设置<%@page session=”false”%>
,说明这个页面不能使用session使用情况
主要功能:
表示当前页面 和其他域对象一样,他们都有共同的方法:
void setAttribute(String name, Object value)
Object getAttribute(String name)
void removeAttribute(String name)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试Page域对象title>
head>
<body>
<%
//在page域中存放数据
pageContext.setAttribute("name", "zhangsan");
%>
<%
//从page域中获取数据
System.out.println(pageContext.getAttribute("name"));
%>
body>
html>
可以使用pageContext向request、session、application对象中存取数据,“一个顶四个”
void setAttribute(String name, Object value, int scope)
:在指定范围中添加数据
Object getAttribute(String name, int scope)
:获取指定范围的数据
void removeAttribute(String name, int scope)
:移除指定范围的数据
pageContext.setAttribute("x", "X");
pageContext.setAttribute("x", "XX", PageContext.REQUEST_SCOPE);
pageContext.setAttribute("x", "XXX", PageContext.SESSION_SCOPE);
pageContext.setAttribute("x", "XXXX", PageContext.APPLICATION_SCOPE);
创建Servlet
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "MServlet", value = "/MServlet")
public class MServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取Session
HttpSession session = request.getSession();
//在Session域中存放数据
session.setAttribute("name", "lisi");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
创建测试的JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Titletitle>
head>
<body>
<%
//通过page域从session中获取数据
Object name = pageContext.getAttribute("name", PageContext.SESSION_SCOPE);
System.out.println(name);
%>
body>
html>
Object findAttribute(String name)
:依次在page、request、session、application
范围查找名称为name的数据,如果找到就停止查找。这说明在这个范围内有相同名称的数据,那么page范围的优先级最高
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<title>Insert title heretitle>
head>
<body>
<%
pageContext.setAttribute("key", "page_value");
request.setAttribute("key", "request_value");
session.setAttribute("key", "session_value");
application.setAttribute("key", "app_value");
%>
<%
//全域查找
String value = (String)pageContext.findAttribute("key");
out.print(value);
%>
body>
html>
一个pageContext对象等于所有内置对象,即1个当9个。这是因为可以使用pageContext对象获取其它8个内置对象。
JspWriter getOut()
:获取out内置对象
ServletConfig getServletConfig()
:获取config内置对象
Object getPage()
:获取page内置对象
ServletRequest getRequest()
:获取request内置对象
ServletResponse getResponse()
:获取response内置对象
HttpSession getSession()
:获取session内置对象
ServletContext getServletContext()
:获取application内置对象
Exception getException()
:获取exception内置对象
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Titletitle>
head>
<body>
<%
//获取application对象
System.out.println(pageContext.getServletContext().getContextPath());
%>
body>
html>
动作标签的作用是用来简化Java脚本的,JSP动作标签是JavaWeb内置的动作标签,它们是已经定义好的动作标签,我们可以拿来直接使用。
语法:
作用:包含其它JSP页面
与include指令区别:
被包含的JSP:a.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Titletitle>
head>
<body>
<p>11111111111p>
body>
html>
b.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Titletitle>
head>
<body>
<jsp:include page="a.jsp" />
<p>222222222222p>
body>
html>
forward标签的作用是请求转发!forward标签的作用与RequestDispatcher.forward()
方法相同
EL:Expression Language,表达式语言。在JSP页面中可以直接使用,从JSP2.0开始,代替JSP脚本,非Java开发人员也可以使用。
.getAttribute("name")
, 并将从域中获取的数据进行显示;<%=...%>,<%=...%>
代表输出。${scope.name}
获取具体某个作用域中的数据;
${name}
获取作用域中的数据,逐级查找(pageContext、request、session、application)
EL和JSP脚本的区别
<%=request.getAttribute() %>
没有找到返回null${requestScope.name}
没找到返回""<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>el初步title>
head>
<body>
<%
//在request域总存放数据 String
pageContext.setAttribute("name", "Bob");
request.setAttribute("name", "Zhangsan");
request.setAttribute("age", 10);
session.setAttribute("name", "Jim");
application.setAttribute("name", "Lucy");
%>
<%-- 使用EL表达式获取某个域中的数据并在网页上显示
作用域 xxxScope
--%>
<p>${requestScope.name}p>
<p>${requestScope.age}p>
<hr/>
<%--
全域查找
如果没有限定xxxScope,会按照pageContext,request,session,application的顺序进行查找
--%>
<p>${name}p>
<hr/>
<%--
JSP脚本和EL表达式的区别
--%>
<p><%=request.getAttribute("abc")%>p>
<p>${requestScope.abc}p>
<hr/>
body>
html>
使用EL获取作用域中的对象调用属性时,只能访问对象的get方法,必须遵守命名规范定义
创建实体类
/**
* 表示Person的实体类
*/
public class Person {
private Integer id;
private String name;
private Integer age;
//set,get
//toString
}
EL表达式演示
<%@ page import="com.qf.entity.Person" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.HashMap" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>el表达式处理复杂类型title>
head>
<body>
<%
Person p = new Person();
p.setId(100);
p.setName("Tom");
p.setAge(20);
Person p1 = new Person();
p1.setId(200);
p1.setName("zs");
p1.setAge(21);
//将Person对象存放在域当中
request.setAttribute("person", p);
int[] arr = {1, 2, 100, 50};
request.setAttribute("arr", arr);
List<String> names = new ArrayList<>();
names.add("zs");
names.add("ls");
names.add("ww");
request.setAttribute("names", names);
List<Person> persons = new ArrayList<>();
persons.add(p);
persons.add(p1);
request.setAttribute("persons", persons);
Map<String, Object> map = new HashMap<>();
map.put("name", "zs");
map.put("addr", "qd");
request.setAttribute("map", map);
%>
<%--
通过EL表达式在页面上显示对象中的属性
前提:属性要有对应的set和get方法
--%>
<p>${requestScope.person.id}p>
<p>${requestScope.person.name}p>
<p>${requestScope.person.age}p>
<hr/>
<%--
int[]
1 2 100 1000
List<String>
"111" "222" "333"
List<Person>
Map<String, Object>
--%>
<%--
通过EL表达式在页面上显示数组中的元素
--%>
<p>${requestScope.arr[3]}p>
<hr/>
<%--
通过EL表达式在页面上显示集合中的元素,集合中存放的是简单类型(基本数据类型 + String)
--%>
<p>${names[2]}p>
<hr/>
<%--
通过EL表达式在页面上显示集合(List, Map)中的元素,集合中存放的是复杂类型(除了String类型之外的引用数据类型)
--%>
<p>${persons[0].id}p>
<p>${persons[0].name}p>
<p>${persons[0].age}p>
<hr/>
<p>${map.name}p>
<p>${map.addr}p>
<p>${map["addr"]}p>
<hr/>
body>
html>
操作符 | 描述 |
---|---|
. | 访问一个Bean属性或者一个映射条目 |
[] | 访问一个数组或者集合的元素 |
+ | 加 |
- | 减或负 |
* | 乘 |
/ or div | 除 |
% or mod | 取模 |
== or eq | 测试是否相等 |
!= or ne | 测试是否不等 |
< or lt | 测试是否小于 |
> or gt | 测试是否大于 |
<= or le | 测试是否小于等于 |
>= or ge | 测试是否大于等于 |
&& or and | 测试逻辑与 |
or or | |
! or not | 测试取反 |
empty | 测试是否空值 |
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>el_运算符title>
head>
<body>
<%
request.setAttribute("num", 15);
request.setAttribute("name", "");
%>
<%-- el运算符 --%>
<p>${num + 1}p>
<p>${num - 1}p>
<p>${num * 10}p>
<p>${num / 10}p>
<p>${num div 10}p>
<p>${num % 3}p>
<p>${num mod 3}p>
<hr/>
<p>${num == 15}p>
<p>${num eq 15}p> <%-- eq equals --%>
<p>${num != 15}p>
<p>${num ne 15}p><%-- ne not equals --%>
<p>${num lt 20}p><%-- lt less than --%>
<p>${num gt 20}p><%-- gt great than --%>
<hr/>
<p>${true or false}p>
<hr/>
<p>${empty name}p>
body>
html>
关于empty关键字
<%
String s1="";
pageContext.setAttribute("s1", s1);
String s2=null;
pageContext.setAttribute("s2", s2);
String s3="abcdefg";
pageContext.setAttribute("s3", s3);
List list1 = new ArrayList();
pageContext.setAttribute("list1", list1);
%>
${empty s1}<br>
${empty s2}<br>
${empty s3}<br>
${empty list1}<br>
EL 表达式语言定义了11个隐式对象
隐含对象 | 描述 |
---|---|
pageScope | page作用域 |
requestScope | request作用域 |
sessionScope | session作用域 |
applicationScope | application作用域 |
param | request对象的参数,字符串 |
paramValues | request对象的参数,字符串集合 |
header | HTTP信息头,字符串 |
headerValues | HTTP信息头,字符串集合 |
initParam | 上下文初始化参数 |
cookie | Cookie值 |
pageContext | 当前页面的pageContext域对象 |
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Titletitle>
head>
<body>
<%--
访问服务器某个位置的时候
协议://主机名:端口 http://localhost:8080
项目名:在实际开发中不能写死 - “动”
资源的位置
--%>
<%--<a href="/el_jstl/loginServlet?username=bob">登录a>--%>
<a href="${pageContext.request.contextPath}/loginServlet?username=bob">登录a>
body>
html>
使用EL表达式修改登录案例
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录title>
head>
<body>
<%
String errorMsg = (String)request.getAttribute("errorMsg");
if(errorMsg != null) {
%>
<p style="color: red;">${errorMsg}p>
<%
}
%>
<form action="${pageContext.request.contextPath}/LoginServlet" method="post">
<fieldset style="width: 300px;">
<legend>用户登录legend>
<p>
<label>账号label>
<input type="text" name="username" placeholder="请输入用户名" />
p>
<p>
<label>密码label>
<input type="password" name="password" placeholder="请输入密码" />
p>
<p>
<label>验证码label>
<input type="text" name="code" placeholder="请输入验证码" />
<img src="${pageContext.request.contextPath}/CodeServlet" alt="验证码">
p>
<p>
<button type="submit">登录button>
<button type="reset">重置button>
p>
fieldset>
form>
body>
html>
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>successtitle>
head>
<body>
<%
String username = (String) session.getAttribute("username");
if(username != null) {
%>
<p>欢迎${username}p>
<p><a href="${pageContext.request.contextPath}/LogoutServlet">注销a>p>
<%
} else {
%>
<p>您还没有登录,请先<a href="${pageContext.request.contextPath}/login.jsp">登录a>p>
<%
}
%>
body>
html>
EL主要是用于作用域获取数据,虽然可以做运算判断,但是得到的都是一个结果,做展示;
EL不存在流程控制。比如判断;
EL对于集合只能做单点访问,不能实现遍历操作。比如循环。
JSTL是apache对EL表达式的扩展(也就是说JSTL依赖EL),JSTL是标签语言;
不是JSP的内置标签,使用时需要导包
可对EL获取到的数据进行逻辑操作;
与EL合作完成数据的展示。
导入Jar包,standard.jar 和 jstl.jar;
在JSP页面引入标签库<% @taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c">
out标签
<c:out value="aaa"/>
<c:out value="${aaa}"/>
<c:out value="${aaa}" default="xxx"/>
set标签
<c:set var="a" value="hello"/>
<c:set var="a" value="hello" scope="session"/>
remove标签
<c:remove var="a"/>
<c:remove var="a" scope="page"/>
案例
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>jstl输入输出title>
head>
<body>
<%--
JSTL
增强EL表达式的功能,实现复杂的逻辑操作
--%>
<%-- 输出 --%>
<p><c:out value="hello world"/>p>
<p>hello worldp>
<hr/>
<%-- 定义变量
int age = 10;
在域对象中存放数据,默认在page域中存放
scope:指定数据存放在哪个域中
--%>
<c:set var="name" value="Zhangsan" />
<p>${pageScope.name}p>
<c:set var="age" value="10" scope="application" />
<p>${age}p>
body>
html>
if标签
<c:if test="${条件}">
hello
c:if>
choose标签
<c:choose>
<c:when test="${条件1}">ac:when>
<c:when test="${条件2}">bc:when>
<c:when test="${条件3}">cc:when>
<c:otherwise>dc:otherwise>
c:choose>
案例
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>jstl分支结构title>
head>
<body>
<c:set var="num" value="10" />
<c:if test="${num gt 2}">
${num}大于2是成立的
c:if>
<hr/>
<%-- 判断是否及格 --%>
<c:set var="score" value="80" />
<c:choose>
<c:when test="${score >= 60}">
<p>及格p>
c:when>
<c:otherwise>
<p>不及格p>
c:otherwise>
c:choose>
<hr/>
<c:choose>
<c:when test="${score >= 80}">
<p>优秀p>
c:when>
<c:when test="${score >= 70}">
<p>良好p>
c:when>
<c:when test="${score >= 60}">
<p>及格p>
c:when>
<c:otherwise>
<p>不及格p>
c:otherwise>
c:choose>
body>
html>
forEach标签
<c:forEach var="i" begin="1" end="10" step="1">
${i}
c:forEach>
<c:forEach items="${strs }" var="str">
${str }<br/>
c:forEach>
案例
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="com.qf.entity.Person" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSTL循环结构title>
head>
<body>
<%--
循环 - 反复做某件事情
1.输出1 2 3 ... 5
for(int i = 1; i <= 5; i++) {
System.out.println(i);
}
--%>
<c:forEach var="i" begin="1" end="5" step="1">
<p>${i}p>
c:forEach>
<hr/>
<%--
1+2+3+...+100
int sum = 0;
for(int i = 1; i <= 100; i++) {
sum = sum + i;
}
System.out.println(sum);
2+4+6+8+...+100
--%>
<c:set var="sum" value="0" />
<c:forEach var="i" begin="2" end="100" step="2">
<c:set var="sum" value="${sum + i}" />
c:forEach>
<p>${sum}p>
<hr/>
<%-- 通过jstl遍历一个集合,在h1标题中显示
假设集合从servlet中保存到request域中,然后转发到该页面
--%>
<%
List<String> list = new ArrayList<>();
list.add("1111");
list.add("2222");
list.add("333");
list.add("******");
request.setAttribute("list", list);
%>
<%--
items:表示被遍历的集合
var:每一次被遍历到的元素
int[] arr = {1, 2, 3};
for(int i : arr) {
}
--%>
<c:forEach items="${list}" var="item">
<h1>${item}h1>
c:forEach>
<hr/>
<%
//模拟
List<Person> persons = new ArrayList<>();
Person p = new Person();
p.setId(100);
p.setName("Tom");
p.setAge(20);
Person p1 = new Person();
p1.setId(200);
p1.setName("zs");
p1.setAge(21);
persons.add(p);
persons.add(p1);
request.setAttribute("persons", persons);
//emp
%>
<table border="1">
<tr>
<th>idth>
<th>nameth>
<th>ageth>
<th colspan="2">optth>
tr>
<c:forEach items="${persons}" var="p">
<tr>
<td>${p.id}td>
<td>${p.name}td>
<td>${p.age}td>
<td><a href="${pageContext.request.contextPath}/xxxServlet?id=${p.id}">修改a>td>
<td><a href="${pageContext.request.contextPath}/aaaServlet?id=${p.id}">删除a>td>
tr>
c:forEach>
table>
body>
html>
修改login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<head>
<title>登录title>
head>
<body>
<p style="color: red;">${errorMsg}p>
<form action="${pageContext.request.contextPath}/LoginServlet" method="post">
<fieldset style="width: 300px;">
<legend>用户登录legend>
<p>
<label>账号label>
<input type="text" name="username" placeholder="请输入用户名" />
p>
<p>
<label>密码label>
<input type="password" name="password" placeholder="请输入密码" />
p>
<p>
<label>验证码label>
<input type="text" name="code" placeholder="请输入验证码" />
<img src="${pageContext.request.contextPath}/CodeServlet" alt="验证码">
p>
<p>
<button type="submit">登录button>
<button type="reset">重置button>
p>
fieldset>
form>
body>
html>
修改success.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
Created by IntelliJ IDEA.
User: Maxwell
Date: 2021/09/08
Time: 17:31
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>successtitle>
head>
<body>
<c:choose>
<c:when test="${not empty username}">
<p>欢迎${username}p>
<p><a href="${pageContext.request.contextPath}/LogoutServlet">注销a>p>
c:when>
<c:otherwise>
<p>您还没有登录,请先<a href="${pageContext.request.contextPath}/login.jsp">登录a>p>
c:otherwise>
c:choose>
body>
html>
MVC是软件工程中的一种架构模式,是一种软件设计思想,将数据操作、页面展示、业务逻辑分为三个层级(模块),独立完成,相互调用 ,MVC并不是Java独有的,现在几乎所有的B/S的架构都采用了MVC模式,三个层级如下:
优点:
WEB层
:包含JSP和Servlet等与WEB相关的内容
业务层
:业务层中不包含JavaWeb API,它只关心业务逻辑
数据层
:封装了对数据库的访问细节,进行最细粒度的增删改查的操作
调用关系:web层调用—业务层(Service)—数据层(Dao)—DB
关于业务:
注意:
准备工作:
SQL语句如下
CREATE DATABASE webtest;
USE webtest;
DROP TABLE IF EXISTS user;
CREATE TABLE user (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(20),
password varchar(20),
PRIMARY KEY (id)
);
INSERT INTO user VALUES (1, 'zhangsan', '123456');
INSERT INTO user VALUES (2, 'lisi', '123456');
对应的实体类User
public class User {
private Integer id;
private String username;
private String password;
//set和get
//toString
}
Dao层接口
import com.qfedu.entity.User;
import java.sql.SQLException;
public interface UserDao {
User findByUsernameAndPassword(String username, String password) throws SQLException;
}
Dao层接口实现类
import com.qfedu.dao.UserDao;
import com.qfedu.entity.User;
import com.qfedu.utils.JdbcUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import java.sql.SQLException;
public class UserDaoImpl implements UserDao {
@Override
public User findByUsernameAndPassword(String username, String password) throws SQLException {
String sql = "select * from user where username=? and password=?";
Object[] params = {username, password};
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
User user = qr.query(sql, new BeanHandler<User>(User.class), params);
return user;
}
}
Service层接口
import com.qfedu.entity.User;
public interface UserService {
User login(String username, String password);
}
Service层实现类对象
import com.qfedu.dao.UserDao;
import com.qfedu.dao.impl.UserDaoImpl;
import com.qfedu.entity.User;
import com.qfedu.service.UserService;
import java.sql.SQLException;
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public User login(String username, String password) {
User user = null;
try {
user = userDao.findByUsernameAndPassword(username, password);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return user;
}
}
用于登录验证的Servlet
import com.qfedu.entity.User;
import com.qfedu.service.UserService;
import com.qfedu.service.impl.UserServiceImpl;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取登录信息
String username = request.getParameter("username");
String password = request.getParameter("password");
String code = request.getParameter("code");
HttpSession session = request.getSession();
//使用用户输入的验证码和生成的验证码进行比较
String generateCode = (String)session.getAttribute("generateCode");
//忽略大小写比较强
if(generateCode.equalsIgnoreCase(code)) {
//登录验证
User user = userService.login(username, password);
if(user != null) {
//将成功登录的用户信息放在Session中
session.setAttribute("username", "admin");
request.getRequestDispatcher("/success.jsp").forward(request, response);
} else {
request.setAttribute("errorMsg", "用户名或密码错误,请重新登录");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
} else {
request.setAttribute("errorMsg", "验证码错误,请重新登录...");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
用于生成验证码的Servlet
import cn.dsna.util.images.ValidateCode;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "CodeServlet", value = "/CodeServlet")
public class CodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//生成验证码
ValidateCode codeImg = new ValidateCode(200, 30, 4, 5);
String code = codeImg.getCode();
System.out.println(code);
//将生成的验证码存放在session中
request.getSession().setAttribute("generateCode", code);
//发送给浏览器
codeImg.write(response.getOutputStream());
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
用于退出登录的Servlet
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "LogoutServlet", value = "/LogoutServlet")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//让session失效
request.getSession().invalidate();
//重定向到login.jsp
response.sendRedirect(request.getContextPath() + "/login.jsp");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
登录页面login.jsp
注意这个页面中引入jquery用于实现单击刷新验证码,如果加载jquery不能运行成功,删除out目录重试。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<head>
<title>登录title>
<script src="${pageContext.request.contextPath}/js/jquery-3.4.1.min.js">script>
<script>
$(function () {
//单击验证码图片,刷新验证码
$("#codeImg").click(function () {
var date = new Date();
$("#codeImg").prop("src", "${pageContext.request.contextPath}/CodeServlet?timeStamp=" + date.getTime());
});
});
script>
head>
<body>
<p style="color: red;">${errorMsg}p>
<form action="${pageContext.request.contextPath}/LoginServlet" method="post">
<fieldset style="width: 300px;">
<legend>用户登录legend>
<p>
<label>账号label>
<input type="text" name="username" placeholder="请输入用户名" />
p>
<p>
<label>密码label>
<input type="password" name="password" placeholder="请输入密码" />
p>
<p>
<label>验证码label>
<input type="text" name="code" placeholder="请输入验证码" />
<img id="codeImg" src="${pageContext.request.contextPath}/CodeServlet" alt="验证码" />
p>
<p>
<button type="submit">登录button>
<button type="reset">重置button>
p>
fieldset>
form>
body>
html>
成功页面success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>successtitle>
head>
<body>
<c:choose>
<c:when test="${not empty username}">
<p>欢迎${username}p>
<p><a href="${pageContext.request.contextPath}/LogoutServlet">注销a>p>
c:when>
<c:otherwise>
<p>您还没有登录,请先<a href="${pageContext.request.contextPath}/login.jsp">登录a>p>
c:otherwise>
c:choose>
body>
html>
JavaWeb三大组件(Servlet,Filter,Listener)之一;
与Servlet类似,用来拦截请求,不是用来处理请求的;
执行地位在Servlet之前,客户端发送请求时,会先经过Filter,再到达目标Servlet中;响应时,会根据执行流程再次反向执行Filter。可以解决多个Servlet共性代码的冗余问题(例如:乱码处理、登录验证)
Servlet API中提供了一个Filter接口,开发人员编写一个Java类实现了这个接口即可,这个Java类称之为过滤器。
import javax.servlet.*;
import java.io.IOException;
public class TestFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
System.out.println("filter....");
chain.doFilter(request, response);
}
}
编写Servlet用来验证Filter
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "TestServlet", value = "/TestServlet")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("servlet...");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
在web.xml中配置Filter,类似在web.xml中配置Servlet
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>TestFilterfilter-name>
<filter-class>com.qfedu.filter.TestFilterfilter-class>
filter>
<filter-mapping>
<filter-name>TestFilterfilter-name>
<url-pattern>/TestServleturl-pattern>
filter-mapping>
web-app>
启动项目,访问TestServlet,发现先运行TestFilter再运行TestServlet。
init
:在服务器启动时会创建Filter实例,并且每个类型的Filter只创建一个实例,从此不再创建,在创建完Filter实例后,会马上调用init()方法完成初始化工作,这个方法只会被执行一次;
doFilter
:这个方法会在用户每次访问目标资源(web.xml文件配置Filter的url-pattern中的路径)时执行,如果需要“放行”,那么需要调用FilterChain的doFilter(ServletRequest,ServletResponse)方法,如果不调用FilterChain的doFilter()方法,那么目标资源将无法执行;
destroy
:服务器会在创建Filter对象之后,把Filter放到缓存中一直使用,通常不会销毁它。一般会在服务器关闭时销毁Filter对象,在销毁Filter对象之前,服务器会调用Filter对象的destory()方法。
修改TestFilter的代码,进行测试
public class TestFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
System.out.println("init...");
}
public void destroy() {
System.out.println("destroy...");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
System.out.println("filter....");
chain.doFilter(request, response);
}
}
Filter的配置和Servlet的配置类似,分为xml和注解两种配置方式。
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>TestFilterfilter-name>
<filter-class>com.qfedu.filter.TestFilterfilter-class>
filter>
<filter-mapping>
<filter-name>TestFilterfilter-name>
<url-pattern>/TestServleturl-pattern>
filter-mapping>
web-app>
在自定义的Filter类上使用注解@WebFilter
,注解常用属性:
@WebFilter
只写了字符串,这个字符串Filter过滤目标资源的地址。import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebFilter(filterName = "AFilter", value = "/AServlet")
public class AFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
System.out.println("AFilter...");
chain.doFilter(request, response);
}
}
对应的Servlet
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "AServlet", value = "/AServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("AServlet...");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
过滤器的过滤路径通常有三种形式:
/index.jsp、/myservlet1
;*.jsp、*.html、*.jpg
;/*
,表示拦截所有。注意过滤器不能使用/
匹配,/aaa/bbb/*
允许。客户端对服务器请求之后,服务器调用Servlet之前会执行一组过滤器(多个过滤器),那么这组过滤器就称为一条过滤器链。
每个过滤器实现某个特定的功能,当第一个Filter的doFilter方法被调用时,Web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则Web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
创建BServlet
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "BServlet", value = "/BServlet")
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("BServlet...");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
创建BFilter1和BFilter2
//BFilter1
public class BFilter1 implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
System.out.println("BFilter1...");
//放行,如果有后续Filter,继续调用后续Filter执行过滤
chain.doFilter(request, response);
}
}
//BFilter2
public class BFilter2 implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
System.out.println("BFilter2...");
//放行,如果有后续Filter,继续调用后续Filter执行过滤
chain.doFilter(request, response);
}
}
在web.xml中分别配置两个Filter
<filter>
<filter-name>BFilter1filter-name>
<filter-class>com.qfedu.filter.BFilter1filter-class>
filter>
<filter-mapping>
<filter-name>BFilter1filter-name>
<url-pattern>/BServleturl-pattern>
filter-mapping>
<filter>
<filter-name>BFilter2filter-name>
<filter-class>com.qfedu.filter.BFilter2filter-class>
filter>
<filter-mapping>
<filter-name>BFilter2filter-name>
<url-pattern>/BServleturl-pattern>
filter-mapping>
访问BServlet测试
在一个Web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。 优先级:
关于“注解和web.xml同时配置,会创建多个过滤器对象,造成过滤多次”,见下面的代码(注意注释中的内容)
/**
* 修改BFilter2代码,开发其注解配置,注意:注解的filterName和web.xml中filter-name值要是不同的
*/
@WebFilter(filterName = "BFilter2x", value = "/BServlet")
public class BFilter2 implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
//过滤时,打印BFilter2的hash值
System.out.println("BFilter2...." + this.hashCode());
chain.doFilter(request, response);
}
}
测试,访问BServlet,打印如下,两次的hash值不同,说明“注解和web.xml同时配置,会创建多个过滤器对象,造成过滤多次”。
BFilter1...
BFilter2....768904998
BFilter2....703274564
BServlet...
修改登录案例,使用Filter进行权限验证:
用于登录验证的Filter代码如下:
/**
* 用于登录验证的Filter
*/
@WebFilter(filterName = "LoginFilter", value = {"/success.jsp"})
public class LoginFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
//获取Session
HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
if(user == null) {
request.setAttribute("errorMsg", "您还未登录,请先登录");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
chain.doFilter(request, response);
}
}
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>successtitle>
head>
<body>
<p>欢迎${user.username}p>
<p><a href="${pageContext.request.contextPath}/LogoutServlet">注销a>p>
body>
html>
@WebFilter(filterName = "EncodingFilter", value = "/*")
public class EncodingFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
//统一处理请求和响应的乱码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
chain.doFilter(request, response);
}
}
在项目中,文件的上传是常见的功能。很多程序或者软件中都经常使用到文件的上传。
当用户在前端页面点击文件上传后,用户上传的文件数据提交给服务器端,实现保存。
method="post"
;enctype="multipart/form-data"
;
<form action="xxx" method="post" enctype="multipart/form-data">
用户名;<input type="text" name="username"/><br/>
照 片:<input type="file" name="photo"/><br/>
<input type="submit" value="上传"/>
form>
request.getParameter("xxx")
这个方法在表单为enctype="multipart/form-data"
时,它作废了,它永远都返回null
。
创建工厂:DiskFileItemFactory factory = new DiskFileItemFactory();
创建解析器:ServletFileUpload sfu = new ServletFileUpload(factory);
使用解析器来解析request
,得到FileItem
集合:
List
FileItem
中常用方法:
boolean isFormField()
:是否为普通表单项!返回true为普通表单项,如果为false即文件表单项String getFieldName()
:返回当前表单项的名称;String getString(String charset)
:返回表单项的值;String getName()
:返回上传的文件名称;long getSize()
:返回上传文件的字节数;InputStream getInputStream()
:返回上传文件对应的输入流;void write(File destFile)
:把上传的文件内容保存到指定的文件中。//创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//创建解析器
ServletFileUpload sfu = new ServletFileUpload(factory);
//使用解析器来解析request,得到FileItem集合
try {
List<FileItem> list = sfu.parseRequest(request);
for (FileItem item : list) {
if(item.isFormField()) {
String fieldName = item.getFieldName();
String value = item.getString("utf-8");
System.out.println(fieldName + ":" + value);
} else {
String fileName = item.getName();
long size = item.getSize();
System.out.println("文件名:" + fileName);
System.out.println("文件大小" + size);
ServletContext servletContext = this.getServletConfig().getServletContext();
String realPath = servletContext.getRealPath("/img");
item.write(new File(realPath + "/" + fileName));
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
如何解决:
request.setCharacterEncoding("utf-8");
servletFileUpload.setHeaderEncoding("utf-8");
以上两个方案二选一
//创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//创建解析器
ServletFileUpload sfu = new ServletFileUpload(factory);
request.setCharacterEncoding("utf-8");
//sfu.setHeaderEncoding("utf-8");
try {
//使用解析器来解析request,得到FileItem集合
List<FileItem> list = sfu.parseRequest(request);
for (FileItem item : list) {
if(item.isFormField()) {
String fieldName = item.getFieldName();
String value = item.getString("utf-8");
System.out.println(fieldName + ":" + value);
} else {
String fileName = item.getName();
long size = item.getSize();
System.out.println("文件名:" + fileName);
System.out.println("文件大小" + size);
ServletContext servletContext = this.getServletConfig().getServletContext();
String realPath = servletContext.getRealPath("/img");
item.write(new File(realPath + "/" + fileName));
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
使用UUID:fileName = UUID.randomUUID().toString().replace("-", "") + "_" + fileName;
//创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//创建解析器
ServletFileUpload sfu = new ServletFileUpload(factory);
request.setCharacterEncoding("utf-8");
//sfu.setHeaderEncoding("utf-8");
try {
//使用解析器来解析request,得到FileItem集合
List<FileItem> list = sfu.parseRequest(request);
for (FileItem item : list) {
if(item.isFormField()) {
String fieldName = item.getFieldName();
String value = item.getString("utf-8");
System.out.println(fieldName + ":" + value);
} else {
String fileName = item.getName();
long size = item.getSize();
System.out.println("文件名:" + fileName);
System.out.println("文件大小" + size);
ServletContext servletContext = this.getServletConfig().getServletContext();
String realPath = servletContext.getRealPath("/img");
item.write(new File(realPath +
"/" +
UUID.randomUUID().toString().replace("-", "") +
"_" +
fileName));
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}