活用DD, 比如, 我想设置一个email地址, 但是不像在servlet中硬编码, 如果能再web.xml中设置一个参数, 直接拿到这个参数就更好一点.
容器建立一个servlet时, 它会读DD(web.xml), 并为ServletConfig创建名/值对, 容器不会再读初始化参数了, 一旦参数放在ServletConfig中,就不会再读了. 除非你重新部署 servlet.
可见web.xml只读取一次, 所以如果你想修改点web.xml内容时, 就需要重新部署整个内容, 但是这比将内容写在servlet或Jsp中强一点,后者不仅要重新部署, 还要重新编译.
例如我想把email地址作为servlet 来响应的内容, 把它写在 web.xml 比在servlet中强, 因为不需要编译.
web.xml 中的初始化参数如下:
<servlet>
<init-param>
<param-name>abc</param-name>
<param-value>abc@123.com</param-value>
</init-param>
</servlet>
以上内容, 会作为一个参数名和值传给ServletConfig, 然后init()方法执行时, 会将这些内容读取, 只读取这一次
getServletConfig().getInitParameter("abc") // 可以获得参数 abc的值 abc@123.com
另外, init 初始化参数只能针对某个特定的servlet, 即 <init-param> 要在 <servlet>内部
以上的 init 初始化参数, 只能 servlet 使用, jsp 不能使用, jsp如果想使用, 可以通过增加jsp属性的方法, 上一章有提过来增加, 但是这样做也不好, 解决办法是上下文初始化参数.
上下文初始化参数, 对整个Web应用而不只是对servlet可用. 并且, 上下文参数不在 <servlet>内部, 不是针对特定的 servlet.
<context-param>
<param-name>aaa</param-name>
<param-value>aaa@123</param-value>
</context-param>
在servlet中使用代码, getServletContext().getInitParameter(“aaa”);
上下文参数, 与普通 parameter 参数区别
容器在创建servlet时, 会读取DD, 并为ServletConfig创建 名/值对, 容器不会再读初始化参数了. 除非重新部署
成熟的 容器可以提供动态部署, 即不用关闭, 重启就可以部署.
每个 servlet 一个 servletConfig, 每个 Web应用一个 ServletContext
如果你希望应用初始化参数是一个数据库 datasource ? 不可能还利用xml来初始化数据库, 另外也不能把对象放到XML文件中来进行初始化啊?
如果 xml 能够激活一个对象或一个函数, 而这个对象或函数能够初始化对象和数据库(比如数据库连接, 关闭等), 所以就由了xml监听类.
xml中设置监听类
我们需要一个单独的对象, 它能做到:
上下文初始化时得到通知( 应用得到部署 )
- 从ServletContext得到上下文的初始化参数
- 使用初始化参数查找名建立一个数据库连接
- 把数据库连接存储为一个属性, 使得web应用的各个部分都能访问
上下文撤销时得到通知 ( 应用取消部署或结束 )
- 关闭数据库连接
容器通过web.xml 发现和使用监听者
用来初始化的对象
import javax.servlet.*; public class MyServletContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { // 初始化数据库连接代码 // 并且保存这段连接代码为一个 context attribute } public void contextDestoryed(ServletContextEvent event) { // 关闭数据库连接的代码 } }
<listener></listener>
process
例子:我们把String初始化参数转换为一个真正的对象, 一个 Dog
0. 首先创建一个监听者类 , 把它放在 WEB-INF/classes目录中, 并在部署描述文件中放一个<listener>元素来告诉容器.
package com.example; import javax.servlet.*; public class MyServletContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { // 由事件得到 ServletContext servletContext sc = event.getServletContext(); // 使用上下文的到初始化参数 String dogBreed = sc.getInitParameter("breed"); // 根据参数建立对象 Dog d = new Dog(dogBreed); // 使用上下文, 增加了属性 sc.setAttribute("dog", d); } public void contextDestroyed(ServletContextEvent event) { } }
1. web.xml
2. Dog class 只是一个比方, 连接数据库的内容类似
3. Listener 主要用来实例化 Dog
4. Servlet, testlistener
5. 测试时, 只需要执行 http://localhost:8080/listenerTest/ListenTest.do
总结:首先, 容器在启动的时候, 会去读取 web.xml, 并实例化其中的<Listener> 类和servlet类, 并且将所有的上下文参数<context-param> 做成了 (key-value 对的形式交给servletContext), 注意:实例化<Listener>类时会调用contextInitialized()这个方法, 这个方法的实际意义是用来连接数据库的, 这个方法具体做什么事情, 你可以自己编写, 只不过这个方法可以访问到ServletContext所有的key和value对, 通过这些可以访问到得key和value, 来初始化数据库或者创建对象等等, 并将结果以 key, value的形式添加到 ServletContext的atribute中, 此时这个方法可以通过 getServletContext()获得<context-param>中的参数来进行一些处理, 例如连接数据库, 然后再将数据库连接的内容设置成servletContext的一个属性, 供所有人调用, 这个时候如果有别人需要请求时, 其他的servlet就可以调用这个属性来确认连接数据库. 由此可见, <Listener>会在容器启动时先运行并实例化完毕, 在哪里等待, 等待别的servlet调用 (本例中就是 ListenerTester).
这样也就实现了, 属性可以通过编程设置. 另外可以用很多类型的监听者, 不单单是监听上下文参数的.
另外要注意, 上下文属性是大家都能访问的, 换句话说, 它不是线程安全的.
其他的监听者
属性和参数的区别
上下文, 请求, 会话
多线程访问时, 属性也并非是安全的, 比如 A线程修改了一个属性的值, 而后B线程也修改了属性的值, 但是A不知道, A还以为属性是刚刚他修改的内容. 类似之前说的实例变量不安全, 一样.
RequestDispatcher 只有两个方法, forward() 和 include()