JavaWeb基础

Servlet

Servlet中重要对象:

  1. HttpServletRequest对象:封装请求信息

  2. HttpServletResponse对象:封装响应信息

  3. ServletConfig对象: 封装一个servlet配置参数信息

  4. ServletContext对象: 封装web应用环境信息

ServletContext(主要)

ServletContext叫Servlet上下文对象,该对象表示当前的web应用环境信息。一个web应用只会创建一个ServletContext对象。

创建时间

ServletContext对象是在tomcat服务器加载完当前web应用后创建出来。ServletContext对象是作为ServletConfig对象成员变量传入servlet中。通过ServletConfig的getServletContext()方法得到ServletContext对象。

  隐藏对象application是javax.servlet.ServletContext类的对象。application封装JSP所在Web应用程序的信息,例如web.xml中国配置的全局的初始化信息。Servlet中application对象需要通过ServletConfig.getServletContext()来获取。整个Web应用程序对应一个application对象。application对象常用的方法如下:

  1. Object getAttribute(String name)  返回application中属性为name的对象
  2. Enumeration getAttributeNames()   返回application中的所有属性名
  3. void setAttribute(String name,Object value)  设置application属性
  4. void removeAttribute(String name)   移除application属性
  5. String getInitParameter(String name)  返回全局初始话函数
  6. Enumeration getInitParameterNames()  返回所有的全局初始话参数
  7. String getMimeType(String filename)  返回文件的文档类型,例如getMimeType(“abc.html”)将返回“text.html”

  8. String getRealPath(String relativePath)  返回Web应用程序内相对网址对应的绝对路径

servlet特点

  1. servlet就是一个普通的java类,继承HttpServlet类

  2. 一个普通的java类实现了Servlet接口,也叫Servlet程序。我们通常继承HttpServlet是为了创建一个基于http协议的servlet程序。

  3. servlet程序交给tomcat服务器运行

一些内容

Servlet线程安全问题

servlet对象特点: 在tomcat服务器中是单实例多线程的

引发Servlet多线程问题:

多个线程同时操作了Servlet的成员变量(共享数据)。

避免Servlet并发问题建议:

  1. 尽量不要在servlet类中使用成员变量。

  2. 如果要使用成员变量,那么就要给使用到成员变量的代码块加上代码锁,尽量缩小同步锁的范围,以避免因为同步产生代码并发执行效率降低的问题。

Servlet不是线程安全的,多线程并发的读写会导致数据不同步的问题。 解决的办法是尽量不要定义name属性,而是要把name变量分别定义在doGet()和doPost()方法内。虽然使用synchronized(name){}语句块可以解决问题,但是会造成线程的等待,不是很科学的办法。 注意:多线程的并发的读写Servlet类属性会导致数据不同步。但是如果只是并发地读取属性而不写入,则不存在数据不同步的问题。因此Servlet里的只读属性最好定义为final类型的。

web.xml的servlet简单配置,在servlet3.0以后就可以直接在servlet类上加@WebServlet来配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 浏览器访问: http://localhost:8080/test/hello -->
<!-- 配置一个servlet -->
<!-- servlet的配置 -->
<servlet>
<!-- servlet内部名称。可以自定义 -->
<servlet-name>HelloServlet</servlet-name>
<!-- servlet类的全名:包名+简单类名 -->
<servlet-class>cn.footman.HelloServlet</servlet-class>
<!-- 正整数:数值越大,创建对象的优先级越低 -->
<load-on-startup>1</load-on-startup>

</servlet>

<!-- servlet的映射配置 -->
<servlet-mapping>
<!-- sevlet内部名称。和servlet配置的内部名称保持一致!! -->
<servlet-name>HelloServlet</servlet-name>
<!-- servlet的路径映射。访问servlet的名称 -->
<url-pattern>/hello</url-pattern>
</servlet-mapping>

@WebServlet常用属性 servlet3.0 api

属性 类型 是否必须 说明
asyncSupported boolean 指定Servlet是否支持异步操作模式
displayName String 指定Servlet显示名称
initParams WebInitParam[] 配置初始化参数
loadOnStartup int 标记容器是否在应用启动时就加载这个Servlet
name String 指定Servlet名称
urlPatterns/value String[] 这两个属性作用相同,指定Servlet处理的url

Sevlet缺省路径

/ : 表示servlet的缺省路径。

在tomcat服务器中配置了一个DefaultServlet,叫默认Servlet,该默认Servlet的url-pattern就是/ 。默认Servlet的作用,用于解析web应用下的静态资源。

