Web服务器

2023-11-04

1. HTTP 协议

1.1 概述

概述

  • HTTP协议是 Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
  • HTTP 是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。
  • HTTP 是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。
  • HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。

简而言之,就是定义了,客户端和服务器端通信时,发送数据的格式。

主要特点

  1. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
  2. 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
  3. 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  4. 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
  5. 支持 B/S 及 C/S 模式。
  6. 默认端口号 80

1.2 URI和URL的区别

  • URL 是一种特殊类型的 URI ,可以说 URI 是抽象的,而具体要使用URL来定位资源。
  • 比如 身份证的地址为 URL,身份证号为 URI 。

URI

URI,是 uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。Web上可用的每种资源如HTML文档、图像、视频片段、程序等都是一个来URI来定位的。

URI一般由三部组成:

  1. 访问资源的命名机制
  2. 存放资源的主机名
  3. 资源自身的名称,由路径表示,着重强调于资源。

URL

URLuniform resource locator,统一资源定位器,它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。URL是Internet上用来描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上,特别是著名的Mosaic。

采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL一般由三部组成:

  1. 协议(或称为服务方式)
  2. 存有该资源的主机IP地址(有时也包括端口号)
  3. 主机资源的具体地址。如目录和文件名等

URN

URNuniform resource name,统一资源命名,是通过名字来标识资源,比如mailto:java-net@java.sun.com

URI是以一种抽象的,高层次概念定义统一资源标识,而 URL 和URN则是具体的资源标识的方式。URL和URN都是一种URI。笼统地说,每个 URL 都是 URI,但不一定每个 URI 都是 URL。这是因为 URI 还包括一个子类,即统一资源名称 (URN),它命名资源但不指定如何定位资源。上面的 mailto、news 和 isbn URI 都是 URN 的示例。

在Java的 URI 中,一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的。

在Java类库中,URI类不包含任何访问资源的方法,它唯一的作用就是解析。相反的是,URL类可以打开一个到达资源的流。

1.3. 请求消息 Request

客户端发送一个HTTP请求到服务器的请求消息包括以下四部分

  • 请求行(request line)
  • 请求头部(header)
  • 请求空行
  • 请求体

请求行

		请求方式 请求url 请求协议/版本
		GET /login.html	HTTP/1.1
  • 请求方式:HTTP协议有 7 中请求方式,常用的有 2 种:
  • GET:请求参数在请求行中,在 url 后;请求的url长度有限制的;不太安全获取资源(查询用)
  • POST:请求参数在请求体中,请求的url长度没有限制的,相对安全,向指定资源提交数据进行处理请求(例如提交表单或者上传文件)新增用
  • PUT: 用于向指定的URI传送更新资源,是幂等的,自身不带验证机制,存在安全性问题,更新用
  • HEAD:获得报文首部
  • DELETE:删除文件
  • OPTIONS: 询问支持的方法
  • TRACE:追踪路径
  • CONNECT: 要求用隧道协议代理

请求头:客户端浏览器告诉服务器一些信息

请求头名称: 请求头值。

常见的请求头:

  • User-Agent:浏览器告诉服务器,使用的浏览器版本信息(用于解决浏览器的兼容性问题)
  • Referer:http://localhost/login.html告诉服务器,当前请求从哪里来?
    • 防盗链,统计工作

请求空行

空行,就是用于分割 POST 请求的请求头,和请求体的。

请求体(正文)

封装POST请求消息的请求参数的

POST /login.html	HTTP/1.1
		Host: localhost
		User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
		Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
		Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
		Accept-Encoding: gzip, deflate
		Referer: http://localhost/login.html
		Connection: keep-alive
		Upgrade-Insecure-Requests: 1

1.4.响应消息 Response

一般情况下,服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息。

HTTP 响应也由四个部分组成,分别是:

  1. 响应行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
  2. 响应头,用来说明客户端要使用的一些附加信息
    • Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式
    • Content-disposition:服务器告诉客户端以什么格式打开响应体数据
    • in-line:默认值,在当前页面内打开
    • attachment;filename=xxx:以附件形式打开响应体。文件下载
  3. 响应空行,响应头后面的空行是必须的
  4. 响应正文,服务器返回给客户端的文本信息。
HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8

<html>
      <head></head>
      <body>
            <!--body goes here-->
      </body>
</html>

1.5.状态码

状态码 响应类别
1xx 指示信息–表示请求已接收,继续处理
2xx 成功–表示请求已被成功接收、理解、接受
3xx 重定向–要完成请求必须进行更进一步的操作
4xx 客户端错误–请求有语法错误或请求无法实现
5xx 服务器端错误–服务器未能实现合法的请求

选取几个代表的

  • 200(成功)
  • 302(重定向)
  • 304(访问缓存)
  • 404(请求路径没有对应的资源)
  • 405(请求方式没有对应的doXxx方法)
  • 500(服务器内部出现异常)

1.6. HTTP 1.0 和 HTTP 1.1

主要区别

  1. 长连接 : 在HTTP/1.0中,默认使用的是短连接,也就是说每次请求都要重新建立一次连接。HTTP 是基于TCP/IP协议的,每一次建立或者断开连接都需要三次握手四次挥手的开销,如果每次请求都要这样的话,开销会比较大。因此最好能维持一个长连接,可以用个长连接来发多个请求。HTTP 1.1起,默认使用长连接 ,默认开启Connection: keep-alive。 HTTP/1.1的持续连接有非流水线方式和流水线方式 。流水线方式是客户在收到HTTP的响应报文之前就能接着发送新的请求报文。与之相对应的非流水线方式是客户在收到前一个响应后才能发送下一个请求。
  2. 错误状态响应码 :在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
  3. 缓存处理 :在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
  4. 带宽优化及网络连接的使用 :HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。

长连接与短连接

  • HTTP/1.0中默认使用短连接,客户端和服务器每进行一次HTTP操作(如请求资源),就建立一次连接,任务结束就中断连接。
  • HTTP/1.1起,默认使用长连接,用以保持连接特性。当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。

1.7. Cookie

客户端会话技术,将数据保存到客户端

实现原理:基于响应头 set-cookie和 请求头 cookie 实现

使用步骤:

  1. 创建Cookie对象,绑定数据
    * new Cookie(String name, String value)
  2. 发送Cookie对象
  • response.addCookie(Cookie cookie)
  1. 获取Cookie,拿到数据
  • Cookie[] request.getCookies()

一些细节

一次可不可以发送多个cookie?

可以创建多个Cookie对象,使用 response 调用多次 addCookie 方法发送 cookie 即可。

cookie 在浏览器中保存多长时间?

  • 默认情况下,当浏览器关闭后,Cookie 数据被销毁
  • 持久化存储:setMaxAge(int seconds)
    • 正数:将Cookie数据写到硬盘的文件中。持久化存储。并指定cookie存活时间,时间到后,cookie文件自动失效
    • 负数:默认值
    • 零:删除cookie信息

