言成言成啊 | Kit Chen's Blog

Servlet

发布于2020-06-11 23:11:31,更新于2020-07-06 23:05:22,标签:java web  文章会持续修订,转载请注明来源地址:https://meethigher.top/blog

先附上JavaEE官方文档

之前课堂上学过jsp,jsp的每次修改之后不需要重启服务器,而Servlet修改一次需要重启一次。

servlet和jsp本质都是servlet,运行时都是运行.class文件。但是它们的部署方式不一样。

servlet是先编译后部署,修改完以后,IDE进行编译,然后部署.class文件到servlet容器中。如果web服务器已启动,则之前class已被servlet容器加载,可能修改后的class文件不会被servlet容器执行。

而jsp是web服务器进行编译。tomcat可以设置为监视jsp文件的改动,改动之后则重新编译、执行。

一、入门Servlet

概念:运行在服务器端的小程序

本质:Servlet就是一个接口,定义了java类被浏览器访问到(Tomcat识别到)的规则。

1.1 快速入门

  1. 创建JavaEE项目
  2. 创建类,实现Servlet接口
  3. 实现接口中的抽象方法
  4. 配置Servlet

配置Servlet,修改WEB-INF下的web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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-->
<servlet>
<servlet-name>demo01</servlet-name>
<servlet-class>demo01_servlet.Demo01Servlet</servlet-class>
</servlet>
<!--Servlet映射-->
<servlet-mapping>
<servlet-name>demo01</servlet-name>
<!-- 访问路径,过滤器中这个地方是拦截路径 -->
<url-pattern>/demo01</url-pattern>
</servlet-mapping>
</web-app>

Demo01Servlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Servlet快速入门
* @author https://github.com/meethigher
*/
public class Demo01Servlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {

}

@Override
public ServletConfig getServletConfig() {
return null;
}

/**
* 提供服务的方法
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello Servlet");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}

访问localhost:8080/demo01,然后控制台就会输出Hello Servlet

之前一直在用eclipse,最近改用idea,不得不说,真香!

二、详解Servlet

2.1 执行原理

浏览器访问localhost:8080/demo01,tomcat从web.xml找资源路径/demo01,通过url找到配对的servlet-name,然后找到类文件(看到全类名,首先应该想到反射)

tomcat将全类名对应的字节码文件加载进内存Class.forName,创建对象class.newInstance,调用方法service,这些过程都是在服务器完成的。

总结

  1. 当服务器接收到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
  2. 查找web.xml,是否有对应的<url-pattern>便签体内容
  3. 如果有,则再找到对应的<servlet-class>找到全类名,Tomcat将字节码文件加载进内存,并创建其对象
  4. 调用方法

2.2 生命周期

  1. 被创建:执行init方法,只执行一次。
    • 默认情况下,第一次访问时,Servlet被创建
    • 可以指定Servlet启动的时机为启动服务器时,Servlet被创建,一般用于加载资源
  2. 提供服务:执行service方法,执行多次
    • 每次访问Servlet时,service都会被调用一次
  3. 被销毁:执行destroy方法(服务器正常关闭时),只执行一次
    • Servlet被销毁时执行。如果服务器不是正常关闭,不会执行。destroy可以理解为“临终遗言”,在Servlet销毁之前执行。一般用于释放资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Demo02Servlet implements Servlet {
/**
* 初始化方法
* 在Servlet被创建时执行。只会执行一次
* @param servletConfig
* @throws ServletException
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init...");
}

/**
* 获取Servlet对象
* @return
*/
@Override
public ServletConfig getServletConfig() {
return null;
}

/**
* 提供服务的方法
* 每一次Servlet被访问,都会被执行。执行多次
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service...");
}

/**
* 获取Servlet的一些信息,版本、作者等等
* @return
*/
@Override
public String getServletInfo() {
return null;
}

/**
* 销毁方法
* 在服务器正常关闭时,执行一次
*/
@Override
public void destroy() {
System.out.println("destory...");
}
}

配置Servlet启动的时机

  1. 第一次被访问时创建。<load-on-startup>为负数,一般为-1
  2. 在服务器启动时创建。<load-on-startup>为0或正整数,一般为0-10
