【Servlet】
1.工程结构
2.J2EE平台
Java EE,Java平台企业版(Java Platform Enterprise Edition),之前称为Java 2 Platform, Enterprise Edition (J2EE),2018年3月更名为Jakarta EE。是Sun公司为企业级应用推出的标准平台。Java平台共分为三个主要版本Java EE、Java SE和Java ME。
Sun公司在1998年发表JDK1.2版本的时候,使用了新名称Java 2 Platform,即“Java2平台”,修改后的JDK称为Java 2 Platform Software Developing Kit,即J2SDK。并分为标准版(Standard Edition,J2SE),企业版(Enterprise Edition,J2EE),微型版(MicroEdition,J2ME)。J2EE便由此诞生。
2005年6月,JavaOne大会召开,SUN公司公开Java SE 6。此时,Java的各种版本已经更名以取消其中的数字“2”:J2EE更名为Java EE, J2SE更名为Java SE,J2ME更名为Java ME。
随着Java技术的发展,J2EE平台得到了迅速的发展,成为Java语言中最活跃的体系之一。现如今,J2EE不仅仅是指一种标准平台,它更多的表达着一种软件架构和设计思想。
2.1 J2EE平台组件
Java EE是一系列技术标准所组成的平台(一系列规范),包括:
1.Applet - Java Applet
2.EJB - 企业级JavaBean(Enterprise Java Beans)
3.JAAS - Java Authentication and Authorization Service
4.JACC - J2EE Authorization Contract for Containers
5.JAF - Java Beans Activation Framework
6.JAX-RPC - Java API for XML-Based Remote Procedure Calls
7.JAX-WS - Java API for XML Web Services
8.JAXM - Java API for XML Messaging
9.JAXP - Java XML解析API(Java API for XML Processing)
10.JAXR - Java API for XML Registries
11.JCA - J2EE连接器架构(J2EE Connector Architecture)
12.JDBC - Java数据库联接(Java Database Connectivity)
13.JMS - Java消息服务(Java Message Service)
14.JMX - Java Management
15.JNDI - Java名称与目录接口(Java Naming and Directory Interface)
16.JSF - Java Server Faces
17.JSP - Java服务器页面(Java Server Pages)
18.JSTL - Java服务器页面标准标签库(Java Server Pages Standard Tag Library)
19.JTA - Java事务API(Java Transaction API)
20.JavaMail
21.Servlet - Java Servlet API
22.StAX - Streaming APIs for XML Parsers
23.WS - Web Services
3.servlet
Servlet(Server Applet),全称Java Servlet。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。
Servlet运行于支持Java的应用服务器中。从实现上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。
最早支持Servlet标准的是JavaSoft的Java Web Server。此后,一些其它的基于Java的Web服务器开始支持标准的Servlet。
3.1 Servlet的作用
Servlet是服务端的一个小程序,程序其作用只有2个:
3.1.1 获取客户的请求信息
Servlet获取客户端的请求信息,基于ServletRequest对象,ServletRequest的子接口: HttpServletRequest。
HttpServletRequest这个接口包含:客户端的请求参数,客户端的请求头信息。
3.1.2 向客户端做出响应
Servlet向客户端做出响应,基于ServletResponse对象,ServletResponse的子接口:HttpServletResponse。
HttpServletResponse这个接口:获取输出流(字节流,字符流),向客户端返回响应头信息。
3.2 Servlet的使用方式
3.2.1 创建Servlet的3个步骤
步骤
导入servlet-api.jar
在tomcat的lib文件夹下也有此jar包,开发过程中为防止报错需要导入,实际运行时需要移除此jar包避免版本冲突。
自定义类继承HttpServlet 继承后,重写doGet、doPost方法。
配置虚拟路径 1)可以通过注解配置 2)通过xml形式配置
3.2.2 使用注解形式
JavaWeb开发中,Servlet开发配置相对繁琐。在web3.0之后,提供一些简化web配置的数据,@WebServlet就可以取代在web.xml中对servlet的配置。
package com.powernode.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;
/**
* 内容: JavaWeb
*/
//@WebServlet(urlPatterns= {"/login.html","/a.html","/login.php","/login.aspx"})
@WebServlet("/LoginServlet") //简写
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("哈哈,我是get请求");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("哈哈,我是post请求");
}
}
3.2.3 使用xml形式
向服务器注册这个Servlet,servlet是要提供服务的
1.声明/定义servlet类
2.servlet提供服务访问方式
package com.powernode.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;
/**
* 内容: JavaWeb
*/
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("哈哈,我是get请求");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("哈哈,我是post请求");
}
}
<?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">
<!--通过xml形式配置servlet的虚拟路径-->
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.powernode.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login.html</url-pattern>
</servlet-mapping>
</web-app>
3.2.4 两种配置形式区别
平时开发中能用注解形式则用注解形式。但如果类是第3方的,由于我们不能去修改第3方的源代码,无法在类上添加注解,则使用xml形式最为合适。例如:后期SpringMVC框架中的核心类DispatcherServlet就是使用的xml形式作为配置。
3.2.5 通过Servlet输出html标签等富文本信息
package com.powernode.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;
import java.io.PrintWriter;
/**
* 内容: JavaWeb
*/
@WebServlet("/Demo1Servlet")
public class Demo1Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//在服务器端与浏览器端建立输出流管道
PrintWriter pw = resp.getWriter();
pw.write("<h1 style='color:red'>我是servlet输出</h1>");
pw.close();
/*ServletOutputStream sos = resp.getOutputStream();
String str = "<h1 style='color:red'>我是servlet输出</h1>";
sos.write(str.getBytes("UTF-8"));
sos.close();*/
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
3.3 为什么使用tomcat加载类,而不是使用JDK自带的加载器加载
JDK自带的类加载器3个,双亲委派.bootstrap类加载器,加载JDK核心类.Ext 扩展类加载器,加载默认ext目录相关类.application类加载器加载当前项目的类.双亲委派加载机制,避免重复加载,其次为了安全.因为开发者可以自定义类的包名,如果定义一个与系统包名同名的类,自定义的类会覆盖系统的类,可能到导致系统不安全.但是如果tomcat也使用双亲为加载机制加载,会导致如果多个web项目,其中类的名臣完全相同,但是实现不同就出现覆盖.tomcat通过打破双亲委派加载机制,做到了类在应用级别隔离.
3.4 Servlet的生命周期(重点)
Servlet的生命周期,是指Servlet的实例的创建到到销毁的过程。
代码示例:
package com.powernode.servlet;
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 LifeServlet extends HttpServlet {
/**
* init 方法 只 执行了1遍
* 初始化Servlet 对象的方法
* @throws ServletException
*/
@Override
public void init() throws ServletException {
System.out.println(this);
System.out.println("我是 init 方法");
}
/**
* service 方法 执行多次
* 每次有请求时都会执行
* @param req 就是封装了所有请求信息的对象
* @param resp 与请求对应的做出响应的对象
* @throws ServletException
* @throws IOException
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(this);
System.out.println("我是 service 方法");
}
/**
* 当服务器 停止时 destroy 方法执行
*/
@Override
public void destroy() {
System.out.println(this);
System.out.println("我是 destroy 方法");
}
}
package com.powernode.servlet;
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 LifeServlet extends HttpServlet {
/**
* init 方法 只 执行了1遍
* 初始化Servlet 对象的方法
* @throws ServletException
*/
@Override
public void init() throws ServletException {
System.out.println(this);
System.out.println("我是 init 方法");
}
/**
* service 方法 执行多次
* 每次有请求时都会执行
* @param req 就是封装了所有请求信息的对象
* @param resp 与请求对应的做出响应的对象
* @throws ServletException
* @throws IOException
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(this);
System.out.println("我是 service 方法");
}
/**
* 当服务器 停止时 destroy 方法执行
*/
@Override
public void destroy() {
System.out.println(this);
System.out.println("我是 destroy 方法");
}
}
Tomcat服务器在加载servlet类时,默认是不会创建对象的.只有在servlet实际被使用时,才会去创建对象,tomcat提供了init方法,供开发者进行对象属性的设置,所以该方法被称之为对象初始化方法.
每次访问时,具体的访问业务,tomcat会将访问的数据进行封装,封装为HttpServletRequest对象和HttpServletResponse对象,然后调用service方法.
当tomcat服务器停止时,又会去调用destroy方法.开发者,在这个方法中释放资源,留遗书.
init方法执行一次,如果在配置中配置load-on-startup,则tomcat在启动时就会创建servlet对象,调用init方法.
service方法,会执行多次,只有有请求就会执行service方法.请求数据会被封装到HttpServletRequest对象中,响应数据会被封装到HttpServletResponse对象中。
destroy,servlet对象销毁方法,当tomcat服务器停止时调用.可以释放资源.
3.5 Servlet线程安全问题
Servlet本质是单例模式,如果存在多个并发请求,所有请求使用同一个对象,如果请求中使用了对象的成员变量,则可能出现数据不一致的情况,servlet是非线程安全的.
解决方案:
-
不要在servlet使用成员变量.
-
使用ThreadLocal,线程局部变量存储当前线程自己的数据.
3.6 Servlet初始化参数配置
可以在web.xml中配置servlet初始化时的参数,也可以在注解中配置(鸡肋).在xml中配置示例如下:
<servlet>
<servlet-name>life</servlet-name>
<servlet-class>com.powernode.servlet.ServletLife</servlet-class>
<!-- 配置servlet 初始化参数 -->
<init-param>
<param-name>paramKey1</param-name>
<param-value>paramValue1</param-value>
</init-param>
<init-param>
<param-name>paramKey2</param-name>
<param-value>paramValue2</param-value>
</init-param>
<!-- 启动时创建servlet对象 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>life</servlet-name>
<url-pattern>/lifeServlet</url-pattern>
</servlet-mapping>
@Override
public void init() throws ServletException {
System.out.println("init执行下了!!!....");
System.out.println("当前对象为:"+this);
// servlet 对象
// 配置信息
ServletConfig config = this.getServletConfig();
// 所有的初始化配置参数的名称
Enumeration<String> initParameterNames = config.getInitParameterNames();
while ( initParameterNames.hasMoreElements()){
// 获取参数名称
String name = initParameterNames.nextElement();
String value = config.getInitParameter(name);
System.out.println(name +" : " + value);
}
// 根据参数名获取对应的参数值
Enumeration<String> names = this.getInitParameterNames();
while ( names.hasMoreElements()){
// 获取参数名称
String name = names.nextElement();
String value = this.getInitParameter(name);
System.out.println(name +" : " + value);
}
}
注解配置示例,仅供了解.
@WebServlet(value ="/initServlet",initParams = {
@WebInitParam(name = "paramName1",value = "paramValue1"),
@WebInitParam(name = "paramName2",value = "paramValue2"),
} )
public class ServletInitParam extends HttpServlet {
}
3.7 Servlet的Api
3.7.1 Servlet的请求Api
3.7.1.1 获取请求行数据
protected void reqLine(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的方法 : GET POST DELETE PUT OPTION
String method = req.getMethod();
// 获取请求的资源路径
String uri = req.getRequestURI();
// 获取请求的URL地址
StringBuffer url = req.getRequestURL();
// 获取请求的协议
String protocol = req.getProtocol();
System.out.println(method);
System.out.println(uri);
System.out.println(url);
System.out.println(protocol);
}
3.7.1.2 请求头数据
/**
* 获取请求头信息
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void reqHeader(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取所有的请求头信息的头
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
// 获取具体的请求头名称
String headerName = headerNames.nextElement();
// 根据请求头获取对应的值
String headerValue = req.getHeader(headerName);
System.out.println(headerName + ":" + headerValue);
}
// 如果比较核心的请求头:
// content-type 请求头数据
String contentType = req.getContentType();
//content-length 请求头数据
int contentLength = req.getContentLength();
System.out.println("content-type:" + contentType);
System.out.println("content-length:" + contentLength);
}
3.7.1.3 获取网络信息
/**
* 获取网络信息
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void reqNet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 协议
String protocol = req.getProtocol();
//端口
int localPort = req.getLocalPort();
// 远程主机
String remoteHost = req.getRemoteHost();
// 远程主机的端口
int remotePort = req.getRemotePort();
System.out.println(protocol);
System.out.println(localPort);
System.out.println(remoteHost);
System.out.println(remotePort);
}
3.7.1.4 请求参数
/**
* 请求参数
* 在HTTP协议中 默认的传输格式 ISO-8859-1 程序使用的是UTF-8的编码.并且tomcat 的 post 请求处理数据的格式 也是ISO-8859-1的编码.
* 两种编码不一致导致乱码。注意: GET请求使用的UTF-8编码.在Tomcat 7.0之后
* 如果乱码要进行转码
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void reqParam(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置默认的编码格式
req.setCharacterEncoding("UTF-8");
// 获取请求参数的Map 集合
// 参数的name 做为key
// 参数的值作为value
// 所有的请求参数 默认字符串类型 由于 name 可能对应的多个值 使用数组存储 (复选框时 name 可能对应多个值)
Map<String, String[]> parameterMap = req.getParameterMap();
parameterMap.forEach((k, v) -> {
System.out.println(k + " : " + Arrays.toString(v));
});
// 根据名称获取值 获取指定数据
String username = req.getParameter("username");
String password = req.getParameter("password");
String sex = req.getParameter("sex");
String[] gfs = req.getParameterValues("gf");
String city = req.getParameter("city");
String comment = req.getParameter("comment");
// 转码:
// 按照 ISO-8859-1 进行转码 还原
//byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1);
// 使用新的编码格式转字符串
//username = new String(bytes, StandardCharsets.UTF_8);
System.out.println("username:" + username);
System.out.println("password:" + password);
System.out.println("sex:" + sex);
System.out.println("gfs:" + Arrays.toString(gfs));
System.out.println("city:" + city);
System.out.println("comment:" + comment);
}
3.7.1.5 Servlet内部转发
Servlet内部转发是指当前servlet将请求和响应信息移交其他的servlet进行处理.对于请求方而言,只需要请求一次,但是实际上经历多个具体servlet(具体次数要看实际的转发次数).浏览器的URL地址不会发生变化,并且servlet之间可以进行参数传递操作.
这种内部转发的方式,由于将请求对象进行了传递,在实际开发中使用的比较多的.但是又由于URL地址没有发生变化,所以一旦浏览器进行刷新操作,之前所有相关的程序会重新执行.首先会导致无意义资源消耗,其次可能导致脏数据.
能不使用内部转发就不要使用内部转发.
/**
* 服务器转发
* 服务器转发 是指 处理客户端请求时,当前本该处理的程序不进行完整的处理,而是将请求移交给其他程序处理,这种方式就叫服务器内部转发
* 例如:
* 张三去银行办业务 本该在3号窗口办理业务
* 1. 张三去3号窗口
* 2. 3号窗口业务员在办理张三的业务时,突然要上个厕所.
* 3. 3号业务叫X号业务继续协助办理业务
* 4. x号业务员完成整个业务
* 这种形式就是内部转发
* 对于张三而言,只进行一次挂号. 张三经历业务员当前 2个
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void forward(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("3号业务员....");
System.out.println("3号业务员去厕所....");
// 服务器内部转发
// 将请求 移交给 servlet04.do
// req
// resp 张三的请求对象 和 响应对象
req.getRequestDispatcher("servlet04.do").forward(req,resp);
}
package com.powernode.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;
import java.util.Arrays;
@WebServlet("/servlet04.do")
public class Servlet04 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置编码格式
req.setCharacterEncoding("UTF-8");
System.out.println(" 4号业务员 ");
System.out.println("4 号业务员办理业务");
// 根据名称获取值 获取指定数据
String username = req.getParameter("username");
String password = req.getParameter("password");
String sex = req.getParameter("sex");
String[] gfs = req.getParameterValues("gf");
String city = req.getParameter("city");
String comment = req.getParameter("comment");
System.out.println("username:" + username);
System.out.println("password:" + password);
System.out.println("sex:" + sex);
System.out.println("gfs:" + Arrays.toString(gfs));
System.out.println("city:" + city);
System.out.println("comment:" + comment);
// 添加数据库操作
//......
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
get 请求参数会显示在URL上面,明文显示
post请求参数会封装在HTTP协议内部
post 请求相对于get请求安全一些.
不论get还是post请求都是明文传输,HTTP协议也是不安全的协议.
一般而言,修改时才用post请求,查询时使用get请求.
-->
<form action="param.do" method="post">
<p>用户名:<input name="username" /></p>
<p>密码:<input name="password" /></p>
<p>性别:
<input name="sex" type="radio" value="1" /> 男
<input name="sex" type="radio" value="2" /> 女
</p>
<p>女朋友:
<input name="gf" type="checkbox" value="韩梅梅" /> 韩梅梅
<input name="gf" type="checkbox" value="Lily" /> Lily
<input name="gf" type="checkbox" value="Lucy" />Lucy
</p>
<p>城市:
<select name="city">
<option value="武汉">武汉</option>
<option value="上海">上海</option>
<option value="深圳">深圳</option>
</select>
</p>
<p>简介:
<textarea name="comment"></textarea>
</p>
<button>提交</button>
</form>
</body>
</html>
3.7.2 Servlet的响应Api
在servlet中,请求对象和响应对象是成对出现的.哪怕没有数据的响应数据,tomcat服务器会先为每一个请求对象创建相应的响应对象,只是响应内容默认空的.在实际操作中,只需要向响应对象中提供数据即可.
3.7.2.1 响应对象的核心Api
HttpServletResponse核心api就是获取输出流.由于输出的数据可能是字符串也可能是字节.提供两个方法,但是注意,两个输出流实际使用只能使用其中的一个.
package com.powernode.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 响应数据的Api
*/
@WebServlet("/resp.do")
public class ResponseApiServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 返回字符数据
// text(req,resp);
// 返回字节数据
bytes(req,resp);
}
/**
* 返回字符数据
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void text(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置数据内容的编码格式 通知浏览器 按照这种编码进行解析
resp.setContentType("text/html;charset=utf-8");
// 设置将字符串转byte的编码
resp.setCharacterEncoding("UTF-8");
// 获取字符输出流
PrintWriter writer = resp.getWriter();
writer.println("Hello 字符流!");
writer.flush();
writer.close();
}
protected void bytes(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletOutputStream out = resp.getOutputStream();
// 文件对象
File file = new File("E://imgs/2.png");
FileInputStream fis = new FileInputStream(file);
// 定义变量接收数据
byte[] b = new byte[1024];
int len = -1;
while ((len = fis.read(b) ) != -1){
out.write(b,0,len);
}
out.flush();
out.close();
fis.close();
}
}
3.7.2.2 重定向和内部转发
protected void redirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 重定向
// resp.sendRedirect("success.html");
// 内部转发
req.getRequestDispatcher("success.html").forward(req,resp);
/**
* 重定向和内部转发对比
* 相同点 :
* 都能实现页面的跳转
* 不同点:
* 内部转发 :
* 1. URL地址栏 不会发生改变
* 2. 浏览器请求一次
* 3. 可以将request 和 response对象进行传递
*
* 重定向 :
* 1. URL地址栏会发生改变
* 2. 浏览器请求多次
* 3. 不能传递request 和 response对象进行传递
*
* 结论 : 如果要进行参数传递则使用内部转发,否则都建议使用重定向.因为重定向的URL地址栏发生了变化,刷新浏览器时不会造成不必要的请求.
* 不会产生不必要的服务器负担.
*/
}
3.7.2.3 向响应头添加数据
protected void respHeader(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过 addHeader 向响应头中添加数据
resp.addHeader("token", DateUtil.today());
resp.sendRedirect("success.html");
}
3.8 html与servlet相互跳转总结
描述
html跳html
a标签、js(location.href=’xxx’)、form表单的action属性
html跳servlet
a标签、js(location.href=’xxx’)、form表单的action属性
servlet跳html
重定向:response.sendRedirect(“/页面路径”);
转发:request.getRequestDispatcher(“/views/demo3.jsp”).forward(request,response);
servlet跳servlet
推荐重定向
3.8.1 练习1
demo1.html---->点击a标签----->demo2.html------>OneServlet------>TwoServlet----->demo3.html
3.8.2 练习2
demo1.html-------->不做任何点击跳------->demo2.html------->点击a标签------>demo3.html----->
不做任何点击----->OneServlet---------->TwoServlet------->demo4.html----->点击button按钮---->跳demo1.html
3.9 用户登录案例
用户在界面输入用户名和密码,servlet接收用户输入的用户名和密码,与数据库中的用户信息进行对比,如果用户名和密码都正确.跳转到登录成功页面,否则返回登录页面.
3.9.1 技术点
前端界面:HTML.数据存储:mysql.访问数据库使用JDBC.接收用户数据使用servlet.
3.9.2 前期准备
3.9.2.1 数据库准备
创建数据库erp
创建用户表user
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(20) NOT NULL COMMENT '登录名',
`password` varchar(20) NOT NULL COMMENT '登录密码',
`realname` varchar(20) NOT NULL COMMENT '用户姓名',
`img` varchar(100) DEFAULT NULL COMMENT '用户头像',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
3.9.3 导入相关jar包
mysql驱动包
3.9.4 引入相关的工具类
3.9.4.1 数据库配置文件
#驱动
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/erp?useUnicode=true&useSSL=false&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123456
3.9.4.2 BaseDao
package com.powernode.dao;
import com.powernode.util.DBUtils;
import com.powernode.util.PageInfo;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* JDBC工具类:旨在为客户提供更加便捷的操作数据库的方式
*/
public class BaseDao {
/**
* 修改:可以执行添加、删除、修改单条操作
* @param sql
* @param params
* @return
*/
public int executeUpdate(String sql,Object...params){
Connection conn = null;
PreparedStatement st = null;
int i=0;
try {
//参数校验
if(sql==null || "".equals(sql.trim())){
return 0;
}
//获取连接
conn = DBUtils.getConn();
//创建Statement对象
st = conn.prepareStatement(sql);
//给sql赋值参数
for (int n=0;params!=null && params.length>=1 &&n<params.length;n++){
st.setObject(n+1,params[n]);
}
//向mysql发送sql语句,接受结果
i = st.executeUpdate();
} catch (Exception throwables) {
throwables.printStackTrace();
}finally {
//释放资源
DBUtils.close(null,st,conn);
}
return i;
}
/**
* 批量删除
* @param sql
* @param params
* @return
*/
public int executeBatchDel(String sql,Object...params){
Connection conn = null;
PreparedStatement st = null;
int[] arr = null;
try {
//基本参数校验
if(sql==null || "".equals(sql.trim())){
return 0;
}
//获取连接
conn = DBUtils.getConn();
//在同一个事务中执行
conn.setAutoCommit(false);
st = conn.prepareStatement(sql);
//给参数赋值
for (int i = 0;params!=null && params.length>=1 && i < params.length; i++) {
st.setObject(1,params[i]);
st.addBatch();
}
//执行sql
arr = st.executeBatch();//[0,0,0,0]
} catch (Exception throwables) {
throwables.printStackTrace();
} finally {
try {
conn.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//释放资源
DBUtils.close(null,st,conn);
}
return arr.length;
}
/**
* 查询多条记录
* @param sql
* @param c
* @param params
* @param <T>
* @return
*/
public <T> List<T> executeQueryList(String sql, Class<T> c ,Object...params){
Connection conn= null;
PreparedStatement st = null;
ResultSet rs = null;
List<T> dataList = new ArrayList<>();//{}
try {
//参数校验
if(sql==null || "".equals(sql.trim())){
return dataList;
}
//获取Connection连接
conn = DBUtils.getConn();
//创建PrepareStatement对象
st = conn.prepareStatement(sql);
for (int i = 0;params!=null && params.length>=1 && i < params.length; i++) {
st.setObject(i+1,params[i]);
}
//发送sql,接受结果
rs = st.executeQuery();
//获取查询数据的字段个数
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for(;rs.next();){//取记录的次数
//创建一个c字节码的对象,用来装一条记录
T t = c.newInstance();
for (int i = 1; i <=columnCount ; i++) {
//获取某个字段名称
String columnName = metaData.getColumnName(i);
//获取字段对应的值
Object value = rs.getObject(columnName);
//内部for全部执行完了,则代表一条记录取出来了
Field field = c.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t,value);
field.setAccessible(false);
}
//将对象装入到List中
dataList.add(t);
}
} catch (Exception throwables) {
throwables.printStackTrace();
}finally {
DBUtils.close(rs,st,conn);
}
return dataList;
}
/**
* 查询单条记录
* @param sql
* @param c
* @param param
* @param <T>
* @return
*/
public <T> T executeQueryOne(String sql,Class<T> c,Object...param){
return executeQueryList(sql,c,param).size()==0?null:executeQueryList(sql,c,param).get(0);
}
/**
* 分页查询
* @param sql 基本分页sql,不用写limit
* @param pageNum 页码
* @param pageSize 每页显示的数据条数
* @param c 某一条数据的类型
* @param params 分页太哦建
* @param <T>
* @return
*/
public <T> PageInfo<T> executeQueryPage(String sql,int pageNum,int pageSize,Class<T> c,Object...params){
Connection conn=null;
PreparedStatement st = null;
ResultSet rs = null;
PageInfo<T> pageInfo = new PageInfo<>();
try {
//校验参数
if(sql==null || "".equals(sql.trim())){
return pageInfo;
}
if(pageNum<=0 || pageSize<=0){
return pageInfo;
}
if(c==null){
return pageInfo;
}
//去除;
sql = sql.replaceAll(";","");
//获取数据库连接:Connection对象
conn = DBUtils.getConn();
//准备sql语句
//创建PrepareStatement对象 (此处有个;的小bug)
String newSql = sql + " limit "+(pageNum-1)*pageSize+","+pageSize+"";
st = conn.prepareStatement(newSql);
//给sql占位符?赋值
for (int i = 0;params!=null && params.length>=1 && i < params.length; i++) {
st.setObject(i+1,params[i]);
}
//执行sql,处理结果
rs = st.executeQuery();
//数据表头信息
ResultSetMetaData metaData = rs.getMetaData();
//获取查询sql语句中字段的个数
int columnCount = metaData.getColumnCount();
for (;rs.next();){
//创建一个对象,用于接收一条记录
T t = c.newInstance();
for (int i = 1; i <=columnCount ; i++) {
//获取字段名
String columnName = metaData.getColumnName(i);
//获取字段值
Object columnValue = rs.getObject(columnName);
//将对应字段的值装入到实体类对象中去
Field field = c.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t,columnValue);
field.setAccessible(false);
}
//将某一条记录放入到List集合中去
//往pageInfo对象中添加分页数据
pageInfo.getData().add(t);
}
//往pageInfo对象中添加总记录数
long total = executeQueryCount(sql);
pageInfo.setTotal(total);
//往pageInfo对象中添加总页数
long temp = total % pageSize;//3%2=1
int pages = temp==0? (int)(total/pageSize): (int)Math.ceil((double)total/(double)pageSize);
pageInfo.setPages(pages);
} catch (Exception throwables) {
throwables.printStackTrace();
} finally {
//释放资源
DBUtils.close(rs,st,conn);
}
return pageInfo;
}
/**
* 统计满足sql条件的总记录数
* @param sql
* @return
*/
private long executeQueryCount(String sql){
long total = 0;
Connection conn=null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = DBUtils.getConn();
String newSql = "select count(*) from ("+sql+") t";
st = conn.prepareStatement(newSql);
rs = st.executeQuery();
for (;rs.next();){
total = rs.getLong("count(*)");
}
return total;
} catch (Exception throwables) {
throwables.printStackTrace();
} finally {
DBUtils.close(rs,st,conn);
}
return total;
}
}
3.9.4.3 DButil
package com.powernode.util;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* 自定义JDBC工具类:1)提供获取链接Connection的方法 2)关闭资源的方法 3)实现jdbc配置软编码
*/
public final class DBUtils {
/**
* 驱动名称
*/
private static String driverClassName;
/**
* 链接信息
*/
private static String url;
/**
* 用户名
*/
private static String username;
/**
* 用户密码
*/
private static String password;
static {
try {
//关联.properties配置文件
Properties prop = new Properties();
InputStream ins = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
prop.load(ins);
//读取.properties配置文件的属性值
driverClassName = prop.getProperty("jdbc.driverClassName");
url = prop.getProperty("jdbc.url");
username = prop.getProperty("jdbc.username");
password = prop.getProperty("jdbc.password");
Class.forName(driverClassName);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接
*
* @return
*/
public static Connection getConn() {
Connection conn = null;
try {
conn = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 关闭资源
*
* @param acs
*/
public static void close(AutoCloseable acs) {
try {
if (acs != null) {
acs.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭资源
*
* @param rs
* @param st
* @param conn
*/
public static void close(ResultSet rs, Statement st, Connection conn) {
try {
if (rs != null)
rs.close();
if (st != null)
st.close();
if (conn != null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
3.9.4.4 PageInfo
package com.powernode.util;
import java.util.ArrayList;
import java.util.List;
/**
* 此实体类对象中封装的是分页相关数据信息: 分页数据、总记录数、页码等
*/
public class PageInfo<T> {
/**
* 分页数据
*/
private List<T> data = new ArrayList<>();
/**
* 满足sql条件的总记录数
*/
private long total;
/**
* 总页数
*/
private int pages;
public PageInfo() {
}
public PageInfo(List<T> data, long total, int pages) {
this.data = data;
this.total = total;
this.pages = pages;
}
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public int getPages() {
return pages;
}
public void setPages(int pages) {
this.pages = pages;
}
@Override
public String toString() {
return "PageInfo{" +
"data=" + data +
", total=" + total +
", pages=" + pages +
'}';
}
}
3.9.5 数据操作类-dao
3.9.5.1 UserDao
package com.powernode.dao;
import com.powernode.domain.User;
/**
* 用户数据操作类
*/
public class UserDao extends BaseDao{
/**
* 根据用户名和密码查询用户
* @param username
* @param password
* @return
*/
public User login(String username, String password){
String sql = "select id,username,password,realname,img from user where username = ? and password = ?";
User user = super.executeQueryOne(sql, User.class, username, password);
return user;
}
}
3.9.6 数据控制类-servlet
3.9.6.1 UserServlet
package com.powernode.servlet;
import cn.hutool.core.util.ObjectUtil;
import com.powernode.dao.UserDao;
import com.powernode.domain.User;
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("/user.do")
public class UserServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取页面数据
String username = req.getParameter("username");
String password = req.getParameter("password");
UserDao userDao = new UserDao();
User user = userDao.login(username, password);
/*
* 可能user 对象存在 可能不存在
* 存在 说明登录成功 跳转到成功页面
* 不存在是说明 用户名或者密码错误 返回 登录页面
* */
if (ObjectUtil.isNull(user)){
System.err.println("用户名或者密码错误!");
// 跳转到登录页面
resp.sendRedirect("index.html");
return;
}
System.out.println("登录成功!");
resp.sendRedirect("success.html");
}
}
3.9.7 前端页面
3.9.7.1 登录页面-index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="user.do" method="post">
<p>用户名:<input name="username"/></p>
<p>密码:<input name="password"/></p>
<button>登录</button>
</form>
</body>
</html>
3.9.7.2 登录成功页面-success.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录成功!
</body>
</html>