cookie 能不能存中文?

  • 在tomcat 8 之前 cookie中不能直接存储中文数据。
    • 需要将中文数据转码— 一般采用URL 编码(%E3)
  • 在tomcat 8 之后,cookie 支持中文数据。特殊字符还是不支持,建议使用 URL 编码存储,URL 解码解析

cookie共享问题?

假设在一个tomcat服务器中,部署了多个web项目,那么在这些web项目中cookie能不能共享?

  • 默认情况下cookie不能共享
    • setPath(String path):设置cookie的获取范围。默认情况下,设置当前的虚拟目录
  • 如果要共享,则可以将path设置为"/"

不同的tomcat服务器间cookie共享问题?

  • setDomain(String path):如果设置一级域名相同,那么多个服务器之间cookie可以共享
  • setDomain(".baidu.com")那么tieba.baidu.comnews.baidu.com中cookie可以共享

1.8 Session

服务器端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中。HttpSession

原理: Session的实现是**依赖于 Cookie **的。

快速使用

  1. 获取HttpSession对象:
    - HttpSession session = request.getSession();
  2. 使用HttpSession对象:
  • Object getAttribute(String name)
  • void setAttribute(String name, Object value)
  • void removeAttribute(String name)

一些细节

当客户端关闭后,服务器不关闭,两次获取session是否为同一个?

  • 默认情况下。不是。
    * 如果需要相同,则可以创建 Cookie ,键为 JSESSIONID ,设置最大存活时间,让 cookie 持久化保存。
			 Cookie c = new Cookie("JSESSIONID",session.getId());
	         c.setMaxAge(60*60);
	         response.addCookie(c);

客户端不关闭,服务器关闭后,两次获取的session是同一个吗?

  • 不是同一个,但是要确保数据不丢失。tomcat自动完成以下工作
    * session 的钝化:在服务器正常关闭之前,将 session对象系列化到硬盘上
    • session 的活化:在服务器启动后,将 session文件转化为内存中的 session对象即可。

session什么时候被销毁?

  1. 服务器关闭
  2. session对象调用 invalidate()
  3. session默认失效时间 30分钟

选择性配置修改

			<session-config>
		        <session-timeout>30</session-timeout>
		    </session-config>

两者区分

Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。

Cookie 一般用来保存用户信息。比如:

  1. 我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;
  2. 一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);
  3. 登录一次网站后访问网站其他页面不需要重新登录。
  4. 浏览器对于单个cookie 的大小有限制(4kb) 以及 对同一个域名下的总cookie数量也有限制(20个)

Session 的主要作用就是通过服务端记录用户的状态。

  • 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。

  • session可以存储任意类型,任意大小的数据

  • Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端

  • Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。

简而言之

  1. session 存储数据在服务器端,Cookie 在客户端
  2. session 没有数据大小限制,Cookie有
  3. session 数据安全,Cookie相对于不安全

HTTP是不保存状态的协议,如何保存用户状态?

HTTP 是一种不保存状态,即无状态(stateless)协议。

Session 机制的存在就是为了解决这个问题,Session 的主要作用就是通过服务端记录用户的状态。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了(一般情况下,服务器会在一定时间内保存这个 Session,过了时间限制,就会销毁这个Session)。

在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库redis保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是通过在 Cookie 中附加一个 Session ID 来方式来跟踪。

Cookie 被禁用怎么办?

最常用的就是利用 URL 重写把 Session ID 直接附加在URL路径的后面。

2. Tomcat

常见的java相关的web服务器软件:

  • webLogic:oracle公司,大型的 JavaEE 服务器,支持所有的 JavaEE 规范,收费的。
  • webSphere:IBM公司,大型的 JavaEE 服务器,支持所有的 JavaEE 规范,收费的。
  • JBOSS:JBOSS公司的,大型的 JavaEE 服务器,支持所有的 JavaEE 规范,收费的。
  • Tomcat:Apache基金组织,中小型的 JavaEE 服务器,仅仅支持少量的 JavaEE 规范 servlet/jsp。开源的,免费的。

搭建

  • 下载:http://tomcat.apache.org/

  • 安装:解压压缩包即可。安装目录建议不要有中文和空格

  • 卸载:删除目录就行了

  • 启动:

    • bin/startup.bat,双击运行该文件即可
    • 访问:浏览器输入:http://localhost:8080 回车访问自己; http://别人的ip:8080访问别人
  • 可能遇到的问题

    • 黑窗口一闪而过:正确配置 JAVA_HOME 环境变量
    • 启动报错:netstat -ano找到占用的端口号,并且找到对应的进程,杀死该进程
    • conf/server.xml 中修改自身的端口号
    • 一般会将 tomcat 的默认端口号修改为80**。80端口号是 http 协议的默认端口号。**
  • 关闭:

    • bin/shutdown.bat
      • ctrl+c
      • 点击启动窗口的 ×

配置部署

建议参考

  1. 直接将项目放到 webapps 目录下即可。
    * /hello:项目的访问路径–>虚拟目录

    • 简化部署:将项目打成一个 war 包,再将 war 包放置到 webapps 目录下。(war包会自动解压缩)
  2. 配置conf/server.xml文件

  • <Host>标签体中配置<Context docBase="D:\hello" path="/hehe" />
    • docBase:项目存放的路径
    • path:虚拟目录
  1. conf\Catalina\localhost创建任意名称的 xml 文件。在文件中编写<Context docBase="D:\hello" />
  • 虚拟目录:xml 文件的名称

目录结构

在这里插入图片描述

java 动态项目的目录结构:

					- 项目的根目录
						-- WEB-INF目录:
							--- web.xml:web项目的核心配置文件
							--- classes目录:放置字节码文件的目录
							--- lib目录:放置依赖的jar包

3. Servlet

概述

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。参考菜鸟教程

Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势:

  • Servlet 在 Web 服务器的地址空间内执行。
    • 在传统的CGI中,每个请求都要启动一个新的进程,如果CGI程序本身的执行时间较短,启动进程所需要的开销很可能反而超过实际执行时间。
    • 而在Servlet中,每个请求由一个轻量级的Java线程处理(而不是重量级的操作系统进程)。
    • 一般来说,Servlet 进程只是在Web Server卸载时被卸载。
  • 方便 :Servlet提供了大量的实用工具例程,例如自动地解析和解码HTML表单数据、读取和设置HTTP头、处理Cookie、跟踪会话状态等。
  • 功能强大:在Servlet中,许多使用传统CGI程序很难完成的任务都可以轻松地完成。例如,Servlet能够直接和Web服务器交互,而普通的CGI程序不能。Servlet还能够在各个程序之间共享数据,使得数据库连接池之类的功能很容易实现。
  • 可移植性好: Servlet用 Java 编写,Servlet API具有完善的标准。因此,为IPlanet Enterprise Server写的Servlet无需任何实质上的改动即可移植到ApacheMicrosoft IIS或者WebStar。几乎所有的主流服务器都直接或通过插件支持Servlet。

3.1.三种实现方法

  1. 实现 Servlet 接口: 接口,我们需要实现接口里所有方法。
