动态资源分为Servlet和JSP,前者是基础。
简介
Sun公司在其API中提供了一个Servlet接口,用户若想开发一个动态web资源(即开发一个java程序向浏览器输出数据),需要完成以下两个步骤:
- 编写一个Java类,实现Servlet接口;
- 把开发好的Java类部署到web服务器;
生命周期
如同人一样,六岁上学,十八岁上大学,三十岁结婚,六十岁退休……这些事件是和人这个对象的生命周期相关的,到了某个特定时间就会做。同样,Servlet中有些方法和Servlet的生命周期也是相关的,文档中原文
1 | This interface defines methods to initialize a servlet, to service requests, and to remove a servlet from the server. These are known as life-cycle methods and are called in the following sequence: |
也就是初始化init()、响应请求service()、移除destory()这些方法在某个特定时刻必定会执行。具体来说,客户端第一次访问web资源时会创建一个servlet对象,随后init方法会完成对象的初始化,servlet对象不会随着此次访问结束而摧毁,而是留于内存中,客户端访问调用service方法,调用service方法时先创建request对象和response对象,直至关闭服务器或者web应用从服务器删除才会调用servlet的destory()方法。
初试
- 在tomcat中新建一个servlettest应用,在应用中新建一个WEB-INF/classes目录;
- 在classes目录中新建一个FirstServlet.java
1 | package cn.itcast; |
- 进入classes文件夹,进行编译,但需先导入jar包
1 | set classpath=%classpath%;E:\apache-tomcat-8.5.45\lib\servlet-api.jar |
再进行编译
1 | javac -d . FirstServlet.java |
- 编写配置文件,在WEB-INF目录下新建web.xml,配置servlet对外访问路径
1 | <?xml version="1.0" encoding="UTF-8"?> |
- 启动服务器,浏览器地址栏输入http://localhost:8080/servlettest/FirstServlet即可。
在Eclipse中初始
参考这几个博客:
这篇介绍的最基本的,截至2.3都是没问题的,但是按照他的方法启动有些问题,浏览器一直404,也可能是自己启动方法不对;
这篇主要参考设置服务器,也就是第10项;
这篇主要参考最后总结修改class文件生成路径部分;
参考以上,最后右击项目,Run As=>Run on Server,成功。
但是有一个问题,在工程的web.xml下也没有对servlet进行配置,如何成功的呢?这里存疑。
(找到了!!!在servlet3.0后,eclipse中开发服务器的时候,Eclipse不会自动在web.xml中生成该Servlet对应的mapping信息,而是在Servlet代码中加入注解@WebServlet。)
Servlet接口实现类
Servlet接口Sun公司定义了两个默认实现类,分别为:GenericServlet和HttpServlet,前者就是之前讲过的,后者指能够处理HTTP请求的servlet,它在原有接口上添加了一些与HTTP协议处理方法,它比servlet接口的功能更为强大,因此在开发过程中,通常应该继承这个类,在刚才Eclipse自动创建servlet对象中也是继承的这个类。
HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用doGet方法;如为POST请求,则调用doPost方法,因此,在实际开发中,通常只需要覆写这两种方法,而不去覆写service方法。
Servlet的一些细节
web.xml配置
这部分内容其实已经不需要了,但是为了深入了解,还是在此记录,简单学习一下。
由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用< servlet >元素和< servlet-mapping >元素完成。
< servlet >元素用于注册Servlet,它包含两个主要的元素:< servlet-name >和< servlet-class >,分别用于设置Servlet的注册名称和Servlet的完整类名。
< servlet-mapping >元素用于映射一个已注册的Servlet的一个对外访问路径,也就是说同一个Servlet可以被映射到多个URL上,每一个< servlet-mapping >包含两个子元素:< servlet-name >和< url-pattern >,分别用于指定Servlet的注册名称和Servlet的对外访问路径。
如果在< servlet >元素中配置了一个< load-on-startup >元素,那么web应用程序在启动时,就会装载并创建Servlet的实例对象以及调用Servlet实例对象的init()方法,< load-on-startup >2< /load-on-startup >中的数字代表创建的优先级,正整数越小代表优先级越高。
Servlet引擎
需要说明的是,Servlet是一个供其他Java程序调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。这里的Servlet引擎其实就是调用它的其他Java程序,也就是web服务器。
缺省Servlet
如果某个Servlet的映射路径仅仅为一个正斜杠/,那么这个Servlet就成为当前web应用的缺省Servlet。
凡是在web.xml文件中找不到匹配的< servlet-mapping >元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理其他Servlet都不处理的访问请求。
当访问Tomcat服务器中某个静态HTML文件或者图片时,实际上是在访问这个缺省Servlet。也就是说你在浏览器中输入动态web资源也好,静态web资源也好,其实浏览器客户端都交给了Servlet,只不过输入静态web资源,例如访问xxxxxxxx/a.html,这个a.html资源并没有在< servlet-mapping >中进行映射,所以调用缺省Servlet,缺省Servlet寻找同名的a.html进行显示,若缺省Servlet也找不到,则返回404界面。
线程安全
1 | public class Webtest extends HttpServlet { |
假设有两个用户,第一个用户的页面应该输出1,但是假如在该用户线程休眠过程中有另一个用户访问,则最后输出2,这样就涉及到了线程安全,当然,可以通过synchronized()方法来保证线程安全
1 | synchronized(this){ |
但是,很显然在实际开发中,不可能这么做,否则一个用户访问其他用户只能等待。还有另外一种方法
1 | public class Webtest extends HttpServlet implements SingleThreadModel |
SingleThreadModel接口中没有定义任何内容,称为标记接口,相当于只是给该类贴了一个线程安全的标签,这种方法在servlet 2.4之后就已经Deprecated。
线程安全问题很大部分是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。所以,在Servlet中避免使用实例变量是保证Servlet线程安全的最佳选择。
Java 内存模型中,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。
Servlet对象
服务器在创建Servlet时,除了有request、response对象,还有ServletConfig、ServletContext、Session、Cookie等对象。
ServletConfig对象
在Servlet配置文件中,可以在< servlet >元素中使用一个或多个< init-param >标签为Servlet配置一些初始化参数,配置之后,web容器在创建Servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用init()方法时,将ServletConfig对象传递给servlet,进而,开发人员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
1 | <init-param> |
java程序中调用即可得到“2019”:
1 | String value = this.getServletConfig().getInitParameter("data"); |
在实际开发中,例如字符码表、要连接的数据库以及读取哪个配置文件等不适合在程序中显示,就用到了ServletConfig。
1 | <init-param> |
1 | <init-param> |
1 | <init-param> |
ServletContext对象
web容器在启动时,会为每个web应用都创建一个ServletContext对象,ServletConfig对象中维护ServletContext对象的引用,可以通过ServletConfig().ServletContext()方法获得ServletContext对象。、
由于一个web应用中所有Servlet共用一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象实现通讯。ServletContext对象通常也称之为context域对象。
在实际开发中,ServletContext具体有以下几个应用:
- 多个servlet之间实现数据共享,一个写入,其他就可以读到;
- 获取WEB应用的初始化参数,和上边ServletConfig设置初始化参数类似,只不过这里设置的是整个web应用,使用< context-param >
- 实现servlet转发,关于转发和重定向的区别,打个比方,你找我借钱,我让你去找他借,这是重定向;你找我借钱,我帮你去找他借,这是转发。在开发中,转发用的很多,比如servlet的内容肯定要转发,让html去排版再在浏览器显示。
1 | RequestDispatcher rd = this.getServletContext().getRequestDispatcher("/1.jsp"); |
- 读取资源文件
假设读取应用下的db.properties:(常见的两种配置文件,如果配置文件内数据有关联,则用xml,否则properties)
1 | url=jdbc:mysql://localhost:3306/test |
在servlet中读取:
1 | InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties"); |
如果读取资源文件的不是servlet,而是普通的Java文件,就只能通过类加载器,例如在dao读取db.properties:
1 | #servlet |
1 | #UserDao.java |
扩展一:
类加载器读取的文件不能太大,因为类加载器加载的文件直接存在内存中,文件太大会导致内存溢出,JVM爆掉。
扩展二:
UserDao.java中除了update方法还可能有find、delete等方法,但是这写方法只加载一次就可以,不用每个方法里都加载,所以可以写成静态代码块,实际开发中常这样写。
1 | public class UserDao( |
扩展三:
这样的话,启动服务器后,在db.properties中进行修改,这边是无法更新的,因为类加载加载到内存中,要想更新,只能重启服务器,显然不是想要的,所以应该如下
1 | public class UserDao( |
注:转载文章请注明出处,谢谢~