1
2
3
4
5
6
7
8
9
10
<!--配置Servlet-->
<servlet>
<servlet-name>demo02</servlet-name>
<servlet-class>demo01_servlet.Demo02Servlet</servlet-class>
<!--指定Servlet的创建时机
1. 第一次被访问时,创建。<load-on-startup>为负数,一般为-1
2. 在服务器启动时,创建。<load-on-startup>为0或正整数,一般为0-10
-->
<load-on-startup>1</load-on-startup>
</servlet>

Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的。

多个用户同时访问时,可能存在线程安全问题。就比方说买票,容易数据错乱

解决方法:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对其赋值和修改值。

2.3 注解配置

在Servlet3.0之前,需要通过配置文件web.xml来配置。但是,太麻烦。

在Servlet3.0之后,可以通过注解来配置,web.xml可以不需要了。

从JavaEE6之后,才开始支持Servlet3.0。

步骤

  1. 创建JavaEE项目,选择Servlet的版本为3.0以上,可以不创建web.xml
  2. 定义一个类,实现Servlet接口
  3. 实现接口中的抽象方法
  4. 在类上使用注解@WebServlet
    • @WebServlet("资源路径"),当然@WebServlet还可以配置别的内容,具体参照其源码

参照文章注解

如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值

如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略(可以参考@SuppressWarnings的源码)

数组赋值时,用{}包裹。如果其中只有一个值的时候,{}可以省略不写

2.4 编译器与其Tomcat相关配置

如果直接使用tomcat的bin目录下脚本打开服务器,他的配置文件就放在服务器根目录下了。

对于编译器,他们会给集成到自身的tomcat单独建立配置文件。eclipse和idea这两款编译器,我都用过。

如果使用eclipse,那么eclipse会给他单独配置一个路径

同样地,idea也会单独配置路径来存储配置文件

项目分为工作空间目录和tomcat部署的Web项目

  • tomcat真正访问的是tomcat部署的Web项目,而tomcat会将编译后的工作空间目录内容的字节码文件放到WEB-INF下的classes中
  • WEB-INF下的文件不能直接被浏览器访问
  • 断点调试:不能直接运行服务器,需要通过Debug模式来运行服务器

2.5 Servlet体系结构

每次都像上面那样实现Servlet接口,却只用到了service方法,多余!那就需要通过一个实现类来帮助完成。Servlet自身有这种体系结构。

HttpServlet继承自GenericServlet,GenericServlet实现Servlet接口。看文档或者源码

GenericServlet:将Servlet接口中的其他方法做了默认空实现,只将service()方法作为抽象

  • 将来定义Servlet类时,可以继承GenericServlet,实现service()方法即可。其他方法可以自选重写

HttpServlet:对http协议的一种封装,简化操作

  1. 定义类继承HttpServlet
  2. 重写doGet和doPost方法

在实际使用中,继承HttpServlet的方法会更好

2.6 Servlet相关配置