//Servlet的生命周期:从Servlet被创建到Servlet被销毁的过程
//一次创建,到处服务
//一个Servlet只会有一个对象,服务所有的请求
/*
 * 1.实例化(使用构造方法创建对象)
 * 2.初始化  执行init方法
 * 3.服务     执行service方法
 * 4.销毁    执行destroy方法
 */
public class ServletDemo1 implements Servlet {

    //public ServletDemo1(){}

     //生命周期方法:当Servlet第一次被创建对象时执行该方法,该方法在整个生命周期中只执行一次
    public void init(ServletConfig arg0) throws ServletException {
                System.out.println("=======init=========");
        }

    //生命周期方法:对客户端响应的方法,该方法会被执行多次,每次请求该servlet都会执行该方法
    public void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException {
        System.out.println("hehe");

    }

    //生命周期方法:当Servlet被销毁时执行该方法
    public void destroy() {
        System.out.println("******destroy**********");
    }
//当停止tomcat时也就销毁的servlet。
    public ServletConfig getServletConfig() {

        return null;
    }

    public String getServletInfo() {

        return null;
    }
}
  1. 继承 GenericServlet 类,极少用。
public class ServletDemo2 extends GenericServlet {

    @Override
    public void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException {
        System.out.println("heihei");

    }
}
  1. 继承 HttpServlet 方法(常用)
public class ServletDemo3 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("haha");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("ee");
        doGet(req,resp);
    }

}

Servlet的体系结构

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

    • 将来定义 Servlet 类时,可以继承 GenericServlet,实现 service() 方法即可
  • HttpServlet:对 http 协议的一种封装,简化操作

    1. 定义类继承 HttpServlet
    2. 复写doGet/doPost方法

3.2.配置

3.2.1. xml 配置

web.xml中配置:

<!--配置Servlet -->
	    <servlet>
	        <servlet-name>demo1</servlet-name>
	        <servlet-class>cn.leyou.web.servlet.ServletDemo1</servlet-class>
	    </servlet>
	
	    <servlet-mapping>
	        <servlet-name>demo1</servlet-name>
	        <url-pattern>/demo1</url-pattern>
	    </servlet-mapping>

执行原理

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

3.2.2.生命周期

Servlet 的生命周期: 从 Servlet 被创建到 Servlet 被销毁的过程:

  1. 实例化:使用构造方法创建对象

  2. 初始化:执行 init 方法

  3. 服务:执行 service 方法

  4. 销毁:执行 destroy 方法

被创建:执行 init 方法,只执行一次

Servlet什么时候被创建?

  • 默认情况下,第一次被访问时,Servlet 被创建
  • 可以配置执行 Servlet 的创建时机。 在<servlet>标签下配置
    • 第一次被访问时,创建 <load-on-startup>的值为负数
    • 在服务器启动时,创建<load-on-startup>的值为0或正整数

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

  • 多个用户同时访问时,可能存在线程安全问题。
    • 解决:尽量不要在 Servlet 中定义成员变量。即使定义了成员变量,也不要对修改值。

提供服务:执行service方法,执行多次

每次访问 Servlet 时,Service 方法都会被调用一次。

被销毁:执行destroy方法,只执行一次

  • Servlet被销毁时执行。服务器关闭时,Servlet 被销毁
  • 只有服务器正常关闭时,才会执行 destroy 方法。
  • destroy 方法在 Servlet 被销毁之前执行,一般用于释放资源

3.2.3.注解配置

Servlet3.0 支持注解配置。可以不需要 web.xml 了。

  • 在类上使用 @WebServlet 注解,进行配置:@WebServlet("资源路径")
  • 一个 Servlet 可以定义多个访问路径@WebServlet({"/d4","/dd4","/ddd4"})

路径定义规则:

  • /xxx:路径匹配
  • /xxx/xxx:多层路径,目录结构
  • *.do:扩展名匹配 (这里的 do 也可以替换成其它任意的字符)

注解配置的源码:

			@Target({ElementType.TYPE})
			@Retention(RetentionPolicy.RUNTIME)
			@Documented
			public @interface WebServlet {
			    String name() default "";//相当于<Servlet-name>
			
			    String[] value() default {};//代表urlPatterns()属性配置
			
			    String[] urlPatterns() default {};//相当于<url-pattern>
			
			    int loadOnStartup() default -1;//相当于<load-on-startup>
			
			    WebInitParam[] initParams() default {};
			
			    boolean asyncSupported() default false;
			
			    String smallIcon() default "";
			
			    String largeIcon() default "";
			
			    String description() default "";
			
			    String displayName() default "";
			}

3.2.4. ServletConfig

当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig 对象中,并在调用 servlet 的 init() 方法时,将 ServletConfig 对象传递给 servlet 。进而,通过ServletConfig 对象就可以得到当前 servlet 的初始化参数。 参考链接

相关方法

  • String getServletName() – 获取当前Servlet在web.xml中配置的名字
  • String getInitParameter(String name) – 获取当前Servlet指定名称的初始化参数的值
  • Enumeration getInitParameterNames() – 获取当前Servlet所有初始化参数的名字组成的枚举
  • ervletContext getServletContext() – 获取代表当前web应用的ServletContext对象

1)初始化参数配置在<init-param>标签中

<servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:/conf/app-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

2)服务器会自动封装在ServletConfig中,我们利用init()或者init(ServletConfig config)方法,可以只获取一次ServletConfig对象,然后通过其getInitParameter()方法,获取配置好的参数。

ServletConfig 对象也可以直接通过父类 GenericServlet 封装好的 getServletConfig() 方法获取或直接通过父类封装的 getInitParameter() 直接获取。

@Override  
public void init(ServletConfig config) throws ServletException {  
      
    // 如果重写父类带参数的init()方法,这句一定写。  
    // 不然拿不到ServletConfig 对象,空指针。  
    // 原因在于,父类中代参的init()实现的时候,先this.config = config;然后调用无参this.init();  
    super.init(config);  
      
    String userName = config.getInitParameter("userName");  
      
    String userName2 = this.getInitParameter("userName");  
      
    String userName3 = this.getServletConfig().getInitParameter("userName");  
      
    logger.debug("初始化参数1: "+userName+" | "+"初始化参数2: "+userName2+" | 初始化参数3: "+userName3);  
      
}  
  
@Override  
public void init() throws ServletException {  
      
    String userName2 = this.getInitParameter("userName");  
      
    String userName3 = this.getServletConfig().getInitParameter("userName");  
      
    logger.debug("初始化参数4: "+userName2+" | 初始化参数5: "+userName3);  
      
}  

3)注意点:如果重写父类带参数的 init() 方法,这句一定写,super.init(config),不然拿不到 ServletConfig 对象,空指针。原因在于,父类中代参的init()实现的时候,先this.config = config;然后调用无参this.init()。其实你复写两个 init() 方法后,输出的顺序也解释了这一点。推荐使用不带参数的 init() 方法。父类代码以及上例输出如下:

 public void init(ServletConfig config) throws ServletException {  
    this.config = config;  
    this.init();  
 } 

