1.工程目录介绍
三层架构
2.Servlet
手动实现
实现Servlet接口,如果servlet报红,为模块添加web框架
public class Test_Servlet 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 {
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
<servlet>
<!--起别名,一般是类名-->
<servlet-name>Test_Servlet</servlet-name>
<!--是servlet程序全类名-->
<servlet-class>com.example._2_Servlet.Test_Servlet</servlet-class>
</servlet>
<servlet-mapping>
<!--告诉服务器,当前配置地址给哪个Servlet用-->
<servlet-name>Test_Servlet</servlet-name>
<!--配置访问地址-->
<url-pattern>/test</url-pattern>
</servlet-mapping>
获取请求
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("被访问了");
HttpServletRequest hr = (HttpServletRequest) servletRequest;
String method = hr.getMethod();
if("GET".equals(method)){
doGet();
}
else if("POST".equals(method)){
doPost();
}
}
<form action="http://localhost:8080/_2_Servlet_war_exploded/test" method="get"><input type="submit"></form>
<hr>
<form action="http://localhost:8080/_2_Servlet_war_exploded/test" method="post"><input type="submit"></form>
通过HttpServlet实现
- 编写一个类去继承 HttpServlet 类
- 根据业务需要重写 doGet 或 doPost 方法
- 到 web.xml 中的配置 Servlet 程序的访问地址
public class HelloServlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
System.out.println("HelloServlet2 的 doGet 方法");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
System.out.println("HelloServlet2 的 doPost 方法");
}
}
ServletConfig
三大作用
-
可以获取 Servlet 程序的别名 servlet-name 的值
-
获取初始化参数 init-param
-
获取 ServletContext 对象
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.atguigu.servlet.HelloServlet</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>
@Override
public void init(ServletConfig servletConfig) throws ServletException {
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());
}
ServletContext
上下文参数
<!--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>
protected void doGet(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException {
// 1、获取 web.xml 中配置的上下文参数 context-param
ServletContext context = getServletConfig().getServletContext();
String username = context.getInitParameter("username");
System.out.println("context-param 参数 username 的值是:" + username);
System.out.println("context-param 参数 password 的值是:" +
context.getInitParameter("password"));
// 2、获取当前的工程路径,格式: /工程路径
System.out.println( "当前工程路径:" + context.getContextPath() );
// 3、获取工程部署后在服务器硬盘上的绝对路径
// 斜杠被服务器解析地址为:http://ip:port/工程名/ 映射到 IDEA 代码的web 目录<br/>*/
System.out.println("工程部署的路径是:" + context.getRealPath("/"));
System.out.println("工程下 css 目录的绝对路径是:" + context.getRealPath("/css"));System.out.println("工程下 imgs 目录 1.jpg 的绝对路径是:" + context.getRealPath("/imgs/1.jpg"));}
可以像Map一样存储数据
public class ContextServlet1 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException {
// 获取 ServletContext 对象
ServletContext context = getServletContext();
System.out.println(context);
System.out.println("保存之前: Context1 获取 key1 的值是:"+ context.getAttribute("key1"));
context.setAttribute("key1", "value1");
System.out.println("Context1 中获取域数据 key1 的值是:"+ context.getAttribute("key1"));}
}
HttpServletRequest
每次只要有请求进入 Tomcat 服务器,Tomcat 服务器就会把请求过来的 HTTP 协议信息解析好封装到Request 对象中。然后传递到 service 方法(doGet 和 doPost)中给我们使用。我们可以通过 HttpServletRequest 对象,获取到所有请求的信息。
乱码解决
//get
// 获取请求参数
String username = req.getParameter("username");
//1 先以 iso8859-1 进行编码
//2 再以 utf-8 进行解码
username = new String(username.getBytes("iso-8859-1"), "UTF-8");
//post
// 设置请求体的字符集为 UTF-8,从而解决 post 请求的中文乱码问题
req.setCharacterEncoding("UTF-8");
System.out.println("-------------doPost------------");
// 获取请求参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String[] hobby = req.getParameterValues("hobby");
请求转发
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/servlet2");
// RequestDispatcher requestDispatcher = req.getRequestDispatcher("http://www.baidu.com");
// 走向 Sevlet2
requestDispatcher.forward(req,resp);
base标签
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--base 标签设置页面相对路径工作时参照的地址href 属性就是参数的地址值-->
<base href="http://localhost:8080/07_servlet/a/b/">
</head>
<body>
这是 a 下的 b 下的 c.html 页面<br/>
<a href="../../index.html">跳回首页</a><br/>
</body>
</html>
HttpServletResponse
HttpServletResponse 类和 HttpServletRequest 类一样。每次请求进来,Tomcat 服务器都会创建一个Response对象传递给 Servlet 程序去使用。HttpServletRequest 表示请求过来的信息,HttpServletResponse 表示所有响应的信息,我们如果需要设置返回给客户端的信息,都可以通过 HttpServletResponse 对象来进行设置
- 字节流 getOutputStream(); 常用于下载(传递二进制数据)
- 字符流 getWriter(); 常用于回传字符串(常用)
回传数据
// 要求 : 往客户端回传 字符串 数据。
PrintWriter writer = resp.getWriter();
writer.write("response's content!!!");
解决乱码
// 它会同时设置服务器和客户端都使用 UTF-8 字符集,还设置了响应头
// 此方法一定要在获取流对象之前调用才有效
resp.setContentType("text/html; charset=UTF-8");
请求重定向
resp.sendRedirect("http://localhost:8080");
3.JSP
原理
jsp 的本质 ,其实是一个 Servlet 程序。
我们打开 index_jsp.java 文件查看里面的内容不难发现。jsp 中的 html 页面内容都被翻译到Servlet 中的service方法中直接输出。
jsp 的语法
page指令
三种脚本
声明脚本
声明脚本格式如下:
<%! java 代码 %>
1. 我们可以定义全局变量。
2. 定义 static 静态代码块
3. 定义方法
4. 定义内部类
5. 几乎可以写在类的内部写的代码,都可以通过声明脚本来实现
表达式脚本 ※
<%= 表达式 %>
表达式脚本 用于向页面输出内容。
表达式脚本 翻译到 Servlet 程序的 service 方法中 以 out.print() 打印输出
out 是 jsp 的一个内置对象,用于生成 html 的源代码
注意:表达式不要以分号结尾,否则会报错
表达式脚本可以输出任意类型。
比如:
1.输出整型
2.输出浮点型
3.输出字符串
4.输出对象
代码脚本 ※※
<% java 代码 %>
代码脚本里可以书写任意的 java 语句。
代码脚本的内容都会被翻译到 service 方法中。
所以 service 方法中可以写的 java 代码,都可以书写到代码脚本中
九大内置对象
九大内置对象,都是我们可以在【代码脚本】中或【表达式脚本】中直接使用的对象。
四大域对象
out 输出和response.getWriter输出的区别
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
// out 输出
out.write("这是 out 的第一次输出<br/>");
// out flush 之后。会把输出的内容写入 writer 的缓冲区中
out.flush();
// 最后一次的输出,由于没有手动 flush,会在整个页面输出到客户端的时候,自动写入到writer缓冲区
out.write("这是 out 的第二次输出<br/>");
// writer 的输出
response.getWriter().write("这是 writer 的第一次输出<br/>");
response.getWriter().write("这是 writer 的第二次输出<br/>");
%>
</body>
</html>
由于 jsp 翻译之后,底层源代码都是使用 out 来进行输出,所以一般情况下。我们在jsp 页面中统一使用out 来进行输出。避免打乱页面输出内容的顺序。
- out.write() 输出字符串没有问题
- out.print() 输出任意数据都没有问题(都转换成为字符串后调用的 write 输出)
深入源码,浅出结论:在 jsp 页面中,可以统一使用 out.print()来进行输出
常用标签
//静态包含--常用
<%@ include file="" %>
1、静态包含不会翻译被包含的 jsp 页面。
2、静态包含其实是把被包含的 jsp 页面的代码拷贝到包含的位置执行输出。
//动态包含--很少用
<jsp:include page=""></jsp:include>
1、动态包含会把包含的 jsp 页面也翻译成为 java 代码
2、动态包含底层代码使用如下代码去调用被包含的 jsp 页面执行输出。
JspRuntimeLibrary.include(request, response, "/include/footer.jsp", out,false);
3、动态包含,还可以传递参数
<jsp:include page="/include/footer.jsp">
<jsp:param name="username" value="bbj"/>
<jsp:param name="password" value="root"/>
</jsp:include>
//页面转发--常用
<jsp:forward page=""></jsp:forward>
九九乘法表
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<style type="text/css">
table{
width: 650px;
}
</style>
</head>
<body>
<%-- 练习一:在 jsp 页面中输出九九乘法口诀表 --%>
<h1 align="center">九九乘法口诀表</h1>
<table align="center">
<%-- 外层循环遍历行 --%>
<% for (int i = 1; i <= 9; i++) { %>
<tr>
<%-- 内层循环遍历单元格 --%>
<% for (int j = 1; j <= i ; j++) { %>
<td><%=j + "x" + i + "=" + (i*j)%></td>
<% } %>
</tr>
<% } %>
</table>
</body>
</html>
4.监听器
Listener 监听器
Listener 监听器它是 JavaWeb 的三大组件之一。JavaWeb 的三大组件分别是:Servlet 程序、Filter 过滤器、Listener监听器。
ServletContextListener 监听器
- ServletContextListener 它可以监听 ServletContext 对象的创建和销毁。
- ServletContext 对象在 web 工程启动的时候创建,在 web 工程停止的时候销毁。
- 监听到创建和销毁之后都会分别调用 ServletContextListener 监听器的方法反馈。
使用步骤如下:
- 编写一个类去实现 ServletContextListener
- 实现其两个回调方法
- 到 web.xml 中去配置监听器
public class MyServletContextListenerImpl implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext 对象被创建了");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext 对象被销毁了");
}
}
<!--配置监听器-->
<listener>
<listener-class>com.atguigu.listener.MyServletContextListenerImpl</listener-class>
</listener>
5.el表达式
EL 表达式的什么作用:EL 表达式主要是代替 jsp 页面中的表达式脚本在 jsp 页面中进行数据的输出。因为 EL 表达式在输出数据的时候,要比 jsp 的表达式脚本要简洁很多。
EL 表达式的格式是:${表达式}
EL 表达式在输出 null 值的时候,输出的是空串。jsp 表达式脚本输出 null 值的时候,输出的是null 字符串。
<body>
<%
request.setAttribute("key","值");
%>
表达式脚本输出 key 的值是:
<%=request.getAttribute("key1")==null?"":request.getAttribute("key1")%><br/>
EL 表达式输出 key 的值是:${key1}
</body>
输出顺序
EL 表达式主要是在 jsp 页面中输出数据。 主要是输出域对象中的数据。
当四个域中都有相同的 key 的数据的时候,EL 表达式会按照四个域的从小到大的顺序去进行搜索,找到就输出。
empty运算
empty 运算可以判断一个数据是否为空,如果为空,则输出 true,不为空输出false。
以下几种情况为空:
- 值为 null 值的时候,为空
- 值为空串的时候,为空
- 值是 Object 类型数组,长度为零的时候
- list 集合,元素个数为零
- map 集合,元素个数为零
${ empty xxx }
11 个隐含对象
EL 个达式中 11 个隐含对象,是 EL 表达式中自己定义的,可以直接使用。
6.JSTL 标签库
EL 表达式主要是为了替换 jsp 中的表达式脚本,而标签库则是为了替换代码脚本。这样使得整个jsp页面变得更佳简洁。
在 jsp 标签库中使用 taglib 指令引入标签库
CORE 标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
XML 标签库
<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>
FMT 标签库
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
SQL 标签库
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
FUNCTIONS 标签库
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
使用步骤
//1、先导入 jstl 标签库的 jar 包。
taglibs-standard-impl-1.2.1.jar
taglibs-standard-spec-1.2.1.jar
//第二步,使用 taglib 指令引入标签库。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
core 核心库使用
<%-- <c:set />(使用很少)--%>
<c:set scope="session" var="abc" value="abcValue"/>
<%-- <c:if />判断 test 属性表示判断的条件(使用 EL 表达式输出) --%>
<c:if test="${ 12 == 12 }">
<h1>12 等于 12</h1>
</c:if>
<%-- <c:choose> <c:when> <c:otherwise>多路判断 --%>
<c:choose>
<c:when test="${ requestScope.height > 190 }">
<h2>小巨人</h2>
</c:when>
<c:when test="${ requestScope.height > 180 }">
<h2>很高</h2>
</c:when>
<c:when test="${ requestScope.height > 170 }">
<h2>还可以</h2>
</c:when>
<c:otherwise>
<c:choose>
<c:when test="${requestScope.height > 160}">
<h3>大于 160</h3>
</c:when>
<c:when test="${requestScope.height > 150}">
<h3>大于 150</h3>
</c:when>
<c:when test="${requestScope.height > 140}">
<h3>大于 140</h3>
</c:when>
<c:otherwise>
其他小于 140
</c:otherwise>
</c:choose>
</c:otherwise>
</c:choose>
<%-- <c:forEach /> --%>
<c:forEach begin="1" end="10" var="i">
<tr>
<td>第${i}行</td>
</tr>
</c:forEach>
//Object
<c:forEach items="${ requestScope.arr }" var="item">
${ item } <br>
</c:forEach>
//Map
<c:forEach items="${ requestScope.map }" var="entry">
<h1>${entry.key} = ${entry.value}</h1>
</c:forEach>
//List
<c:forEach begin="2" end="7" step="2" varStatus="status" items="${requestScope.stus}" var="stu"><tr>
<td>${stu.id}</td>
<td>${stu.username}</td>
<td>${stu.password}</td>
<td>${stu.age}</td>
<td>${stu.phone}</td>
<td>${status.step}</td>
</tr>
</c:forEach>
7.文件上传
commons-fileupload.jar
commons-fileupload.jar 需要依赖 commons-io.jar 这个包,所以两个包我们都要引入。
commons-fileupload-1.2.1.jar
commons-io-1.4.jar
//常用类
ServletFileUpload 类,用于解析上传的数据。
FileItem 类,表示每一个表单项。
boolean ServletFileUpload.isMultipartContent(HttpServletRequest request);
判断当前上传的数据格式是否是多段的格式。
public List<FileItem> parseRequest(HttpServletRequest request)
解析上传的数据
boolean FileItem.isFormField()
判断当前这个表单项,是否是普通的表单项。还是上传的文件类型。
true 表示普通类型的表单项
false 表示上传的文件类型
String FileItem.getFieldName()
获取表单项的 name 属性值
String FileItem.getString()
获取当前表单项的值。
String FileItem.getName();
获取上传的文件名
void FileItem.write( file );
将上传的文件写到 参数 file 所指向抽硬盘位置 。
上传步骤
<form action="http://192.168.31.74:8080/09_EL_JSTL/uploadServlet" method="post"
enctype="multipart/form-data">
用户名:<input type="text" name="username" /> <br>
头像:<input type="file" name="photo" > <br>
<input type="submit" value="上传">
</form>
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
//1 先判断上传的数据是否多段数据(只有是多段的数据,才是文件上传的)
if (ServletFileUpload.isMultipartContent(req)) {
// 创建 FileItemFactory 工厂实现类
FileItemFactory fileItemFactory = new DiskFileItemFactory();
// 创建用于解析上传数据的工具类 ServletFileUpload 类
ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);try {
// 解析上传的数据,得到每一个表单项 FileItem
List<FileItem> list = servletFileUpload.parseRequest(req);
// 循环判断,每一个表单项,是普通类型,还是上传的文件
for (FileItem fileItem : list) {
if (fileItem.isFormField()) {
// 普通表单项
System.out.println("表单项的 name 属性值:" + fileItem.getFieldName());
// 参数 UTF-8.解决乱码问题
System.out.println("表单项的 value 属性值:" + fileItem.getString("UTF-8"));}
else {
// 上传的文件
System.out.println("表单项的 name 属性值:" + fileItem.getFieldName());
System.out.println("上传的文件名:" + fileItem.getName());
//写入
fileItem.write(new File("e:\\" + fileItem.getName()));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
下载步骤
//下载常用API
servletContext.getMimeType();
servletContext.getResourceAsStream();
response.getOutputStream();
response.setContentType();
//下载文件名
String downloadfile = "xxx";
//通知客户端
ServletContext sc = getServletContext();
String minetype = sc.getMimeType("/file/" + downloadfile);
res.setContentType(minetype)
resp.setHeader("Content-Disposition", "attachment; fileName=" + downloadfile);
//读取下载内容
InputStream is = sc.getResourceAsStream("/file/" + downloadfile);
//获取响应输出流
OutputStream os = resp.getOutputStream();
//输入流写给输出流
IOUtils.copy(is,os);
解决附件乱码
//ie,chrome
// 把中文名进行 UTF-8 编码操作。
String str = "attachment; fileName=" + URLEncoder.encode("中文.jpg","UTF-8");
// 然后把编码后的字符串设置到响应头中
response.setHeader("Content-Disposition", str);
//firefox
// 使用下面的格式进行 BASE64 编码后
String str = "attachment; fileName=" + "=?utf-8?B?"+ new BASE64Encoder().encode("中文.jpg".getBytes("utf-8")) +"?=";
// 设置到响应头中
response.setHeader("Content-Disposition", str);
//通用
String ua = request.getHeader("User-Agent");
// 判断是否是火狐浏览器
if (ua.contains("Firefox")) {
String str = "attachment; fileName=" + "=?utf-8?B?"
+ new BASE64Encoder().encode("中文.jpg".getBytes("utf-8")) +"?=";
response.setHeader("Content-Disposition", str);
} else {
String str = "attachment; fileName=" + URLEncoder.encode("中文.jpg","UTF-8");
response.setHeader("Content-Disposition", str);
}
8.cookie
cookie使用
//创建cookie
protected void createCookie(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
//1 创建 Cookie 对象
Cookie cookie = new Cookie("key4", "value4");
//2 通知客户端保存 Cookie
resp.addCookie(cookie);
//1 创建 Cookie 对象
Cookie cookie1 = new Cookie("key5", "value5");
//2 通知客户端保存 Cookie
resp.addCookie(cookie1);
resp.getWriter().write("Cookie 创建成功");
}
//接收cookie Cookie[]
req.getCookies()
//修改cookie 一种直接创建,同名cookie会覆盖,一种修改值
cookie.setValue("newValue2");
//生存时间
cookie.setMaxAge(60 * 60); // 设置 Cookie 一小时之后被删除。无效
//删除
cookie.setMaxAge(0);
//默认的会话级别的 Cookie
protected void defaultLife(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
Cookie cookie = new Cookie("defalutLife","defaultLife");
cookie.setMaxAge(-1);//设置存活时间
resp.addCookie(cookie);
}
有效路径
//Cookie 的 path 属性可以有效的过滤哪些 Cookie 可以发送给服务器。哪些不发。path 属性是通过请求的地址来进行有效的过滤。
protected void testPath(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
Cookie cookie = new Cookie("path1", "path1");
// getContextPath() ===>>>> 得到工程路径
cookie.setPath( req.getContextPath() + "/abc" ); // ===>>>> /工程路径/abc
resp.addCookie(cookie);
resp.getWriter().write("创建了一个带有 Path 路径的 Cookie");
}
9.session
session使用
//第一次调用是:创建 Session 会话,之后调用都是:获取前面创建好的 Session 会话对象。
request.getSession();
//判断到底是不是刚创建出来的(新的)true 表示刚创建 false 表示获取之前创建
isNew();
//每个会话都有一个身份证号。也就是 ID 值。而且这个 ID 是唯一的。 得到 Session 的会话 id 值。
getId()
//创建
protected void setAttribute(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
req.getSession().setAttribute("key1", "value1");
resp.getWriter().write("已经往 Session 中保存了数据");
}
//获取
protected void getAttribute(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
Object attribute = req.getSession().getAttribute("key1");
resp.getWriter().write("从 Session 中获取出 key1 的数据是:" + attribute);
}
生命周期
public void setMaxInactiveInterval(int interval);
//设置 Session 的超时时间(以秒为单位),超过指定的时长,Session就会被销毁。
//值为正数的时候,设定 Session 的超时时长。
//负数表示永不超时(极少使用)
public int getMaxInactiveInterval()//获取 Session 的超时时间
public void invalidate() //让当前 Session 会话马上超时无效。
//Session 默认的超时时间长为 30 分钟。可以在xml中设置
<session-config>
<session-timeout>20</session-timeout>
</session-config>
10.验证码解决重复提交
表单重复提交有三种常见的情况:
一:提交完表单。服务器使用请求转来进行页面跳转。这个时候,用户按下功能键F5,就会发起最后一次的请求。造成表单重复提交问题。解决方法:使用重定向来进行跳转
二:用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户以为提交失败,就会着急,然后多点了几次提交操作,也会造成表单重复提交。
三:用户正常提交服务器。服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提交。也会造成表单重复提交。
谷歌 kaptcha 图片验证码的使用
- 导入谷歌验证码的 jar 包 kaptcha-2.3.2.jar
- 在 web.xml 中去配置用于生成验证码的 Servlet 程序
<servlet>
<servlet-name>KaptchaServlet</servlet-name>
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>KaptchaServlet</servlet-name>
<url-pattern>/kaptcha.jpg</url-pattern>
</servlet-mapping>
<form action="http://localhost:8080/tmp/registServlet" method="get">
用户名:<input type="text" name="username" > <br>
验证码:<input type="text" style="width: 80px;" name="code">
<img src="http://localhost:8080/tmp/kaptcha.jpg" alt="" style="width: 100px; height:28px;"><br><input type="submit" value="登录">
</form>
3.比较使用
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
// 获取 Session 中的验证码,第一次是正确的code,第二次是空
String token = (String) req.getSession().getAttribute(KAPTCHA_SESSION_KEY);
// 删除 Session 中的验证码
req.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
String code = req.getParameter("code");
// 获取用户名
String username = req.getParameter("username");
//不为空且验证码对的
if (token != null && token.equalsIgnoreCase(code)) {
System.out.println("保存到数据库:" + username);
resp.sendRedirect(req.getContextPath() + "/ok.jsp");
} else {
System.out.println("请不要重复提交表单");
}
}
切换验证码
// 给验证码的图片,绑定单击事件
$("#code_img").click(function () {
// 在事件响应的 function 函数中有一个 this 对象。这个 this 对象,是当前正在响应事件的dom对象
// src 属性表示验证码 img 标签的 图片路径。它可读,可写
// alert(this.src);
this.src = "${basePath}kaptcha.jpg?d=" + new Date();
});
11.过滤器filter
基本使用
public class AdminFilter implements Filter {
//doFilter 方法,专门用于拦截请求。可以做权限检查
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest hr = (HttpServletRequest) servletRequest;
HttpSession session = hr.getSession();
Object user = session.getAttribute("user");
// 如果等于 null,说明还没有登录
if (user == null) {
hr.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
return;
} else {
// 让程序继续往下访问用户的目标资源
filterChain.doFilter(servletRequest,servletResponse);
}
}
}
<filter>
<!--给 filter 起一个别名-->
<filter-name>AdminFilter</filter-name>
<!--配置 filter 的全类名-->
<filter-class>com.atguigu.filter.AdminFilter</filter-class>
</filter>
<!--filter-mapping 配置 Filter 过滤器的拦截路径-->
<filter-mapping>
<!--filter-name 表示当前的拦截路径给哪个 filter 使用-->
<filter-name>AdminFilter</filter-name>
<!--url-pattern 配置拦截路径 -->
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
FilterConfig
Tomcat 每次创建 Filter 的时候,也会同时创建一个 FilterConfig 类,这里包含了 Filter 配置文件的配置信息。
- 获取 Filter 的名称 filter-name 的内容
- 获取在 Filter 中配置的 init-param 初始化参数
- 获取 ServletContext 对象
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 1、获取 Filter 的名称 filter-name 的内容
System.out.println("filter-name 的值是:" + filterConfig.getFilterName());
// 2、获取在 web.xml 中配置的 init-param 初始化参数
System.out.println("初始化参数 username 的值是:" + filterConfig.getInitParameter("username"));
System.out.println("初始化参数 url 的值是:" + filterConfig.getInitParameter("url"));
// 3、获取 ServletContext 对象
System.out.println(filterConfig.getServletContext());
}
<filter>
<filter-name>AdminFilter</filter-name>
<filter-class>com.atguigu.filter.AdminFilter</filter-class>
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost3306/test</param-value>
</init-param>
</filter>
FilterChain 过滤器链
Filter 的拦截路径
//精确匹配
<url-pattern>/target.jsp</url-pattern>
//目录匹配
<url-pattern>/admin/*</url-pattern>
//后缀名匹配
<url-pattern>*.html</url-pattern>
以上配置的路径,表示请求地址必须以.html 结尾才会拦截到
<url-pattern>*.do</url-pattern>
以上配置的路径,表示请求地址必须以.do 结尾才会拦截到
<url-pattern>*.action</url-pattern>
以上配置的路径,表示请求地址必须以.action 结尾才会拦截到
Filter 过滤器它只关心请求的地址是否匹配,不关心请求的资源是否存在!!!
Filter 过滤器统一给所有的 Service 方法都加上try-catch
通过filter调用,后置代码进行提交,如果出现异常进行回滚
public class TransactionFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChainfilterChain) throws IOException, ServletException {
try {
filterChain.doFilter(servletRequest,servletResponse);
JdbcUtils.commitAndClose();// 提交事务
} catch (Exception e) {
JdbcUtils.rollbackAndClose();//回滚事务
e.printStackTrace();
}
}
}
<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>com.atguigu.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!-- /* 表示当前工程下所有请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
一定要记得把 BaseServlet(方法调用类) 中的异常往外抛给 Filter 过滤器
public abstract class BaseServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
doPost(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
// 解决 post 请求中文乱码问题
// 一定要在获取请求参数之前调用才有效
req.setCharacterEncoding("UTF-8");
String action = req.getParameter("action");
try {
// 获取 action 业务鉴别字符串,获取相应的业务 方法反射对象
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class,HttpServletResponse.class);
// System.out.println(method);
// 调用目标业务 方法
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);// 把异常抛给 Filter 过滤器
}
}
}
12.ThreadLocal
- ThreadLocal 的作用,它可以解决多线程的数据安全问题。
- ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)
- ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据,key 为当前线程)
- 每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal 对象实例。
- 每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型
- ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放。
数据关联
使用Hashtable,保证线程安全
public class Test_ThreadLocal {
public static Map<String,Object> data = new Hashtable<String ,Object>();
public static Random random = new Random();
public static void main(String[] args) {
for(int i = 0; i<3 ;i++){
new Thread(new Task()).start();
}
}
}
class Task implements Runnable{
@Override
public void run() {
Integer i = Test_ThreadLocal.random.nextInt(1000);
String name = Thread.currentThread().getName();
System.out.println(name +" " + i);
Test_ThreadLocal.data.put(name,i);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object o = Test_ThreadLocal.data.get(name);
System.out.println("结束"+name + " " + o);
}
}
使用ThreadLocal
class OrderService {
public void createOrder(){
String name = Thread.currentThread().getName();
System.out.println("OrderService 当前线程[" + name + "]中保存的数据是:" +
Test_ThreadLocal2.threadLocal.get());
new OrderDao().saveOrder();
}
}
class OrderDao {
public void saveOrder(){
String name = Thread.currentThread().getName();
System.out.println("OrderDao 当前线程[" + name + "]中保存的数据是:" +
Test_ThreadLocal2.threadLocal.get());
}
}
public class Test_ThreadLocal2 {
// public static Map<String,Object> data = new Hashtable<String,Object>();
public static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
private static Random random = new Random();
public static class Task implements Runnable {
@Override
public void run() {
// 在 Run 方法中,随机生成一个变量(线程要关联的数据),然后以当前线程名为key 保存到map中
Integer i = random.nextInt(1000);
// 获取当前线程名
String name = Thread.currentThread().getName();
System.out.println("线程["+name+"]生成的随机数是:" + i);
// data.put(name,i);
threadLocal.set(i);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new OrderService().createOrder();
// 在 Run 方法结束之前,以当前线程名获取出数据并打印。查看是否可以取出操作// Object o = data.get(name);
Object o = threadLocal.get();
System.out.println("在线程["+name+"]快结束时取出关联的数据是:" + o);
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++){
new Thread(new Task()).start();
}
}
}
ThreadLocal 来确保所有 dao 操作都在同一个Connection连接对象中完成,每次dao操作不新new一个connection对象
13.友好的错误页面
在 web.xml 中我们可以通过错误页面配置来进行管理。
<!--error-page 标签配置,服务器出错之后,自动跳转的页面-->
<error-page>
<!--error-code 是错误类型-->
<error-code>500</error-code>
<!--location 标签表示。要跳转去的页面路径-->
<location>/pages/error/error500.jsp</location>
</error-page>
<!--error-page 标签配置,服务器出错之后,自动跳转的页面-->
<error-page>
<!--error-code 是错误类型-->
<error-code>404</error-code>
<!--location 标签表示。要跳转去的页面路径-->
<location>/pages/error/error404.jsp</location>
</error-page>
14.json
- JSON.stringify( json ); 此方法可以把一个 json 对象转换成为json 字符串
- JSON.parse( jsonString ); 此方法可以把一个 json 字符串转换成为json 对象
javaBean与json相互转换
// 要把复杂的 json 字符串转换成为 java 对象。需要继承 TypeToken 类。// 并把返回的类型当成 TypeToken 的泛型注入
static class PersonType extends TypeToken<List<Person>> {
}
public static void main(String[] args) {
// json 操作,一定要先 new 一个 gson 对象。
Gson gson = new Gson();
// java 对象--json
Person person = new Person(12, "wzg168");
// 把对象转成为 json 字符串
String personjson = gson.toJson(person);
System.out.println(personjson);
// 把 json 字符串转换成为 java 对象
Person p = gson.fromJson(personjson, Person.class);
System.out.println(p);
System.out.println("------------------------------------------");
// 2、java 对象 list 集合和 json 的转换
List<Person> list = new ArrayList<Person>();
for (int i = 0; i < 3; i++) {
list.add(new Person(10 * i, "name-" + i));
}
String jsonListString = gson.toJson(list);
System.out.println(jsonListString);
// 把 json 数组转换成为 List 对象
// List<Person> ps = gson.fromJson(jsonListString, new PersonType().getType());
// 我们也可以使用匿名内部类
List<Person> ps = gson.fromJson(jsonListString, new TypeToken<List<Person>>(){}.getType());
System.out.println(ps);
System.out.println("------------------------------------------");
// 3、map 对象和 json 的转换
Map<String, Person> mapPerson = new HashMap<String, GsonTest.Person>();// 添加 person 到 map 中
mapPerson.put("p1", new Person(1, "person-1"));
mapPerson.put("p2", new Person(2, "person-2"));
// 把 map 转换成为 json 对象
String jsonMapString = gson.toJson(mapPerson);
System.out.println(jsonMapString);
// 通过使用匿名内部类的方式
Map<String, Person> map = gson.fromJson(jsonMapString,
new TypeToken<HashMap<String, Person>>() {}.getType());
System.out.println(map);
}
}
其他问题
Idea中Tomcat服务器乱码
- tomcat中logging.properties,所有encoding改为UTF-8
- IDEA ->帮助->编辑自定义VM选项->添加 -Dfile.encoding=UTF-8
- IDEA->编辑tomcat配置->虚拟机选项->添加 -Dfile.encoding=UTF-8
- 重启idea
jsp方法使用不了
要手动导入tomcat中lib的jsp-api