urlPattens:Servlet访问路径

  • 一个Servlet可以定义多个访问路径:@WebServlet({“/d6”,”/dd6”,”/demo06”})
  • 路径定义规则:如/xxx/xxx/xxx(多层路径,目录结构)、/**的优先级很低,如果在访问已经配置的路径时,则会访问已配置的路径)、*.do(如果写*.do是会报错的,这个只是换了一个扩展名而已)

在注解中,如果属性值是value,那么value可以省略。

在配置访问路径的时候,是走的value,而不是urlPattens。我就在想,那么实现类是如何将value转换成url的呢?

找了半天,百度,和官网源码,也没找到@WebServlet的实现类。

后来再文档中看到说,value跟urlPatterns是一个东西。WTF!可能官方是为了让书写更加简洁?

三、HTTP请求协议

概念:Hyper Text Transfer Protocol 超文本传输协议

传输协议:定义了客户端和服务端通信时,发送数据的格式

特点

  1. 基于TCP/IP的高级协议
  2. 默认端口号:80
  3. 基于请求/响应模型的:一次请求对应一次响应
  4. 无状态的协议:每次请求之间相互独立

历史版本

  • 1.0:每一次请求响应都会建立新的连接
  • 1.1:复用连接

具体的各版本的区别

3.1 请求消息数据格式

  1. 请求行
    • 请求方式 请求url 请求协议/版本。如GET /login.html HTTP/1.1
  2. 请求头
    • 请求头名称:请求头值1,请求头值2,…
  3. 请求空行:作分割作用
  4. 请求体(正文):get方式是没有请求体的。用来封装post请求消息的请求参数的

字符串格式的请求数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST http://meethigher.top/admin/login.php HTTP/1.1
Host: meethigehr.top
Connection: keep-alive
Content-Length: 35
Cache-Control: max-age=0
Origin: http://baixiu-dev.top
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://meethigher.top/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=cdtnkpj4apnarjhtqugqegsfmn

email=admin@qq.com&password=admin

请求方式

Http协议中有7种请求方式,常用的有2种

  • get
    • 请求参数在请求行中(也可以理解在url中)
    • 请求的url长度是有限制的
    • 相对不安全
  • post
    • 请求参数在请求体中
    • 请求的url长度是没有限制的
    • 相对安全

请求头

常见的请求头

  1. User-Agent:浏览器访问服务器内容时,使用的浏览器版本信息
  2. Referer:告诉服务器,当前请求从哪里来
  3. Connection:keep-alive一直连接,表示可复用

Referer有两个作用

  1. 防盗链(盗取超链接)
  2. 统计功能

比如看斗罗大陆,一般是通过选了集数,跳转到腾讯播放页观看,这是用户正常的播放流程。但是如果,用户通过自己建的网页,直接指向腾讯播放页,这个过程就叫做盗链

3.2 响应消息数据格式

  1. 响应行
    • 请求协议/版本 响应状态码 状态码描述
  2. 响应头
    • 响应头名称:响应头值1,响应头值2,…
  3. 响应空行
  4. 响应体

字符串格式的响应数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
HTTP/1.1 302 Found
Date: Tue, 09 Jun 2020 01:43:26 GMT
Server: Apache/2.4.34 (Win64) PHP/7.2.10
X-Powered-By: PHP/7.2.10
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: /admin/index.php
Content-Length: 2508
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

<html>返回一套html代码,此处省略</html>

响应状态码

服务器告诉客户端浏览器本次请求和响应的状态,状态码都是三位数字

状态码分类

  1. 1xx:服务器接收客户端消息,但是没有接收完成,等待一段时间后,发送1xx多状态码
  2. 2xx:表示成功。最常见的就是200
  3. 3xx:表示重定向。最常见的302(重定向),304(访问缓存)
  4. 4xx:客户端错误。最常见的404(代表请求路径没有对应的资源),405(请求方式没有相应的方法,比方说在Servlet中,没有doGet方法或者doPost方法)
  5. 5xx:服务器端错误 。最常见的500(代表服务器内部错误)

响应头

常见的响应头

  1. Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式
  2. Content-disposition:服务器告诉客户端以什么格式打开响应体数据
    • in-line:默认值(在当前页面内打开)
    • attachment;filename=xxx:以附件形式打开响应体。在文件下载中,会使用这个,文件名是xxx

响应体

响应体就是真实的传输的数据

四、Request

Request对象和Response对象的原理

  1. request和response是由服务器来创建,开发者来使用
  2. request获取请求消息,response设置响应消息

4.1 Request对象继承体系结构

tomcat里面的RequestFacade类实现了继承自ServletRequest接口的实现类HttpServletRequest

具体的实现可以在Tomcat的源码中查看。

4.2 Request功能

  1. 获取请求消息
    • 获取请求行数据(标❤比较常用)
      • 获取请求方式:String getMethod()
      • ❤获取虚拟目录:String getContextPath()
      • 获取Servlet路径:String getServletPath()
      • 获取get请求参数:String getQueryString()
      • ❤获取请求URI:String getRequestURI()和String getRequestURL()
      • 获取协议以及版本:String getProtocol()
      • 获取客户机ip地址:String getRemoteAddr()
    • 获取请求头数据(标❤比较常用,请求头是不区分大小写的)
      • ❤String getHeader(String name):通过请求头的名称,获取请求头的值
      • EnumerationgetHeaderNames():获取所有的请求头名称
    • 获取请求体数据:只有post请求方式,才有请求体,在请求体中封装了post请求的请求参数
      1. 获取流对象
        • BufferedReader getReader():获取字符输入流,只能操作字符
        • ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据。用于文件上传比较好
      2. 流对象中拿数据
  2. ❤其他功能
    • 获取请求参数通用方式:不论get或是post请求,都可以使用下面这些方法
      • String getParameter(String name):根据参数名称获取参数值
      • String[] getParameterValues(String name):根据参数名称获取参数值数组,多用于复选框
      • EnumerationgetParameterNames():获取所有请求的参数名称
      • Map<String,String[]> getParameterMap():获取所有参数的map集合
    • 请求转发:一种在服务器内部的资源跳转方式
    • 共享数据
    • 获取ServletContext对象

获取请求消息

获取请求行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@WebServlet("/demo01request")
public class Demo01Request extends HttpServlet {

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*
* 获取请求方式:String getMethod()
* ❤获取虚拟目录:String getContextPath()
* 获取Servlet路径:String getServletPath()
* 获取get请求参数:String getQueryString()
* ❤获取请求URI:String getRequestURI()和String getRequestURL()
* 获取协议以及版本:String getProtocol()
* 获取客户机ip地址:String getRemoteAddr()
*/
System.out.println(request.getMethod());// GET
System.out.println(request.getContextPath());// /demo
System.out.println(request.getServletPath());// /demo01request
System.out.println(request.getQueryString());// user=hh&password=123
System.out.println(request.getRequestURI()+"---"+request.getRequestURL());// /demo/demo01request---http://localhost:8080/demo/demo01request
System.out.println(request.getProtocol());// HTTP/1.1
System.out.println(request.getRemoteAddr());// 这是获取的ipv6地址 0:0:0:0:0:0:0:1
}
}