4)通过 getInitParameterNames() ,可以获取枚举类型的所有初始化参数。

3.3. Request

  • request 和 response 对象是由服务器创建的。我们来使用它们
  • request 对象是来获取请求消息,response 对象是来设置响应消息

request对象继承体系结构:

	ServletRequest		--	接口
		|	继承
	HttpServletRequest	-- 接口
		|	实现
	org.apache.catalina.connector.RequestFacade 类(tomcat)

接下来我们将介绍他的功能

3.3.1.获取请求数据

获取请求行数据 :GET /day14/demo1?name=zhangsan HTTP/1.1

  • 获取请求方式 :GET
  • String getMethod()
  • 获取虚拟目录/day14
  • String getContextPath()
  • 获取 Servlet 路径: /demo1
    • String getServletPath()
  • 获取 get 方式请求参数:name=zhangsan
    • String getQueryString()
  • 获取请求URI/day14/demo1
    • String getRequestURI(): `/day14/demo1``
    • ``StringBuffer getRequestURL()http://localhost/day14/demo1`
    • URL:统一资源定位符 :http://localhost/day14/demo1 xxxxx医院
    • URI:统一资源标识符:/day14/demo1 医院
  • 获取协议及版本:`HTTP/1.1``
    • ``String getProtocol()`
  • 获取客户机的IP地址:String getRemoteAddr()

获取请求头数据

  • String getHeader(String name)通过请求头的名称获取请求头的值
  • Enumeration<String> getHeaderNames():获取所有的请求头名称

获取请求体数据

请求体:只有 POST 请求方式,才有请求体,在请求体中封装了POST请求的请求参数

获取流对象,再从流对象中拿数据

  • BufferedReader getReader():获取字符输入流,只能操作字符数据
  • ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据

3.3.2.获取请求参数通用方式

不论 get 还是 post 请求方式都可以使用下列方法来获取请求参数

  • String getParameter(String name):根据参数名称获取参数值 username=zs&password=123
  • String[] getParameterValues(String name):根据参数名称获取参数值的数组 hobby=xx&hobby=game
  • Enumeration<String> getParameterNames():获取所有请求的参数名称
  • Map<String,String[]> getParameterMap():获取所有参数的 map集合

中文乱码

  • get 方式:tomcat 8 已经将 get 方式乱码问题解决了
  • post方式:会乱码,在获取参数前,设置 request 的编码 request.setCharacterEncoding("utf-8");

3.3.3.请求转发

一种在服务器内部的资源跳转方式

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

特点

  • 浏览器地址栏路径不发生变化
  • 只能转发到当前服务器内部资源中
  • 转发是一次请求

3.3.4.其它功能

共享数据

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

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

相关方法

  • void setAttribute(String name,Object obj):存储数据
  • Object getAttitude(String name):通过键获取值
  • void removeAttribute(String name):通过键移除键值对
  • ServletContext getServletContext()获取ServletContext

3.4. Response 对象

3.4.1.设置响应消息

设置响应行

  • 格式:HTTP/1.1 200 ok
  • 设置状态码:setStatus(int sc)

设置响应头

  • setHeader(String name, String value)

设置响应体

获取输出流,使用输出流,将数据输出到客户端浏览器

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

3.4.2.重定向与转发

重定向:资源跳转的方式

			//1. 设置状态码为302
	        response.setStatus(302);
	        //2.设置响应头location
	        response.setHeader("location","/day15/responseDemo2");
	        /简单的重定向方法
	        response.sendRedirect("/day15/responseDemo2");

过程分析

重定向过程:

​ 客户浏览器发送http请求——>web服务器接受后发送302状态码响应及对应新的location给客户浏览器——>客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址——>服务器根据此请求寻找资源并发送给客户。

​ 在这里location可以重定向到任意URL, 既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器路径栏显示的是其重定向 的路径,客户可以观察到地址的变化的。 重定向行为是浏览器做了至少两次的访问请求的。

转发过程:

	客户浏览器发送http请求——>web服务器接受此请求——>调用内部的一个方法在容器内部完成请求处理和转发动作——>将目标资源发送给客户;

​ 在这里,转发的路径必须是同一个web容器下的url,其不能转向到其他的web路径上去,中间传递的是自己的容器内的request。 在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。

重定向的特点:redirect

  • 地址栏发生变化
  • 重定向可以访问其他站点(服务器)的资源
  • 重定向是两次请求。不能使用 request 对象来共享数据

转发的特点:forward

  • 转发地址栏路径不变
  • 转发只能访问当前服务器下的资源
  • 转发是一次请求,可以使用 request 对象来共享数据

相对路径:通过相对路径不可以确定唯一资源

不以/开头,以.开头路径,如:./index.html

规则:找到当前资源和目标资源之间的相对位置关系

  • ./:当前目录
  • ../:后退一级目录

绝对路径:通过绝对路径可以确定唯一资源

/开头的路径如:

  • 绝对路径:http://localhost/day15/responseDemo2
  • 相对路径:/day15/responseDemo2

转发还是重定向?

规则:判断定义的路径是给谁用的?判断请求将来从哪儿发出

  • 给客户端浏览器使用:需要加虚拟目录(项目的访问路径)
    • 建议虚拟目录动态获取:request.getContextPath()
    • <a> , <form> 重定向…
    • 给服务器使用:不需要加虚拟目录
      * 转发路径

乱码问题

设置编码,是在获取流之前设置:response.setContentType("text/html;charset=utf-8");

3.5. ServletContext 对象

概述

  • 生命周期:Web容器在启动时,它会为每个Web应用程序都创建一个对应的 ServletContext 对象,它代表当前Web应用。ServletContext对象只在Web应用被关闭时才被销毁;不同的Web应用,ServletContext各自独立存在。
  • 只要在同一个项目里面都可以取
  • 作用:由于一个Web应用中的所有 Servlet 共享同一个 ServletContext 对象,因此 Servlet 对象之间可以通过ServletContext 对象来实现通讯。 ServletContext 对象通常也对称之为 context 域对象

更多详细,参考原文链接

ServletContext 的获取

  • 通过SevletConfig 对象 SevletConfig.getServletContext() 方法
  • 直接调用封装好的 getServletContext ()方法即可获取。
    • 通过 HttpServlet 获取 this.getServletContext();
    • 通过 request 对象获取 request.getServletContext();

接下来我们介绍几个它的功能

获取MIME类型

MIME 类型:在互联网通信过程中定义的一种文件数据类型

  • 格式: 大类型/小类型 text/html image/jpeg
  • 获取:String getMimeType(String file)

域对象:共享数据

  • setAttribute(String name,Object value)
  • getAttribute(String name)
  • removeAttribute(String name)

ServletContext 对象范围:所有用户所有请求的数据

获取文件的真实(服务器)路径

  • String getRealPath(String path)

4. Filter 过滤器

web 中的过滤器:当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能。如:登录验证、统一编码处理、敏感字符过滤…