浏览器输入一个资源名称时,查找资源的顺序是如何?

  1. 首先,在当前web应用下的web.xml文件中查找是否有匹配的url-pattern

  2. 如果匹配到,执行对应的servlet(动态资源)

  3. 如果没有匹配到,就交给tomcat服务器的默认Servlet去处理

  4. 默认Servlet会到当前web应用下读取对应名称的静态资源文件。

  5. 如果读到对应的静态资源文件,那么就把内容返回给浏览器

  6. 如果读不到对应的静态资源文件,那么就返回404的错误页面。

先找动态资源,再找静态资源

Servlet生命周期

servlet的生命周期由tomcat服务器控制的。

Servlet的四个生命周期

  • 构造方法: 在创建servlet对象时调用。只调用1次。证明servlet对象在tomcat服务器中是单实例的。

  • init方法:在创建完servlet对象后调用。只调用1次。

  • service方法: 在每次请求servlet时调用。调用n次。

  • destroy方法: servlet对象销毁时调用。只调用1次。tomcat服务器停止或web应用重新部署时调用

Servlet和CGI的区别

概括来讲,Servlet可以完成和CGI相同的功能。

​ CGI(Common Gateway Interface通用网关接口)程序来实现数据在Web上的传输,使用的是如Perl这样的语言编写的,它对于客户端作出的每个请求,必须创建CGI程序的一个新实例,这样占用大量的内存资源。由此才引入了Servlet技术。

​ Servlet是一个用java编写的应用程序,在服务器上运行,处理请求信息并将其发送到客户端。对于客户端的请求,只需要创建Servlet的实例一次,因此节省了大量的内存资源。Servlet在初始化后就保留在内存中,因此每次作出请求时无需加载。

  CGI应用开发比较困难,因为它要求程序员有处理参数传递的知识,这不是一种通用的技能。CGI不可移植,为某一特定平台编写的CGI应用只能运行于这一环境中。每一个CGI应用存在于一个由客户端请求激活的进程中,并且在请求被服务后被卸载。这种模式将引起很高的内存、CPU开销,而且在同一进程中不能服务多个客户。

  Servlet提供了Java应用程序的所有优势——可移植、稳健、易开发。使用Servlet Tag技术,Servlet能够生成嵌于静态HTML页面中的动态内容。

  Servlet对CGI的最主要优势在于一个Servlet被客户端发送的第一个请求激活,然后它将继续运行于后台,等待以后的请求。每个请求将生成一个新的线程,而不是一个完整的进程。多个客户能够在同一个进程中同时得到服务。一般来说,Servlet进程只是在Web Server卸载时被卸载。

CGI的不足之处:

1,需要为每个请求启动一个操作CGI程序的系统进程。如果请求频繁,这将会带来很大的开销。

2,需要为每个请求加载和运行一个CGI程序,这将带来很大的开销

3,需要重复编写处理网络协议的代码以及编码,这些工作都是非常耗时的。

Servlet的优点:

1,只需要启动一个操作系统进程以及加载一个JVM,大大降低了系统的开销

2,如果多个请求需要做同样处理的时候,这时候只需要加载一个类,这也大大降低了开销

3,所有动态加载的类可以实现对网络协议以及请求解码的共享,大大降低了工作量。

4,Servlet能直接和Web服务器交互,而普通的CGI程序不能。Servlet还能在各个程序之间共享数据,使数据库连接池之类的功能很容易实现。

补充:Sun Microsystems公司在1996年发布Servlet技术就是为了和CGI进行竞争,Servlet是一个特殊的Java程序,一个基于Java的Web应用通常包含一个或多个Servlet类。Servlet不能够自行创建并执行,它是在Servlet容器中运行的,容器将用户的请求传递给Servlet程序,并将Servlet的响应回传给用户。通常一个Servlet会关联一个或多个JSP页面。以前CGI经常因为性能开销上的问题被诟病,然而Fast CGI早就已经解决了CGI效率上的问题,所以面试的时候大可不必信口开河的诟病CGI,事实上有很多你熟悉的网站都使用了CGI技术。


Post和Get区别

通常内容

GET后退按钮/刷新无害,POST数据会被重新提交(浏览器应该告知用户数据会被重新提交)。
GET书签可收藏,POST为书签不可收藏。
GET能被缓存,POST不能缓存 。
GET编码类型application/x-www-form-url,POST编码类型encodedapplication/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。
GET历史参数保留在浏览器历史中。POST参数不会保存在浏览器历史中。
GET对数据长度有限制,当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。POST无限制。
GET只允许 ASCII 字符。POST没有限制。也允许二进制数据。
与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET !POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。
GET的数据在 URL 中对所有人都是可见的。POST的数据不会显示在 URL 中。