URL 统一资源定位符

URI 统一资源标识符

URI代表的范围更大一些(并不是取决于路径的长度的),URL限制了指定的服务器,而URI是没有限制的,应该这样理解

获取请求头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@WebServlet("/demo02request")
public class Demo02Request extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String str = headerNames.nextElement();
if ("user-agent".equals(str)) {
String agent = request.getHeader(str);
if (agent.contains("Chrome")) {
System.out.println("Chrome");
} else if (agent.contains("Firefox")) {
System.out.println("Firefox");
}

}
}

}
}

获取请求体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebServlet("/demo03request")
public class Demo03Request extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求消息体 请求参数
request.setCharacterEncoding("utf-8");
//1.获取字符流
BufferedReader br=request.getReader();
//2.读取数据
String line=null;
while((line=br.readLine())!=null){
System.out.println(line);
}
}
}

这个部分出了点问题,就是它输出的内容,将字符串以url编码形式打印出来了。而后面的getParameter是直接原样输出的。那这两个的区别是啥?我尝试查看HttpServletRequest这个接口的实现类,最后通过request.getClass().getName()输出全类名,发现实现类是在tomcat里面。

结果里面的代码,也是超级简洁,我也没咋看明白两者的不同之处,惆怅。

4.3 其他功能

获取请求参数通用方式

这个方式既适用于post也适用于get,所以说是通用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@WebServlet("/demo04request")
public class Demo04Request extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
//post获取请求参数
//根据参数名称获取参数值
// String user = request.getParameter("user");
// String password = request.getParameter("password");
// System.out.println(user+"--"+password);
// //根据参数名称获取参数值数组
// String[] hobbies=request.getParameterValues("beauty");
// for (String hobby: hobbies) {
// System.out.println(hobby);
// }
//获取所有请求的参数名称
// Enumeration<String> parameterNames = request.getParameterNames();
// while(parameterNames.hasMoreElements()){
// System.out.println(parameterNames.nextElement());
// }
//测试热部署
System.out.println("奥利给");//没卵用,百度一下,发现要用debug模式运行才可以

//获取所有参数的Map集合
Map<String, String[]> parameterMap = request.getParameterMap();
//遍历
Set<String> strings = parameterMap.keySet();
for(String key:strings){
System.out.println(key+"↓↓↓↓↓↓");
//根据key获取值
String[] strings1 = parameterMap.get(key);
//遍历值数组
for (String value:strings1) {
System.out.println(value);
}
System.out.println("------");
}

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}

请求转发

步骤

  1. 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
  2. 使用RequestDispatcher对象进行转发:void forward(ServletRequest request, ServletResponse response)

特点

  1. 浏览器地址栏路径没有发生变化
  2. 只能转发到当前的服务器内部中
  3. 转发是一次请求

共享数据

域对象:一个有作用范围的对象,可以在范围内共享数据

request域:代表一次请求的范围。一般用于请求转发的多个资源中共享数据

方法

  1. setAttribute(String name,Object object):存储数据
  2. Object getAttribute(String name):通过键获取值
  3. removeAttribute(String name):通过键移除值
1
2
3
4
5
6
7
8
9
10
11
12
13
@WebServlet("/demo05request")
public class Demo05Request extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo05request");
request.setAttribute("女神","胡列娜");
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/demo06request");
requestDispatcher.forward(request,response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}

