Head First Servlet&JSP 5.attributes&listeners

Being a Web App

No servlet stands alone. 考虑各个组件协同工作、共享信息、隐藏信息、线程安全。

1. servlet & ServletContext initialization parameters

2. fundamental servlet attribute scopes

3. describe the elements of the Web container request processing model(covered in the Filters chapter)

4. describe the Web Container lifecycle event model

5.describe the RequestDispatcher mechanism

在 DD(Deployment Descriptor, web.xml file)中配置参数而非在 servlet 中硬编码(硬编码意味着任何改动都需要重新编译)。

// In the DD(web.xml) file
<servlet>
    ...
    <init-param>
        <param-name>adminEmail</param-name>
        <param-value>likewecare@wickedlysmart.com</param-value>
    </init-param>
</servlet>
// In the servlet code
out.println(getServletConfig().getInitParameter("adminEmail");
// you can't call it from your constructor, because that's too early in the servlet's life.

容器初始化 servlet 时,会给 servlet 一个 ServletConfig。容器把初始化参数从 DD 读到 ServletConfig 中(容器只读这么一次。一旦参数被读到 ServletConfig 中,除非重新部署,否则是不会变了),然后再传递给 servlet 的 init() 方法。即,ServletConfig 给你的 servlet 提供初始化参数。
读取初始化参数需要等servlet创建完毕容器带参数初始化servlet的过程重新获取DD参数,需要重新部署,重启Tomcat可以,但是太过于武断,在容器中其他应用访问量多的情况下,直接重启不可取。可以采用热部署,Tomcat 有这样的工具,不用重启容器就可以重新部署一个应用(http://jakarta.apache.org/)。但在现实情况下,频繁通过DD改变初始化参数是不良的。如果真的需要频繁改变,应该考虑从文件或数据库中读取数据。但如此以来,意味着 servlet 代码运行时会有更多的开销,不像 DD,只读一次。

初始化参数读进 servlet 之后,只能被该 servlet 用,如果要传递给别的 servlet 或者 JSP,需要 request.setAttribute(“key”,value),如此,所有获得这个请求的 servlet 或 JSP 就都能够使用了。

Setting a request attribute works… but only for the JSP to which you forwarded the request

setAttribute() 只能让得到 request 的servlet 或者 JSP 使用参数,而不能在 web app 的全局使用。我们需要更 global 的办法。

Context init parameters 可以达到全局可用的目的。

<servlet>
    <servlet-name>BeerParamTests</servlet-name>
    <servlet-class>com.example.TestInitParams</servlet-class>
    <!-- <init-param>
            <param-name>adminEmail</param-name>
            <param-value>likewecare@wickedlysmart.com</param-value>
        </init-param>
        <init-param>
            <param-name>mainEmail</param-name>
            <param-value>blooper@wickedlysmart.com</param-value>
        </init-param> -->
    <!-- 拿掉 init-param -->
</servlet>
<context-param>
    <param-name>adminEmail</param-name>
    <param-value>likewecare@wickedlysmart.com</param-value>
</context-param>
<!-- 注意 <context-param> 标签在 <servlet> 标签外,因为其属于整个 <web-app> -->
// In the servlet code
out.println(getServletContext().getInitParameter("adminEmail"));
// 区别于 getServletConfig()
//OR
ServletContext context = getServletContext();
out.println(context.getInitParameter("adminEmail"));

比较 Context init parameters 和 Servlet init parameters 的异同
比较getServletContext-getServletConfig的异同

注:ServletConfig is one per servlet, ServletContext is one per web app(Servlet & JSP 都可以用).
示意图如下
ServletContext作用范围示意图

  • 如果是分布式应用,则一个 JVM 一个 ServletContext。
  • 一个参数到底是放在 <init-para> 还是 <context-param 取决于应用的哪些部分会使用它。放在 <context-param> 可能更常见,如存放“数据库查找名称”。
  • 放在两个地方的参数可以重名,因为是两个不同的对象(ServletContext or ServletConfig)。
  • 参数的修改在重新部署时生效。(因为只在初始化 servlet 时读一次 DD并赋值)。
  • ServletContext or ServletConfig 没有 setter,所以运行时无法修改。Think of init parameters as deploy-time constants!
  • 若无特别说明,视 “init parameter” 作 “servlet init parameter”,视 “application parameter” 作 “context init parameter”。

javax.servlet.ServletContext 需要熟记的方法在下图中用粗体标出:
javax.servlet.ServletContext
取得 ServletContext 的两种方式如下所示:

getServletConfig().getServletContext().getInitParameter()
// OR
this.getServletContext().getInitParameter()
// 完全等同,因为 A serlet's ServletConfig object always holds a reference to the ServletContext for that servlet

一个 servlet类真正需要经由 ServletConfig 来获取 ServletContext 的情况只有一种:该 servlet class 并非继承自 HttpServlet or GenericServlet (getServletContext() 方法视从 GenericServlet 继承来的)。但是使用 non-HTTP servlet 的情况几乎不会出现,所以,直接调用 getServletContext() 就好。
但是如果不是 servlet 类,而是其他的工具类,可能传入了 ServletConfig,这种情况下代码会用 getServletContext() 来得到 ServletContext 对象。
JSP 使用 ServletContext 是通过其 “implicit objects”,具体等到讲 JSP 的章节再说。

ServletContext 中的 log(String) 方法不怎么用,除非是特别小的 app。也可以用 J2SE 1.4 之后 java.util.logging 自带的方法。生产环境中更常见的是使用 Log4jhttps://logging.apache.org/log4j/2.x/. 如果不用 Log4j,你可以在 Java Servlet & JSP Cookbook form O’Reilly 找到关于 logging 的参考。

What it if you want an app init parameter that’s a database DataSource?

ServletContext 参数是字符串类型(Strings),毕竟 xml 里也不好写别的玩意儿?(即使 XML 支持表达序列化对象,Servlet spec 中也没有 facility 去处理)。
常见的用法是把数据的查找名称作为字符产放进 content init parameter 里。但是如何把字符串参数转变为全局可用的真正的 Database reference 呢?这部分代码不写在 serlvet 里。

listener : something like a main method for my whole web app

javax.servlet.ServletContextListener(interface):在应用启动时被通知,这个对象唯一的目的就是初始化应用程序(或者逆初始化,在应用死亡(demise)时清理资源)。
两个关键方法:

  1. contextInitialized(ServletContextEvent):初始化时(deployed),从 ServletContext获取参数,使用参数中的 lookup name 建立数据库连接(创建对象),将连接保存为一个attribute,这样整个 app 就都能访问了。
  2. contextDestroyed(ServletContextEvent):销毁时(undeployed or goes down),关闭数据库连接。

A ServletContextListener Class:

import javax.servlet.*;

public class MyServletContextListener implements ServletContextListener{

    public void contextInitialized(ServletContextEvent event){
        //code to initialize the database connection
        //and store it as a context attribute
    }

    public void contextDestroyed(ServletContextEvent event){
        //code to close the database connection
    }
}

p168 Tutorials example: 用参数建立狗对象,再将狗对象插入 ServletContext attribute 供全局使用。
<listen> 通过DD让容器发现并使用我们的 listener。注意标签的位置不在 <servlet> 中。

<web-app>
	<!-- ... -->
    <listener>
        <listener-class>
           com.example.MyServletContextListener
        </listener-class>
    </listener>
</web-app>

servlet atrribute 只有在分布式(有多个 JVM) 的情况下才有必要 Serializable,但是因为不能保证绝对不会有人远程调用方法来获取类,或者绝对不会在分布式情况下使用类,那么默认 Serializable 是最好的。

getAttribute() returns type Object! You need to cast the return!

Dog dog = (Dog) getServletContext().getAttribute("dog"); 

相较之下,getInitParameter() 返回的是 String

创建“狗”对象具体过程如下图所示:
listener-full-story

Listener: not just for context events

Besides context events, you can listen for events related to context attributes, servlet requests and attributes, and HTTP sessions and session attributes.
除了 ServletContextListener,不必记住 listener 的其他所有 API,但要知道哪些事件可以被监听.
练习如何选择监听器,Pick a listener:
the-eight-listenerHttpSessionBindingListener 示例

package com.example;

/**
 * Dog
 */
public class Dog implements HttpSessionBindingListener{

    private String breed;

    public Dog(String breed){
        this.breed = breed;
    }

    public String getBreed(){
        return breed;
    }

    public void valueBound(HttpSessionBindingEvent event){
        //code to run now that I konw I'm in a session
    }// bound - added to 
    
    public void valueUnbound(HttpSessionBindingEvent event){
        //code to run now that I konw I'm in a session
    } // unbound - remove from
}

如果 Dog 是 Customer 类,每一个活动的实例(active instance)表示一个顾客的姓名、住址、订单等信息。真实的数据存放在底层的数据库中。你用数据库中的数据填充 Customer 对象的字段,但问题是怎样以及何时保证数据库记录与 Customer 对象中的数据是同步的(how and when do you keep the database record and the Customer info synchronized?)
每当将客户对象添加到会话时,都应该使用该客户在数据库中的记录中的数据刷新该客户的字段。(Whenever a Customer object is added to a session, it’s time to refresh the fields of the Customer with this customer’s data from his record in the database.)
所以,valueBound() 方法就像在说:“去把数据库里的新数据加载给我……以防它在我上次使用后发生了变化。”,然后 valueUnbound() 说:“使用Customer对象字段的值更新数据库。”

Listener 接口名称和方法记忆练习:
错误原因:审题不清,没读懂英文题干,掌握不牢。
Remembering-the-Listeners

What exactly is an attribute?

An attribute is an object set (referred to as bound) into one of three other servlet API objects – ServletContext, HttpServletRequest (or ServletRequest), or HttpSession. You can think of it as simply a name/value pair (where the name is a String and the value is an Object) in a map instance variable. In reality, we don’t really care about how it’s actually implemented – all we really care about is the scope in which the attribute exists. In other words, who can see it and how long does it live.

An attribute is like an object pinned to a bulletin board. Somebody stuck it on the board so that others can get it.
The big questions are: who has access to the bulletin board, and how long does it live? In other words, what is the scope of the attribute?

Attributes are not parameters

区分二者,考试中有相关习题。
attributes-vs-parameters### The Three Scope: Context, request, and Session

the-three-scopeattribute-scope不同的作用域有不同的垃圾回收机制。

Attribute API

Object getAttribute(String name)
setAttribute(String name, Object value)
removeAttribute(String name)
enumeration getAttributeNames()

context, request, session三个作用域由ServletContext, ServletRequest, HttpSession三个接口来处理。
attribute-api

Context scope isn’t thread-safe

everyone in the app has access to context attributes.
multiple servlets means you might have multiple threads

multiple-servlets-multiple-theads### How to make context attributes thread-safe ?
synchronized doGet() methods: say goodbye to concurrency, the servlet can handle only one client at a time.
Synchronizing the service method means that only one thread in a servlet can be running at a time, 但是并不能阻止其他 servlet 和 JSP 访问数据。无济于事。
事实上,不需要给 servlet 上锁,而是要给 context 对象上锁。

public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException,ServletException{
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        out.println("test context attributes<br>");

        synchronized(getServletContext()) { // lock on the context itself
            getServletContext().setAttribute("foo","22");
            getServletContext().setAttribute("bar","42");

            out.println(getServletContext().getAttribute("foo"));
            out.println(getServletContext().getAttribute("bar"));
        }
    }

Session Attribute 只对一个客户端有效,并且物理规则杜绝了一个客户端在同一时刻创建多个请求。这样从容器的角度看,任何时间都只有一个线程可以访问 Session Attribute ,看似了安全了。但是,同一个客户端,是可以创建多个请求的——新建浏览器窗口,新旧不同的浏览器实例仍然创建的是同一个 session。所以,Session Attribute 仍然不是线程安全的,需要用 synchronzed 保护。

Protecting session attributes by synchronizing on the HttpSession
public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException,ServletException{
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        out.println("test context attributes<br>");

        synchronized(getServletContext()) { // synchronizing on the HttpSession object, to protect the session attribute
            getServletContext().setAttribute("foo","22");
            getServletContext().setAttribute("bar","42");

            out.println(getServletContext().getAttribute("foo"));
            out.println(getServletContext().getAttribute("bar"));
        }
    }

注:慎用 synchronized,因为其会造成大量开销(add more expense in checking, acquiring, and releasing locks),并损害并发行。该用才用,不该用别用。用到的使用被同步的代码块应尽可能的小。

永远不要用 SingleThreadModel(已废弃) 这个接口。

Only Request attributes and local variables are thread-safe!

判断是否线程安全
local variables 包含 method parameters.

判断线程安全-解读

Request Attribute and Request dispatching

RequestDispatcherRequestDispatcher-diagram<< interface >>
javax.servlet.RequestDispatcher

  • forward(ServletRequest, ServletResponse) // 多用于 Servlet
  • include(ServletRequest, ServletResponse) // 多用于 JSP < jsp: include >

两种办法获取 RequestDispatcher

// Getting a RequestDispatcher from a ServletRequest
ReuqestDispatcher view = request.getReuqestDispatcher("result.jsp"); //相对路径,相对路径与逻辑路径的关系在部署相关章节进一步讨论。
// Getting a RequestDispatcher from a ServletContext
ReuqestDispatcher view = getServletContext().getReuqestDispatcher("/result.jsp");// 这里必须有'/'
// Calling forward() on a RequestDispather
view.forward(request, response);

注:容器根据路径查找是,若有 ‘/’ = “starting form the root of this web app”,若没有 ‘/’ = “relative to the original request”

forward 要在 commit response 之前。如,先view.forward(),后os.flush(). 否则容器会抛出 IllegalStateException 异常。

第五章毕,Mock Exam 错得相当多

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值