快速入门

  1. 定义一个类,实现接口Filter
  2. 复写方法
  3. 配置拦截路径
    • web.xml
    • 注解
	@WebFilter("/*")//访问所有资源之前,都会执行该过滤器
		//实现 Filter 类
    public class LogFilter implements Filter  {
        public void  init(FilterConfig config) throws ServletException {
            // Filter 的 init 方法中提供了一个 FilterConfig 对象。
            //获取初始化参数
            String site = config.getInitParameter("Site"); 

            // 输出初始化参数
            System.out.println("网站名称: " + site); 
        }
        public void  doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException {

            // 输出站点名称
            System.out.println("站点网址:http://www.runoob.com");

            // 把请求传回过滤链
            chain.doFilter(request,response);
        }
        public void destroy( ){
            /* 在 Filter 实例被 Web 容器从服务移除之前调用 */
        }
    }

一些细节

web.xml 配置

	<web-app>  
		<filter>
          <filter-name>LogFilter</filter-name>
          <filter-class>com.runoob.test.LogFilter</filter-class>
          <init-param>
            <param-name>Site</param-name>
            <param-value>菜鸟教程</param-value>
          </init-param>
        </filter>
		<!-- 一个过滤器,配置一个 filter-mapping -->
	    <filter-mapping>
	        <filter-name>>LogFilter</filter-name>
			<!-- 拦截路径 -->
	        <url-pattern>/*</url-pattern>
	    </filter-mapping>

        <servlet>  
          <!-- 类名 -->  
          <servlet-name>DisplayHeader</servlet-name>  
          <!-- 所在的包 -->  
          <servlet-class>com.runoob.test.DisplayHeader</servlet-class>  
        </servlet>  
        <servlet-mapping>  
          <servlet-name>DisplayHeader</servlet-name>  
          <!-- 访问的网址 -->  
          <url-pattern>/TomcatTest/DisplayHeader</url-pattern>  
        </servlet-mapping>  
       </web-app>  

过滤器生命周期方法

  1. init:在服务器启动后,会创建Filter对象,然后调用 init 方法。只执行一次。用于加载资源
  2. doFilter:每一次请求被拦截资源时,会执行。执行多次
  3. destroy:在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行 destroy 方法。只执行一次。用于释放资源

过滤器配置详解

拦截路径配置:

  1. 具体资源路径: /index.jsp 只有访问index.jsp资源时,过滤器才会被执行
  2. 拦截目录: /user/* 访问/user下的所有资源时,过滤器都会被执行
  3. 后缀名拦截: *.jsp 访问所有后缀名为 jsp 资源时,过滤器都会被执行
  4. 拦截所有资源:/* 访问所有资源时,过滤器都会被执行

拦截方式配置:资源被访问的方式

  • 注解配置:设置dispatcherTypes属性
    • REQUEST:默认值。浏览器直接请求资源
    • FORWARD:转发访问资源
    • INCLUDE:包含访问资源
    • ERROR:错误跳转资源
    • ASYNC:异步访问资源
  • web.xml配置:设置<dispatcher></dispatcher>标签即可,可以设置多个

过滤器执行流程

  1. 执行过滤器
  2. 执行放行后的资源
  3. 回来执行过滤器放行代码下边的代码

过滤器链(配置多个过滤器)

执行顺序:如果有两个过滤器:过滤器1和过滤器2

  1. 过滤器1
  2. 过滤器2
  3. 资源执行
  4. 过滤器2
  5. 过滤器1

过滤器先后顺序问题

  1. 注解配置:按照类名的字符串比较规则比较,值小的先执行
    * 如: AFilter 和 BFilter,AFilter就先执行了。
  2. web.xml配置: <filter-mapping>谁定义在上边,谁先执行
  3. 如果同时配置,xml 的先执行

案例:过滤敏感词汇

过滤敏感词汇

5. Listener 监听器

web 的三大组件(Servlet、Filter、Listener)之一。

事件监听机制

	* 事件	:一件事情
  • 事件源 :事件发生的地方
  • 监听器 :一个对象
  • 注册监听:将事件、事件源、监听器绑定在一起。 当事件源上发生某个事件后,执行监听器代码

ServletContextListener: 监听 ServletContext 对象的创建和销毁

  • void contextDestroyed(ServletContextEvent sce) :ServletContext 对象被销毁之前会调用该方法
  • void contextInitialized(ServletContextEvent sce):ServletContext 对象创建后会调用该方法

配置

  1. web.xml
<listener>					 <listener-class>cn.leyou.web.listener.ContextLoaderListener</listener-class>
</listener>
  1. 注解
  • @WebListener

6. 线程安全问题

参考链接

Servlet 不是线程安全的。

每一个 Servlet 对象再Tomcat容器中只有一个实例对象,即是单例模式,共享一个对象

ServletRequest 对象是线程安全的

对于每一个请求由一个工作线程来执行,都会创建一个

ServletContext 是线程不安全

ServletContext:它是线程不安全的,多线程下可以同时进行读写,因此我们要对其读写操作进行同步或者深度的clone。

HttpSession:同样是线程不安全的,和 ServletContext 的操作一样。

Application对象也是线程不安全的

Application 对象用于存储和访问来自任何页面的变量,类似于 session 对象。不同之处在于,所有的用户分享一个 Application 对象,而 session 对象和用户的关系是一一对应的。

Spring 中 bean对象的作用域

默认 spring 容器中的 bean 是单例的。当单例中存在竞态条件,即有线程安全问题。spring 并不能保证 bean 的线程安全。

作用域 说明
singleton 默认的作用域,这种情况下的bean都会被定义为一个单例对象,该对象的生命周期是与Spring IOC容器一致的(但出于Spring懒加载机制,只有在第一次被使用时才会创建)
prototype bean被定义为在每次注入时都会创建一个新的对象
request bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象
session bean被定义为在一个session的生命周期内创建一个单例对象
application bean被定义为在ServletContext的生命周期中复用一个单例对象
websocket bean被定义为在websocket的生命周期中复用一个单例对象

7.日志组件

此处参考

第一步:导入log4j-1.2.15.jar(版本自定)包,在src目录下创建src/com/company/resource/log4J.properties文件。

  • log4j.appender.INFO.File指定日志文件存储目录;
  • log4j.appender.INFO.layout.ConversionPattern形成的日志不仅包括打印的日志信息,还会存储日志产生的时间和产生日志的方法(具体参数可百度
log4j.rootLogger=DEBUG,CONSOLE,A,INFO,ERROR,WARN
log4j.addivity.org.apache=false

#console
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=DEBUG
log4j.appender.CONSOLE.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
log4j.appender.CONSOLE.Target=System.log
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout

#info
#log4j.logger.INFO=INFO
log4j.appender.INFO=org.apache.log4j.DailyRollingFileAppender  
log4j.appender.INFO.File= ${catalina.home}/logs/CcbClient/info/info.log
log4j.appender.INFO.Append=true
log4j.appender.INFO.Threshold=INFO
log4j.appender.INFO.layout=org.apache.log4j.PatternLayout  
log4j.appender.INFO.layout.ConversionPattern=[CcbClient]   [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
log4j.appender.INFO.filter.F1=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.INFO.filter.F1.LevelMin=INFO
log4j.appender.INFO.filter.F1.LevelMax=INFO

#debug
#log4j.logger.DEBUG=DEBUG
log4j.appender.A=org.apache.log4j.DailyRollingFileAppender  
log4j.appender.A.File= ${catalina.home}/logs/CcbClient/debug/debug.log
log4j.appender.A.Append=true
log4j.appender.A.Threshold=DEBUG
log4j.appender.A.layout=org.apache.log4j.PatternLayout  
log4j.appender.A.layout.ConversionPattern=[CcbClient]   [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
log4j.appender.A.filter.F1=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.A.filter.F1.LevelMin=DEBUG
log4j.appender.A.filter.F1.LevelMax=DEBUG

#error
#log4j.logger.ERROR=ERROR
log4j.appender.ERROR=org.apache.log4j.DailyRollingFileAppender  
log4j.appender.ERROR.File=${catalina.home}/logs/CcbClient/error/error.log
log4j.appender.ERROR.Append=true
log4j.appender.ERROR.Threshold=ERROR
log4j.appender.ERROR.layout=org.apache.log4j.PatternLayout  
log4j.appender.ERROR.layout.ConversionPattern=[CcbClient]  [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
log4j.appender.ERROR.filter.F1=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.ERROR.filter.F1.LevelMin=ERROR
log4j.appender.ERROR.filter.F1.LevelMax=ERROR

#WANR
log4j.appender.WARN=org.apache.log4j.DailyRollingFileAppender  
log4j.appender.WARN.File=${catalina.home}/logs/CcbClient/warn/warn.log
log4j.appender.WARN.Append=true
log4j.appender.WARN.Threshold=WARN
log4j.appender.WARN.layout=org.apache.log4j.PatternLayout  
log4j.appender.WARN.layout.ConversionPattern=[CcbClient]   [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
log4j.appender.WARN.filter.F1=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.WARN.filter.F1.LevelMin=WARN
log4j.appender.WARN.filter.F1.LevelMax=WARN

该配置可以在控制台或者后台文件中输出日志记录。info,error,debug都可以输出。导出文件路径自己可以修改,格式内容自己也可以修改。

第二步:在 src/util 下创建 log4j 加载文件(servlet)

public class Log4jInit extends HttpServlet {  
    private static final long serialVersionUID = 1L;  
  
    @Override  
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException {  
        // TODO Auto-generated method stub  
        super.doGet(req, resp);  
    }  
  
    @Override  
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException {  
        // TODO Auto-generated method stub  
        super.doPost(req, resp);  
    }  
  
    @Override  
    public void init() throws ServletException {  
        // TODO Auto-generated method stub  
        super.init();  
        String prefix = getServletContext().getRealPath("/");  
        String file = getInitParameter("log4j-init-file");  
        if (file != null) {  
            System.out.println("read log4j.properties:"+prefix + file);  
            PropertyConfigurator.configure(prefix + file);  
        }  
    }  
  
}

第三步:在 web.xml 中配置 log4j 加载 servlet 路径,项目启动时进行加载

	<servlet>
	    <servlet-name>log4j-init</servlet-name>
	    <servlet-class>util.Log4jInit</servlet-class>
	    <init-param>
	      <param-name>log4j-init-file</param-name>
	      <param-value>\WEB-INF\classes\com\company\resource\log4J.properties</param-value>
	    </init-param>
	    <load-on-startup>1</load-on-startup>
	  </servlet>

第四步:在需要输入日志信息的类中,创建 log 对象进行引用

	public  Logger log=Logger.getLogger(GetAccountInfo.class);   
		log.info("something");
		log.debug("something");
		log.warn("something");
		log.error("something");

8.整合 JDBC

用户登录案例需求:

  1. 编写 login.html 登录页面 username & password 两个输入框
  2. 使用 Druid 数据库连接池技术,操作mysql,day14 数据库中 user 表
  3. 使用 JdbcTemplate 技术封装JDBC
  4. 登录成功跳转到 SuccessServlet 展示:登录成功!用户名,欢迎您
  5. 登录失败跳转到 FailServlet 展示:登录失败,用户名或密码错误

创建项目,导入html页面,配置文件,jar包

创建数据库环境

		CREATE DATABASE day14;
		USE day14;
		CREATE TABLE USER(		
			id INT PRIMARY KEY AUTO_INCREMENT,
			username VARCHAR(32) UNIQUE NOT NULL,
			PASSWORD VARCHAR(32) NOT NULL
		);

创建包 cn.leyou.domain ,创建类 User

		/**
		 * 用户的实体类
		 */
		public class User {
		
		    private int id;
		    private String username;
		    private String password;
            //省略 getter,setter及tostring
		   }