demo05将请求转发到demo06,同时在request域内共享数据

1
2
3
4
5
6
7
8
9
10
11
@WebServlet("/demo06request")
public class Demo06Request extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo06request");
System.out.println(request.getAttribute("女神"));
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}

获取ServletContext

ServletContext getServletContext()

4.4 中文乱码问题

get方式:Tomcat8已经将get方式乱码解决了

post方式:会乱码。解决方式是在获取参数前,将request的编码设置为utf-8

1
request.setCharacterEncoding("utf-8")

五、用户登录案例

5.1 需求

  1. 编写登录页面,账号密码
  2. 使用Druid数据库连接技术
  3. 使用JDBCTemplate技术封装JDBC
  4. 登录成功,跳转SuccessServlet,提示登录成功
  5. 登录失败,跳转到FailServlet,提示登录失败

5.2 实现

  1. 创建项目、登录界面,配置文件以及数据库,导入jar包(在Web项目下,jar包一般是放在WEB-INF的lib目录下)
  2. 创建包domain,包下建User表,对应着数据库表
  3. 创建包dao,包下建UserDao,提供Login方法
  4. 创建包utils,包下建JDBCUtils(连接池工具类)
  5. 创建包servlet,包下建响应的Servlet
  6. 创建包test,通过Junit进行单元测试

代码

login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="loginServlet" method="post">
名:<input type="text" name="user"><br>
密:<input type="password" name="password"><br>
<input type="submit" value="提交">
</form>
</body>
</html>

dao层下的UserDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 操作数据中user表的类
* @author https://github.com/meethigher
*/
public class UserDao {
private JdbcTemplate temp=new JdbcTemplate(JDBCUtils.getDs());
/**
* 登录方法
* @param loginUser 只有用户名和密码
* @return 包含用户全部方法
*/
public User login(User loginUser){
try {
//1.编写sql,只查询一条数据,因为我数据库里存储的内容有重复值,所有...
String sql="select * from user where user=? and password=? limit 0,1";
//2.调用template的查询方法
User user = temp.queryForObject(sql,
new BeanPropertyRowMapper<User>(User.class),
loginUser.getUser(),loginUser.getPassword());
return user;
} catch (DataAccessException e) {
//如果没有查到内容,返回null
e.printStackTrace();//后期不再会使用打印异常,而是通过记录日志的方式
return null;
}
}
}

domain层的User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 用户的JavaBean
* @author https://github.com/meethigher
*/
public class User {
private int id;
private String user;
private String password;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getUser() {
return user;
}

public void setUser(String user) {
this.user = user;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

utils层下的JDBCUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* JDBC工具类,使用Druid连接池
*
* @author https://github.com/meethigher
*/
public class JDBCUtils {
private static DataSource ds;

/**
* 静态代码块初始化连接池对象
*/
static {
try {
//1.加载配置文件
Properties pro = new Properties();
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
//2.初始化连接池对象
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}


}

/**
* 获取连接池对象
* @return
*/
public static DataSource getDs() {
return ds;
}

/**
* 获取连接对象
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}

test层下的UserDaoTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author https://github.com/meethigher
*/
public class UserDaoTest {
@Test
public void testLogin(){
User u=new User();
u.setUser("胡列娜");
u.setPassword("123f");
UserDao udao=new UserDao();
udao.login(u);
}
}

servlet层下的LoginServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@WebServlet("/example/loginServlet")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置编码
request.setCharacterEncoding("utf-8");
//获取请求参数
String user = request.getParameter("user");
String password = request.getParameter("password");
//封装User对象
User loginUser = new User();
loginUser.setUser(user);
loginUser.setPassword(password);
//调用UserDao的登录方法
User login = new UserDao().login(loginUser);
//判断是否登录成功
if(login!=null){
request.setAttribute("user",login);
request.getRequestDispatcher("/example/successServlet").forward(request,response);
}else{
request.getRequestDispatcher("/example/failServlet").forward(request,response);
}

}
}

servlet层下的SuccessServlet和FailServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//SuccessServlet.java
@WebServlet("/example/successServlet")
public class SuccessServle extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
User user = (User) request.getAttribute("user");
response.getWriter().write(user.getUser()+"登录成功");
}
}
//----------------------------------------------

//FailServlet.java
@WebServlet("/example/failServlet")
public class FailServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("登录失败");
}
}

BeanUtils工具类

JavaBean:标准的Java类

