Servlet
Java SE 与Java EE
Java SE(Standard Edition)和 Java EE(Enterprise Edition)是 Java 平台的两个主要版本,它们各自有不同的用途和功能。
Java SE(标准版)
- 定义:Java SE 是 Java 的标准基础版,提供了核心功能和库,用于开发一般的应用程序,如桌面应用和小型工具。
- 主要特性:
- 包含 Java 语言的基本语法和标准库(如集合框架、IO 操作、网络编程等)。
- 适合开发独立的应用程序和一些简单的客户端-服务器应用。
Java EE(企业版)
- 定义:Java EE 是在 Java SE 基础上扩展而来,专为企业级应用开发而设计,提供了一系列用于构建大规模、分布式和可扩展的应用程序的工具和 API。
- 主要特性:
- 包含了额外的 API,如 Servlet、JSP(JavaServer Pages)、EJB(Enterprise JavaBeans)、JPA(Java Persistence API)等。
- 支持多层架构、事务管理、安全性、并发处理等企业级功能。
- 通常用于开发大型的企业应用、Web 应用和服务。
主要区别
- 应用范围:Java SE 主要用于普通应用,而 Java EE 针对企业级的复杂系统。
- 功能组件:Java EE 包含 Java SE 的所有功能,并增加了许多用于企业应用的特殊 API 和服务。
- 架构复杂性:Java SE 应用通常较简单,Java EE 应用结构复杂,支持多层和分布式系统。
简而言之,开发桌面应用或简单的应用程序,Java SE 是合适的选择;目标是构建企业级的分布式应用程序,Java EE 更加适合。
Servlet
Servlet 是 Server Applet 的简称,是用Java编写的,可以运行在 Web 服务器(Tomcat)上的程序,是Sun公司制定的一套规范(接口)。
Servlet的主要用途:
接受、处理来自浏览器端(BS架构中的B端)的请求和用户输入
响应来自数据库或者服务端(BS架构中的S端)产生的数据到浏览器端,动态构建网页。
手动实现Servlet小程序
实现步骤:
自定义一个类型,实现Servlet接口或者继承HttpServlet类
使用javac指令,将源文件编译成字节码文件
将编译完的字节码文件按照一定的组织结构打包,并编写web.xml配置文件
将整个组织结构放入Tomcat的webapps文件夹中
启动Tomcat,在浏览器上输入符合规范的地址去访问Servlet。
第一步:编写一个类LoginServlet,继承HttpServlet类型
package com.youcai;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/*
1 继承HttpServlet或者实现Servlet接口
2 重写service方法
3 从response身上获取输出流
*/
public class LoginServlet extends HttpServlet{
public void service(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
//获取输出流
PrintWriter pw = response.getWriter();
pw.println(" <div style='border: 1px solid red; width: 400px; height: 200px; background-color: aquamarine; margin: 0 auto;'>");
pw.println("<form action=''>");
pw.println("用户名: <input type='text' name='username' id='username'> <br>");
pw.println("密码: <input type='password' name='password' id='password'> <br>");
pw.println(" <input type='submit' value='登录'> <input type='button' value='注册'>");
pw.println("</form>");
pw.println("</div>");
}
}
第二步:编译,生成.class文件
javac LoginServlet.java -classpath xxxxx\xxxxxx\servlet-api.jar
后面的路径为tomcat文件中lib路径
如:javac LoginServlet.java -classpath D:\Program Files\apache-tomcat-9.0.48\lib\servlet-api.jar
第三步:项目的组织结构
appName 项目名称(主要用来区分其他项目的)
-- WEB-INF文件夹
-- classes文件夹: 用来存储class文件的整个路径webapps\firstweb\WEB-INF\classes\com\LoginServlet.java
-- lib(可选文件夹)
-- web.xml: 当前项目的主配置文件
-- index.jsp
-- css
-- js
-- html
比如:
myfirstservlet
-- WEB-INF
--classes
--web.xml
web.xml里的内容如下:
<?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">
<!-- web-app根标记下,可以有很多对servlet和servlet-mapping标记
servlet里与servlet-mapping里的name相同,被认为是一组。
name:随便命名
class: 书写classes目录下的具体类路径,也就是类全名
url-pattern: 寻找指定servlet组件的路径
注意:如果定义了url-pattern,那么浏览器的路径如下:
http://localhost:8080/appName/url-pattern
-->
<servlet>
<servlet-name>a</servlet-name>
<servlet-class>com.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>a</servlet-name>
<url-pattern>/toLogin</url-pattern>
</servlet-mapping>
</web-app>
第四步:将整个文件夹组织结构,拷贝到tomcat的webapps文件夹里。这一步的专业术语叫部署项目
第五步:启动tomcat
找到tomcat的bin目录下的startup.bat可执行文件,启动后,不要关闭。
第六步:在浏览器上输入地址,向tomcat容器发送请求
http://localhost:8080/firstweb/toLogin ,回车后,应该会看到hello world字样
重点:
部署完项目后的执行流程:
1. 浏览器向服务器发送请求,访问服务器上的某一个项目下的某一个资源。
路径如下:http://服务器IP:服务器端口/项目名/资源路径2. 服务器收到请求后,会先确认并找到该项目
3. 如果找到了该项目,会加载里面的web.xml配置文件
4. 然后通过资源路径,去匹配servlet-mapping里的url-pattern,来确定servlet-name。
5. 根据servlet-name,定位到对应的servlet里的servlet-class。
6. 根据servlet-class标签里的类全名,去项目的classes目录下查找对应的类文件
7. 执行类文件里的service方法,将数据最终响应给浏览器
Idea开发Servlet程序
第一步:创建java项目(java module):MyFirstWeb。
第二步:添加web框架支持。 也就是找到add Frameworks Support,选择web application.
帮助我们自动维护web项目的组织结构: web-inf,web.xml
第三步:在WEB-INF下创建lib文件夹,将第三方jar包放入,比如servlet-api.jar文件
注意:别忘记 add as library
第四步:编写Servlet组件
第五步:配置web.xml
第六步:关联tomcat:
edit configuration...
add new ...
tomcat server (注意千万不要用tomEE server)
local:
选择tomcat home
fix 定义项目部署的虚拟名称
apply ok
第七步:启动tomcat 进行测试
常见的HTTP错误代码
404:
Not found,表示您要请求的资源服务器无法帮您找到。原因可能如下:
1. 没有写web.xml配置文件
2. 地址栏上的地址信息写错了,可能没有写部署的项目名称,或者项目名后的资源路径写错误
3. web.xml里的url-pattern对应上了,但是两个name没有对应上。
6:上面都改正确了,但是忘记了重新部署。
405:
Method Not Allowed
servlet组件里没有重写service/doGet/doPost方法,或者是方法写的不标准,比如参数,异常的声明等。
500:
Internal Server Error,表示服务器内部发生错误
1. servlet-class标签里的类名书写有误。
2. 编写的类型不是一个servlet组件,也就是没有继承HttpServlet或者实现Servlet接口
3. Servlet 代码中抛出异常导致的,也就是servlet类里你写的代码有问题
Servlet工作原理
生命周期
servlet容器是如何创建Servlet对象,如何为Servlet对象分配资源,如何调用Servlet对象的方法来处理请求,以及如何销毁Servlet对象的整个过程。而这个过程可以分为四个阶段,分别是实例化阶段、初始化阶段、就绪阶段、销毁阶段。
阶段一:实例化
什么是实例化:
即容器调用Servlet的构造器,创建一个Servlet对象
MySecondServlet s = new MySecondServlet() 【不再是程序员调用 而是服务器帮助我们调用创建】
什么时候开始实例化:
情况一:开始容器里面没有Servlet对象,只有收到请求后才会创建Servlet对象(默认情况)
当浏览器发送请求:http://localhost:8088/employee_v1/listEmp
tomcat容器会收到该请求,该请求是请求的是ListEmp.class这个组件,因此tomcat容器会帮助程序员创建ListEmp这个组件的对象情况二:容器启动之后就立即创建相应的实例,
<servlet>
<servlet-name>addEmp</servlet-name>
<servlet-class>com.shuilidianli.employee.web.AddEmp</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>addEmp</servlet-name>
<url-pattern>/addEmp</url-pattern>
</servlet-mapping>需要添加配置<load-on-startup>1</load-on-startup> 1表示创建,-1表示不创建
1. 谁负责实例化servlet类(也可以叫组件)的对象 : tomcat(servlet容器)
2. 什么时机实例化:
- 浏览器发送请求这个组件时创建
- 添加配置参数load-on-startup, 在tomcat服务器启动时创建。
阶段二:初始化
什么是初始化?
容器在创建好Servlet对象后,会立即调用该对象的init方法
一般情况下,我们不用写init方法,因为genericServlet已经提供了init方法的实现(将容器传递过来的ServletConfig对象保存下来,并且,提供了getServletConfig方法来获取ServletConfig对象)。
init方法只会执行一次
默认情况下,init方法不需要重写。但是有的时候,如果你有这方面的需求时,比如将公司信息作为初始化参数,用于init方法来到程序中,就可以重写init进行获取。
Servlet的初始化参数如何配置,在web.xml中添加如下形式:
<init-param>
<param-name>school</param-name>
<param-value>吉林大学</param-value>
</init-param>注意:init-param的位置必须放在load-on-startup前面
如何读取Servlet的初始化参数
String ServletConfig.getInitParameter("school")
总结:
servlet容器在实例化servlet对象后,会自动调用init方法进行初始化操作。
程序员可以重写init方法。 使用ServletConfig对象来读取初始化参数。
阶段三:就绪
容器收到请求后调用Servlet对象的service()来处理请求
阶段四:销毁
-
容器依据自身算法删除Servlet对象,删除前会调用destroy()
-
只会执行一次
-
可以override destroy方法来实现自己的处理逻辑
-
应用程序卸载时一定会调用destroy方法
演示一下:
项目名:_01firstweb
下文件夹:
-src:
--com:
---life:
FirstTermServlet.java
SecondTermServlet.java
---servlet:
LoginServlet.java
-web:
--WEB-INF:
---lib:
servlet-api.jar
---web.xml
--index.jsp
01firstweb
FirstTermServlet.java:
package com.life; import javax.servlet.*; import java.io.IOException; /** * 时机1: 不配置load-on-startup, 第一次请求到服务器时,服务器才帮助创建对象 * 时机2: 在服务器启动过程中,就创建该Servlet组件的对象 需要配置load-on-startup参数 */ public class FirstTermServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("---FirstTermServlet的对象已经创建了---"); } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("---调用了service方法,执行了相应的代码逻辑---"); } @Override public String getServletInfo() { return ""; } @Override public void destroy() { } }
SecondTermServlet.java:
package com.life; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * Servlet对象的生命周期的第二个阶段: 初始化 * 1. 实例化阶段结束后,立即执行第二个阶段(初始化) * 2. 第二个阶段:就是调用init方法,进行初始化操作,比如给形参赋值,给属性赋值等 * 3. init方法正常情况下不需要我们重写。 * 4. 但是有需求的时候,可以重写。 */ public class SecondTermServlet extends HttpServlet { private String company; private String address; /** * 当有需求时,比如读取web.xml中的init-param里的数据时。 * @param config 配置参数对象 * @throws ServletException */ @Override public void init(ServletConfig config) throws ServletException { company = config.getInitParameter("company"); address = config.getInitParameter("address"); } // 第三阶段: 就是tomat容器调用service方法来执行里面的代码。 @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("---调用了service方法---"); System.out.println("company:"+company); System.out.println("address:"+address); } /* 第四阶段: 销毁阶段,即不再使用该对象了,为了节省内存开销,应该将该对象销毁掉,来释放内存 在发生第四阶段时,会调用destory方法 第四阶段发生的时机: 1. 程序在卸载时,该程序所产生的Servlet对象都会被销毁,销毁前调用destory方法 2. servet容器会根据自己的算法,来销毁Servlet对象 */ @Override public void destroy() { System.out.println("---对象销毁前,调用了该方法---"); } }
web.xml:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>xiaohei</servlet-name> <servlet-class>com.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>xiaohei</servlet-name> <url-pattern>/toLoginPage</url-pattern> </servlet-mapping> <!-- 研究声明周期的第一个阶段: 实例化阶段 --> <servlet> <servlet-name>term1</servlet-name> <servlet-class>com.life.FirstTermServlet</servlet-class> <!-- 时机1: 不配置load-on-startup, 第一次请求到服务器时,服务器才帮助创建对象 时机2: 在服务器启动过程中,就创建该Servlet组件的对象 需要配置load-on-startup参数 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>term1</servlet-name> <url-pattern>/life1</url-pattern> </servlet-mapping> <!-- 研究生命周期的第二个阶段: 初始化阶段 --> <servlet> <servlet-name>term2</servlet-name> <servlet-class>com.life.SecondTermServlet</servlet-class> <init-param> <param-name>company</param-name> <param-value>youcai</param-value> </init-param> <init-param> <param-name>address</param-name> <param-value>http://www.baidu.com</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>term2</servlet-name> <url-pattern>/life2</url-pattern> </servlet-mapping> </web-app>
请求方式
不同的请求方式在数据传输时,会有所不同;在表单提交以及服务器处理时都会采用不同的方式。浏览器针对每种请求方式也会使用不同的缓存技术,提高相应的处理速度。
常用的请求方式有:
get: 请求服务器上指定的资源
post: 向服务器上的资源提交数据
head: 和get相似
put: 上传资源
delete: 删除资源
获得请求方式:
System.out.println("请求方式:"+req.getMethod());
get请求:
浏览器发送get请求的场景:
1. 在地址栏单纯的输入一个地址数据通过 URL 的查询字符串传递,形式为 ?key1=value1&key2=value2
2. a标记产生的请求
3. form表单默认提交方式,也是get请求注意:
1. get请求,如果带有一些提交数据,比如用户名,密码等。那么这些数据会被添加到地址栏中,不安全。
2. get请求提交的数据量比较小,在4KB以内。
post请求:
浏览器发送post请求的场景:
1. form表单的method属性的值为post时。注意:
要传输给服务器的处理数据都会在正文(http信息数据的body)中存储。相对安全
注意:
如果想要提交form表单,两个属性要知道:
1. method属性,用于指定请求方式,默认不写是get请求
2. action属性,是必填项,因为action的值就是地址栏上的url-pattern.两者应用场景:
GET 请求:
获取数据(如网页、图片),适合用于搜索和数据查询。
示例:GET /api/users?id=123。
POST 请求:
提交数据(如注册、登录表单),上传文件或更新资源。
示例:POST /api/users,在请求体中发送用户数据。
service/doGst/doPost
实际上:当浏览器发送请求后,会默认执行servlet组件继承过来的service方法,然后service方法里的逻辑:String method = req.getMethod(); 会获取请求的方法,
如果是get请求,会执行doGet(req, resp); 如果是post请求,会执行doPost(req, resp);
程序员在写servlet时,可以选择覆盖service方法,也可以选择覆盖doGet() 或者是doPost方法。前提如果是get请求,可以直接覆盖doGet(), 如果是post请求,可以直接覆盖doPost()
注意:如果重写了service方法,就不会再执行doGet/doPost方法,除非重写的service方法里调用了doGet/doPost
web下Servlet代码:
/** * service,doGet,doPost方法的区别。 * 1. 三个方法必须重写其中一个,get请求时,重新doGet或者service. post请求时,重新doPost或者service * 2. servlet请求到达容器时,容器一定会调用service方法,如果没有重写该方法,会调用父类的service * 父类service方法中的逻辑会根据请求方式来判断调用对应的doGet/doPost方法 * 3. 重写了service方法, 一定执行重写的service, 则不会再调用doGet/doPost方法,除非重写逻辑中主动调用doGet/doPost * */ public class ThreeMethodServlet extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("---走进了post方法---"); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("---service---"); // if(req.getMethod().equals("get")){ // doGet(req,resp); // }else{ // doPost(req,resp); // } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("---走进了get方法---"); } }
web.xml下加入代码:
<servlet> <servlet-name>three</servlet-name> <servlet-class>com.method.ThreeMethodServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>three</servlet-name> <url-pattern>/test</url-pattern> </servlet-mapping>
html代码:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>登录页面</title> </head> <body> <form action="character" method="get"> 姓名: <input type="text" name="username" id="username"> <br> 密码: <input type="password" name="password" id="password"> <br> <input type="submit" value="登录"> </form> </body> </html>
请求对象:
在Servlet的生命周期的第一个阶段(实例化阶段),Servlet容器也会帮助我们创建另外两个对象,分别是HttpServletRequest类型的一个对象,称之为请求对象; HttpServletResponse类型的一个对象,称之为响应对象。
这两个对象的地址,都会被传入到service/doGet/doPost方法的形参上。
web服务器(tomcat容器)在接收到浏览器发送过来的请求后,发送过来的所有通信数据都会被web服务器进行封装和提供。这些信息主要被封装到这两个对象上。
HttpServletRequest类型的对象:
浏览器发送的所有数据都会被web服务器封装到HttpServletRequest类型的对象上,此类型提供了相应的方法可以从对象上获取相关的数据。
请求数据:三个部分
第一部分: 请求行, 包括请求路径,请求方式,以及状态码等 对应的是NetWork中的general
第二部分: 消息头, 就是浏览器设置的各种键值对信息,对应的是NetWork中的headers
第三部分: 消息体, 就是浏览器使用post请求时的具体传输数据,对应的是NetWork中Payload作用:
读取和设置http的请求数据
取得和设置cookies
实现请求转发
取得路径信息
标识HTTP会话
参数值的获取
不管是get请求提交的少量数据,还是post请求提交的大量的数据。那么服务器端就要接受到这些数据,并处理。
浏览器发送的数据有以下两种情况:
1:1的情况:
localhost:8088/day42_servlet/web?username=zhangsan&password=123456
上述的地址,?后面是要传入到服务端的数据,username对应一个值,password对应一个值
1:N的情况:
localhost:8088/day42_servlet/web?username=zhangsan&password=123456&hobby=book&hobby=movie&gender=f
上述的地址,?后面是要传入到服务端的数据,username对应一个值,password对应一个值,gender对应一个值
而hobby对应两个值,hobby与它的值就是1:N的情况
服务端的请求对象提供了相应的获取参数值的方法:
方法1:String getParameter() :
解析:获取的是1对1的参数值, 比如用户名或密码
用法: String value = request.getParameter("key")
注意: 如果没有获取值,那么返回的是null,而不是异常
方法2:String[] getParameterValues():
解析:获取的是1对多的参数值, 比如 爱好
用法:String[] values = request.getParameter("key")
注意:如果没有获取值,那么返回的是null,而不是异常参数:浏览器封装的参数是以键值对的形式体现的,比如username=zhangsan. username是参数名,zhangsan是参数值。
注意:input元素的name属性用于指定参数名
public class RequestObjectServlet extends HttpServlet { /** * * @param request 请求对象。 * @param response * @throws ServletException * @throws IOException */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //常用方法 System.out.println("请求方法:"+ request.getMethod()); System.out.println("URI:"+request.getRequestURI()); System.out.println("上下文路径:"+request.getContextPath()); System.out.println("路径信息:"+request.getPathInfo()); System.out.println("servlet路径:"+request.getServletPath()); System.out.println("字符编码:"+request.getCharacterEncoding()); System.out.println("内容类型:"+request.getContentType()); //获取所有的消息头的name Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()){ String name = headerNames.nextElement(); String value = request.getHeader(name); System.out.println(name+"="+value); } //获取用户提交的数据: 比如 用户提交了 username=zhangsan 这个控件信息, 该情况属于1:1 //注意: 该方法只能处理1:1的情况,形参是String类型,返回值也是String类型 String username = request.getParameter("username"); System.out.println("username:"+username); int age1 = Integer.parseInt(request.getParameter("age1")); System.out.println("age1:"+age1); String pwd = request.getParameter("pwd"); System.out.println("pwd:"+pwd); String gender = request.getParameter("gender"); System.out.println("gender:"+gender); String abc = request.getParameter("abc"); System.out.println("abc:"+abc); //获取用户提交的数据: 比如 用户提交了 hobbies = book & hobbies = music 该情况属于1:N //注意:该方法是处理1:N的情况,形参是String类型,返回值也是String数组类型 String[] hobbies = request.getParameterValues("hobbies"); System.out.println(Arrays.toString(hobbies)); } }
响应对象:
一个是HttpServletResponse类型的对象
响应对象
1. 响应对象用来封装和存储想要向浏览器反馈的数据(三部分:响应行,消息头,消息体)
2. 该类也提供了相应的方法来设置数据
响应数据:三个部分
1. 第一部分: 响应行, 包括HTTP版本号,状态码(200)以及状态消息(ok)等
2. 第二部分: 消息头, 就是向浏览器传输的键值对信息,比如字符集(charset=utf-8),数据类型 content-type=text/html
3. 第三部分: 实体内容, 就是服务端想要向浏览器发送的具体传输数据,比如html文档,部分代码,xml文档,jsp文档等待
1.web服务器会对浏览器进行响应,响应的所有数据都会被封装到HttpServletResponse类型的对象上。此类型提供了相关设置方法,可以设置状态行、消息头、实体内容等。
2.作用:
设置客户端的输出内容
设置响应状态码
设置浏览器的解码方式
设置cookies
实现重定向
public class ResponseObjectServlet extends HttpServlet { /** * @param request 请求对象。 * @param response 响应对象 * @throws ServletException * @throws IOException */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置传入的数据类型 response.setContentType("text/htmlsssssss"); //设置字符集 // response.setCharacterEncoding("utf-8"); //设置状态码 response.setStatus(100000); //想要向浏览器传入的具体数据,应该使用流来写出 response.getWriter().println("飞流直线三千尺"); } }
中文乱码问题
乱码一般出现在两处:
第一处: 后端响应浏览器时: response向浏览器返回中文时出现乱码
response.getWriter().println("你好,Servlet");
第二处: post请求时,浏览器向后端发送了中文时出现了乱码
String username = request.getParameter("username"); username=张三 是汉字时
乱码的情况原因很简单,就是编码过程使用的字符集与解码过程使用的字符集不一致造成的,不是逻辑问题。
编码:字符串变成字节数组的形式 "中国"-->[-86,-92,-78,-12,-64,-23] 使用字符手册 UTF8 三个字节对应一个汉字
解码:字节数组变成字符串的形式 [-86,-92,-78,-12,-64,-23]-->"中国" 使用字符手册uncode 两个字节对应一个汉字
浏览器端使用post请求方式提交中文时:
1. 确保浏览器的html页面是utf-8编码集
2. 在servlet中(也就是服务端)的service方法的第一行处,使用
request.setCharacterEncoding("utf-8");即可
浏览器使用get请求方式提交中文时:
1. 确保浏览器的html页面是utf-8编码集
2. 因为服务端默认使用iso-8859-1进行解码。我们的解决方式就是在服务端再次使用iso-8859-1进行编码,然后在使用utf-8进行解码。
String username = request.getParameter("username") // 获取乱码(iso-8859-1解出来的)
byte[] bs = username.getBytes("iso-8859-1") ; //把乱码用iso-8859-1编码
//借助string的构造器,重新解码即可
String str = new String(bs,"utf-8");// 用utf-8解码
服务端向浏览器发送中文乱码情况:
只需要在服务端对中文进行编码前,写入这一行代码即可:
response.setContentType("text/html;charset=utf8");
Servlet调用DAO(整体流程)
第一步:引入第三方资源,比如相关依赖的jar包坐标, mysql驱动,druid数据源
第二步:在resources配置druid.properties
第三步:编写DruidUtil工具类,并测试(里面提供main方法)
第四步:根据ORM关系模型,编写emp表的实体类Emp
第五步:编写相关的Dao接口以及Dao接口的实现类
第六步:编写EmpServlet组件
第七步: 编写web.xml
第八步:File–>Project structure–>Artifacts–>Available Elements–>右键点击项目–>put into output root
第九步:重新部署项目(启动tomcat) 在浏览器上输入地址进行测试
总结servlet运行流程
1: 浏览器发送地址请求
2: 根据ip和port号定位到tomcat容器
3: tomcat容器根据appName确定项目
4: 然后tomcat查找当前项目下的web.xml配置信息,用url-pattern与地址信息进行匹配
5: 根据两个相同的name找到对应的servlet组件
6: 执行servlet组件里的service方法(执行的是classes里的class文件)
7: 向浏览器发送响应信息。
8:浏览器收到响应信息后会进行解析和显示。
Servlet对路径的处理
重定向
用户在浏览器输入一个地址,或者点击按钮触发一个请求地址,然后去往服务器端,此时服务端处理完相关计算后,再次向浏览器发送一个302状态和一个新的地址(比如www.baidu.com). 之后浏览器收到了302状态码和一个新的地址,然后浏览器就重新向这个新的地址发送请求。
302: 表示重定向, 当浏览器收到的状态码是302,表示需要重新发送请求,请求地址是和302一起过来的那个新的地址。
适合场景:
用户使用浏览器触发了一次请求后,然后服务端向浏览器发送一个新的地址,由浏览器自动再次发送一个请求。
比如:添加一个新员工后,然后页面自动显示所有员工信息,包含两次请求
第一次:insert, 用户主动触发的
第二次: select, 是服务端告诉浏览器再次触发
再比如: 登录成功后,跳转到主页面(所有员工信息) :
第一次: 登录验证(LoginServlet)
-- 调用DAO的checkLogin方法,返回true或者false
-- 如果登录成功,此时应该向浏览器发送响应数据(302,新地址appName/findAll)
第二次: 跳转到主页面(FindAllServlet)
-- 调用DAO的findAll方法,查询所有的员工信息,返回ResulSet
-- 遍历ResultSet ,然后使用流对象的println方法,拼接成一个table。
-- 响应对象将数据打包,返回给浏览器,浏览器解析成一个table
如何重定向
response.sendRedirect(String url)
url: 是重定向的新地址, 该地址可以是任意地址, 302状态码是响应对象自己设置的,不需要程序员设置
public class RedirectServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("----伪代码:处理了很多逻辑--"); //有一个新的需求:告诉浏览器向百度地址发送请求, // 只需要调用sendRedirect方法书写新地址即可,302状态在该方法内部已经绑定到了响应对象上。 // 不需要程序员来写 resp.sendRedirect("findAll"); resp.getWriter().close(); } }
重定向特点
1. 重定向的地址可以是任何地址
2. 重定向后,浏览器的地址发生了变化
3. 重定向所涉及到的web组件并不共享同一个request和response
4. 重定向前,不可以关闭流 ,即不能调用 pw.close()
转发
一个web组件(servlet/jsp)将未完成的任务转交给另外一个web(servlet/jsp)组件。
如何转发
步骤1: 绑定数据
request.setAttribute("school","shuilidianli")
步骤2: 获取转发器,同时指定目的地的地址
RequestDispatcher rd = request.getRequestDispatcher("servlet03");
步骤3: 开始转发,带上request和response对象
rd.forward(request,response);
转出:
public class DispatcherServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //比如当前的servlet组件的任务就是获取浏览器上发送过来的数字,然后乘以2,结果的平方这个运算不想做 //交给下一个servlet组件来做。 int number = Integer.parseInt(req.getParameter("number")); int a = number*2; //将a的平方这个任务交给下一个组件完成 req.setAttribute("number1",a); // 将a以键值对 number1=a的形式绑定到请求对象上 //获取转发器对象,同时指定下一个组件的访问地址 RequestDispatcher rd = req.getRequestDispatcher("dispatcher2"); //使用转发器的forward()方法进行转发,同时带上请求对象和响应对象 rd.forward(req,resp); } }
接收:
public class Dispatcher2Servlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int number1 = (Integer) req.getAttribute("number1"); System.out.println("number1的平方:"+number1*number1); resp.getWriter().println("整个流程结束"); } }
转发特点
转发是服务器的行为
浏览器在这个过程中只有一次行为,路径不会发生任何的变化
转发可以绑定数据到request对象中
转发只能在当前项目中进行转发,不能转发外部资源
员工管理系统版本1.0
第一步:创建java的项目(注意,是module) employee_v1
第二步:添加web项目支持
第三步:引入相关jar包,放入lib下
mysql-xxx
druid-xxx
servlet-api.jar
dbutils-xxx.jar
第四步:定义项目的源码组织结构
com.employee.util
DruidUtil
EmpDaoFactory
com.employee.vo
Emp
com.employee.dao
EmpDao
com.employee.dao.impl
EmpDaoImpl
com.employee.web
AddEmpServlet
ToUpdateServlet(查询,拼接form表单)
UpdateServlet(提交修改的内容)
FindAllServlet(拼接table表格)
DelEmpServlet
DetailServlet(拼接form表单)
com.employee.test
第五步:编写DruidUtil连接池工具类
第六步:编写emp表的实体类Employee类型
第七步:编写emp表的Dao接口:EmployeeDao
public interface EmpDao {
void addEmp(Emp emp);
void delEmpById(int id);
void modEmp(Emp emp);
Emp findById(int id);
ArrayList<Emp> findAll();
ArrayList<Emp> findByPage(int page,int pageSize);
}
第八步:编写Dao接口的实现类:EmployeeDaoImpl
查询所有信息实现
请求地址: https://localhost:8080/employee_v1/findAll
1)编写员工列表的Servlet组件: FindAllServlet
2)编写web.xml
3)启动tomcat,进行测试。
添加功能实现
1)在web目录下,编写一个addEmp.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>添加员工信息页面</title> </head> <body> <div> <form action="addEmp" method="post"> 员工编号:<input type="text" name="empno" placeholder="员工编号"> <br> 员工姓名:<input type="text" name="ename" placeholder="员工姓名"> <br> 员工职位:<input type="text" name="job" placeholder="员工职位"> <br> 领导编号:<input type="text" name="mgr" placeholder="领导编号"> <br> 入职日期:<input type="text" name="hiredate" placeholder="入职日期"> <br> 员工工资:<input type="text" name="salary" placeholder="员工工资"> <br> 员工奖金:<input type="text" name="bonus" placeholder="员工奖金"> <br> 部门编号:<input type="text" name="deptno" placeholder="部门编号"> <br> <input type="submit" value="提交"> </form> </div> </body> </html>
2)编写添加员工的组件AddEmpServlet
3)修改web.xml,新添加
4)重新部署tomcat,进行测试
- 先访问添加员工的HTMl页面。https://localhost:8088/employee_v1/addEmp.html
- 然后添加数据,点击提交
删除功能实现
1)修改员工列表界面:添加删除按钮
2)修改web.xml,新添加一对配置信息
3)创建对应的组件 DelEmpServlet
4)部署tomcat,进行测试
请求地址 https://localhost:8080/employee_v1/delEmp?empno=xxxx
修改功能实现
逻辑应该经过一个查询组件,进行显示该员工原有的信息,然后在该页面中,进行修改部分值,最后点击提交,保存到数据库。
第一部分:编写跳转到修改页面的逻辑。
1)修改员工列表界面:添加去修改按钮
2)编写web.xml,新添加一对配置
2)编写ToUpdateEmp组件:获取浏览器传输过来的员工id,从数据库中查询出来,显示到一个form表单中,在浏览器中显示
3)启动tomcat,去测试
第二部分:编写在修改页面上修改信息,点击提交的逻辑
4)编写web.xml,新添加一对配置
5)编写对应的UpdateEmp组件
6)启动tomcat,去测试
处理请求资源路径
在浏览器地址栏中输入的地址格式如下:
https://ip:port/appName/xxx.html?..... addEmp.html
http://localhost:8088/day44_servlet/findAll findAllServlet
https://ip:port/ 虚拟资源 / 具体资源
解析:
ip: 服务器所在IP地址
port: 服务器的端口号
appName: 要访问的项目名称/appName/xxx.html?..... 就是请求资源路径,也就是getRequestURI()方法的返回值。
处理请求资源路径的过程
当浏览器地址栏上输入https://ip:port/appName/xxx.html时
-
浏览器依据ip和port,与servlet容器建立连接,然后将后面的请求资源路径发送给servlet容器
-
servlet容器依据应用名/appName找到应用所在的文件夹,容器会默认请求的是一个servlet。
-
开始查找web.xml文件中的所有的servlet配置<url-pattern> ,查看是否有匹配的请求资源路径,如果有,就定位到对应的servlet组件
匹配方式以下有三种:
精确匹配:
通过将请求资源路径中的具体资源名称与web.xml文件中的url-pattern进行对比,严格匹配相等后找到对应资源并执行。 如
<url-pattern>/abc.html<url-pattern>
此时,尽管应用中有abc.html这个具体的页面,也会去执行该url-pattern对应的servlet组件,而不是返回这个具体的abc.html
reg:
<url-pattern>/abc.html<url-pattern>
/appName/abc :匹配失败
/appName/abc/abc:匹配失败
/appName/abc.html/abc:匹配失败
/appName/abc.html:匹配成功
通配符匹配
使用“*”号来匹配0个或者多个字符,如下写法
<url-pattern>/*<url-pattern>
代表输入任何不同的url地址都将匹配成功,比如
http://ip:port/appName/abc.html
http://ip:port/appName/abc/def/ghi 这些地址都会匹配成功
web.xml中/*只能书写一个通配符匹配 前面可以添加前缀
<url-pattern>/dept/*<url-pattern>
<url-pattern>/account/*<url-pattern>
<url-pattern>/sales/*<url-pattern>
后缀匹配
不能使用斜杠开头,要使用*. 开头的任意多个字符,如
<url-pattern>*.do<url-pattern>
会匹配以.do结尾的请求,以下的地址都能匹配成功
http://ip:port/appName/abc.do
http://ip:port/appName/abc/def/gh/abc.do
以什么结尾,自行决定,有意义即可。比如
<url-pattern>*.school<url-pattern>
<url-pattern>*.company<url-pattern>
无匹配的Servlet的处理
如果上述三者匹配都没有匹配成功时,容器就会查找相应的文件
-
查找到对应的文件,则返回(比如web项目下的其他html文件)
-
找不到对应的文件,返回404
优先级
与书写顺序无关 全依靠优先级
精确匹配> 通配符匹配 > 后缀匹配
共存问题
在web.xml文件中可以有精确匹配,也可以有通配符匹配
在web.xml文件中可以有精确匹配,也可以有后缀匹配
在web.xml文件中通配符匹配和后缀匹配可以共存的,但是通配符已经可以拦截所有的后缀匹配了,因此后缀匹配无效。所以写与不写都一样了。
Servlet合并
一般情况下,Servlet的主要作用是充当控制器的角色,即接受请求后,分发给不同的资源,这时Servlet只需要有一个就可以完成分发的过程,所以需要将Servlet合并。
实现合并的步骤如下:
第一步:使用后缀匹配模式,修改web.xml文件
-
将配置的多个相关的Servlet节点删除
-
保留一对servlet、servlet-mapping
-
修改url-pattern节点的内容为: *.emp
第二步:获取请求资源路径,分析具体请求资源后,依据分析结果调用不同的分支处理代码
-
调用request.getRequestURI()方法获取请求资源路径
-
分析对应资源后分发
String uri = request.getRequestURI();
if(uri.contains("/appName/findAll.emp")){
//......
}else if(uri.contains("/appName/addEmp.emp")){
//....
}else if(...){
//....
}
员工管理系统版本2.0
1)创建module名 employee_v2
2)添加web支持,即 add framework support
3)创建lib文件夹,将jar拷贝过来
4))将com包拷贝到新项目下、以及druid.properties拷贝过来
5))将web下的addEmp.html拷贝过来
6)修改web.xml
<servlet> <servlet-name>web</servlet-name> <servlet-class>com.shuilidianli.employee.web.ActionServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>web</servlet-name> <!-- 后缀匹配,千万不要写斜杠--> <url-pattern>*.emp</url-pattern> </servlet-mapping>
7)将五个Servlet组件合成一个ActionServlet
注意事项:
修改所有请求资源路径位置处的内容。添加.emp
1: addEmp.html--->action="addEmp.emp"
2: 修改和删除按钮处的a链接,要添加.action
3: 三处重定向也要改
4: 修改页面的action的值也要改
测试:
localhost:8088/employee_v2/listEmp.action
状态管理
为什么需要状态管理
HTTP协议本身是无状态的。这意味着每个HTTP请求都是独立的,服务器不会自动关联来自同一用户的多个请求。
服务器端有时需要判断是否为同一个客户发送的请求。比如客户的多次选购商品。因此有必要跟踪同一个客户发送的多次请求。
什么是状态管理
有的时候,需要将浏览器与服务器之间的多次交互(请求和响应)看成一个整体(同一个用户的多次请求),并将多次交互时所涉及的数据(即状态)保存下来,提供给后续的交互进行数据的管理即状态管理。
状态就是数据
管理指的是在这个多次交互的过程中对数据的存储、修改、删除。
状态管理的两种常见模式
-
Cookies模式
如果将数据存储在客户端,每次向服务器端发请求时都将存在客户端的数据随着请求发送到服务器端,修改后在发回到客户端保存的这种模式叫做Cookie。
-
Session模式
如果将数据存储在服务器端,并且为这组数据标示一个编号,只将编号发回给客户端。当客户端向服务器发送请求时只需要将这个编号发过来,服务器端按照这个编号找到对应的数据进行管理的这种模式叫做Session。
Cookies状态管理
什么是cookies
当浏览器向服务器发送请求时,服务器收到请求后,会将少量的数据以set-Cookies消息头的方式绑定到response对象上,然后发送浏览器,浏览器会将这些数据保存下来
当浏览器再次向该服务器发送请求时,会将这些保存的数据以cookies消息头的形式发送给服务端。
如何创建Cookie
Servlet API提供了javax.servlet.http.Cookie这种类型。 其中存储的文本以name-value对的形式进行区分,所以创建Cookie时指定name-value对即可。这个name-value最终是以Set-Cookie这种消息头的形式跟随相应数据包到达客户端,所以要想将数据添加到消息头中需要使用response对象提供的方法。
创建Cookie的代码如下所示:
Cookie c = new Cookie(String name,String value);
response.addCookie( c );
-
name:用于区分不同Cookie的名字
-
value:Cookie的值
如何查询Cookie
当客户端向服务器发出请求时,服务器端可以尝试着从请求数据包的消息头中获取是否携带了Cookie信息。实现这一功能的代码如下:
Cookie[] request.getCookies();
注意:如果没有Cookie信息, 返回null.
由于客户端是可以存放多个Cookie的,所以request提供的获取Cookie的方法的返回值是Cookie数组,如果想进一步获取某一个Cookie信息可以通过遍历数组,分别使用相应的getXXX方法来获取每一个Cookie的name和value。
获取一个Cookie对象的名称或值
String getName();
String getValue();
如何修改Cookie
所谓Cookie的修改,本质是获取到要变更值的Cookie,通过setValue方法将新的数据存入到cookie中,然后由response响应对象发回到客户端,对原有旧值覆盖后即实现了修改。
主要实现代码:
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for(Cookie c : cookies){
String cookieName = c.getName();
if(name.equals(“uname”)){
c.setValue(“Mark”);
}
}
其中response.addCookie(c)是非常重要的语句,如果没有这一行代码,那么就算是使用setValue方法修改了Cookie的值,但是不发回到客户端的话,也不会实现数值的改变。所以只要见到response.addCookie这行代码,即服务器端发回了带有Set-Cookie消息头的信息。
Cookie的过期时间
默认情况下,cookie保存到浏览器端的内存中,只要不关闭浏览器,cookie就一直在。只要浏览器已关闭,就消失。 如果想要在关闭浏览器后,还想要将cookie保存一段时间,就可以通过设置cookie过期时间来达到目的。
设置Cookie的过期时间使用如下代码:
void setMaxAge(int seconds);
该方法是Cookie提供的实例方法。参数seconds的单位为秒,但精度不是很高。
seconds > 0 :代表Cookie保存在硬盘上的时长
seconds = 0 : 代表Cookie的生命时长为现在,而这一刻稍纵即逝,所以马上Cookie就等同于过了生存时间,所以会被立即删除。这也是删除Cookie的实现方式。
seconds < 0 :缺省值,浏览器会将Cookie保存在内存中。
以下代码实现了Cookie保存在硬盘上40秒:
import java.io.IOException; import java.io.PrintWriter; import java.net.URLEncoder; import javax.servlet.*; import javax.servlet.*; public class AddCookieServlet extends HttpServlet { public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType( "text/html;charset=utf-8"); PrintWriter out = response.getWriter(); //创建cookie Cookie c = new Cookie("username","Lisa"); Cookie c2 = new Cookie("city","NewYork"); response.addCookie(c); response.addCookie(c2); out.close(); } }
Cookie的路径问题
1)客户端如何保存cookie的路径
客户端存储Cookie之后,并不是针对同一个应用访问任何资源时都自动发送Cookie到服务器端,而是会进行路径的判断。只有符合路径规范的请求才会发送Cookie到服务器端。
客户端在接受Cookie时会为该Cookie记录一个默认路径,这个路径记录的是添加这个Cookie的Web组件的路径。
如,当客户端向 http://localhost:8080/test/file/addCookie.jsp
发送请求时创建了cookie,那么该cookie的路径就是 /test/file.
2)客户端什么时候发送Cookie
只有当访问的地址是Cookie的路径或者其子路径时,浏览器才发送Cookie到服务器端。
如,Cookie的路径是 /test/file,那么如果访问的是 /test/file/a.jsp 或者 /test/file/b/c.jsp时,都会发送Cookie。
如果访问的是 /test/d.jsp,则浏览器不会发送Cookie。
3) 如何设置Cookie的路径
设置Cookie的路径可以使用Cookie的API方法,setPath(String uri)
;
如以下代码就实现了设置Cookie的路径为应用的顶级目录,这样所有资源路径要么与此路径相等,要么是子路径,从而实现了客户端发送任何请求时都会发送Cookie。
Cookie c = new Cookie(“uname”,“jack”); c.setPath(“/appName”); response.addCookie(c); cookie.setPath("/appName/account") appName/acount/add appName/acount/del appName/acount/update appName/manager/add appName/manager/del appName/manager/update appName/program/add appName/program/del appName/program/update
Cookie的限制
--cookie可以被用户禁止
--cookie会将状态数据保存到浏览器端,不安全,
--cookie只能保存少量数据,大约在4kb左右
--cookie的个数有限制
--cookie只能保存字符串