Head First Servlet & JSP 笔记 02

本文内容均来自书籍《Head First Servlets & JSP》


四、作为 Servlet

Servlet 的生命周期和 API

一个 Servlet 大概的生命周期流程:
用户点击一个链接 -> 容器解析该链接,发现请求的是一个 Servlet -> 创建两个对象 HttpServletRequest 和 HttpServletResponse -> 查找到正确的 Servlet,为其创建或分配一个线程 -> 将请求和响应对象作为参数传入 Servlet 的 service 方法,并调用该方法 -> 根据用户时 GET 或 POST 请求,service 方法调用 doGet 或 doPost 方法 -> 响应给用户后,该线程撤销或返回到容器的线程池中
每个客户为每个请求分配一个单独的线程,容器会创建一对新的请求和响应对象

«interface»
Servlet
service(ServletRequest, ServletResponse)
init(ServletConfig)
destory()
GenericServlet
service(ServletRequest, ServletResponse)
init(ServletConfig)
destory()
HttpServlet
service(HttpServletRequest, HttpServletResponse)
MyServlet
doPost(HttpServletRequest, HttpServletResponse)

  • ServletConfig

    • 每一个 Servlet 对象存在一个 ServletConfig 对象,用于向 Servlet 传递部署时的信息
    • 用于访问 ServletContext
  • ServletContext

    • 每个 Web 应用存在一个 ServletContext,用于访问 Web 参数或放置应用消息
  • Http 方法

    • GET :请求 URL 上的资源文件
    • POST:请求一个服务并携带请求体参数
    • HEAD:获取请求头信息
    • TRACE:要求请求消息回送,方便测试和排错
    • PUT:将要包含的信息体放在请求的 URL 上
    • DELETE:请求删除资源文件
    • OPTIONS:获取一个 HTTP 方法列表,所请求 URL 上的东西可以对这些 HTTP 请求做出响应
    • CONNECT:要求连接以便建立隧道
  • 幂等和非幂等请求

    • 幂等操作:可以多次执行同一件事,而不会产生副作用
      特点:任意多次执行所产生的影响均与一次执行的影响相同,不会影响系统状态

    • GET 请求是幂等的,能执行多次,且不会产生不好的副作用;POST 请求是非幂等的,当 POST 提交数据时可能产生不可逆的影响

重定向与请求分派

Servlet 重定向:

// 不以斜线开头,相对原请求增加 URL
response.sendRedirect("https://www.baidu.com");
// 以斜线开头,容器会相对于 Web 应用本身建立 URL
response.sendRedirect("/foo/czen.html");

Servlet 请求分派:

RequestDispatcher view = request.getRequestDisapatcher("result.jsp");
view.forward(request, response);

重定向:将客户的请求直接转发到 另一个 URL 来处理,是在客户端(浏览器)上发生的;请求分派:让另一个服务接管当前服务,或者说是将当前请求传递给服务器上另一个组件,是在服务器端发生的。

五、作为 Web 应用

初始化参数

容器初始化一个 Servlet 时,会为这个 Servlet 创建一个唯一的 ServletConfig 。容器从 DD 中读取 Servlet 初始化参数交给 ServletConfig,当调用 Servlet 的 init() 方法时,传入 ServletConfig 的引用。

  • ServletConfig
    其主要任务时提供初始化参数,用于 Servlet 的配置
out.println(getServletConfig().getInitParameter("adminEmail"));
  • ServletContext
    上下文初始化参数,相较于 ServletConfig 对单个 Servlet 初始化参数,ServletContext 针对整个 Web 应用初始化数据,对于整个 Web 应用只存在一个 ServletContext。
out.println(getServletContext().getInitParameter("adminEmail"));

每个 Servlet 有一个 ServletConfig;每个 Web 应用有一个 ServletContext,ServletContext 中的参数内容对整个 Web 应用中的 Servlet 和 JSP 都可用。

«interface»
ServletContext
getInitParameter(String)
getAttribute(String)
setAttrubute(Sring, Object)
removeAttribute(String)
getRequestDispatcher(String)