创建包 cn.leyou.util ,编写工具类 JDBCUtils

/**
		 * JDBC工具类 使用Durid连接池
		 */
		public class JDBCUtils {
		
		    private static DataSource ds ;
		
		    static {
		
		        try {
		            //1.加载配置文件
		            Properties pro = new Properties();
		            //使用ClassLoader加载配置文件,获取字节输入流
		            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();
		        }
		    }
		
		    /**
		     * 获取连接池对象
		     */
		    public static DataSource getDataSource(){
		        return ds;
		    }
		    
		    /**
		     * 获取连接Connection对象
		     */
		    public static Connection getConnection() throws SQLException {
		        return  ds.getConnection();
		    }
		}

创建包 cn.leyou.dao ,创建类UserDao,提供login方法


		/**
		 * 操作数据库中User表的类
		 */
		public class UserDao {
		
		    //声明JDBCTemplate对象共用
		    private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
		
		    /**
		     * 登录方法
		     * @param loginUser 只有用户名和密码
		     * @return user包含用户全部数据,没有查询到,返回null
		     */
		    public User login(User loginUser){
		        try {
		            //1.编写sql
		            String sql = "select * from user where username = ? and password = ?";
		            //2.调用query方法
		            User user = template.queryForObject(sql,
		                    new BeanPropertyRowMapper<User>(User.class),
		                    loginUser.getUsername(), loginUser.getPassword());
		                    return user;
		        } catch (DataAccessException e) {
		            e.printStackTrace();//记录日志
		            return null;
		        }
		    }
		}

使用 queryForObject 需注意:

  • 其实支持的是标量子查询,只能传入一个基本类型的包装类的class,并返回一个基本类型对应包装类型的对象.
  • 如果 sql 语句查询出0条或者多条数据的话, queryForObject 会抛出 EmptyResultDataAccessExceptionIncorrectResultSetColumnCountException的异常,而如果干脆使用方法 query ,或者 queryForList 则可以在编码中处理掉这种问题,而无需 try-catch

