Author:相忠良

Email: ugoood@163.com

起始于:April 14, 2018

最后更新日期:April 18, 2018

声明:本笔记依据传智播客方立勋老师 Java Web 的授课视频内容记录而成,中间加入了自己的理解。本笔记目的是强化自己学习所用。若有疏漏或不当之处,请在评论区指出。谢谢。

涉及的图片,文档写完后,一次性更新。

day05 Servlet 开发和 ServletConfig 与 ServletContext 对象

1. Servlet 开发入门 - hello world

Servelet 是动态 web 资源开发技术。html 是静态 web 资源开发技术。Jsp 是另一种动态web资源开发技术,但 jsp 里面就是 Servelet。所以应先搞明白 Servelet。

sun公司提供了一个servlet接口,用户若想开发一个动态web资源,需完整下面2个步骤:

  1. 编写一个 java 类,实现 servlet 接口;
  2. 把开发好的 java 类部署到 web 服务器中。

快速入门:用servlet向浏览器输出"hello servlet"。

阅读 J2EE 的 servlet api 文档,解决2个问题:

  1. 输出 "hello servlet" 的 java 代码应该写在 servlet 的那个方法内?
  2. 如何向 IE 浏览器输出数据?

查看 api 后,做实验。

过程如下:

1.在tomcat webapps 中新建 day05 目录, 然后再web应用中建立WEB-INF/classes目录;

2.在classes目录中新建一个FirstServlet.java

  1. package cn.wk;
  2. import java.io.*;
  3. import javax.servlet.*;
  4. // GenericServlet已经覆盖了Servlet接口中的方法,继承它,用起来方便
  5. public class FirstServlet extends GenericServlet{
  6. // 服务器接受客户请求并给出响应,我们需重写 service 方法
  7. public void service(ServletRequest req,
  8. ServletResponse res)
  9. throws ServletException,java.io.IOException{
  10. // 向浏览器输出的流
  11. OutputStream out = res.getOutputStream();
  12. //OutputStream是字节流,故用getBytes()把字符转字节
  13. out.write("hello servlet!!!".getBytes());
  14. }
  15. }

注意到,servlet 程序是跑在 web 服务器中,受 web 服务器调用的。服务器会给 servlet 程序的service方法送来一个客户发来的 request, 向客户发 “hello servelt!!!”响应的话,应由servlet程序中service方法中的ServletResponse对象去处理。我猜测OutputStream输出流由servlet程序发给web服务器,再由web服务器封装后,输出给客户的浏览器。

3.编译servlet程序FirstServlet.java。其中需在cmd下将servlet-api.jar添加到classpath中。我机器例子:C:\apache-tomcat-8.5.9\webapps\day05\WEB-INF\classes>set classpath=%classpath%;C:\apache-tomcat-8.5.9\lib\servlet-api.jar

然后编译:C:\apache-tomcat-8.5.9\webapps\day05\WEB-INF\classes>javac -d . FirstServlet.java

4.在WEB-INF目录中新建web.xml文件,并配置servlet的对外访问路径,内容抄自C:\apache-tomcat-8.5.9\conf\web.xml文件,并做修改。如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  5. http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  6. version="3.1">
  7. <servlet>
  8. <!-- 名字 -->
  9. <servlet-name>FirstServlet</servlet-name>
  10. <!-- 位置 -->
  11. <servlet-class>cn.wk.FirstServlet</servlet-class>
  12. </servlet>
  13. <!-- 配置servlet的对外访问路径 -->
  14. <servlet-mapping>
  15. <!-- 名字 -->
  16. <servlet-name>FirstServlet</servlet-name>
  17. <!-- 对外路径 -->
  18. <url-pattern>/FirstServlet</url-pattern>
  19. </servlet-mapping>
  20. </web-app>

5.启动tomcat,开启浏览器,访问http://localhost:8080/day05/FirstServlet查看结果。

2. Servlet 的调用过程和生命周期



生命周期:servlet第一次被访问时被创建一个实例对象,web服务器会调用init方法完成对象初始化,service方法会执行,用来响应客户端的请求,当web服务器关闭时或者当前web应用被删掉时,web服务器会调用destroy方法,从web容器中移除该servlet对象。

3. 使用 Eclipse 开发 Servlet

步骤:

1.新建一个 Web Project,不直接点 Finish,点下一步勾选上 web.xml;

2.src目录下新建一个ServletDemo1的类并继承GenericServlet实现类;

3.把apache-tomcat-8.5.9-src源码attached到项目中去,然后在ServletDemo1中写入:

  1. public class ServletDemo1 extends GenericServlet {
  2. @Override
  3. public void service(ServletRequest req, ServletResponse res)
  4. throws ServletException, IOException {
  5. res.getOutputStream().write("hello servlet!!!!!".getBytes());
  6. }
  7. }

4.这个servlet程序外界无法访问,需用web.xml配置对外访问路径,添加如下内容:

注意:要想获得这种类名cn.wk.ServletDemo1,需将ServletDemo1.java点开,然后右键点击类名,再点 copy qualified name 按钮获取类全名!

  1. <servlet>
  2. <servlet-name>ServletDemo1</servlet-name>
  3. <servlet-class>cn.wk.ServletDemo1</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>ServletDemo1</servlet-name>
  7. <url-pattern>/ServletDemo1</url-pattern>
  8. </servlet-mapping>

5.想发布这个web应用,需先为Eclipse集成一个tomcat服务器(我用的是 tomcat 8),如下:

windows->preference->myeclipse->servers->tomcat->tomcat 8.x

然后选择 tomcat 8 所在目录,再点 Enable,其他默认,点确定即可。

6.点击屏幕上方的 Deploy MyEclipse J2EE Project to Server 按钮,把本工程部署到服务器;

7.点击屏幕上方的启动服务器按钮(选择tomcat 8.x);

8.浏览器中输入http://localhost:8080/day05/ServletDemo1查看结果。

实际开发中要想建 servlet 程序,直接右键 new 一个 servlet 即可,Eclipse 直接在 web.xml 中把<servlet>元素和<servlet-mapping>元素写好了!

4. HttpServlet 和一些开发细节

写sevlet程序继承HttpServlet就行了,再根据想处理的请求复写doGet()或者doPost()等方法就可以了,不必再复写service()方法了,读api看下HttpServlet就会完全明白了!

实验,在day05项目中cn.wk包下直接建立名为ServletDemo2的servlet程序,如下:

  1. public class ServletDemo2 extends HttpServlet {
  2. public void doGet(HttpServletRequest request, HttpServletResponse response)
  3. throws ServletException, IOException {
  4. response.getOutputStream().write("Hello, HttpServlet!!!!".getBytes());
  5. }
  6. public void doPost(HttpServletRequest request, HttpServletResponse response)
  7. throws ServletException, IOException {
  8. doGet(request, response);
  9. }
  10. }

web.xml中对于该资源的外部访问路径就不用配了,Eclipse 已经给咱配好了。

浏览器中输入http://localhost:8080/day05/servlet/ServletDemo2查看结果。

注意:servlet程序重命名后(按F2重命名,而不能在程序中改!),相关引用该类的程序也自动修改,但web.xml中的名字没改,得手动修改!!!方立勋老师特意强调:写servlet程序要小心,写错名字的话,web.xml中的内容有很多地方需修改,工程很大的话,就是灾难了!

可在MyEclipse目录下查找Servlet.java修改模板,使得doGet和doPost方法变清爽,但我没成功。

5. Servlet 开发的一些重要细节

拷贝别人的工程后,可能涉及到修改web访问路径。方法:Eclipse中右击点属性->MyEclipse->找Web->修改Web Context-root

配置servlet程序的访问地址URL:

一个servlet程序可以有多个URL,还涉及到通配符:

例子,修改day05工程下的 web.xml 如下:

  1. <!-- servlet注册 -->
  2. <servlet>
  3. <servlet-name>ServletDemo1</servlet-name>
  4. <servlet-class>cn.wk.ServletDemo1</servlet-class>
  5. </servlet>
  6. <servlet>
  7. <servlet-name>ServletDemo2</servlet-name>
  8. <servlet-class>cn.wk.ServletDemo2</servlet-class>
  9. </servlet>
  10. <!-- servlet URL 映射 -->
  11. <servlet-mapping>
  12. <servlet-name>ServletDemo1</servlet-name>
  13. <url-pattern>/ServletDemo1</url-pattern>
  14. </servlet-mapping>
  15. <servlet-mapping>
  16. <servlet-name>ServletDemo2</servlet-name>
  17. <url-pattern>/servlet/ServletDemo2</url-pattern>
  18. </servlet-mapping>
  19. <servlet-mapping>
  20. <servlet-name>ServletDemo2</servlet-name>
  21. <url-pattern>/1.html</url-pattern>
  22. </servlet-mapping>
  23. <servlet-mapping>
  24. <servlet-name>ServletDemo2</servlet-name>
  25. <url-pattern>/*</url-pattern>
  26. </servlet-mapping>
  27. <servlet-mapping>
  28. <servlet-name>ServletDemo2</servlet-name>
  29. <url-pattern>*.html</url-pattern>
  30. </servlet-mapping>

多个通配符表达的url,涉及到具体匹配哪一个servlet程序的问题(了解):

Servlet 引擎: Web 服务器中用来调 servlet 程序的那个程序。 Servlet 程序不能单独运行。

servlet程序的init()和destroy():

实验,新创建了一个ServletDemo3.java的 servlet 程序,并覆盖了init()destroy()。结果显示客户 第一次访问 该servlet程序时,web容器执行init(),当 服务器关闭或该servlet程序从web容器被移除 时,web容器执行该servlet程序的destroy()

  1. public class ServletDemo3 extends HttpServlet {
  2. @Override
  3. public void init() throws ServletException {
  4. super.init();
  5. System.out.println("init");
  6. }
  7. public void doGet(HttpServletRequest request, HttpServletResponse response)
  8. throws ServletException, IOException {
  9. response.getOutputStream().write("Hello ServletDemo3!!!".getBytes());
  10. }
  11. public void doPost(HttpServletRequest request, HttpServletResponse response)
  12. throws ServletException, IOException {
  13. doGet(request, response);
  14. }
  15. @Override
  16. public void destroy() {
  17. super.destroy();
  18. System.out.println("destroy");
  19. }
  20. }

ServletDemo3设置启动服务器就创建对象并执行init()方法。在 web.xml的<servlet>元素里设置<load-on-startup>元素,并设置其内容为1,如下:

  1. <servlet>
  2. <servlet-name>ServletDemo3</servlet-name>
  3. <servlet-class>cn.wk.ServletDemo3</servlet-class>
  4. <load-on-startup>1</load-on-startup>
  5. </servlet>

<load-on-startup>1</load-on-startup>里的1表示优先级,越小优先级越高,但必须是正整数。

该技术用在随服务器的启动,启动某些框架上。

缺省的 Servlet 程序

  1. <servlet-mapping>
  2. <servlet-name>ServletDemo3</servlet-name>
  3. <url-pattern>/</url-pattern>
  4. </servlet-mapping>

注意上面代码<url-pattern>/</url-pattern>中只有/,意味着其他servlet都不支持的 url 由这个缺省的ServletDemo3 servlet程序来处理!即 处理别人都不处理的请求

注意:即使我们没配置该web应用的缺省servlet程序,web容器也会为我们弄一个缺省的servlet程序。如果我们配置了,服务器为我们准备的缺省servlet程序会被覆盖。程序员自己不要把某个servlet弄成缺省的,因为html文件将无法访问。如:http://localhost:8080/day05/1.html将访问自己设置的缺省servlet程序。

6. Servlet的线程安全

6.1 Servlet的线程安全的产生及同步锁解决方案(然并卵方案)

静态成员变量要慎用,可能导致 线程安全问题内存溢出,如:

  1. public class Person{
  2. public static List<String> list = new ArrayList<String>();
  3. }

若 Person 类随服务器启动而加载,另一个 Demo 类访问 Person 类,当有很多用户用 Demo 类代码访问 Person 类时均向list添加数据,静态成员list会无限变大,最终可能导致内存溢出,Demo 代码如下:

  1. public class Demo{
  2. public static void main(String[] args) {
  3. Person p = new Person();
  4. p.list.add("aaa");
  5. }
  6. }

下面代码有线程安全问题:

  1. // 线程安全问题
  2. public class ServletDemo4 extends HttpServlet {
  3. int i = 0;
  4. public void doGet(HttpServletRequest request, HttpServletResponse response)
  5. throws ServletException, IOException {
  6. try {
  7. Thread.sleep(1000 * 3);
  8. } catch (InterruptedException e) {
  9. // TODO Auto-generated catch block
  10. e.printStackTrace();
  11. }
  12. i++;
  13. response.getOutputStream().write((i + "").getBytes());
  14. }
  15. }

开两个浏览器窗口,同时访问http://localhost:8080/day05/servlet/ServletDemo4会出线程安全问题。

加上同步锁,解决了线程安全问题,代码如下:

  1. // 线程安全问题
  2. public class ServletDemo4 extends HttpServlet {
  3. int i = 0;
  4. public void doGet(HttpServletRequest request, HttpServletResponse response)
  5. throws ServletException, IOException {
  6. response.getOutputStream().write((i + "").getBytes());
  7. i++;
  8. try {
  9. Thread.sleep(1000 * 5);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. response.getOutputStream().write((i + "").getBytes());
  14. }
  15. }

加同步锁解决线程安全问题:

  1. // 加同步锁
  2. public class ServletDemo4 extends HttpServlet {
  3. int i = 0;
  4. public void doGet(HttpServletRequest request, HttpServletResponse response)
  5. throws ServletException, IOException {
  6. synchronized (this) {
  7. response.getOutputStream().write((i + "").getBytes());
  8. i++;
  9. try {
  10. Thread.sleep(1000 * 5);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. response.getOutputStream().write((i + "").getBytes());
  15. }
  16. }
  17. }

以上线程安全问题的解决方案是不行的,最后一个用户访问这个页面等1年?

6.2 Servlet的线程安全可行解决方案(结论:还得用 6.1 的解决方案)

接口中没有任何定义,这种接口是标记接口,如:SerializableCloneable以及本节所涉及的SingleThreadModel

但这试验我没做出来,达不到开2个ie窗口,都显示结果为1的效果,代码如下:

  1. public class ServletDemo4 extends HttpServlet implements SingleThreadModel {
  2. int i = 0;
  3. public void doGet(HttpServletRequest request, HttpServletResponse response)
  4. throws ServletException, IOException {
  5. i++;
  6. try {
  7. Thread.sleep(1000 * 8);
  8. } catch (InterruptedException e) {
  9. // TODO Auto-generated catch block
  10. e.printStackTrace();
  11. }
  12. response.getOutputStream().write((i + "").getBytes());
  13. }
  14. }

Java中的谚语:子类在覆盖父类的方法时,不能抛出比父类更多的异常。想法是:子类比父类强。

7. ServletConfig 对象 - 用于封装 servlet 的配置信息

Web 服务器会给 Servlet 传各种对象,我们编写的 Servlet 程序接收这些对象后,解析它,再搞对应的操作,如图:

认识 ServletConfig 对象:

问题:为什么要在 web.xml 这个配置文件里添加 servlet 所需的数据,而不是在 servlet 程序里写入数据?

实际开发中,有一些东西不适合在 servlet 程序中写死,这类数据就可以通过 配置的方式 配给 servlet 程序,例如:

  • servlet 采用哪个码表;
  • servlet 连接哪个数据库;
  • servlet 用哪个配置文件(用 Struts 举的例子)。

举实际的例子,ServletDemo5如下:

  1. // ServletConfig 对象:用于封装 servlet 的配置信息
  2. public class ServletDemo5 extends HttpServlet {
  3. public void doGet(HttpServletRequest request, HttpServletResponse response)
  4. throws ServletException, IOException {
  5. // 得到指定的
  6. String value = this.getServletConfig().getInitParameter("config");
  7. System.out.println(value);
  8. // 得到所有的
  9. Enumeration e = this.getServletConfig().getInitParameterNames();
  10. while (e.hasMoreElements()) {
  11. String name = (String) e.nextElement();
  12. String value_ = this.getServletConfig().getInitParameter(name);
  13. System.out.println(name + " = " + value_);
  14. }
  15. }
  16. }

修改 web.xml,想该 servlet 注册中添加配置数据,如下:

  1. <servlet>
  2. <servlet-name>ServletDemo5</servlet-name>
  3. <servlet-class>cn.wk.ServletDemo5</servlet-class>
  4. <!-- 配置 ServletConfig -->
  5. <init-param>
  6. <param-name>charset</param-name>
  7. <param-value>UTF-8</param-value>
  8. </init-param>
  9. <init-param>
  10. <!-- 配置数据库连接地址 -->
  11. <param-name>url</param-name>
  12. <param-value>jdbc:mysql://localhost:3306/test</param-value>
  13. </init-param>
  14. <init-param>
  15. <param-name>username</param-name>
  16. <param-value>root</param-value>
  17. </init-param>
  18. <init-param>
  19. <param-name>password</param-name>
  20. <param-value>root</param-value>
  21. </init-param>
  22. <init-param>
  23. <!-- 本 servlet 读哪个配置文件 -->
  24. <param-name>config</param-name>
  25. <param-value>/struts-config.xml</param-value>
  26. </init-param>
  27. </servlet>

浏览器中输入http://localhost:8080/day05/servlet/ServletDemo5,则控制台中输出结果为:

  1. config = /struts-config.xml
  2. username = root
  3. charset = UTF-8
  4. config = /struts-config.xml
  5. password = root
  6. url = jdbc:mysql://localhost:3306/test

8. ServletContext 对象(为整个 web 应用而生)

产生:web 服务器有多少 web 应用,服务器就创建多少个 ServletContext 容器!

销毁:停服务器或者删除了某 web 应用,对应的 Context 容器们或容器会被销毁。

8.1 获取 ServletContext 对象

ServletContext 用来操作整个该 servlet 程序所在的 web 应用全局性资源的。

获取 ServletContext 对象:

  1. // ServletContext 示例
  2. public class ServletDemo6 extends HttpServlet {
  3. public void doGet(HttpServletRequest request, HttpServletResponse response)
  4. throws ServletException, IOException {
  5. // 获取 ServletContext 对象
  6. ServletContext context = this.getServletContext();
  7. }
  8. }

8.2 ServletContext 域

ServletContext 域,表达了以下几点意思:

  1. 这是一个容器;
  2. ServletContext 域代表这个容器的作用范围时 整个特定的 web 应用,如day05这个应用

下面的例子,实现了 day05 这个 web 应用范围内的,ServletDemo7ServletDemo8这2个servlet程序的数据共享

  1. public class ServletDemo7 extends HttpServlet {
  2. public void doGet(HttpServletRequest request, HttpServletResponse response)
  3. throws ServletException, IOException {
  4. String data = "aaa";
  5. // 向 Context 容器内添加 全web应用域 都能共享的数据
  6. this.getServletContext().setAttribute("data", data);
  7. }
  8. }
  1. public class ServletDemo8 extends HttpServlet {
  2. public void doGet(HttpServletRequest request, HttpServletResponse response)
  3. throws ServletException, IOException {
  4. // 获取 ServletContext 对象
  5. ServletContext context = this.getServletContext();
  6. // 获取 Context 容器内的数据
  7. String value = (String) context.getAttribute("data");
  8. // 向浏览器输出
  9. response.getOutputStream().write(value.getBytes());
  10. }
  11. }

浏览器中输入http://localhost:8080/day05/servlet/ServletDemo7,再开一个小窗,地址栏中输入http://localhost:8080/day05/servlet/ServletDemo8,在当前的这个浏览器中会出现ServletDemo7输出到ServletContext域中的共享数据aaa

我们可以在 web.xml 文件中写<context-param> 为整个 web 应用做配置,如下:

  1. <!-- 为当前整个web应用做的配置 -->
  2. <!-- 所有servlet都可通过ServletContext容器访问下面的数据 -->
  3. <context-param>
  4. <param-name>data</param-name>
  5. <param-value>xxxx</param-value>
  6. </context-param>

然后建一个servlet程序,输出Context容器中内容即可,String value = this.getServletContext.getAttribute("data"),输出这个value即可。

问题:什么情况下,需为整个 web 应用配置初始化参数?

上百个 servlet 都需连数据库的话,难道上百个servlet配置都用ServletConfig来配?这时就得用这个 代表全局的ServletContext容器来配置

8.3 ServletContext 域 - Servlet 转发技术

Servlet的转发和重定向

转发:你向我借钱,我没有,我帮你去找他;

重定向:你向我借钱,我没有,你自己去找他!

Servlet 只适合产生数据,不适合美化输出,输出得靠html和css。这时就用到转发,既servlet产生的数据转发到 jsp,由 jsp 负责输出数据,这时将大量使用到转发。

例子,ServletDemo10产生的数据转发到1.jsp

  1. // 通过 ServletContext 实现请求转发
  2. public class ServletDemo10 extends HttpServlet {
  3. public void doGet(HttpServletRequest request, HttpServletResponse response)
  4. throws ServletException, IOException {
  5. String data = "aaaaaaaaa";
  6. // 把数据带给1.jsp(只是演示,正常的话不能用context域,要通过request域)
  7. this.getServletContext().setAttribute("data", data);
  8. RequestDispatcher rd = this.getServletContext().getRequestDispatcher("/1.jsp");
  9. rd.forward(request, response);
  10. }
  11. }
  1. <%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
  2. <%
  3. String path = request.getContextPath();
  4. String basePath = request.getScheme() + "://"
  5. + request.getServerName() + ":" + request.getServerPort()
  6. + path + "/";
  7. %>
  8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  9. <html>
  10. <head>
  11. <base href="<%=basePath%>">
  12. <title>My JSP '1.jsp' starting page</title>
  13. <meta http-equiv="pragma" content="no-cache">
  14. <meta http-equiv="cache-control" content="no-cache">
  15. <meta http-equiv="expires" content="0">
  16. <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
  17. <meta http-equiv="description" content="This is my page">
  18. <!--
  19. <link rel="stylesheet" type="text/css" href="styles.css">
  20. -->
  21. </head>
  22. <body>
  23. <!-- 这里开始写代码 -->
  24. <h1>
  25. <font color="red">
  26. <%
  27. String data = (String)application.getAttribute("data");
  28. out.write(data);
  29. %>
  30. </font>
  31. </h1>
  32. <!-- 结束 -->
  33. </body>
  34. </html>

打开浏览器输入http://localhost:8080/day05/servlet/ServletDemo10查看结果。

8.4 ServletContext 域 - 读取 Web 应用资源文件 - .properties 属性文件

一般不管理servlet程序,管理的是 web 应用的其他资源文件。

实际应用场景:

众多servlet程序要访问数据库,则一般来说,数据库相关信息要用资源文件保存。

有两种文件用来保存数据库配置信息:

.properties文件(信息无关联用这个)和.xml文件(信息有关联用这个)

连接数据库的这种信息,认为是平行信息(无关联信息),故应用.properties文件保存。

试验,涉及2文件,ServletDemo11db.properties(该文件在/src目录下):

db.properties内容如下:

  1. url=jdbc:mysql://localhost:3306/test
  2. username=root
  3. password=root

ServletDemo11内容如下:

注意:

读取.properties文件是重点,且不要用FileInputStream对象读,因为它默认的相对路径起始处为 java 虚拟机所在的位置。

注意观察路径:/WEB-INF/classes/db.properties

ServletDemo11执行时,工程里的src/db.properties文件早已部署到/WEB-INF/classes/db.properties了,所以应写这个路径!

  1. // 通过 ServletContext 读取资源文件 方法1
  2. public class ServletDemo11 extends HttpServlet {
  3. public void doGet(HttpServletRequest request, HttpServletResponse response)
  4. throws ServletException, IOException {
  5. // FileInputStream 这么用是不行滴
  6. // FileInputStream in = new
  7. // FileInputStream("/WEB-INF/classes/db.properties");
  8. // 此处最好不要用 FileInputStream 对象,需好用 ServletContext 去读
  9. InputStream in = this.getServletContext().getResourceAsStream(
  10. "/WEB-INF/classes/db.properties");
  11. Properties props = new Properties(); // map
  12. props.load(in);
  13. String url = props.getProperty("url");
  14. String username = props.getProperty("username");
  15. String password = props.getProperty("password");
  16. System.out.println(url);
  17. System.out.println(username);
  18. System.out.println(password);
  19. }
  20. }

浏览器输入http://localhost:8080/day05/servlet/ServletDemo11,则控制台输出结果为:

  1. jdbc:mysql://localhost:3306/test
  2. root
  3. root

也可以这样读取:

  1. // 通过 ServletContext 读取资源文件 方法2
  2. public class ServletDemo11 extends HttpServlet {
  3. public void doGet(HttpServletRequest request, HttpServletResponse response)
  4. throws ServletException, IOException {
  5. // 获取资源绝对路径后,可用 FileInputStream
  6. String path = this.getServletContext().getRealPath(
  7. "/WEB-INF/classes/db.properties");
  8. // 获取文件名
  9. String filename = path.substring(path.lastIndexOf("\\") + 1);
  10. System.out.println("当前读取到的资源名称是: " + filename);
  11. FileInputStream in = new FileInputStream(path);
  12. Properties props = new Properties(); // map
  13. props.load(in);
  14. String url = props.getProperty("url");
  15. String username = props.getProperty("username");
  16. String password = props.getProperty("password");
  17. System.out.println("当前读取到的资源数据是:");
  18. System.out.println(url);
  19. System.out.println(username);
  20. System.out.println(password);
  21. }
  22. }

9. Web 应用中普通 Java 程序如何读取资源文件? - 通过类装载器

故事:若 servlet 程序想访问数据库,正常情况应通过 dao 层访问,而不是直接访问。而 dao 层的类基本上是普通 java 程序,而不是 servlet 程序。问题是:普通的 java 程序,如何访问当前 web 应用中的某个 web 资源(如:想访问 src/db.properties)?

这就要通过类装载器来完成。

类装载器:当前web应用有一个类装载器,装载了所有当前web应用下的类的字节码文件。可通过该web应用下的任意java程序,获得这个类装载器。通过这个类装载器,找到要访问的资源,获得对应的输入流对象!

试验,涉及3个文件:要访问的资源src/db.properties(与上节内容相同),ServletDemo12.java和一个普通 java 程序UserDao.java

ServletDemo12.java如下:

  1. // servlet调用其他普通java程序,这个普通java程序需通过 类加载器 读取web资源文件
  2. public class ServletDemo12 extends HttpServlet {
  3. public void doGet(HttpServletRequest request, HttpServletResponse response)
  4. throws ServletException, IOException {
  5. UserDao dao = new UserDao();
  6. dao.update();
  7. }
  8. }

UserDao.java位于cn.wk.dao包里,涉及到静态代码块和类加载器,代码如下:

  1. package cn.wk.dao;
  2. import java.io.InputStream;
  3. import java.util.Properties;
  4. public class UserDao {
  5. private static Properties dbconfig = new Properties();
  6. // 类加载时 就获得资源
  7. static {
  8. try {
  9. // 类加载器
  10. InputStream in = UserDao.class.getClassLoader()
  11. .getResourceAsStream("db.properties");
  12. dbconfig.load(in);
  13. } catch (Exception e) {
  14. // 认为访问不到资源,是重大错误,而不是异常,故抛出错误
  15. throw new ExceptionInInitializerError(e);
  16. }
  17. }
  18. public void update() {
  19. System.out.println(dbconfig.getProperty("url")); // map
  20. }
  21. }

浏览器输入http://localhost:8080/day05/servlet/ServletDemo12,然后在控制台查看结果。

9.1 通过类加载器获取更新后的资源

故事:服务器开启后,当有人修改了db.properties资源文件后,再通过类装载器直接获得资源时,无法获得最新的,原因是类装载器里的字节码文件随服务器启动后只加载1次。如下代码:

  1. public class UserDao {
  2. public void update() throws IOException {
  3. // 以下代码虽然可以读取资源文件的数据,但无法获取更新后的数据
  4. Properties dbconfig = new Properties();
  5. InputStream in = UserDao.class.getClassLoader().getResourceAsStream(
  6. "db.properties");
  7. dbconfig.load(in);
  8. System.out.println(dbconfig.getProperty("url")); // map
  9. }
  10. }

正确姿势:

  1. public class UserDao {
  2. public void update() throws IOException {
  3. // 通过类装载的方式得到资源文件的位置,再通过传统方式读取资源文件的数据
  4. // 这样可以读取资源更新后的数据
  5. String path = UserDao.class.getClassLoader()
  6. .getResource("db.properties").getPath();
  7. FileInputStream in = new FileInputStream(path);
  8. Properties dbconfig = new Properties();
  9. dbconfig.load(in);
  10. System.out.println(dbconfig.getProperty("url")); // map
  11. }
  12. }

day05 Servlet 开发和 ServletConfig 与 ServletContext 对象的更多相关文章

  1. Servlet第二篇【Servlet调用图、Servlet细节、ServletConfig、ServletContext】

    Servlet的调用图 前面我们已经学过了Servlet的生命周期了,我们根据Servlet的生命周期画出Servlet的调用图加深理解 Servlet的细节 一个已经注册的Servlet可以被多次映 ...

  2. Servlet(八):ServletContext对象和ServletConfig对象

    ServletContext 对象:问题: Request 解决了一次请求内的数据共享问题,session 解决了用户不同请求的数据共享问题,那么不同的用户的数据共享该怎么办呢?解决: 使用 Serv ...

  3. JavaEE:Servlet简介及ServletConfig、ServletContext

    Servlet简介 1.Servlet是sun公司提供的一门用于开发动态web资源的技术*静态web资源:固定数据文件*动态web资源:通过程序动态生成数据文件2.Servlet技术基于Request ...

  4. Servlet入门和ServletConfig、ServletContext

    Servlet是一门用于开发动态web资源的技术. 若想开发一个动态web资源,需要完成以下2个步骤: 1)编写一个Java类,实现servlet接口: 2)把开发好的Java类部署到web服务器中. ...

  5. ServletConfig与ServletContext对象(接口)

    ServletConfig:封装servlet的配置信息. 在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数. <ser ...

  6. ServletConfig与ServletContext对象详解

    一.ServletConfig对象 在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数.(配置在某个servlet标签或者整个w ...

  7. JavaWeb(一)Servlet中的ServletConfig与ServletContext

    前言 前面我介绍了一下什么是servlet,它的生命周期,执行过程和它的原理.这里我们做一个简单的回顾! 什么是Servlet? servlet 是运行在 Web 服务器中的小型 Java 程序(即: ...

  8. Servlet学习笔记(二)之Servlet路径映射配置、Servlet接口、ServletConfig、ServletContext

    Servlet路径映射配置 要使Servlet对象正常的运行,需要进行适当的配置,以告诉Web容器哪个请求调用哪个Servlet对象处理,对Servlet起到一个注册的作用.Servlet的配置信息包 ...

  9. Servlet第二篇(介绍、ServletConfig;ServletContext)

    什么是Serlvet? Servlet其实就是一个遵循Servlet开发的java类.Serlvet是由服务器调用的,运行在服务器端. 为什么要用到Serlvet? 我们编写java程序想要在网上实现 ...

随机推荐

  1. 新概念英语(1-73)The way to King Street

    The way to King Street 到国王街的走法Why did the man need a phrasebook?Last week Mrs. Mills went to London. ...

  2. Ajax.html:35 [Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org

    AJAX的容易错误的地方 Ajax.html:35 [Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated ...

  3. python Flask

    python Flask Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请 ...

  4. baidu地图:实现多点连线渲染

    <script type="text/javascript"> var points = [ {"Lng":125.17787645967861,& ...

  5. QT 设计师使用样式表添加背景

    QT create中样式表可以用来设置背景图.背景颜色.字体大小格式颜色等 1.添加背景图的话需要先添加资源文件 右击项目文件选择添加新文件,再选择QT资源文件(QT resource file)然后 ...

  6. HTML5新增的标签及使用

    HTML5和HTML其实是很相似的,但是有些内容有发生了改变,今天我学习了一下HTML5发现还是挺好学的,只要有html+css基础就可以,今天知识看了下新的标签. 一.定义文档类型 在文件的开头总是 ...

  7. volatile 到i++ 原子操作 详解

    1.可见性(Visibility) 可见性是指,当一个线程修改了某一个全局共享变量的数值,其他线程是否能够知道这个修改. 显然,在串行程序来说可见性的问题是不存在的.因为你在任何一个地方操作修改了某个 ...

  8. 毕业回馈--89C51keil工程的创建

    声明:毕业回馈类博客均为大学毕业前夕同同学共享内容.为了给大学做一个总结,报答母校的栽培,才发起这样一个活动. ******************************************** ...

  9. 报错pymysql.err.DataError: (1406, "Data too long for column 'gender' at row 1")

    在Django默认的admin后台创建超级用户时, 报错pymysql.err.DataError: (1406, "Data too long for column 'gender' at ...

  10. [SDOI 2017]数字表格

    Description 题库链接 记 \(f_i\) 为 \(fibonacci\) 数列的第 \(i\) 项. 求 \[\prod_{i=1}^n\prod_{j=1}^mf_{gcd(i,j)}\ ...