JavaBean要求

  • 类必须被public修饰
  • 必须提供空参的构造方法
  • 成员变量必须使用private修饰
  • 提供公共的getter和setter方法
  • 一般在项目中放在domain或者entity下

功能:简化数据封装

概念

  • 成员变量
  • 属性:setter和getter方法截取后的产物,比方说getUser(),对应的属性就是user

方法

  • setProperty()
  • getProperty()
  • populate()

我个人的理解,其实属性跟成员变量是一回事。只不过有的库在封装(比如BeanUtils)的时候,需要拆分出方法的设置或获取的属性,由此产生属性这个概念。

其实,从这个角度来想,好多东西,都是人规定的,因为你整的东西好,用的人多了,自然而然也就成了规定。就像js,php,这种叫做弱类型语言,底层可能还是通过基本数据类型来实现,但是我也可以说,他没有数据类型,因为在用的时候,根本不需要去定义数据类型。

1
2
3
4
5
6
7
8
9
public class BeanUtilsTest {
@Test
public void test() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
User user = new User();
BeanUtils.setProperty(user,"user","胡列娜");
String user1 = BeanUtils.getProperty(user, "user");
System.out.println(user+"--"+user1);// User{id=0, user='胡列娜', password='null'}--胡列娜
}
}

优化

登录功能只有两条数据需要封装成User对象。获取数据,创建对象,赋值,6步完成。如果是一个很多条数据,要获取十几次数据,赋值十几次,岂不是很麻烦。这就需要优化一下了。

一次能获取所有数据,一次赋完所有值。可以通过下载BeanUtils来完成数据的封装

优化后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@WebServlet("/example/loginServlet")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置编码
request.setCharacterEncoding("utf-8");
// //获取请求参数
// String user = request.getParameter("user");
// String password = request.getParameter("password");
// //封装User对象
// User loginUser = new User();
// loginUser.setUser(user);
// loginUser.setPassword(password);

//改用BeanUtils优化
//获取所有请求参数
Map<String, String[]> parameterMap = request.getParameterMap();
//创建对象
User loginUser = new User();
//使用BeanUtils封装
try {
BeanUtils.populate(loginUser,parameterMap);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//调用UserDao的登录方法
User login = new UserDao().login(loginUser);
//判断是否登录成功
if(login!=null){
request.setAttribute("user",login);
request.getRequestDispatcher("/example/successServlet").forward(request,response);
}else{
request.getRequestDispatcher("/example/failServlet").forward(request,response);
}

}
}

六、Response

6.1 Response功能

设置响应消息

  • 设置响应行
    • 设置状态码:setStatus(int sc)
  • 设置响应头:setHeader(String name,String value)
  • 设置响应体
    • 获取输出流
      • 字符输出流:PrintWriter getWriter();
      • 字节输出流:ServletOutputStream getOutputStream()
    • 使用输出流,将输出流输出到客户端

6.2 重定向

重定向:资源跳转的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@WebServlet("/demo01Response")
public class Demo01Response extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo01Response...");
//访问 /demo01Response会自动跳转/demo02Response
// //1.设置状态码为302
// response.setStatus(302);
// //2.设置响应头location
// response.setHeader("location","/demo02Response");

//因为重定向的步骤是一样的,所以java提供了简化版
System.out.println(response.getClass().getName());
response.sendRedirect("/demo02Response");
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}

转发的特点(forward)

  1. 浏览器地址栏路径没有发生变化
  2. 只能转发到当前的服务器内部中
  3. 转发是一次请求,可以使用request来共享数据

重定向的特点(redirect)

  1. 地址栏发生变化
  2. 可以访问其他站点资源
  3. 重定向是两次请求,不能使用request来共享数据

6.3 路径

分类

  1. 相对路径:通过相对路径,不可以确定唯一资源
    • 如./index.html(当前路径下的index.html,./是可以省略的),../index.html(上一级的index.html,上上级之类的以此类推)
    • 不以/开头,以.开头路径;./表示当前路径,当然,这个是可以省略的。
    • 规则:要使用相对路径,要确定当前的资源和目标资源之间的相对位置关系
  2. 绝对路径:通过绝对路径,可以确定唯一的资源

规则:判断定义的路径是给谁用的

  • 给客户端浏览器使用:需要加虚拟目录(项目的访问路径),比方说重定向
  • 给服务器使用:不需要加虚拟目录,比方说请求转发