RFC7231里定义了HTTP方法的几个性质:

  1. Safe - 安全
    这里的「安全」和通常理解的「安全」意义不同,如果一个方法的语义在本质上是「只读」的,那么这个方法就是安全的。客户端向服务端的资源发起的请求如果使用了是安全的方法,就不应该引起服务端任何的状态变化,因此也是无害的。 此RFC定义,GET, HEAD, OPTIONS 和 TRACE 这几个方法是安全的。
    但是这个定义只是规范,并不能保证方法的实现也是安全的,服务端的实现可能会不符合方法语义,正如上文说过的使用GET修改用户信息的情况。
    引入安全这个概念的目的是为了方便网络爬虫和缓存,以免调用或者缓存某些不安全方法时引起某些意外的后果。User Agent(浏览器)应该在执行安全和不安全方法时做出区分对待,并给用户以提示。
  2. Idempotent - 幂等
    幂等的概念是指同一个请求方法执行多次和仅执行一次的效果完全相同。按照RFC规范,PUT,DELETE和安全方法都是幂等的。同样,这也仅仅是规范,服务端实现是否幂等是无法确保的。
    引入幂等主要是为了处理同一个请求重复发送的情况,比如在请求响应前失去连接,如果方法是幂等的,就可以放心地重发一次请求。这也是浏览器在后退/刷新时遇到POST会给用户提示的原因:POST语义不是幂等的,重复请求可能会带来意想不到的后果。
  3. Cacheable - 可缓存性 顾名思义就是一个方法是否可以被缓存,此RFC里GET,HEAD和某些情况下的POST都是可缓存的,但是绝大多数的浏览器的实现里仅仅支持GET和HEAD。关于缓存的更多内容可以去看RFC7234。

GET的语义是请求获取指定的资源。GET方法是安全、幂等、可缓存的(除非有 Cache-ControlHeader的约束),GET方法的报文主体没有任何语义。

POST的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST不安全,不幂等,(大部分实现)不可缓存。

GET产生一个TCP数据包;

POST产生两个TCP数据包。

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。


转发和重定向的区别

区别一:

  重定向时浏览器上的网址改变
  转发是浏览器上的网址不变

区别二:

  重定向实际上产生了两次请求
​ 转发只有一次请求
重定向:

  发送请求 –>服务器运行–>响应请求,返回给浏览器一个新的地址与响应码–>浏览器根据响应码,判定该响应为重定向,自动发送一个新的请求给服务器,请求地址为之前返回的地址–>服务器运行–>响应请求给浏览器
转发:

  发送请求 –>服务器运行–>进行请求的重新设置,例如通过request.setAttribute(name,value)–>根据转发的地址,获取该地址的网页–>响应请求给浏览器

区别三:

  重定向时的网址可以是任何网址
  转发的网址必须是本站点的网址

区别四:用处

​ forward:一般用于用户登陆的时候,根据角色转发到相应的模块.

​ redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等

区别五:效率

forward:高.

redirect:低.

详解:
重定向:以前的request中存放的变量全部失效,并进入一个新的request作用域。
转发:以前的request中存放的变量不会失效,就像把两个页面拼到了一起。

自动刷新(Refresh)

自动刷新不仅可以实现一段时间之后自动跳转到另一个页面,还可以实现一段时间之后自动刷新本页面。Servlet中通过HttpServletResponse对象设置Header属性实现自动刷新例如:

1
Response.setHeader("Refresh","5;URL=http://localhost:8080/servlet/example.htm");

其中5为时间,单位为秒。URL指定就是要跳转的页面(如果设置自己的路径,就会实现每过一秒自动刷新本页面一次)

JSP

JSP和Servlet关系

JSP(Java Server Page) java服务页面

Jsp就是一个servlet文件

(servlet的知识点在jsp中全部适用,但jsp的部分知识点未必在servlet都能适用)

JSP的生命周期

  1. 翻译成java源文件(第一次访问时)
  2. java源文件编译成class字节码
  3. 构造方法(第一次访问时)
  4. _jspInit()方法(第一次访问时)
  5. _jspService方法(第n次访问时)
  6. _jspDestroy()方法

JSP的三个指令

  • taglib 用于导入jsp的标签库

  • include 用于导入其他页面文件。

    语法: <%@include file=”导入的页面路径”%>

    注意:

    1.包含与被包含的页面先合并内容翻译到一个java源文件中,再编译执行一个java文件。(先合 并再翻译),叫静态包含(源码包含)

    2.被包含的页面不要使用全局的html标签。(html/head/title/body)

  • page 用于告诉tomcat服务器如何翻译jsp文件(详细看链接)