编写 cn.leyou.web.servlet.LoginServlet类

		@WebServlet("/loginServlet")
		public class LoginServlet extends HttpServlet {
			@Override
		    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		        //1.设置编码
		        req.setCharacterEncoding("utf-8");
		        //2.获取请求参数
		        String username = req.getParameter("username");
		        String password = req.getParameter("password");
		        //3.封装user对象
		        User loginUser = new User();
		        loginUser.setUsername(username);
		        loginUser.setPassword(password);
		
		        //4.调用UserDao的login方法
		        UserDao dao = new UserDao();
		        User user = dao.login(loginUser);
		
		        //5.判断user
		        if(user == null){
		            //登录失败
		            req.getRequestDispatcher("/failServlet").forward(req,resp);
		        }else{
		            //登录成功
		            //存储数据
		            req.setAttribute("user",user);
		            //转发
		            req.getRequestDispatcher("/successServlet").forward(req,resp);
		        }
		
		    }
		
		    @Override
		    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		        this.doGet(req,resp);
		    }
		}

编写FailServlet和SuccessServlet类

		@WebServlet("/successServlet")
		public class SuccessServlet extends HttpServlet {
		    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		        //获取request域中共享的user对象
		        User user = (User) request.getAttribute("user");
		
		        if(user != null){
		            //给页面写一句话
		
		            //设置编码
		            response.setContentType("text/html;charset=utf-8");
		            //输出
		            response.getWriter().write("登录成功!"+user.getUsername()+",欢迎您");
		        }
           }
		@WebServlet("/failServlet")
		public class FailServlet extends HttpServlet {
		    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		        //给页面写一句话
		
		        //设置编码
		        response.setContentType("text/html;charset=utf-8");
		        //输出
		        response.getWriter().write("登录失败,用户名或密码错误");
		
		    }
		
		    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		        this.doPost(request,response);
		    }
		}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Web服务器 的相关文章

  • 使用 JDBC 获取 Oracle 11g 的最后插入 ID

    我是使用 Oracle 的新手 所以我将放弃之前已经回答过的内容这个问题 https stackoverflow com questions 3131064 get id of last inserted record in oracle
  • “java.io.IOException:连接超时”和“SocketTimeoutException:读取超时”之间有什么区别

    如果我设置一个套接字 SoTimeout 并从中读取 当读取时间超过超时限制时 我会收到 SocketTimeoutException 读取超时 这是我的例子中的堆栈 java net SocketTimeoutException Read
  • 使用 WebDriver 单击新打开的选项卡中的链接

    有人可以在这种情况下帮助我吗 场景是 有一个网页 我仅在新选项卡中打开所有指定的链接 现在我尝试单击新打开的选项卡中的任何一个链接 在下面尝试过 但它仅单击主 第一个选项卡中的一个链接 而不是在新选项卡中 new Actions drive
  • (Java) App Engine 中的静态文件无法访问

    The 示例文档 http code google com appengine docs java gettingstarted staticfiles html表示您只需将文件放在 war 或子目录 中 并且应该可以从主机访问它们 只要它
  • HAProxy SSL终止+客户端证书验证+curl/java客户端

    我希望使用我自己的自签名证书在 HAProxy 上进行 SSL 终止 并使用我创建的客户端证书验证客户端访问 我通过以下方式创建服务器 也是 CA 证书 openssl genrsa out ca key 1024 openssl req
  • 文本在指定长度后分割,但不要使用 grails 打断单词

    我有一个长字符串 需要将其解析为长度不超过 50 个字符的字符串数组 对我来说 棘手的部分是确保正则表达式找到 50 个字符之前的最后一个空格 以便在字符串之间进行彻底的分隔 因为我不希望单词被切断 public List
  • 在 Struts 2 中传递 URL 参数而不使用查询字符串

    我想使用类似的 URL host ActionName 123 abc 而不是像这样传递查询字符串 host ActionName parm1 123 parm2 abc 我怎样才能在 Struts 2 中做到这一点 我按照下面的方法做了
  • 为什么Iterator接口没有add方法

    In IteratorSun 添加了remove 方法来删 除集合中最后访问的元素 为什么没有add方法来向集合中添加新元素 它可能对集合或迭代器产生什么样的副作用 好的 我们开始吧 设计常见问题解答中明确给出了答案 为什么不提供 Iter
  • 如何检测图像是否像素化

    之前有人在 SO 上提出过这样的问题 在Python中检测像素化图像 https stackoverflow com questions 12942365 detecting a pixelated image in python还有关于q
  • Android蓝牙java.io.IOException:bt套接字已关闭,读取返回:-1

    我正在尝试编写一个代码 仅连接到运行 Android 5 0 KitKat 的设备上的 目前 唯一配对的设备 无论我尝试了多少方法 我仍然会收到此错误 这是我尝试过的最后一个代码 它似乎完成了我看到人们报告为成功的所有事情 有人能指出我做错
  • 是否可以从 servlet 内部以编程方式设置请求上下文路径?

    这是一个特殊情况 我陷入了处理 企业 网络应用程序的困境 企业应用程序正在调用request getContext 并将其与另一个字符串进行比较 我发现我可以使用 getServletContext getContextPath 获取 se
  • 从休眠乐观锁定异常中恢复

    我有一个这样的方法 Transactional propagation Propagation REQUIRES NEW public void doSomeWork Entity entity dao loadEntity do some
  • 通过 appassembler-maven-plugin 生成的脚本无法在 Spring Boot 应用程序中找到主类

    我使用 appassembler maven plugin 生成的启动脚本有问题 我有一个基本的 spring boot 应用程序 只有一个类 SpringBootApplication public class ScriptDemoApp
  • 用于缓存的 Servlet 过滤器

    我正在创建一个用于缓存的 servlet 过滤器 这个想法是将响应主体缓存到memcached 响应正文由以下方式生成 结果是一个字符串 response getWriter print result 我的问题是 由于响应正文将不加修改地放
  • IntelliJ 组织导入

    IntelliJ 是否具有类似于 Eclipse 中的组织导入功能 我拥有的是一个 Java 文件 其中多个类缺少导入 例子 package com test public class Foo public Map map public J
  • Lombok @Builder 不创建不可变对象?

    在很多网站上 我看到 lombok Builder 可以用来创建不可变的对象 https www baeldung com lombok builder singular https www baeldung com lombok buil
  • 使用Java绘制维恩图

    我正在尝试根据给定的布尔方程绘制维恩图 例如 a AND b AND c我想在 Android 手机上执行此操作 因此我需要找到一种使用 Java 来执行此操作的方法 我找到了一个完美的小部件 它可以完成我在这方面寻找的一切布尔代数计算器
  • 如何让 Emma 或 Cobertura 与 Maven 一起报告其他模块中源代码的覆盖率?

    我有一个带有 Java 代码的多模块 Maven 设置 我的单元测试在其中一个模块中测试多个模块中的代码 当然 这些模块具有相互依赖性 并且在测试执行之前根据需要编译所有相关模块中的代码 那么 如何获得整个代码库覆盖率的报告 注意 我不是问
  • Hadoop NoSuchMethodError apache.commons.cli

    我在用着hadoop 2 7 2我用 IntelliJ 做了一个 MapReduce 工作 在我的工作中 我正在使用apache commons cli 1 3 1我把库放在罐子里 当我在 Hadoop 集群上使用 MapReduceJob
  • 替换文件中的字符串

    我正在寻找一种方法来替换文件中的字符串而不将整个文件读入内存 通常我会使用 Reader 和 Writer 即如下所示 public static void replace String oldstring String newstring