在实际项目中,虚拟目录的路径不能写死,可以通过request.getContextPath()动态获取虚拟目录

6.4 服务器输出数据到客户端

获取流对象

  • 获取字符输出流:PrintWriter getWriter()
  • 获取字节输出流:ServletOutputStream getOutputStream()

字符输出流

步骤

  1. 获取字符输出流
  2. 输出数据

注意:乱码问题,客户端的默认编码,与服务端返回消息体的编码不一致

response.getWriter()获取的流的默认编码是ISO-8859-1,我们需要设置该流的默认编码

response获取的流,不需要刷新和关闭。因为response在一次响应结束之后,自动销毁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@WebServlet("/demo03Response")
public class Demo03Response extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//response获取的流,不需要刷新和关闭。因为response在一次响应结束之后,自动销毁
//获取流对象之前,设置流的默认编码,但是如果客户端的机器有的是u8,有的是国标,还是会乱码
// response.setCharacterEncoding("utf-8");
//设置消息体数据的编码,建议浏览器使用该编码。不区分大写
// response.setHeader("content-type","text/html;charset=utf-8");
//当然,javaEE也给提供了更简便的方法
response.setContentType("text/html;charset=utf-8");

//1.获取字符输出流
PrintWriter pw = response.getWriter();
//2.输出数据
pw.write("Hello Response");
pw.write("胡列娜 我女神");
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}

字节输出流

步骤

  1. 获取字节输出流:response.getOutputStream()
  2. 输出数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebServlet("/demo04Response")
public class Demo04Response extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//1.获取字节输出流
ServletOutputStream os = response.getOutputStream();
//2.输出数据
os.write("胡列娜 我女神".getBytes("utf-8"));
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}

6.5 案例-验证码

本质:图片

目的:防止表单恶意注册

关于Graphics的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int width=500;
int height=300;
//1.创建对象,在内存中画图(验证码图片对象)
BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
//2.美化图片
//2.1填充背景色
Graphics g=image.getGraphics();//绘图对象
g.setColor(Color.PINK);
g.fillRect(0,0,width,height);
//2.2画边框
g.setColor(Color.RED);
g.drawRect(0,0,width-1,height-1);
System.out.println("奥利给");
//2.3写验证码
String str="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
Random r=new Random();
for (int i = 0; i < 4; i++) {
g.drawString(str.charAt(r.nextInt(str.length()))+"",width/5*(i+1),height/2);
}
//2.4 画干扰线
g.setColor(Color.green);
for (int i = 0; i < 10; i++) {
int x1=r.nextInt(width);
int y1=r.nextInt(height);
int x2=r.nextInt(width);
int y2=r.nextInt(height);
g.drawLine(x1,y1,x2,y2);
}
//3.将图片输出到页面展示
ImageIO.write(image,"jpg",response.getOutputStream());
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}

通过html对齐进行访问以及单击修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<img src="/checkCodeServlet">
<a href="javascript:void(0)">看不清换一张</a>
<script>
let $img=document.querySelector("img");
$img.onclick = function () {
let url = "/checkCodeServlet";
let date=new Date().getTime();
this.setAttribute("src", url+"?"+date)
};
document.querySelector("a").onclick=()=>{
let url = "/checkCodeServlet";
let date=new Date().getTime();
$img.setAttribute("src", url+"?"+date)
}
</script>
</body>
</html>

七、ServletContext对象

概念:代表整个web应用,可以和程序的容器(服务器)来通信

获取

  1. 通过request获取:request.getServletContext()
  2. 通过HttpServlet获取:getServletContext

其实通过这两个方式获取的都是同一个对象。因为本身HttpServletRequest就是HttpServlet的子类,调用的就是其父类的方法

功能

  1. 获取MIME类型
    • MIME类型:在互联网通信过程中定义的一种文件数据类型
    • 格式:大类型/小类型 如:htm或者html对应text/html、jpg对应image/jpeg
    • 获取:String getMimeType(String filename)
  2. 域对象:共享数据
    • setAttribute(String name,Object value)
    • getAttribute(String name)
    • removeAttribute(String name)
  3. 获取文件的真实路径(服务器完整路径)
    • String getRealPath(String path)

7.1 获取MIME类型

具体的MIME的相关的映射,都在tomcat的web.xml里面配置着。Tomcat的web.xml相当于是所有集成到ide的web.xml的父类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebServlet("/demo02ServletContext")
public class Demo02ServletContext extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
//定义文件名称
String name="a.jpg";
//获取MIME类型
String mimeType = servletContext.getMimeType(name);
System.out.println(mimeType);//image/jpeg
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}