JSP的9大内置对象

对象名 对应的类型 备注
request HttpServletRequest 请求对象,封装请求信息
response HttpServletResponse 响应对象,封装响应信息
config ServletConfig servlet配置对象,封装servlet配置信息
application ServletContext servlet的上下文对象,代表整个web应用环境
session HttpSession 会话对象。用于保存会话数据
exception Throwable 异常对象,用于封装异常信息
page Object 代表当前jsp翻译成java类对象
out JspWriter jsp页面缓存对象,相当于带缓存功能的PrintWriter
pageContext PageContext jsp的上下文件对象,代表当前jsp的环境

out对象

out对象,类型JspWriter

out.wirter(“内容”)

  1. PrintWrite类: 直接往浏览器写出内容

    out.write(“内容”);

  2. JspWriter类: 相当于带缓存的PrintWriter

    out.write(“内容”): 把内容写入缓存区

当JspWriter缓存区满足以下条件,缓存区内容会写入到PrintWriter中。

  • 缓冲区满了。

  • 刷新缓冲区(JspWriet.flush())

  • 关闭缓冲区 (<%@page buffer=”0kb”%> buffer属性用于设置Jsp’Writer缓存区大小,默认8KB)

  • 执行完Jsp页面​

得到当前缓冲区大小: out.getBufferSize()

得到当前缓冲区剩余大小: out.getRemaining()

JSP的四个域对象

page域: 处于同一个jsp页面中数据共享是有效的

request域:处于同一个请求中数据共享是有效的(使用转发)

session域:处于同一个会话中数据共享是有效的(同一个session对象)

application域:处于同一个web应用中数据共享是有效的

EL表达式

  1. 获取数据
  • 在四个域中获取 ${变量}

  • 指定域获取数据 ${域范围.变量}

域范围: pageScope < requestScope < sessionScope < applicationScope

  1. EL获取普通对象数据

${student.name} 注意name表示调用getName()方法

  1. EL获取集合数据(List和Map集合)

${map[key].name} 注意:map[key] 表示调用map对象的get(key)方法获取map的值对象

  1. EL可以使用表达式
  • 算术表达式 : ${a+b}

  • 比较表达式: ${a>b}

  • 逻辑表达式 : ${true && true}

  • 判空表达式: ${empty name} 表示判断name为null或者空字符

会话(cookie 、session)

  1. 会话数据保存在客户端

  2. 会话数据只能是字符串类型,不能保存中文的

  3. 会话数据容量有限制,一个cookie不能超过4kb,一个站点20个cookie

  4. 不适合存在敏感数据

向客户端发送Cookie

1
2
3
Cookie c =new Cookie("name","value"); //创建Cookie 
c.setMaxAge(60*60*24); //设置最大时效,此处设置的最大时效为一天
response.addCookie(c); //把Cookie放入到HTTP响应中

从客户端读取Cookie

1
2
3
4
5
6
7
8
9
10
11
12
String name ="name"; 
Cookie[]cookies =request.getCookies();
if(cookies !=null){
for(int i= 0;i<cookies.length;i++){
Cookie cookie =cookies[i];
if(name.equals(cookis.getName()))
//something is here.
//you can get the value
cookie.getValue();

}
}

session

  1. session的会话数据保存服务器端(这个数据可以保存在集群、数据库、文件中)

  2. session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)

  3. session 可以放在 文件、数据库、或内存中都可以。

  4. 用户验证这种场合一般会用 session

session实现原理

  1. 服务器创建Session对象,分配一个唯一的标记(JSESSIONID),会话数据保存sessino对象中,然后服务器把JSESSIONID作为cookie发送给浏览器保存

    响应头: JSESSIONID=7EBC5D0B44D9D3DDE7FAD83C077E3D3E

  2. 浏览器得到JSESSIONID的cookie,保存在浏览器的目录中

  3. 浏览器在下次访问服务器时,带着JSESSIONID的cookie数据访问服务器。

    请求头:Cookie: JSESSIONID=7EBC5D0B44D9D3DDE7FAD83C077E3D3E

  4. 服务器得到JSESSIONID,在服务器内存中查询是否存在对应的编号的session对象。

  5. 如果找到对应的session对象,返回这个对象

  6. 如果找不到对应的session对象,有可能返回null,也有可能是创建新的session对象。

------------- 感谢阅读-------------