随机推荐

  • 《Pytorch深度学习和图神经网络(卷 1)》学习笔记——第八章

    本书之后的内容与当前需求不符合不再学习 信息熵与概率的计算关系 联合熵 条件熵 交叉熵 相对熵 KL散度 JS散度 互信息 无监督学习 监督训练中 模型能根据预测结果与标签差值来计算损失 并向损失最小的方向进行收敛 无监督训练中 无法通过样
  • 如何添加PYNQ-Z2板文件到Vivado

    添加板文件到vivado 先下载pynq z2板文件 PYNQZ2板文件 含约束文件 原理图 zip 下载后将文件复制到 Vivado安装目录 2018 3 data boards board files 重启vivado 完成
  • 重磅!AI与区块链技术知识分享交流会!特邀贾志刚老师、双一流211高校研究生!

    重磅 AI与区块链技术第一次知识交流分享会即将拉开帷幕 本交流会旨在分享交流人工智能 区块链相关内容 包括基础知识分享 前沿论文分享 具体项目实战 提供一个相同领域学者 工作人员在线交流机会 更多精彩内容 尽在微信公众号 AI与区块链技术
  • PCA算法

    https www cnblogs com dengdan890730 p 5495078 html PCA算法是怎么跟协方差矩阵 特征值 特征向量勾搭起来的 PCA Principle Component Analysis 主成份分析 是
  • 2023年7月31日-8月6日,(上午熟悉公司代码,周一到周五晚上优先工作所急视频教程,其他业余时间进行ue视频教程,为独立游戏做准备,本周10小时,合计2199小时,剩余7801小时)

    按照规划 上午熟悉公司源码 下午进行filament和ue渲染 晚上写工作代码 回家后泛读pbrt或者其他书籍催眠 业余学习ue的各种视频教程 为独立游戏做准备 累了就学其他视频教程 随意 可以按照ue 渲染 gt ue osg gt ue
  • testdbg-测试调试器

    http baidutech blog 51cto com 4114344 743464 testdbg 测试调试器 2011 02 24 14 07 00 标签 测试 休闲 调试器 职场 testdbg 原创作品 允许转载 转载时请务必以
  • C#基础与Java的对比

    一 C 简介 C 是微软公司发布的一种面向对象的 运行于 NET Framework之上的高级程序设计语言 C 看起来与Java有着惊人的相似 它包括了诸如单一继承 接口 与Java几乎同样的语法和编译成中间代码再运行的过程 但是C 与Ja
  • REGEXP基础语法

    个人使用https regex101 com 这个比较多一些 大家可以使用 基础用法 限定符 Quantlfier a a出现0次或者多次 a a出现1次或者多次 a a出现0次或者1次 a 6 a出现6次 a 2 6 a出现2 6次 a
  • Labview设计计算机--组合逻辑与时序逻辑(5)

    在写接下来的内容之前 我要强烈推荐一部书 编码 隐匿在计算机软硬件背后的语言 这是一本由浅入深的书 我也是借鉴了书中的很多想法才有勇气尝试在Labview上构建一个简单的计算机 看这本书的门槛不高 大概有高中知识就差不多了 ok 进入正题
  • angular下载文件

    1 window open 打开新页面下载文件 window open url self 优点 最简洁 缺点 当参数错误时 或其它原因导致接口请求失败 这时页面会停留在新打开的页面中 无法监听到接口返回的错误信息 只在页面中直接输出错误 尽
  • 查看linux安装了哪些编译器,如何查看linux已安装的编译器及其版本

    在Linux环境下 新建一个终端 之后在终端直接输入命令 arm linux gcc v 回车之后 即可出现如下的一些信息 此处安装的是交叉编译器gcc 版本4 2 2 Using built in specs Target arm unk
  • 超详细,AI绘画里你不得不知道的SD算法详解

    前言 哈喽 各位小伙伴们大家好 说到AI绘画 可谓是近几年来异军突起 犹如洪水猛兽一般 各种的本土化 商业化 但是相信也有很多朋友跟我一样 对AI绘画的原理一知半解 甚至根本不知道它是怎么工作的 这样只靠着在网上复制粘贴别人的prompt
  • java自学笔记6:UML简介+阶段练习

    1 UML概念 Unified Modeling language UML 又称统一建模语音或标准建模语言 是一个支持模型化和软件系统开发的图形化语言 为软件开发的所有阶段提供模型化和可视化支持 2 UML图示 UML2 2中一共定义了14
  • 使vue组件居中、中下

    position absolute left 50 top 50 transform translate 50 50 中下 居中往下 login form position absolute 绝对定位 bottom 0px 最底下 widt
  • 相机成像 - ISP之RGB域处理

    在之前的文章中 介绍了ISP 的基本流程和RAW域中的处理方法 完美成像中最重要的环节 ISP之RGB域处理 今天延续之前的内容 讲解中间的部分 RGB域的处理 ISP的流程具体如图所示 我们继续围绕这个图把RGB域中每个部分的处理给大家讲
  • 【最远点采样FPS】点云采样方式(一) — 最远点采样

    本文为博主原创文章 未经博主允许不得转载 本文为专栏 python三维点云从基础到深度学习 系列文章 地址为 https blog csdn net suiyingy article details 124017716 点云最远点采样FPS
  • CC2640R2F低功耗蓝牙芯片相关设计分享

    电路设计简介 CC2640 的 RF 差分线越短越好 做差分 100 阻抗匹配 天线部分阻抗 50 欧姆匹配 本次设计采用了陶瓷天线 AN9520 245 减少天线面积 蓝牙芯片在底层 阻抗参考平面第三层 在天线部分下方覆铜 GND 使用嘉
  • nginx配置部署一个域名,多个端口

    最近用基于windows下的nginx部署了服务器 1 安装好windows下的nginx以后 会有以下文件 找到conf下的nginx 此文件为nginx的配置文件 2 初始只有一个默认80端口 这是nginx的默认端口号 server
  • 高并发的epoll+线程池,业务在线程池内

    epoll是linux下高并发服务器的完美方案 因为是基于事件触发的 所以比select快的不只是一个数量级 单线程epoll 触发量可达到15000 但是加上业务后 因为大多数业务都与数据库打交道 所以就会存在阻塞的情况 这个时候就必须用
  • Web服务器

    文章目录 1 HTTP 协议 1 1 概述 1 2 URI和URL的区别 1 3 请求消息 Request 1 4 响应消息 Response 1 5 状态码 1 6 HTTP 1 0 和 HTTP 1 1 1 7 Cookie 1 8 S