7.2 域对象

ServletContext对象范围:所有用户所有请求的数据(范围最大的)

在一个servlet设置属性

1
2
3
4
5
6
7
8
9
10
11
12
13
@WebServlet("/demo03ServletContext")
public class Demo03ServletContext extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
//设置数据
servletContext.setAttribute("fairy","胡列娜");

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}

在另外的servlet访问属性

1
2
3
4
5
6
7
8
9
10
11
12
13
@WebServlet("/demo04ServletContext")
public class Demo04ServletContext extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
//获取数据
Object fairy = servletContext.getAttribute("fairy");
System.out.println(fairy.toString());//胡列娜
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}

一般用这个,也比较谨慎,所有用户都是可以共享和操作的。而且,生命周期也非常长,服务器启动,它就创建了,服务器关闭,才会销毁。驻留在内存的时间非常久,造成的内存的压力会很大。

7.3 获取文件真实路径(服务器完整路径)

  • 获取web目录下文件
  • 获取WEB-INF目录下文件
  • 获取src目录下文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@WebServlet("/demo05ServletContext")
public class Demo05ServletContext extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
String realPath = servletContext.getRealPath("/example/login.html");//获取web下文件
System.out.println(realPath);//E:\Develop\JavaDev\JavaEE\out\artifacts\JavaEE_war_exploded\example\login.html
String realPath1 = servletContext.getRealPath("/WEB-INF/web.xml");//获取WEB-INF目录下文件
System.out.println(realPath1);
//如果是直接放在src根目录下的,需要去/WEB-INF/classes/下找
String realPath2 = servletContext.getRealPath("/WEB-INF/classes/a.html");//获取src目录下文件
System.out.println(realPath2);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}

八、文件下载与上传案例

8.1 文件下载

需求

  1. 页面显示超链接
  2. 点击超链接后弹出下载框
  3. 完成图片文件下载

HTML中超链接指向的资源如果能够被浏览器解析,则在浏览器中展示;若不能解析,就会进行下载到本地。当然,我们也可以通过添加download属性,直接让它所有的都下载。但是这个是没有提示框的。

我们可以使用响应头设置资源打开的方式

content-disposition:attachment;filename=xxx这个表示已附件的形式打开,文件名为xxx

步骤

  1. 定义页面
  2. 定义Servlet
    • 获取文件名称
    • 用字节输入流加载进内存
    • 设置响应头为附件形式打开
    • 将内存写出到响应输出流

中文名称的文件下载时会乱码,改成utf-8的即可,垃圾ie浏览器已经没救了,兼容不了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
//1.获取请求参数,文件名称
String filename = request.getParameter("filename");
//2.使用字节输入流加载文件进内存
//2.1找到文件服务器路径
ServletContext sc = getServletContext();
String realPath = sc.getRealPath("/壁纸5.jpg");
//2.2用字节流关联
FileInputStream fis = new FileInputStream(realPath);
//3设置response响应头
//3.1设置响应格式
String mimeType = sc.getMimeType(filename);
// response.setHeader("content-type",mimeType);
response.setContentType(mimeType);
//3.2设置响应头打开格式
//解决中文文件名问题,垃圾ie,去他妈的吧,兼容不了
filename= URLEncoder.encode(filename,"utf-8");
response.setHeader("content-disposition","attachment;filename="+filename);
//4.将输入流数据写出到输出流中
ServletOutputStream sos = response.getOutputStream();
byte[] bytes = new byte[1024 * 8];
int len=0;
while((len=fis.read(bytes))!=-1){
sos.write(bytes,0,len);
}
//5.释放资源
fis.close();

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}

html中的代码

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="downloadServlet?filename=壁纸5.jpg">图片下载</a>
</body>
</html>

8.2 文件上传

我一开始以为文件上传跟文件下载一样,获取请求的传过来的数据,然后写。

但是,一直写出数据都是0kb,我就很纳闷了,我的代码是没有问题的。想了一会,搞明白了,request获取的输入流,是获取到的请求体的数据,而不是获取上传文件的内容

发布:2020-06-11 23:11:31
修改:2020-07-06 23:05:22
链接:https://meethigher.top/blog/2020/servlet/
标签:java web 
付款码 打赏 分享
Shift+Ctrl+1 可控制工具栏