一个简单的 ServletContextListener

1、创建一个上下文监听者类,用于在 Servlet 初始化前从 ServletContext 中获取该数据(String 或者对象类型)

public class MyServletContextListener implements ServletContextListener {
	public void contextInitialized(ServletContextEvent event) {
		ServletContext context = event.getServletContext();
		// 从上下文中获取参数
		String dogBreed = context.getInitParameter("breed");
		Dog dog = new Dog(dogBreed);
		context.setAttribute("dog", dog);
	}
	public void contextDestoryed(ServletContextEvent event) {}
}

2、编写部署描述文件

<webapp>
	<listener>
		<listener-class>
			com.example.MyServletContextListener
		</listener-class>
	</listener>
</webapp>

3、编写 Servlet 测试类判断上下文监听者是否初始化数据成功

// 在 Servlet 的 doGet() 方法中获取上下文参数
Dog dog = (Dog) getServletContext().getAttribute("dog");
out.println("Dog's breed is " + dog.getBreed());

有了 ServletContextListener 后,Servlet 的执行流程:
-容器读取 DD 中的 <listener> 和 <context-param> 标签,创建一个新的 ServletContext
-为 ServletContextListener 创建一个新的实例,传入 ServletContextEvent ,获取 ServletContext 的一个引用,获取并初始化参数,构造完成对象后,再放入 ServletContext 中
-容器创建一个新的 Servlet,利用初始化参数构建 ServletConfig,为 ServletConfig 提供 ServletContext 的一个引用,调用 Servlet 的 init() 方法,Servlet 得到一个请求,从 ServletContext 中获取提前初始化好的参数

  • 8 个监听者
    • ServletContextAttributeListener
    • HttpSessionListener
    • ServletRequestListener
    • ServletRequestAttributeListener
    • HttpSessionBindingListener
      属性本身在增加到一个会话或者从会话删除时,得到通知
    • HttpSessionAttributeListener
      会话中增加、删除和替换某种类型的属性时,得到通知
    • ServletContextListener
    • HttpSessionActivationListener

三个作用域:上下文、请求和会话

上下文 ServletContext
应用中的每一部分都能访问,上下文作用域不是线程安全的。上下文的生命周期就是该应用部署的生命周期,服务器或应用关闭时,其生命周期结束。

Head First Quest01
Q:如何确保上下文作用域的线程安全?
A:对上下文加锁,而非 Sevlet 加锁;若对 Servlet 的服务方法 doGet() 方法加锁,无法保证线程安全,因为它只会防止同一个 Servlet 中的其他线程访问上下文属性,无法阻止另外一个 Servlet 访问上下文属性;正确的做法是对上下文对象本身同步。

public void doGet(HttpServletRequest request, HttpServletResponse response)
				throws IOException, ServletException {
	// 其他逻辑代码
	synchronized(getServletContext()) { // 在需要获取的上下文作用域中加锁保证线程安全
		getServletContext().setAttribute("hello","22");
		out.println(getServletContext().getAttribute("hello"));
	}
}

TIPS:通过加锁保证线程同步一定要在最短时间内完成目标,使持有锁的时间最短,让同步块尽可能的小,进入同步块获取资源后尽快释放锁,以保证其他线程的运行速度。

会话 HttpSession
能访问特定 HttpSession 的部分才能访问;用于维护与一个客户的会话状态,一个客户一个会话中存在多个请求;会话属性也是非线程安全的,可以通过在 Httpsession 对象中加锁来保证线程的安全。

请求 ServletRequest
能访问特定 ServletRequest 的部分才能访问,为线程安全的。请求的生命周期会持续到 Servlet 的 Service() 方法结束。

Person p = new Person();
ArrayList result = p.getCatsNames(p);
// 将数据放在请求作用域中确保线程安全性
request.setAttribute("personCatsNames", result);
// 为视图 JSP 获取一个分派器
RequestDispatcher view = request.getRequestDispatcher("result.jsp");
// 让 JSP 接管该请求
view.forward(request, response);
/*
 * view.include()
 * 将请求发送给别人来完成,再返回发送者
 * view.forward()
 * 将请求完全交给其他应用处理
 */

