java web的基础知识
写Web应用时,直接用的spring框架,没有接触过Servlet,于是遇到问题不知道根本原因,所以还是学一下吧!
一、 什么是Servlet
看见这个词我就晕,到底是什么?我试着说清楚,从大的方面来说,它是一个在Web中生成动态内容的标准,即是用于开发Web应用程序的基本技术;从小的方面来说它是Java提供的一个接口Servlet,我们也把实现这个接口的类称为Servlet。它和CGI相对,但是它不向CGI一样每次接受一个Http请求都会创建一个进程(想想Chrome),它每次执行完它的第一个请求之后都会驻留在内存中,等待后续的请求。
一个Servlet应用程序经常包括一个或者多个Servlet,Servlet是运行在Servlet容器中的,它不能自动运行。Servlet容器又是什么东西?我的理解就是实现了Servlet规范,然后暴露出来一些编程的接口供我们实现自己的逻辑,所以每个自定义的Servlet类都要实现Servlet接口,它能够生成动态的内容,不向Web服务器一样,只是提供静态资源。更细节的内容看如下的相关文章。Servlet容器将用户的请求传给Servlet应用程序,并将Servlet应用程序的响应回传给用户。比较流行的Servlet容器有Tomcat和Jetty。这两个本质上是Servlet容器 ,而不是Web服务器,但是它们把Web服务器要做的工作也集成到容器中了,所以不需要额外的Web服务器。
二、 Servlet API
我使用的是Servlet的3.1.0版本,有4个Java包:
- javax.servlet:包含Servlet与Servlet容器进行通信需要的类和接口,比如请求、响应、过滤和监听等;
- javax.servlet.http:包含Servlet的实现类httpServlet与Servlet容器进行通信需要的类和接口;
- javax.servlet.annotation:使用Servlet、Filter和Listener时需要的注解;
- javax.servlet.descriptor:提供对配置信息进行编程式访问的类型;
在Servlet技术的核心当然是Servlet接口,每个自定义的类都是直接或者间接实现了这个接口。在这个接口中说明了Servlet怎么被Servlet容器调用,首先容器会把Servlet类加载到内存中,并在Servlet类调用特定的方法,在每个应用程序中,每个Servlet只有一个实例(单实例)。下面试一下Servlet中的方法:
[java] view plain copy
- public class TestServlet implements Servlet {
- private transient ServletConfig servletConfig;
- public void init(ServletConfig config) throws ServletException {
- this.servletConfig = config;
- System.out.println("init");
- }
- public ServletConfig getServletConfig() {
- return servletConfig;
- }
- public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
- System.out.println("service");
- }
- public String getServletInfo() {
- return this.servletConfig.getServletName();
- }
- public void destroy() {
- System.out.println("destroy");
- }
- }
除了要自定义一个Servlet类,我们还要想一个办法来确定怎么访问到我们自定义的Servlet?显然要指定url,有两种方法:
通过注解,比较简单,直接在类上加上注解:
[java] view plain copy
- @WebServlet(name="testServlet", urlPatterns="/testServlet",loadOnStartup=-1)
- public class TestServlet implements Servlet {}
[html] view plain copy
- <</span>servlet>
- <</span>servlet-name>TestServlet</</span>servlet-name>
- <</span>servlet-class>com.javaservlet.servlet.TestServlet</</span>servlet-class>
- </</span>servlet>
- <</span>servlet-mapping>
- <</span>servlet-name>TestServlet</</span>servlet-name>
- <</span>url-pattern>/TestServlet</</span>url-pattern>
- </</span>servlet-mapping>
注意:
- 配置url-pattern时,可以对一个Servlet配置多个url,也就是可以通过多个url来访问同一个servlet;
- 配置url-pattern时,也可以使用通配符进行配置,但是只有两种固定方法:一是“*.扩展名”,类似“*.html”;另外一个则是以“/”开头,以“/*”结尾,类似“/test/*”;
Servlet接口中有三个生命周期方法:
- init():刚启动Servlet容器时不会调用这个方法,当第一次请求来的时候才会调用该方法,其实什么时候调用由loadOnStartup属性决定,如果是非0值则该方法会在容器启动时被调用,但是该值默认是-1,在后续的请求时不会再调用该方法,所以说在该方法中进行程序初始化相关的代码,另外值得注意的是该方法总是在servlet的构造器之后运行;
- service():每次请求Servlet时候,Servlet容器会调用这个方法,是具体编写业务逻辑的地方;
- destroy():销毁Servlet的时候,Servlet容器会调用这个方法,发生在卸载应用程序或者关闭Servlet容器时,另外从上面的输出来看,每次修改代码后保存都会reload,这个时候其实销毁该Servlet类,关闭Servlet容器并重新开启,可以在这个方法中进行资源清理的工作;
该接口中还有其他两个非生命周期的方法:getServletInfo()感觉不是很常用,只是返回对Servlet的描述;getServletConfig()返回init()传入的ServletConfig类实例,是关于Servlet的配置信息。
由于Servlet实例是单实例,在每一个应用程序中,多次请求都是针对一个对象,很容易造成并发问题,所以可以把它当做只读的,或者使用java并发机制中成员。
三、 与Servlet相关的类
有4个有关的类,通过servlet可以获得其中的三个,然后通过ServletConfig间接获取ServletContext。
1、 ServletConfig
该类是在Servlet容器初始化Servlet时,将初始化参数交给servlet的init()方法;另外它还可以获得ServletConfig类的实例。类里两个方法比较常用,就是获得初始化参数。
首先我们要知道怎么配置初始化参数?两个办法:
通过注解:
[java] view plain copy
- @WebServlet(name="testServlet", urlPatterns="/testServlet", loadOnStartup=1,
- initParams={
- @WebInitParam(name="user", value="lmy"),
- @WebInitParam(name="pass", value="123")
- }
- )
- public class TestServlet implements Servlet {}
通过web.xml:
[html] view plain copy
- <</span>servlet>
- <</span>servlet-name>TestServlet</</span>servlet-name>
- <</span>servlet-class>com.javaservlet.servlet.TestServlet</</span>servlet-class>
- <</span>init-param>
- <</span>param-name>age</</span>param-name>
- <</span>param-value>21</</span>param-value>
- </</span>init-param>
- <</span>init-param>
- <</span>param-name>address</</span>param-name>
- <</span>param-value>nicai</</span>param-value>
- </</span>init-param>
- <</span>load-on-startup>1</</span>load-on-startup>
- </</span>servlet>
由于参数都是以键值对形式存在,可以获取参数:
[java] view plain copy
- System.out.println("user : " + servletConfig.getInitParameter("user"));
- System.out.println("pass : " + servletConfig.getInitParameter("pass"));
- Enumeration enums = servletConfig.getInitParameterNames();
- while(enums.hasMoreElements()){
- String paramName = enums.nextElement();
- String paramValue = servletConfig.getInitParameter(paramName);
- System.out.println(paramName + "-" + paramValue);
- }
注意,该参数只是属于Servlet自己的,所以配置参数的时候要注意所放的位置。
2、 ServletContext
通过ServletConfig类的getServletConext()方法可以获得ServletContext类实例,该对象表示整个web应用程序,在一个程序中只有一个,程序中所有的servlet共享该对象,可以说是一个全局对象,我们也可以获取web应用的初始化参数,该初始化参数可供所有的servlet使用,首先设置web初始化参数:
[html] view plain copy
- <</span>context-param>
- <</span>param-name>country</</span>param-name>
- <</span>param-value>cn</</span>param-value>
- </</span>context-param>
[java] view plain copy
- ServletContext context = servletConfig.getServletContext();
- context.getInitParameter("country");
- Enumeration contextParams = context.getInitParameterNames();
- while(contextParams.hasMoreElements()){
- String paramName = contextParams.nextElement();
- String paramValue = context.getInitParameter(paramName);
- System.out.println(paramName + "-" + paramValue);
- }
获取web程序名称:
[java] view plain copy
- // 获取当前web程序的context path,也可以说是程序名称
- System.out.println(context.getContextPath()); //output result: /JavaServlet
[java] view plain copy
- System.out.println(context.getRealPath("readme.txt"));
输出结果:
[java] view plain copy
- C:\Program Files\tomcat7\wtpwebapps\JavaServlet\readme.txt
可以看出该文件的路径是Server path + deploy path + 应用程序中的路径,记得和师兄之前还讨论过这个问题,当时真是傻死了!!
获取当前web应用的一个文件输入流:
getResourceAsStream(String path):注意其中的path也是相对于web程序根目录的路径,即也是在部署之后的路径,不是部署之前的路径;
与Classloader的方法对比如下:
[java] view plain copy
- InputStream is1 = context.getResourceAsStream("readme.txt");
- System.out.println("inputStream1 : " + is1);
- InputStream is2 = this.getClass().getClassLoader().getResourceAsStream("readme.txt");
- System.out.println("inputStream2 : " + is2);
- InputStream is3 = context.getResourceAsStream("/WEB-INF/classes/readme.txt");
- System.out.println("inputStream3 : " + is3);
可以看出第一个使用部署前的路径根本没有获取到资源,而使用相对于部署后的web程序的根目录(从/WEB-INF开始)则成功获取到了资源。
总之就是一句话,在web程序中获取路径时一定要小心,因为获取到的路径很多都是根据程序部署之后相对于服务器部署目录的路径,开发时的路径是不会获取到的,即开发和部署两个阶段中调试程序是有差异的。