目录
(实现 Servlet 程序方法1)GET 和 POST 请求的分发处理
(实现 Servlet 程序方法2)通过继承 HttpServlet 实现 Servlet 程序
继承 HttpServlet 实现 Servlet 程序步骤:
HTTP协议中的Content-Type数据类型——MIME 类型
HttpServletRequest类入门2——获取请求参数
服务器doGet、postGet接受请求参数时出现乱码解决方法
通过HTML中base标签修复"请求转发进行页面跳转就会出现错误路径",并演示其的作用
前置工作——创建一个新的servlet程序,名为ResponseIOServlet.java
HttpServletResponse作用演示——往客户端回传数据并解决中文乱码问题
Servlet介绍
- Servlet 是 JavaEE 规范之一。规范就是接口
- Servlet 就 JavaWeb 三大组件之一。三大组件分别是:Servlet 程序、Filter 过滤器、Listener 监听器。
- Servlet 是运行在服务器上的一个 java 小程序,它可以接收客户端发送过来的请求,并响应数据给客户端。
如何通过实现Servlet程序接收请求响应数据呢?
前置工作——创建 javaweb06 项目
项目结构如下
Servlet入门
- 编写一个类去实现 Servlet 接口实现 service 方法,处理请求,并响应数据
import javax.servlet.*; import java.io.IOException; //1.自定义类实现Servlet接口 public class HelloServlet implements Servlet { //定义java处理请求返回响应 /** * service 方法是专门用来处理请求和响应的 */ @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("Hello Servlet 被访问了"); } //下面的方法先不用管,开始设置web.xml的配置 @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }
- 到 web.xml 中去配置客户端发送请求时映射的web服务器 servlet 程序的地址
<?xml version="1.0" encoding="UTF-8"?> <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_4_0.xsd" version="4.0"> <!-- servlet 标签给 Tomcat 配置 Servlet 程序 --> <servlet> <!--servlet-name标签 是给 Servlet 程序起一个别名(一般是类名) --> <servlet-name>HelloServlet</servlet-name> <!--servlet-class 是 Servlet 程序的全类名--> <servlet-class>HelloServlet</servlet-class> </servlet> <!--servlet-mapping 标签给 servlet 程序配置访问地址--> <servlet-mapping> <!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用--> <servlet-name>HelloServlet</servlet-name> <!--url-pattern 标签配置访问地址 <br/> / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/> /hello 表示地址为:http://ip:port/工程路径/hello <br/> --> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>
- (常见web.xml文件异常1)url-pattern 中配置的路径没有以斜杠开头,控制台会出现,客户端会出现404
- (常见web.xml文件异常2)servlet-name 配置的值不存在,出现编译错误提示,服务器启动或者重新部署时控制台会出现如下图情况,客户端会出现404
- (常见web.xml文件异常3)servlet-class 标签的全类名配置错误,出现编译错误提示,服务端出现500错误(也就是服务器出异常了)
- (常见web.xml文件异常1)url-pattern 中配置的路径没有以斜杠开头,控制台会出现,客户端会出现404
- 部署好Tomcat服务器
- 输入地址访问
- 原理图解
Servlet的<url-pattern>的映射规则
- 当一个请求发送到servlet容器的时候,容器先会将请求的url减去当前应用上下文的路径作为servlet的映射url,比如我访问的是 http://localhost/test/aaa.html,我的应用上下文是test,容器会将http://localhost/test去掉, 剩下的/aaa.html部分拿来做servlet的映射匹配。这个映射匹配过程是有顺序的,而且当有一个servlet匹配成功以后,就不会去理会剩下 的servlet了(filter不同,后面会提及到)
注意Tomcat的默认设置
- 我们使用的web服务器是Tomcat,Tomcat有个全局配置文件 apache-tomcat-8.5.79\conf\web.xml 是有一个默认servlet可以来处理除了JSP以外的所有静态资源的请求的, url-pattern是 “/”,也有专门处理JSP文件的Servlet,url-pattern参考下面代码。当有别的Servlet的url-pattern与其冲突时,就会将Tomcat默认设置的Servlet覆盖掉
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- The mapping for the default servlet --> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet> <!-- The mappings for the JSP servlet --> <servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> <url-pattern>*.jspx</url-pattern> </servlet-mapping>
Servlet的<url-pattern>的映射规则六种匹配方式
精确匹配
- <url-pattern>中配置的项必须与url完全精确匹配才能别触发
- 比如servletA 的url-pattern为 /test,servletB的url-pattern为 /* ,这个时候,如果我访问的url为http://localhost/test ,这个时候容器就会先进行精确路径匹配,发现/test正好被servletA精确匹配,那么就去调用servletA,也不会去理会其他的 servlet了
- 注意:<url-pattern>/user/*/hi</url-pattern> 这里的*不是通配符,是精确匹配。也就是当上下文为/user/*/hi才能触发该Servlet程序;并且如果是非法的URL比如“http://localhost:8080/appDemo/user/addUser/ ”也会识别不到,正确应该是“http://localhost:8080/appDemo/user/addUser/”,url后面可以跟随任意查询条件也会被匹配到例如“http://localhost:8080/appDemo/user/addUser?username=Tom&age=23”
扩展名匹配
- 以“*.”开头的字符串被用于扩展名匹配,如果url最后一段包含扩展,容器将会根据扩展选择合适的servlet。
- 比如:servletA的url-pattern:*.action
- 注意:<url-pattern>/user/*.html</url-pattern> 会产生IllegalArgumentException错误,因为不是这么写的。应该参考下面说的组合匹配
路径匹配
- 以“/”字符开头,并以“/*”结尾的字符串用于路径匹配
- 注意:<url-pattern>/user/*/hi</url-pattern> 这里不是路径匹配,是精确匹配,并且“/*”会覆盖所有的Servlet,包括JSP和缺省的Servlet,无论发送什么请求都会被该Servlet拦截处理,不建议在Servlet使用,但是可以在Filter过滤器使用
最长路径匹配
- 比如:servletA的url-pattern为/test/*,而servletB的url-pattern为/test/a/*,此 时访问http://localhost/test/a时,容器会选择路径最长的servlet来匹配,也就是这里的servletB。
缺省匹配(默认匹配)
- <url-pattern>/</url-pattern>
- 如果前面三条规则都没有找到一个servlet,如果应用定义了一个default servlet,则容器会将请求丢给default servlet。如果没有定义了一个default servlet,容器会根据url选择对应的请求资源。
组合匹配
- 如果你要匹配/web/test目录下的所有.jsp文件和.html文件时,可以参考如下配置
<url-pattern>/web/test</url-pattern> <url-pattern>*.jsp</url-pattern> <url-pattern>*.html</url-pattern>
Servlet的<url-pattern>的映射规则六种匹配方式的顺序
- 精确匹配
- 最长路径匹配
- 路径匹配
- 拓展名匹配
- 组合匹配
- 缺省匹配
Servlet 的生命周期
- 执行 Servlet 构造器方法
- 执行 init 初始化方法
- 第一、二步,是在第一次访问的时候创建 Servlet 程序会调用。
- 执行 service 方法,每次访问都会调用。
- 执行 destroy 销毁方法,在 web 工程停止的时候调用。
- 完整结构
(实现 Servlet 程序方法1)GET 和 POST 请求的分发处理
代码演示(修改原有的HelloServlet.java)
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
//1.自定义类实现Servlet接口
public class HelloServlet implements Servlet {
//定义java处理请求返回响应
/**
* service 方法是专门用来处理请求和响应的
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello Servlet 被访问了");
//类型转换,因为ServletRequest没有getMethod()方法,但它的实现类HttpServletRequest有
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
// 获取请求的方式
String method = httpServletRequest.getMethod();
if ("GET".equals(method)) {
doGet();
} else if ("POST".equals(method)) {
doPost();
}
}
/**
* 做 get 请求的操作
*/
public void doGet() {
System.out.println("get 请求");
}
/**
* 做 post 请求的操作
*/
public void doPost() {
System.out.println("post 请求");
}
//下面的方法先不用管,开始设置web.xml的配置
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
浏览器默认只能发送get请求,因此我们通过编写一个可以发送post和get请求的表格的html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.action="http://localhost:8083/javaweb06_war_exploded/hello"是点击提交表单的请求按钮后向该地址发送一个请求
2.method="get"表示发送get请求
3.target="iframeName"表示发送提交表单的请求后如生成事件的元素、如生成事件的元素、文档或窗口等事件
由name="iframeName"的<iframe>标签处理
-->
<form action="http://localhost:8083/javaweb06_war_exploded/hello" method="get" target="iframeName">
<button type="submit">发送get请求</button>
</form>
<!--与上同理-->
<form action="http://localhost:8083/javaweb06_war_exploded/hello" method="post" target="iframeName">
<button type="submit">发送post请求</button>
</form>
<!--style="display:none;"隐藏该标签,只是为了实现提交表单但不跳转页面,也不显示任何内容-->
<iframe name="iframeName" style="display:none;"></iframe>
</body>
</html>
启动服务器通过html页面点击按钮发送Get、Post测试效果
(实现 Servlet 程序方法2)通过继承 HttpServlet 实现 Servlet 程序
- 一般在实际项目开发中,都是使用继承 HttpServlet 类的方式去实现 Servlet 程序
继承 HttpServlet 实现 Servlet 程序步骤:
- 编写一个类去继承 HttpServlet 类,并根据业务需要重写 doGet 或 doPost 方法
import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; //自定义类继承类HttpServlet public class HelloServlet2 extends HttpServlet { /**doGet()在 get 请求的时候调用*/ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("HelloServlet2 的 doGet 方法"); } /**doPost()在 post 请求的时候调用*/ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("HelloServlet2 的 doPost 方法"); } }
- 到 web.xml 中的配置 Servlet 程序的访问地址(便签是可以重复的,但注意内容不要冲突)
<!--标签可以重复--> <servlet> <servlet-name>HelloServlet2</servlet-name> <servlet-class>HelloServlet2</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet2</servlet-name> <url-pattern>/hello2</url-pattern> </servlet-mapping>
- 编写一个html发送get、post请求
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 1.action="http://localhost:8083/javaweb06_war_exploded/hello2"是点击提交表单的请求按钮后向该地址发送一个请求 2.method="get"表示发送get请求 3.target="iframeName"表示发送提交表单的请求后如生成事件的元素、如生成事件的元素、文档或窗口等事件 由name="iframeName"的<iframe>标签处理 --> <form action="http://localhost:8083/javaweb06_war_exploded/hello2" method="get" target="iframeName"> <button type="submit">发送get请求</button> </form> <!--与上同理--> <form action="http://localhost:8083/javaweb06_war_exploded/hello2" method="post" target="iframeName"> <button type="submit">发送post请求</button> </form> <!--style="display:none;"隐藏该标签,只是为了实现提交表单但不跳转页面,也不显示任何内容--> <iframe name="iframeName" style="display:none;"></iframe> </body> </html>
- 查看效果
使用 IDEA 创建 Servlet 程序
这里GIF演示创建的HelloServlet3,但不做web.xml配置
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 = "HelloServlet3")
public class HelloServlet3 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
Servlet 类的继承体系
ServletConfig 类
- ServletConfig 类从类名上来看,就知道是 Servlet 程序的配置信息类。
- Servlet 程序和 ServletConfig 对象都是由 Tomcat 负责创建,我们负责使用。
- Servlet 程序默认是第一次访问的时候创建,ServletConfig 是每个 Servlet 程序创建时,就创建一个对应的 ServletConfig 对象。
ServletConfig 类
package javax.servlet;
import java.util.Enumeration;
public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
}
Servlet类
package javax.servlet;
import java.io.IOException;
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
ServletConfig 类的三大作用
- 可以获取 Servlet 程序的别名 servlet-name 的值
- 获取初始化参数 init-param
- 获取 ServletContext 对象
步骤:
- 编写自定义继承HttpServlet类的类,然后重写init方法,获取Servlet各种信息
import javax.servlet.ServletConfig; 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 = "HelloServlet3") public class HelloServlet3 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } /**重写初始化方法*/ @Override public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig);//重写init方法里面一定要调用父类的init(ServletConfig)操作 System.out.println("2 init 初始化方法"); // 1、可以获取 Servlet 程序的别名 servlet-name 的值 System.out.println("HelloServlet 程序的别名是:" + servletConfig.getServletName()); // 2、获取初始化参数 init-param System.out.println("初始化参数 username 的值是;" + servletConfig.getInitParameter("username")); System.out.println("初始化参数 url 的值是;" + servletConfig.getInitParameter("url")); // 3、获取 ServletContext 对象 System.out.println(servletConfig.getServletContext()); } }
- 在web.xml中添加完整结构的<Servlet>标签
<!-- servlet 标签给 Tomcat 配置 Servlet 程序 --> <servlet> <servlet-name>HelloServlet3</servlet-name> <!--servlet-class 是 Servlet 程序的全类名--> <servlet-class>HelloServlet3</servlet-class> <!--init-param 是初始化参数--> <init-param> <!--是参数名--> <param-name>username</param-name> <!--是参数值--> <param-value>root</param-value> </init-param> <!--init-param初始化参数可重复--> <init-param> <!--是参数名--> <param-name>url</param-name> <!--是参数值--> <param-value>jdbc:mysql://localhost:3306/test</param-value> </init-param> </servlet> <!--servlet-mapping 标签给 servlet 程序配置访问地址--> <servlet-mapping> <servlet-name>HelloServlet3</servlet-name> <url-pattern>/hello3</url-pattern> </servlet-mapping>
- 开启服务器,测试访问看看效果
ServletContext 类
- ServletContext 是一个接口,它表示 Servlet 上下文对象
- 一个 web 工程,只有一个 ServletContext 对象实例
-
ServletContext 对象是一个域对象
-
域对象,是可以像 Map 一样存取数据的对象
-
这里的域指的是存取数据的操作范围,整个 web 工程
-
-
-
ServletContext 是在 web 工程部署启动的时候创建。在 web 工程停止的时候销毁。
ServletContext 类的四个作用
- 获取 web.xml 中配置的上下文参数 context-param
- 获取当前的工程路径,格式: /工程路径
- 获取工程部署后在服务器硬盘上的绝对路径
- 像 Map 一样存取数据
在web.xml中添加上下文参数context-param
<!--context-param 是上下文参数(它属于整个 web 工程),可以重复-->
<context-param>
<param-name>username</param-name>
<param-value>context</param-value>
</context-param>
<!--context-param 是上下文参数(它属于整个 web 工程)-->
<context-param>
<param-name>password</param-name>
<param-value>root</param-value>
</context-param>
在 src\HelloServlet3.java 类的doGet()中演示ServletContext类
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*
1.该方法public javax.servlet.ServletConfig getServletConfig()是被该类继承下来的,
会返回一个该类的ServletConfig对象,调用自己类的方法不需要写this,但是这里写上会更加主观.
每一个Servlet程序对应一个ServletConfig对象
使用该方法getServletConfig()获取 web.xml 中配置的上下文参数 context-param
和下面的init()差不多,因为该上下文参数是整个web工程共有的
*/
ServletContext context = this.getServletConfig().getServletContext();//获取ServletContext对象
//通过ServletContext对象获取上下文参数
String username = context.getInitParameter("username");
System.out.println("context-param 参数 username 的值是:" + username);
System.out.println("context-param 参数 password 的值是:" + context.getInitParameter("password"));
System.out.println("*****************************");
/*
2.获取当前的工程路径,格式: /工程路径
*/
System.out.println( "当前工程路径:" + context.getContextPath() );
System.out.println("*****************************");
/*
3、获取工程部署后在服务器硬盘上的绝对路径
/ 斜杠被服务器解析地址为:http://ip:port/工程名/ 映射到 IDEA 代码的 web 目录
*/
System.out.println("工程部署的路径是:" + context.getRealPath("/"));
System.out.println("工程下 css 目录的绝对路径是:" + context.getRealPath("/css"));
System.out.println("工程下 imgs 目录 1.jpg 的绝对路径是:" + context.getRealPath("/imgs/1.png"));
System.out.println("*****************************");
/*
4.public javax.servlet.ServletContext getServletContext()是被该类继承下来的,
源码是:
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
也就是说我们通过该方法getServletContext()获取一个ServletContext对象
ServletContext 对象可以像 Map 一样存取数据
*/
ServletContext context2 = getServletContext();//获取一个ServletContext对象
System.out.println("ServletContext 对象:"+context);//ServletContext 对象:org.apache.catalina.core.ApplicationContextFacade@984eb1f
System.out.println(context == context2);//true,因为context-param 是上下文参数(它属于整个 web 工程)
System.out.println("保存之前: context 获取 key1 的值是:"+ context.getAttribute("key1"));//保存之前: context 获取 key1 的值是:null
context.setAttribute("key1", "value1");
System.out.println("context2 中获取域数据 key1 的值是:"+ context2.getAttribute("key1"));//context2 中获取域数据 key1 的值是:value1
//通过上面也再次证明context-param 是上下文参数(它属于整个 web 工程)
}
演示效果
HTTP 协议
-
HTTP全称 Hypertext Transefer Protocol 超文本传输协议。
-
协议是指双方,或多方,相互约定好,大家都需要遵守的规则,叫协议。 所谓 HTTP 协议,就是指,客户端和服务器之间通信时,发送的数据,需要遵守的规则,叫 HTTP 协议。
- HTTP 协议中的数据又叫报文。
请求的 HTTP 协议格式
- 请求又分为 GET 请求,和 POST 请求两种
GET 请求
- 请求行:
- 请求方式:GET
- 请求的资源路径:请求资源路径[?请求参数] #中括号表示可有可无
- 请求的协议版本号:HTTP/1.1
- 请求头:
- key : value组成 不同的键值对,表示不同的含义。
- GET请求的HTTP协议内容图解
Post请求
- 请求行:
- 请求方式:Post
- 请求的资源路径:请求资源路径[?请求参数] #中括号表示可有可无
- 请求的协议版本号:HTTP/1.1
- 请求头:
- key : value 不同的请求头,有不同的含义
- 空行:
- 请求体
- 就是发送给服务器的数据,请求体可以为空
- 格式:Key=Value&Key=Value&......
- Post请求的HTTP协议内容图解
常用请求头的说明
- Accept: 表示客户端可以接收的数据类型
- Accpet-Languege: 表示客户端可以接收的语言类型
- User-Agent: 表示客户端浏览器的信息
- Host: 表示请求时的服务器 ip 和端口号
GET 请求、POST 请求分别有什么(区分方式)
GET 请求有哪些:
- form 标签 method=get
- a 标签
- link 标签引入 css
- Script 标签引入 js 文件
- img 标签引入图片
- iframe 引入 html 页面
- 在浏览器地址栏中输入地址后敲回车
POST 请求有哪些:
- form 标签 method=post
响应的 HTTP 协议格式(像Post)
- 响应行:
- 响应的协议和版本号
- 响应状态码
- 响应状态描述符
- 响应头:
- key : value 不同的响应头,有其不同含义
- 空行
- 响应体: 就是回传给客户端的数据
- 响应的HTTP协议内容图解
常用的响应码说明
- 200 表示请求成功
- 302 表示请求重定向(后面讲)
- 404 表示请求服务器已经收到了,但是你要的数据不存在(请求地址错误,之前提及过)
- 500 表示服务器已经收到请求,但是服务器内部错误(代码错误,之前提及过)
HTTP协议中的Content-Type数据类型——MIME 类型
- MIME 是 HTTP 协议中数据类型,也就是响应头上的Content-Type
- MIME 的英文全称是"Multipurpose Internet Mail Extensions" 多功能 Internet 邮件扩充服务。MIME 类型的格式是“大类型/小 类型”,并与某一种文件的扩展名相对应,客户端告诉服务器我发送的请求数据是什么,应该怎么处理;反之就是服务端告诉客户端响应的数据是什么,应该用什么打开
- 常见的MIME类型图
使用浏览器查看HTTP协议
说明:市面很多浏览器都是套壳谷歌Chrome或者IE浏览器或者火狐浏览器而成的,所以开发者调试F12没有什么本质上的区别。(这里我用360极速浏览器举例,只是为了方便)
GET请求源码解析(搜索)
- 在百度搜索栏输入“SEO”-->按F12-->打开NetWork-->查看GET请求源码解析
- 查看全局响应
说明:远程地址应该是183.232.231.172 - 查看请求头
- 查看请求参数
- 查看响应头
- 查看响应体
- 预览响应体的内容
Post请求的源码解析(登录)
- 请求头
- 请求参数解析
- 响应体没有参数原因是响应后跳转到其他页面了,响应码302(重定向)
HttpServletRequest 类
- 每次只要有请求进入 Tomcat 服务器,Tomcat 服务器就会把请求过来的 HTTP 协议信息解析好封装到 Request 对象中。 然后传递到 service 方法(doGet 和 doPost)中给我们使用。
- 我们可以通过 HttpServletRequest 对象,获取到所有请求的 信息。
HttpServletRequest 类的常用方法
- getRequestURI() 获取请求的资源路径
- getRequestURL() 获取请求的统一资源定位符(绝对路径)
- getRemoteHost() 获取客户端的 ip 地址
- getHeader() 获取请求头
- getParameter() 获取请求的参数
- getParameterValues() 获取请求的参数(多个值的时候使用)
- getMethod() 获取请求的方式 GET 或 POST
- setAttribute(key, value); 设置域数据,注意区分“ServletContext”。该方法只在每次进来该servlet程序时候生效,如果出去了,也就意味着这个HttpServletRequest对象已经失效。除非是下面的请求转发
- getAttribute(key); 获取域数据
- getRequestDispatcher() 获取请求转发对象
HttpServletRequest类入门1——获取请求
- 创建一个自定义类继承HttpServlet,编写处理获取到的请求
protected void doPost(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { /* 获取请求参数; 可能请求参数是中文,出现中文乱码; */ req.setCharacterEncoding("UTF-8");//设置请求体的字符集为 UTF-8,从而解决 post 请求的中文乱码问题 String username = req.getParameter("username"); String password = req.getParameter("password"); String[] hobby = req.getParameterValues("hobby"); System.out.println("用户名:" + username); System.out.println("密码:" + password); System.out.println("兴趣爱好:" + Arrays.asList(hobby)); }
- 定向于类RequestAPIServlet
<!--定向于类RequestAPIServlet--> <servlet> <!--servlet-name标签 是给 Servlet 程序起一个别名(一般是类名) --> <servlet-name>RequestAPIServlet</servlet-name> <!--servlet-class 是 Servlet 程序的全类名--> <servlet-class>RequestAPIServlet</servlet-class> </servlet> <!--servlet-mapping 标签给 servlet 程序配置访问地址--> <servlet-mapping> <!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用--> <servlet-name>RequestAPIServlet</servlet-name> <!--url-pattern 标签配置访问地址 <br/> / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/> /hello 表示地址为:http://ip:port/工程路径/hello <br/> --> <url-pattern>/RequestAPIServlet</url-pattern> </servlet-mapping>
- 重新部署服务器,演示效果
HttpServletRequest类入门2——获取请求参数
- 修改 src\RequestAPIServlet.java 上的doPost(),编写处理获取到的请求参数
protected void doPost(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { // 获取请求参数 String username = req.getParameter("username"); String password = req.getParameter("password"); String[] hobby = req.getParameterValues("hobby"); System.out.println("用户名:" + username); System.out.println("密码:" + password); System.out.println("兴趣爱好:" + Arrays.asList(hobby)); }
- 新建一个html页面,用于客户端发送Post请求到服务端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <body> <form action="http://localhost:8083/javaweb06_war_exploded/RequestAPIServlet" method="post" target="tar"> <!--autocomplete="off" 输入框不要有浏览器记录--> 用户名:<input type="text" name="username" autocomplete="off"><br/> 密码:<input type="password" name="password" autocomplete="off"><br/> 兴趣爱好:<input type="checkbox" name="hobby" value="cpp">C++ <input type="checkbox" name="hobby" value="java">Java <input type="checkbox" name="hobby" value="js">JavaScript<br/> <input type="submit"> </form> <iframe name="tar" style="display:none;"></iframe> </body> </body> </html>
- 测试效果
服务器doGet、postGet接受请求参数时出现乱码解决方法
doGet 请求的中文乱码解决(实际直接接受就好了,doGet对中文并不会出现乱码现象!!)
// 获取请求参数
String username = req.getParameter("username");
//1 先以 iso8859-1 进行编码
//2 再以 utf-8 进行解码
username = new String(username.getBytes("iso-8859-1"), "UTF-8");
doPost请求的中文乱码解决
req.setCharacterEncoding("UTF-8");//设置请求体的字符集为 UTF-8,从而解决 post 请求的中文乱码问题
请求的转发
请求转发是指,服务器收到请求后,从一个资源跳转到另一个资源的操作叫请求转发。下方图解
请求的转发入门
- 创建两个Servlet程序,分别为Servlet1、Servlet2
import javax.servlet.RequestDispatcher; 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 = "Servlet1") public class Servlet1 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求的参数(办事的材料)查看 String username = req.getParameter("username");//直接接受就好了,doGet对中文并不会出现乱码现象 System.out.println("在 Servlet1(柜台 1)中查看参数(材料):" + username);//在 Servlet1(柜台 1)中查看参数(材料):11111 // 给材料 盖一个章,并传递到 Servlet2(柜台 2)去查看 req.setAttribute("key1", "柜台 1 的章");//通过HttpServletRequest对象设置一个设置域数据 /* 问路:Servlet2(柜台 2)怎么走 请求转发必须要以斜杠打头,/ 斜杠表示地址为:http://ip:port/工程名/ , 映射到 IDEA 代码的 web 目录 */ RequestDispatcher requestDispatcher = req.getRequestDispatcher("/Servlet2");//转发请求定位,把HttpServletRequest对象设置一个设置域数据也转发出去 // 走向 Sevlet2(柜台 2) requestDispatcher.forward(req, resp);//Servlet1开始执行转发请求给Servlet2程序,并接收响应,再由Servlet1响应给客户端 } }
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 = "Servlet2") public class Servlet2 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { // 获取请求的参数(办事的材料)查看 String username = req.getParameter("username");//直接接受就好了,doGet对中文并不会出现乱码现象 System.out.println("在 Servlet2(柜台 2)中查看参数(材料):" + username);//在 Servlet2(柜台 2)中查看参数(材料):11111 // 查看 柜台 1 是否有盖章 Object key1 = req.getAttribute("key1"); System.out.println("柜台 1 是否有章:" + key1);//柜台 1 是否有章:柜台 1 的章 // 处理自己的业务 System.out.println("Servlet2 处理自己的业务");//Servlet2 处理自己的业务 } }
- 定向于类Servlet1、Servlet2
<!--定向于类Servlet1--> <servlet> <!--servlet-name标签 是给 Servlet 程序起一个别名(一般是类名) --> <servlet-name>Servlet1</servlet-name> <!--servlet-class 是 Servlet 程序的全类名--> <servlet-class>Servlet1</servlet-class> </servlet> <!--servlet-mapping 标签给 servlet 程序配置访问地址--> <servlet-mapping> <!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用--> <servlet-name>Servlet1</servlet-name> <!--url-pattern 标签配置访问地址 <br/> / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/> /hello 表示地址为:http://ip:port/工程路径/hello <br/> --> <url-pattern>/Servlet1</url-pattern> </servlet-mapping> <!--定向于类Servlet2--> <servlet> <!--servlet-name标签 是给 Servlet 程序起一个别名(一般是类名) --> <servlet-name>Servlet2</servlet-name> <!--servlet-class 是 Servlet 程序的全类名--> <servlet-class>Servlet2</servlet-class> </servlet> <!--servlet-mapping 标签给 servlet 程序配置访问地址--> <servlet-mapping> <!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用--> <servlet-name>Servlet2</servlet-name> <!--url-pattern 标签配置访问地址 <br/> / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/> /hello 表示地址为:http://ip:port/工程路径/hello <br/> --> <url-pattern>/Servlet2</url-pattern> </servlet-mapping>
- 新建一个html用于发送get请求
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <body> <form action="http://localhost:8083/javaweb06_war_exploded/Servlet1" method="get" target="tar"> <!--autocomplete="off" 输入框不要有浏览器记录--> 用户名:<input type="text" name="username" autocomplete="off"><br/> <input type="submit"> </form> <iframe name="tar" style="display:none;"></iframe> </body> </body> </html>
- 测试查看效果
HTML中base 标签
说明:永远固定使用了<base>标签的HTML页面相对路径跳转的结果
以前的路径跳转是这样设置的
-
新建一个web\html\a\b\c.html的超文本标记语言
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>这是web工程的html/a/b目录下的c.html页面</h1> <!--相对路径,点击之后跳转到web工程的首页页面index.html--> <a href="../../../index.html">点击之后跳转到web工程的首页页面index.html</a> </body> </html>
-
更改web工程的默认访问文件web\index.jsp为index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>这是web工程的首页页面index.html</h1> <!--相对路径,点击之后跳转到html/a/b目录下的c.html页面--> <a href="html/a/b/c.html">点击之后跳转到html/a/b目录下的c.html页面</a> </body> </html>
-
测试效果
-
原理介绍:
-
当我们在index.html页面点击a标签跳转的时候,浏览器地址栏中的地址是:http://192.168.0.198:8083/javaweb06_war_exploded/html/a/b/c.html
-
当我们在该页面点击调回去的a标签为:../../../index.html 。所有相对路径在工作时都会参照当前浏览器地址栏中的地址进行跳转,包括步骤一。那么参照后的地址时:http://192.168.0.198:8083/javaweb06_war_exploded/index.html
此时的地址时正确的 - 但是使用请求转发进行页面跳转就会出现错误路径
-
请求转发进行页面跳转就会出现错误路径
- 修改web/index.html页面,添加一个a标签,该标签是向服务器发送跳转到web/html/a/b目录下的c.html页面的请求
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>这是web工程的首页页面index.html</h1> <!--相对路径,点击之后跳转到html/a/b目录下的c.html页面--> <a href="html/a/b/c.html">点击之后跳转到html/a/b目录下的c.html页面</a> <br/> <br/> <!--该标签是向服务器发送跳转到web/html/a/b目录下的c.html页面的请求--> <a href="http://localhost:8083/javaweb06_war_exploded/jumpC">向服务器发送跳转到web/html/a/b目录下的c.html页面的请求</a> </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 = "JumpCServlet") public class JumpCServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("JumpIndexServlet类的doGet()被触发"); //里面写web目录下要跳转的页面,这里写相对路径,服务器向客户端发送客户端跳转到web/html/a/b目录下的c.html页面的命令 request.getRequestDispatcher("/html/a/b/c.html") .forward(request, response); } }
-
在web.xml添加与JumpCServlet映射
<!--在web.xml添加与JumpCServlet映射--> <servlet> <!--servlet-name标签 是给 Servlet 程序起一个别名(一般是类名) --> <servlet-name>JumpCServlet</servlet-name> <!--servlet-class 是 Servlet 程序的全类名--> <servlet-class>JumpCServlet</servlet-class> </servlet> <!--servlet-mapping 标签给 servlet 程序配置访问地址--> <servlet-mapping> <!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用--> <servlet-name>JumpCServlet</servlet-name> <!--url-pattern 标签配置访问地址 <br/> / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/> /hello 表示地址为:http://ip:port/工程路径/hello <br/> --> <url-pattern>/jumpC</url-pattern> </servlet-mapping>
-
效果演示,发现可以跳转到c.html页面,但是却不能跳转回index.html
-
原因分析:
-
当我们在 web/index.html 使用请求转发进行跳转的时候,浏览器中当前的地址是:http://localhost:8083/javaweb06_war_exploded/jumpC
-
跳转到web/html/a/b目录下的c.html页面时,浏览器中的地址还是:http://localhost:8083/javaweb06_war_exploded/jumpC
-
但web/html/a/b目录下的c.html页面的<a>标签跳回地址是:
href="../../../index.html" -
浏览器参照后得到的地址是:http://localhost:8083/index.html
这是一个错误的路径,然后就弹出404资源路径错误,此时就要介绍base的作用了
-
HTML中base标签的作用
说明:<base> 标签设置页面相对路径工作时参照的地址 href 属性就是参数的地址值
通过HTML中base标签修复"请求转发进行页面跳转就会出现错误路径",并演示其的作用
- 在web/html/a/b目录下的c.html页面添加base标签,设置当前页面页面相对路径工作时参照的地址
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>这是web工程的html/a/b目录下的c.html页面</h1> <!--相对路径,点击之后跳转到web工程的首页页面index.html--> <a href="../../../index.html">点击之后跳转到web工程的首页页面index.html</a> <!--base 标签设置页面相对路径工作时参照的地址,href 属性就是参数的地址值--> <base href="http://localhost:8083/javaweb06_war_exploded/html/a/b/"> </body> </html>
- 演示效果
Web 中的相对路径和绝对路径
相对路径
- . 表示当前目录
- .. 表示上一级目录
- 资源名 表示当前目录/资源名
绝对路径
- http://ip:port/工程路径/资源路径
实际开发
- 绝对路径(首选)
- <base>+相对
web 中 / 斜杠的不同意义
- 在 web 中 / 斜杠 是一种绝对路径
- / 斜杠 如果被浏览器解析,得到的地址是:http://ip:port/ 例如:
<a href:"/">斜杠<a/>
- / 斜杠 如果被服务器解析,得到的地址是:http://ip:port/工程路径 例如:
1、<url-pattern>/servlet1</url-pattern> 2、servletContext.getRealPath(“/”); 3、request.getRequestDispatcher(“/
- 特殊情况: response.sendRediect(“/”); 把斜杠发送给浏览器解析。得到 http://ip:port/
HttpServletResponse 类
- HttpServletResponse 类和 HttpServletRequest 类一样。每次请求进来,Tomcat 服务器都会创建一个 Response 对象传 递给 Servlet 程序去使用。HttpServletRequest 表示请求过来的信息,HttpServletResponse 表示所有响应的信息
- 如果需要设置返回给客户端的信息,都可以通过 HttpServletResponse 对象来进行设置
(回顾) 两个输出流的说明
- 字节流 getOutputStream(); 常用于下载(不仅可以发送字符,但必须将字符转化为字节才可以使用该方法;而且可以发送字节数据也就是二进制数据)
- 字符流 getWriter(); 常用于向客户端发送字符数据响应(常用)
前置工作——创建一个新的servlet程序,名为ResponseIOServlet.java
两个输出流不能同时使用
说明:
两个流是不可以同时使用的,如果同时使用则会报错,当服务器发现os.write("Hello World".getBytes())这个方法时,他会将字符串封装成response对象返回给web服务,web服务器再将response对象拆分并封装成http响应返回给浏览器,当这一系列动作完成之后,web服务器就会去检查response的流是否被关闭,如果没有关闭,服务器会自动帮你关闭流,这时,out.println("Hello World")就不会被返回,因为流已经被关闭,所以服务器就会报错,也会报500错误给客户端。
两个输出流同时使用的情况1:
protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
// 但是在使用getOutputStream()方法传送字符时,需要将字符转化为二进制字节才可以通过getOutputStream()方法传送,例如:
OutputStream os = resp.getOutputStream();
// os.write("Hello World".getBytes());
// 使用getWriter()方法则可以直接输出字符数据,例如:
PrintWriter out = resp.getWriter();
// out.println("Goodbye World");
/*
两个方法getOutputStream(),getWriter()不能同时使用,否则会出现如下情况告知异常
浏览器:
java.lang.IllegalStateException: 当前响应已经调用了方法getOutputStream()
浏览器日志:
Failed to load resource: the server responded with a status of 500 ()
*/
两个输出流同时使用的情况2:
protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
// 但是在使用getOutputStream()方法传送字符时,需要将字符转化为二进制字节才可以通过getOutputStream()方法传送,例如:
OutputStream os = resp.getOutputStream();
os.write("Hello World".getBytes());
// 使用getWriter()方法则可以直接输出字符数据,例如:
PrintWriter out = resp.getWriter();
out.println("Goodbye World");//没有生效,因为流被关闭了
/*
两个方法getOutputStream(),getWriter()不能同时使用,否则会出现如下情况告知异常
浏览器:
java.lang.IllegalStateException: 当前响应已经调用了方法getOutputStream()
浏览器日志:
Failed to load resource: the server responded with a status of 500 ()
*/
}
HttpServletResponse作用演示——往客户端回传数据并解决中文乱码问题
说明:以下方法都是修改原有的名为 ResponseIOServlet.java 的servlet程序
(不推荐使用)解决中文乱码问题方法一
/*
(不推荐)解决乱码方法1:
1.设置服务器字符集为 UTF-8
2.通过响应头,告知浏览器也使用 UTF-8 字符集打开
*/
resp.setCharacterEncoding("UTF-8");
resp.setHeader("Content-Type", "text/html; charset=UTF-8");
//开始回传数据
PrintWriter writer = resp.getWriter();
writer.write("野兽先辈");
(推荐使用)解决中文乱码问题方法二
/*
(推荐)解决乱码方法2:
1.它会同时设置服务器和客户端都使用 UTF-8 字符集,还设置了响应头
提示:此方法一定要在获取流对象之前调用才有效
*/
resp.setContentType("text/html; charset=UTF-8");
//开始回传数据
PrintWriter writer = resp.getWriter();
writer.write("野兽先辈");
请求重定向
- 之前在<base>标签就演示的是转发请求效果,这里演示的是请求重定向
- 请求重定向,是指客户端给服务器发请求,然后服务器告诉客户端说。我给你一些地址。你去新地址访问。叫请求 重定向(因为之前的地址可能已经被废弃)。
- 图解
(不推荐使用)请求重定向的方案1
- 新建两个servlet程序类,分别为src\Response1.java与src\Response2.java,内容如下
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; /**该类为模拟废弃的Servlet程序,需要重新定向*/ @WebServlet(name = "Response1") public class Response1 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Response1类的doGet方法被处罚"); // 设置响应状态码 302 ,表示重定向,(已搬迁) resp.setStatus(302); // (请求重定向的方案1)设置响应头,说明 新的地址在哪里 resp.setHeader("Location", "http://localhost:8083/javaweb06_war_exploded/response2"); System.out.println("废弃的方法"); } }
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; /**该类为重定向的新的servlet程序*/ @WebServlet(name = "Response2") public class Response2 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("Response2类doGet方法被触发"); response.setContentType("text/html; charset=UTF-8");//防止中文乱码 response.getWriter().write("Response2类返回的响应"); } }
- 设置web.xml与两个servlet程序类映射
<!--在web.xml添加与Response1映射--> <servlet> <!--servlet-name标签 是给 Servlet 程序起一个别名(一般是类名) --> <servlet-name>Response1</servlet-name> <!--servlet-class 是 Servlet 程序的全类名--> <servlet-class>Response1</servlet-class> </servlet> <!--servlet-mapping 标签给 servlet 程序配置访问地址--> <servlet-mapping> <!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用--> <servlet-name>Response1</servlet-name> <!--url-pattern 标签配置访问地址 <br/> / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/> /hello 表示地址为:http://ip:port/工程路径/hello <br/> --> <url-pattern>/response1</url-pattern> </servlet-mapping> <!--在web.xml添加与Response2映射--> <servlet> <!--servlet-name标签 是给 Servlet 程序起一个别名(一般是类名) --> <servlet-name>Response2</servlet-name> <!--servlet-class 是 Servlet 程序的全类名--> <servlet-class>Response2</servlet-class> </servlet> <!--servlet-mapping 标签给 servlet 程序配置访问地址--> <servlet-mapping> <!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用--> <servlet-name>Response2</servlet-name> <!--url-pattern 标签配置访问地址 <br/> / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/> /hello 表示地址为:http://ip:port/工程路径/hello <br/> --> <url-pattern>/response2</url-pattern> </servlet-mapping>
- 测试访问效果
(推荐使用)请求重定向的方案2
- 修改原有的src\Response1.java,添加如下代码
//请求重定向的方案2 resp.sendRedirect("http://localhost:8083/javaweb06_war_exploded/response2");
- 演示效果
请求重定向特点
- 地址会发生改变
- 向服务器发送了两次请求,第一次响应是302,第二次响应是200
- 不共享Servlet程序里的HttpServletRequest对象域中数据(因为不是请求转发)
- 修改src/Response1.java,添加如下代码,通过HttpServletRequest对象设置域值
//通过HttpServletRequest对象设置域值,但是重定向后阈值无法被其他Servlet程序里的HttpServletRequest对象读取 request.setAttribute("key","value");
- 修改src/Response2.java,添加如下代码,试图通过HttpServletRequest对象接收域值
//试图通过HttpServletRequest对象接收域值,但是为空 System.out.println(request.getAttribute("key"));//null
- 演示效果
- 分析原因:setAttribute(key, value); 设置域数据,注意区分“ServletContext”。当每次有请求访问这个servlet程序时,tomcat就会把当前请求封装成HttpServletRequest对象。该方法只对该HttpServletRequest对象生效,如果出去了这个Servlet程序,再进入一个Servlet程序,那么就会创建一个新的HttpServletRequest对象,也就意味着之前那个HttpServletRequest对象已经失效。除非是请求转发
- 修改src/Response1.java,添加如下代码,通过HttpServletRequest对象设置域值
- 不能访问WEB-INF下的资源,会出现404错误
- 通过重定向,浏览器获取到一个新的地址开始发起第二次访问,但因为该资源时私密的,浏览器是不能直接访问的
- 可以访问工程外的资源
- 例如百度之类的.....反正遵循原理就可以了
JavaEE的三层架构
- 分层的目的是为了解耦。解耦就是为了降低代码的耦合度。方便项目后期的维护和升级
- 因此不同的层级会有不同的包
- web 层
- com.atguigu.web/servlet/controller
- service 层
- com.atguigu.service Service 接口包
- com.atguigu.service.impl Service 接口实现类
- dao 持久层
- com.atguigu.dao Dao 接口包
- com.atguigu.dao.impl Dao 接口实现类
- 实体 bean 对象 com.atguigu.pojo/entity/domain/bean JavaBean 类
- 测试包 com.atguigu.test/junit
- 工具类 com.atguigu.utils
- web 层
开始搭建书城项目
说明:以下顺序可以作为以后开发参考,并且不要跳过该章节,因为会涉及到很多的知识点,并非只是学以致用!!!!!
1.创建项目结构
- 先创建一个带有Web框架的JavaEE模块
- 然后把之前做的项目web目录下的page、static、index.html内容复制到java中
- 开始在 src 目录下给不同层级分开创建包,结构如下
2.创建数据库数据表
- 参考用户注册界面,来设计数据库表
- 获取到信息后开始创建数据库-->创建数据表 ,创建SQL语句如下
DROP DATABASE IF EXISTS book; CREATE DATABASE book; USE book; CREATE TABLE t_user( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(20) NOT NULL UNIQUE, `password` VARCHAR(32) NOT NULL, `email` VARCHAR(200) );
- 测试插入一条数据和查询数据的SQL语句是否能成功,SQL语句如下
INSERT INTO t_user(`username`,`password`,`email`) VALUES('admin','admin','admin@atguigu.com'); SELECT * FROM t_user;
3.创建于数据表对应的JavaBean
- 在 src\com\atguigu\pojo 目录下创建一个User类,代码如下
package com.atguigu.pojo; public class User { private Integer id; private String username; private String password; private String email; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public User() { } public User(Integer id, String username, String password, String email) { this.id = id; this.username = username; this.password = password; this.email = email; } }
4.编写工具类 JdbcUtils
说明:为编写DAO做前置工作,一般来说工具类都是由项目经理导入,但是这里我们是负责自己的项目,因此需要我们自己来导入
导入需要的 jar 包(数据库和连接池需要)
- 认识我们该工程需要用到的jar包
- 连接数据库需要:
- druid-1.1.9.jar 数据库连接池
- mysql-connector-java-5.1.7-bin.jar mysql数据库驱动
- 测试需要(之前介绍过了):
- hamcrest-core-1.3.jar
- junit-4.12.ja
- 连接数据库需要:
- (回顾)添加Jar包
- 在 web\WEB-INF\lib 目录下添加工程需要的jar包
- 导入jar包到类库中
- 指定哪个项目可以使用该类库,注意scpoe要设置
说明:JDBC类库要设置成Compile、TEST类库可以设置Compile/Provided,这里推荐设置成Provided,因为服务器运行时已经不需要测试方法了 - 为web项目添加Artifact的配置,才可以部署到服务器中
- 在 web\WEB-INF\lib 目录下添加工程需要的jar包
编写.properties 属性配置文件
- 在 book/resources 编写 jdbc.properties 属性配置文件,内容如下
username=root password=root url=jdbc:mysql://localhost:3306/book driverClassName=com.mysql.jdbc.Driver initialSize=5 maxActive=10
编写 JdbcUtils 工具类
- 在 src\com\atguigu\utils 目录下编写工具类(里面有main方法测试是否能连接上数据库)
package com.atguigu.utils; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class JdbcUtils { //创建一个数据库连接池变量 private static DruidDataSource dataSource; //使用静态代码块给DruidDataSource对象做初始化 static { try { //创建一个没有默认值的空属性列表 Properties properties = new Properties(); // 通过输入流读取 jdbc.properties 属性配置文件 InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"); // 从流中加载数据,需要带有读取了jdbc.properties 属性配置文件的输入流作为参数 properties.load(inputStream); // 创建数据库连接池,需要抛出异常,还需要强转成 dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties); //测试获取连接池连接,如果获取得到,那么就是创建成功了.如果没有获取到就是null,用下面用main方法运行测试 // System.out.println(dataSource.getConnection());//com.mysql.jdbc.JDBC4Connection@3567135c,这里我们注销因为没有用途,只是作为提示 } catch (Exception e) { e.printStackTrace(); } } //用main方法运行测试,因为main方法运行,类加载,然后执行static{} public static void main(String[] args) { /* //控制台打印出 八月 20, 2022 10:52:44 下午 com.alibaba.druid.pool.DruidDataSource info 信息: {dataSource-1} inited com.mysql.jdbc.JDBC4Connection@3339ad8e */ } /** * 获取数据库连接池中的连接 * @return 如果返回 null,说明获取连接失败<br/>有值就是获取连接成功 */ public static Connection getConnection(){ //创建一个Connection变量 Connection conn = null; try { conn = dataSource.getConnection(); } catch (Exception e) { e.printStackTrace(); } return conn; } /** * 关闭连接,放回数据库连接池 * 如果不关闭,就会疯狂后去数据库连接池获取数据库连接,达到数据库连接池最大连接数时,就会进入阻塞状态 * @param conn */ public static void close(Connection conn){ //用if判断conn部位空才关闭,否则会出现空指针异常 if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
- 这里在 src\com\atguigu\test\JdbcUtilsTest.java 使用别的测试方法测试JdbcUtils 测试(是否能从连接池获取数据库连接,使用完后还给连接池)
package com.atguigu.test; import com.atguigu.utils.JdbcUtils; import org.junit.Test; import java.sql.Connection; public class JdbcUtilsTest { @Test public void testJdbcUtils(){ for (int i = 0; i < 10; i++){ Connection connection = JdbcUtils.getConnection(); System.out.println(connection); JdbcUtils.close(connection); } /* 八月 21, 2022 12:10:24 上午 com.alibaba.druid.pool.DruidDataSource info 信息: {dataSource-1} inited com.mysql.jdbc.JDBC4Connection@1e67b872 com.mysql.jdbc.JDBC4Connection@60addb54 com.mysql.jdbc.JDBC4Connection@60addb54 com.mysql.jdbc.JDBC4Connection@60addb54 com.mysql.jdbc.JDBC4Connection@60addb54 com.mysql.jdbc.JDBC4Connection@60addb54 com.mysql.jdbc.JDBC4Connection@60addb54 com.mysql.jdbc.JDBC4Connection@60addb54 com.mysql.jdbc.JDBC4Connection@60addb54 com.mysql.jdbc.JDBC4Connection@60addb54 com.mysql.jdbc.JDBC4Connection@60addb54 */ } }
5.编写 BaseDao
- (前置工作)先导入commons-dbutils-1.3.jar 包
- 编写 src\com\atguigu\dao\impl\BaseDao.java 操作可以用于操作数据库中任意数据表的API
package com.atguigu.dao.impl; import com.atguigu.utils.JdbcUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import java.sql.Connection; import java.sql.SQLException; import java.util.List; /** * 本类是一个可以用于操作任意数据表的API */ public abstract class BaseDao {//这里设置成抽象类是因为该类要继承复用 //使用 DbUtils 操作数据库 private QueryRunner queryRunner = new QueryRunner();//创建一个QueryRunner对象操作数据库 /** * update() 方法用来执行:Insert\Update\Delete 语句 * * @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数 */ public int update(String sql, Object... args) {//该方法传入的参数有sql语句,还有占位符参数 Connection connection = JdbcUtils.getConnection();//从线程池获取数据库连接 try { //参数分别为:sql即我们预编译的sql,是有?的。args是可变参数,用来传递占位符?要给的值。通过占位符防止sql注入 return queryRunner.update(connection, sql, args); } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtils.close(connection); } return -1; } /** * 查询返回 “一个” javaBean 的 sql 语句 * * @param type 返回的对象类型 * @param sql 执行的 sql 语句 * @param args sql 对应的参数值 * @param <T> 返回的类型的泛型 * @return null就表示没有值返回, 否则就是查询到有数据 */ //回顾:这个T和ClassName没有关系,而是调用方法时传入的参数这种指定的 public <T> T queryForOne(Class<T> type, String sql, Object... args) {//这里要传入的参数除了sql语句、占位符参数,还有要用什么类型java对象封装数据 Connection con = JdbcUtils.getConnection(); try { //new BeanHandler<T>()参数的形参列表里面放的是我们查询结果后用什么类型对象封装 return queryRunner.query(con, sql, new BeanHandler<T>(type), args); } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtils.close(con); } return null; } /** * 查询返回多个 javaBean 的 sql 语句 * * @param type 返回的对象类型 * @param sql 执行的 sql 语句 * @param args sql 对应的参数值 * @param <T> 返回的类型的泛型 * @return */ public <T> List<T> queryForList(Class<T> type, String sql, Object... args) { Connection con = JdbcUtils.getConnection(); try { return queryRunner.query(con, sql, new BeanListHandler<T>(type), args);//这里指定要用什么类型的list集合接收结果 } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtils.close(con); } return null; } /** * 执行返回一行一列的 sql 语句(查询单个值时) * * @param sql 执行的 sql 语句 * @param args sql 对应的参数值 * @return */ public Object queryForSingleValue(String sql, Object... args) { Connection conn = JdbcUtils.getConnection(); try { return queryRunner.query(conn, sql, new ScalarHandler(), args); } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtils.close(conn); } return null; } }
6.编写(具体的)UserDao
- src\com\atguigu\dao\UserDao.java编写接口,作为UserDaoImpl的规范
package com.atguigu.dao; import com.atguigu.pojo.User; public interface UserDao { /** * 根据用户名查询用户信息 * * @param username 用户名 * @return 如果返回 null,说明没有这个用户。反之亦然 */ public User queryUserByUsername(String username); /** * 根据 用户名和密码查询用户信息(登录) * * @param username * @param password * @return 如果返回 null,说明用户名或密码错误,反之亦然 */ public User queryUserByUsernameAndPassword(String username, String password); /** * 保存用户信息(注册) * * @param user * @return 返回-1 表示操作失败,其他是 sql 语句影响的行数 */ public int saveUser(User user); }
- src\com\atguigu\dao\impl\UserDaoImpl.java编写实现类,实现真正的数据库操作功能
package com.atguigu.dao.impl; import com.atguigu.dao.UserDao; import com.atguigu.pojo.User; public class UserDaoImpl extends BaseDao implements UserDao { @Override public User queryUserByUsername(String username) { //编写SQL语句 String sql = "select `id`,`username`,`password`,`email` from t_user where username = ?"; //调用继承回来的方法,因此不需要写类名 return queryForOne(User.class, sql, username); } //下面同理 @Override public User queryUserByUsernameAndPassword(String username, String password) { String sql = "select `id`,`username`,`password`,`email` from t_user where username = ? and password = ?"; return queryForOne(User.class, sql, username,password); } @Override public int saveUser(User user) { String sql = "insert into t_user(`username`,`password`,`email`) values(?,?,?)"; return update(sql, user.getUsername(),user.getPassword(),user.getEmail()); } }
- test\com\atguigu\test\UserDaoTest.java编写测试方法,在接口类空处点击 Ctr+Shift+T 进行生成测试方法,然后编写测试方法
package com.atguigu.test; import com.atguigu.dao.UserDao; import com.atguigu.dao.impl.UserDaoImpl; import com.atguigu.pojo.User; import org.junit.Test; public class UserDaoTest { UserDao userDao = new UserDaoImpl(); /**测试查询用户名是否存在*/ @Test public void queryUserByUsername() { if (userDao.queryUserByUsername("admin1234") == null ){ System.out.println("用户名可用!"); } else { System.out.println("用户名已存在!"); } } /**查询用户名和密码是否正确*/ @Test public void queryUserByUsernameAndPassword() { if ( userDao.queryUserByUsernameAndPassword("admin","admin1234") == null) { System.out.println("用户名或密码错误,登录失败"); } else { System.out.println("查询成功"); } } /**保存用户信息是否成功*/ @Test public void saveUser() { System.out.println( userDao.saveUser(new User(null,"wzg168", "123456", "wzg168@qq.com")) ); } }
- 开始执行测试方法查询是否成功
6.编写 UserService 和测试
- 编写 src\com\atguigu\service\UserService.java 接口
package com.atguigu.service; import com.atguigu.pojo.User; public interface UserService { /** * 注册用户 * * @param user */ public void registUser(User user); /** * 登录 * * @param user * @return 如果返回 null,说明登录失败,返回有值,是登录成功 */ public User login(User user); /** * 检查 用户名是否可用 * * @param username * @return 返回 true 表示用户名已存在,返回 false 表示用户名可用 */ public boolean existsUsername(String username); }
- 编写 src\com\atguigu\service\UserService.java 实现类
package com.atguigu.service.impl; import com.atguigu.dao.UserDao; import com.atguigu.dao.impl.UserDaoImpl; import com.atguigu.pojo.User; import com.atguigu.service.UserService; public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); @Override public void registUser(User user) { userDao.saveUser(user); } @Override public User login(User user) { return userDao.queryUserByUsernameAndPassword(user.getUsername(), user.getPassword()); } @Override public boolean existsUsername(String username) { if (userDao.queryUserByUsername(username) == null) { // 等于null,说明没查到,没查到表示可用 return false; } return true; } }
- 在 UserService 接口类空处点击 Ctr+Shift+T 进行生成UserService的测试方法,然后编写测试方法
package com.atguigu.test; import com.atguigu.pojo.User; import com.atguigu.service.UserService; import com.atguigu.service.impl.UserServiceImpl; import org.junit.Test; public class UserServiceTest { UserService userService = new UserServiceImpl(); @Test public void registUser() { userService.registUser(new User(null, "bbj168", "666666", "bbj168@qq.com")); userService.registUser(new User(null, "abc168", "666666", "abc168@qq.com")); } @Test public void login() { System.out.println(userService.login(new User(null, "wzg168", "123456", null))); } @Test public void existsUsername() { if (userService.existsUsername("wzg16888")) { System.out.println("用户名已存在!"); } else { System.out.println("用户名可用!"); } } }
- 开始执行测试方法查询是否成功
7.编写 web 层,实现用户登录和注册功能
实现用户注册功能
- 编写用户注册需求,需求如下:
- 访问注册页面
- 填写注册信息,提交给服务器
- 服务器应该保存用户
- 当用户已经存在----提示用户注册 失败,用户名已存在
- 当用户不存在-----注册成功
- 图解用户注册的流程
- 部署我们的项目在Tomcat中,然后启动,端口为8084
- 修改 regist.html 和 regist_success.html 页面
- 在<title>标签下添加 base 标签,固定注册页面和注册成功页面相对跳转路径结果
<!--写 base 标签,永远固定相对路径跳转的结果,一般只写到工程路径--> <base href="http://localhost:8084/book/">
- 这样的操作意味着其他的相对路径也要发生改变,这就是为什么推荐写绝对路径,这样会减少错误,如果不知道或者漏改那个,打开页面然后按F12,哪个爆红改哪个(后面的操作也如此,这里只是用GIF举regist_success.html为例子)下面有修改流程和修改好的代码。记住还要修改别的相对路径!!!
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>尚硅谷会员注册页面</title> <!--写 base 标签,永远固定相对路径跳转的结果,一般只写到工程路径--> <base href="http://localhost:8084/book/"> <link type="text/css" rel="stylesheet" href="static/css/style.css"> <style type="text/css"> h1 { text-align: center; margin-top: 200px; } h1 a { color: red; } </style> </head> <body> <div id="header"> <img class="logo_img" alt="" src="static/img/logo.gif"> <span class="wel_word"></span> <div> <span>欢迎<span class="um_span">韩总</span>光临尚硅谷书城</span> <a href="static/order/order.html">我的订单</a> <a href="index.html">注销</a> <a href="index.html">返回</a> </div> </div> <div id="main"> <h1>注册成功! <a href="index.html">转到主页</a></h1> </div> <div id="bottom"> <span> 尚硅谷书城.Copyright ©2015 </span> </div> </body> </html>
- 记住也要修改regist.html,记住还要修改点击注册按钮时发送给的是Servlet程序,而不是页面跳转,页面跳转由一会的Servlet程序控制,下面有修改好的代码
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>尚硅谷会员注册页面</title> <!--写 base 标签,永远固定相对路径跳转的结果,一般只写到工程路径--> <base href="http://localhost:8084/book/"> <link type="text/css" rel="stylesheet" href="static/css/style.css"> <style type="text/css"> .login_form { height: 420px; margin-top: 25px; } </style> <!--要引入JS文件写好的jQuery代码前要先引入jQuery库,因为JS里面的代码逻辑要运行,按照从上往下的运行顺序.否则jQuery代码引入进来也无法生效--> <script type="text/javascript" src="static/script/jquery-1.7.2.js"></script> <!--引入JS文件,防止代码冗余,用于判断输入的用户名和密码是否符合规定的代码逻辑--> <script type="text/javascript" src="static/js/regist.js"></script> </head> <body> <div id="login_header"> <img class="logo_img" alt="" src="static/img/logo.gif"> </div> <div class="login_banner"> <div id="l_content"> <span class="login_word">欢迎注册</span> </div> <div id="content"> <div class="login_form"> <div class="login_box"> <div class="tit"> <h1>注册尚硅谷会员</h1> <span class="errorMsg"></span> </div> <div class="form"> <!--按发送按钮时会向http://localhost:8084/book/registServlet 发送post请求--> <form action="registServlet" method="post">> <label>用户名称:</label> <!-- type为输入框类型为文本输入框; placeholder为输入框内的提示输入信息; autocomplete为是否自动完成允许浏览器预测对字段的输入. 当用户在字段开始键入时,浏览器基于之前键入过的值,应该显示出在字段中填写的选项,默认是开启的; tabindex 属性规定当使用 "tab" 键进行导航时元素的顺序;就是当你输入完用户名时,按"Tab"就会进入密码输入框,因为密码输入框是tabindex="2" name为发送的参数的属性,value为文本输入框输入的值 --> <input class="itxt" type="text" placeholder="请输入用户名" autocomplete="off" tabindex="1" name="username" id="username"/> <br/> <br/> <label>用户密码:</label> <input class="itxt" type="password" placeholder="请输入密码" autocomplete="off" tabindex="2" name="password" id="password"/> <br/> <br/> <label>确认密码:</label> <input class="itxt" type="password" placeholder="确认密码" autocomplete="off" tabindex="1" name="repwd" id="repwd"/> <br/> <br/> <label>电子邮件:</label> <!--这里可以使用type="email"来使用邮箱输入框--> <input class="itxt" type="text" placeholder="请输入邮箱地址" autocomplete="off" tabindex="1" name="email" id="email"/> <br/> <br/> <label>验证码:</label> <input class="itxt" type="text" name="code" style="width: 150px;" id="code"/> <img alt="" src="static/img/code.bmp" style="float: right; margin-right: 40px"> <br/> <br/> <input type="submit" value="注册" id="sub_btn"/> </form> </div> </div> </div> </div> </div> <div id="bottom"> <span> 尚硅谷书城.Copyright ©2015 </span> </div> </body> </html>
- 在<title>标签下添加 base 标签,固定注册页面和注册成功页面相对跳转路径结果
- 在 src\com\atguigu\web\RegistServlet.java 编写RegisServlet程序
package com.atguigu.web; import com.atguigu.pojo.User; import com.atguigu.service.UserService; import com.atguigu.service.impl.UserServiceImpl; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class RegistServlet extends HttpServlet { private UserService userService = new UserServiceImpl();//接受请求参数后要调用UserServiceImpl处理 @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1、获取请求的参数 String username = req.getParameter("username"); String password = req.getParameter("password"); String email = req.getParameter("email"); String code = req.getParameter("code"); //2、检查验证码是否正确,这里写死,因为没有说到服务器动态生成验证码返回给客户端,要求验证码为:abcde(忽略用户输入的大小写来比较) if ("abcde".equalsIgnoreCase(code)) { //3、检查 用户名是否在数据库已存在 if (userService.existsUsername(username)) { System.out.println("用户名[" + username + "]已存在!"); //跳回注册页面 req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp); } else { //也就是注册的用户名可用,执行保存到数据库操作 userService.registUser(new User(null, username, password, email)); //并跳到注册成功页面 regist_success.html req.getRequestDispatcher("/pages/user/regist_success.html").forward(req, resp); } //如果验证码输入不正确,就跳回注册页面 } else { System.out.println("验证码[" + code + "]错误"); req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp); } } }
- 更改web.xml,在</web-app>标签内添加如下代码映射RegistServlet程序
<!--在web.xml添加与RegistServlet映射--> <servlet> <!--servlet-name标签 是给 Servlet 程序起一个别名(一般是类名) --> <servlet-name>RegistServlet</servlet-name> <!--servlet-class 是 Servlet 程序的全类名--> <servlet-class>com.atguigu.web.RegistServlet</servlet-class> </servlet> <!--servlet-mapping 标签给 servlet 程序配置访问地址--> <servlet-mapping> <!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用--> <servlet-name>RegistServlet</servlet-name> <!--url-pattern 标签配置访问地址 <br/> / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/> /hello 表示地址为:http://ip:port/工程路径/hello <br/> --> <url-pattern>/registServlet</url-pattern> </servlet-mapping>
- 重新部署项目后测试注册效果
实现用户功能(与注册雷同,这里不做过多的测试演示)
- 编写用户登录需求,需求如下:
- 访问注册页面
- 填写注册信息,提交给服务器
- 服务器应该保存用户
- 当用户已经存在----提示用户注册 失败,用户名已存在
- 当用户不存在-----注册成功
- 图解用户登录的流程
- 修改 login.html 页面和 login_success.html 页面
- 给两个页面添加添加<base>标签
<!--写 base 标签,永远固定相对路径跳转的结果,一般只写到工程路径--> <base href="http://localhost:8084/book/">
- 因为有了<base>标签,因此需要修改页面中所有的相对路径,这里有两个页面修改好的代码
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>尚硅谷会员登录页面</title> <!--写 base 标签,永远固定相对路径跳转的结果,一般只写到工程路径--> <base href="http://localhost:8084/book/"> <!--引入CSS样式--> <link type="text/css" rel="stylesheet" href="static/css/style.css"> <!--引入JS文件,防止代码冗余,用于判断输入的用户名和密码是否符合规定,这里和注册的代码逻辑差不多,就不写了--> <script type="text/javascript" src="#"></script> </head> <body> <!--登录界面左上角的logo--> <div id="login_header"> <img class="logo_img" alt="" src="static/img/logo.gif"> </div> <!--登录面板--> <div class="login_banner"> <!--欢迎登录展示--> <div id="l_content"> <span class="login_word">欢迎登录</span> </div> <!--登录输入目录--> <div id="content"> <div class="login_form"> <div class="login_box"> <!--登录标题--> <div class="tit"> <h1>尚硅谷会员</h1> <!--点击注册后跳转到regist.html注册页面--> <a href="pages/user/regist.html">立即注册</a> </div> <div class="msg_cont"> <!--小灯泡样式--> <b></b> <!--登录标题提示--> <span class="errorMsg">请输入用户名和密码</span> </div> <div class="form"> <!--新建表单,里面是真正输入和验证用户名和密码是否符合规范的地方 按发送按钮时会向http://localhost:8084/book/loginServlet 发送post请求--> <form action="loginServlet" method="post"> <!--用户名--> <label>用户名称:</label> <!-- type为输入框类型为文本输入框; placeholder为输入框内的提示输入信息; autocomplete为是否自动完成允许浏览器预测对字段的输入. 当用户在字段开始键入时,浏览器基于之前键入过的值,应该显示出在字段中填写的选项,默认是开启的; tabindex 属性规定当使用 "tab" 键进行导航时元素的顺序;就是当你输入完用户名时,按"Tab"就会进入密码输入框,因为密码输入框是tabindex="2" --> <input class="itxt" type="text" placeholder="请输入用户名" autocomplete="off" tabindex="1" name="username"/> <br/> <br/> <!--密码--> <label>用户密码:</label> <!--密码输入框--> <input class="itxt" type="password" placeholder="请输入密码" autocomplete="off" tabindex="2" name="password"/> <br/> <br/> <!--登录提交按钮--> <input type="submit" value="登录" id="sub_btn"/> </form> </div> </div> </div> </div> </div> <div id="bottom"> <span> 尚硅谷书城.Copyright ©2015 </span> </div> </body> </html>
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>尚硅谷会员注册页面</title> <!--写 base 标签,永远固定相对路径跳转的结果,一般只写到工程路径--> <base href="http://localhost:8084/book/"> <link type="text/css" rel="stylesheet" href="static/css/style.css"> <style type="text/css"> h1 { text-align: center; margin-top: 200px; } h1 a { color: red; } </style> </head> <body> <div id="header"> <img class="logo_img" alt="" src="static/img/logo.gif"> <div> <span>欢迎<span class="um_span">韩总</span>光临尚硅谷书城</span> <a href="pages/order/order.html">我的订单</a> <a href="index.html">注销</a> <a href="index.html">返回</a> </div> </div> <div id="main"> <h1>欢迎回来 <a href="index.html">转到主页</a></h1> </div> <div id="bottom"> <span> 尚硅谷书城.Copyright ©2015 </span> </div> </body> </html>
- 给两个页面添加添加<base>标签
- 在 src\com\atguigu\web\LoginServlet.java 编写LoginServlet程序
package com.atguigu.web; import com.atguigu.pojo.User; import com.atguigu.service.UserService; import com.atguigu.service.impl.UserServiceImpl; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class LoginServlet extends HttpServlet { private UserService userService = new UserServiceImpl(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1、获取请求的参数 String username = req.getParameter("username"); String password = req.getParameter("password"); // 调用 userService.login()登录处理业务 User loginUser = userService.login(new User(null, username, password, null)); // 如果等于null,说明登录失败! if (loginUser == null) { // 跳回登录页面 req.getRequestDispatcher("/pages/user/login.html").forward(req, resp); } else { // 登录 成功 //跳到成功页面login_success.html req.getRequestDispatcher("/pages/user/login_success.html").forward(req, resp); } } }
- 在 web.xml中添加LoginServlet程序映射
<!--在web.xml添加与LoginServlet映射--> <servlet> <!--servlet-name标签 是给 Servlet 程序起一个别名(一般是类名) --> <servlet-name>LoginServlet</servlet-name> <!--servlet-class 是 Servlet 程序的全类名--> <servlet-class>com.atguigu.web.LoginServlet</servlet-class> </servlet> <!--servlet-mapping 标签给 servlet 程序配置访问地址--> <servlet-mapping> <!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用--> <servlet-name>LoginServlet</servlet-name> <!--url-pattern 标签配置访问地址 <br/> / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/> /hello 表示地址为:http://ip:port/工程路径/hello <br/> --> <url-pattern>/loginServlet</url-pattern> </servlet-mapping>
- 重新部署服务器,测试是否成功
(补充重要功能)IDEA 中 Debug
Debug的作用
- Debug的作用就是给程序员一步一步地运行代码段查看程序的运行过程,深入分析并调试代码
- 下面我们通过流程慢慢介绍Debug功能
Debug需要的元素
- 断点
- Debug 启动服务器
流程介绍Debug功能
- 在有效代码行,点击行号右边的空白区域,设置断点,程序执行到断点将停止,我们可以手动来运行程序
- 点击Debug运行模式
- 程序停止在断点上不再执行,而IDEA最下方打开了Debug调试窗口
- Debug调试窗口介绍
- 测试工具栏
- 变量窗口:它可以查看当前方法范围内所有有效的变量
- 方法调用栈窗口
- 方法调用栈可以查看当前线程有哪些方法调用信息
- 方法调用栈可以查看当前线程有哪些方法调用信息
- 测试工具栏
- 快捷键F8,代码向下执行一行,通过方法调用栈(可以查看当前线程有哪些方法调用信息)可以查看到第9行执行完毕,执行到第10行(第10行还未执行)
- 切换到控制台面板,控制台显示请录入一个字符串,也就是第九行代码执行完了,即将执行第10行
- 快捷键F8,程序继续向后执行,执行键盘录入操作,在控制台录入数据 ababcea
回车之后效果: 断点调试会自动跳到下一行,证明录入控制台已经结束,该方法且属于代码行的变量在代码行隔壁显示了
调试界面效果: - 此时到达findChar方法,快捷键F7,进入非源码的方法findChar(String line)
- 快捷键F8 接续执行,创建了map对象,变量区域显示
- 快捷键F8 接续执行,进入到循环中,循环变量i为 0,F8再继续执行,就获取到变量c赋值为字符‘a’ 字节值97
- 快捷键F8 接续执行,进入到循环中,循环变量i为 0
F8再继续执行,就获取到变量c赋值为字符‘a’ 字节值97(如下图所示) - 快捷键F8 接续执行,因为该字符 不在Map集合键集中,就进入到判断方法里面的离if函数最近的代码块。
- 快捷键F8 接续执行,map存入一个a字符,此时一次循环结束。准备进入循环执行第二次循环,代码执行到18行(还未执行18行)
- 快捷键F8 接续执行,进入下次循环,再继续上面的操作,我们就可以看到代码每次是如何执行的了
- 如果不想继续debug,那么可以使用快捷键F9,程序正常执行到结束,程序结果在控制台显示