六、会话状态

Head First Thinking01
T:现在我们的啤酒网站要实现一个功能,当用户选择一种啤酒品牌后,可以根据该客户以往选择的啤酒数据,返回一个新的建议;如果该客户以往的数据信息不足,则要求用户再回答一个问题,并给出建议
A:主要的解决方法有三个:(1 让 servlet 成为一个有状态会话 bean 的客户端,每次接受请求后,获取到该客户对应的 bean;(2 使用数据库存储客户的历史数据;(3 使用一个 HttpSession 对象保存跨多个请求的会话状态

HttpSession 对象可以保存跨同一个客户多个请求的会话状态,即一个特定的客户在整个会话期间,HttpSession 会持久存在

PrintWriter out = response.getWriter();
/**
 * getSession 方法会使用已存在的会话或创建一个新的会话
 * 这一步表示从请求中获取到会话 ID,会话 ID 由 cookie 携带的 JSESSIONID 存储
 */
HttpSession session = request.getSession();
if(session.isNew()) {
	out.print("This is a new session!")
}
/**
 * 若客户端禁用了 cookie,则每次都会生成新的 session,且不会返回携带由会话 ID 的 cookie 响应数据
 * encodeURL() 相当于对 URL 进行了重写,在 URL 后添加 JSESSIONID 存储该请求的会话状态
 */
 out.print("<a href='" + response.encodeURL("/Beer.do")) + "'> click me </a>");

Head Firse Thinking02
T:服务器中大量 session 会占用系统资源,如何在会话结束时,安全的删除一个会话信息呢?
Q:下面的 session 方法能够启发你,即为每个请求的会话设置一个存活时间

getCreateTime()        // 返回第一次会话创建的时间
getLastAccesedTime()   // 返回最后一次得到该会话 ID 的请求过去了多长时间
setMaxInactiveInterval()   // 设置当前会话的最大存活时间
invalidate()           // 结束会话

cookie 的设计实际上是为了支持 session,cookie 是在客户端和服务端之间进行交互的一段数据(键值对);浏览器发送请求时携带 cookie,服务器根据请求返回一个携带该 cookie 的响应。

  • cookie 的交换是自动完成的
  • 可以使 cookie 的存活时间长于 session,即意味着客户的会话信息能以 cookie 的形式持久化到计算机中
// 一个关于 cookie 的简单示例:获取用户名存储到 cookie 中返回给客户
public class CookieTest extends HttpServlet {
	public void doPost(HttpServletRequest request, HttpServletResponse response) {
		String name = request.getParameter("username");
		Cookie cookie = new Cookie("username", name);
		cookie.setMaxAge(30 * 60); // 设置 cookie 存活时间为 30 分钟
		response.addCookie(cookie);
		RequestDispatcher view = request.getRequestDispatcher("result.jsp");
		view.forward(request, response);
	}
}

当部署的是分布式应用时,只有 HttpSession 对象(及其属性)能从一个 VM 迁移到另一个 VM;对于一个给定的 Web 应用,VM 中不会出现相同的会话 ID。


后记

毕业后因为找不到工作,去餐厅打工了一段时间,最后还是决定去 Java 培训。目前培训课程已经结束,即将面临找工作阶段,有时候有些后悔,当初为什么要选 Java,相比于大多数转计算机的竞争者来说,我当初选 Java 只是想尽快就业。没想到 Java 已经卷到如此地步,不禁令人感慨:对于 Java 这一行来说,新人极难入行,况且大量培训班的存在,本身就让大多企业失去了对新手培训的耐心。有时候又觉得很好笑,大批大批的人去卷是为了赚钱,而另一批卷下来的人又开始教别人怎么去卷,如此循环,造就了如此高的入门壁垒。大环境下,20 年后,整个行业,整个社会又会是什么样呢?

上一篇:Head First Servlets & JSP 笔记 01
下一篇:Head First Servlets & JSP 笔记 03

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值