初识Servlet

Servlet 是sun提供的一门专门用于开发动态web资源的技术,传统的步骤分为两步:

  1. 编写一个java类,实现servlet接口
    不过一般是继承自 HttpServlet ,因为大多都是用于http,它默认已经实现了所有未实现的方法,需要那个覆盖那个即可
  2. 把开发好的java类部署到web服务器

听起来蛮简单的,其中的道道不少呢
Servlet已经是属于J2EE的其中之一,在java SE 的API中是没有的,不过Tomcat中是自带的,因为他要解析啊

什么是Servlet

引用自Wiki上的一句:

Servlet(Server Applet),全称Java Servlet,未有中文译文。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。
狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。

Servlet运行于支持 Java 的应用服务器中。从实现上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。

与JSP的关系

Java服务器页面(JSP)是 HttpServlet 的扩展。由于 HttpServlet 大多是用来响应HTTP请求,并返回Web页面(例如HTML、XML),所以不可避免地,在编写 servlet 时会涉及大量的HTML内容,这给 servlet 的书写效率和可读性带来很大障碍,JSP便是在这个基础上产生的。

其功能是使用HTML的书写格式,在适当的地方加入 Java 代码片段,将程序员从复杂的 HTML 中解放出来,更专注于 servlet 本身的内容。

JSP在首次被访问的时候被应用服务器转换为servlet ,在以后的运行中,容器直接调用这个 servlet,而不再访问 JSP 页面。JSP的实质仍然是 servlet。

生命周期

在 Servlet 中有了生命周期的概念,也就有了相应的方法,比如 init()destroy()

当用户第一次访问的时候创建,执行 init 方法完成初始化,此后会一直存在于容器等待客户机的第二次访问
web服务器关闭的时候 Servlet 才会销毁,也就是说 一个Class文件只会存在一个Servlet对象

但是每一次请求,服务器都会创建一个新的 request 和 response 对象(同时也会创建一个新线程),注意是每一次请求,一个用户就可以发起多次请求,好在它们的生命周期很短,随请求的结束就销毁了,因为一次请求的时间本来就很短
每一次请求调用 Servlet 一次,但是对象只有一个

因为每一次请求都会有各自的线程进行处理,虽然解决了一个对象可以同时被访问(也就是说不同的请求可以同时cao执行一个对象[servlet]里的方法),但是存在线程安全问题,当然如果变量是在类里定义的,那对象只有一个,变量也就只有一个

编写Servlet

我们写一个最简单的 Servlet ,国际惯例使用 Hello World 测试,本质就是一个Java类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@WebServlet("/HelloWorld")
public class HelloWorld extends HttpServlet {
private String msg;

@Override
public void init() throws ServletException {
msg = "is Hello World java";
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");

PrintWriter writer = resp.getWriter();
writer.print(msg);
}

@Override
public void destroy() {
super.destroy();
}
}

这样就会输入 msg 的消息,我是用 IDEA 写的,在类名的上面加上标注(注解) @WebServlet("/HelloWorld") 意思是设置 servlet 对应的url地址

Servlet3.0 之后提供了注解(annotation),使得不再需要在 web.xml 文件中进行 Servlet 的部署描述,简化开发流程
JDK1. 5版本之后, JAVA提供了一种叫做 Annotation 的新数据类型,中文译为注解或标注,它的出现为铺天盖地的XML配置文件提供了一个完美的解决方案,让 JAVA EE 开发更加方便快速,也更加干净了

如果不想使用注解,还有另一种方式,就是在 web.xml 文件中进行手动配置

1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>HelloWorld</servlet-name>
<servlet-class>com.bfchengnuo.test.HelloWorld</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>HelloWorld</servlet-name>
<url-pattern>/hw</url-pattern>
</servlet-mapping>

servlet 标签配置名字以及具体的 class,servlet-mapping 是设置映射关系,将那个名字的 Servlet 映射到什么虚拟目录(浏览器访问时输入)
上面的一段其实和 @WebServlet(name="HelloWorld",value="/hw") 是完全相同的

当标注(注解)与 web.xml 同时配置时,标注无效。
使用标注:由于是在对应的类中配置的信息,因而则可以不用在标注中配置class了。
对于 web.xml 中的配置,在标注中通通都有配置
在 web.xml 中一个 servlet 可以配置多个 servlet-mapping, 只要在其中指定相同的 servlet-name 即可。标注也可以指定多个的,但不再用 value,而是用urlPatterns 数组。
@WebServlet(name="HelloWorld",urlPatterns={"/HelloWroldServlet","/HelloWorld"})

同时如果需要随服务器加载,可以在 servlet 标签下设置 <load-on-startup>1</load-on-startup> 数字越小越优先

关于映射

映射如果写的是类似 /1.html 这种就是伪静态,因为实际访问的是动态的web资源

1
2
3
4
<servlet-mapping>
<servlet-name>HelloWorld</servlet-name>
<url-pattern>/hw.html</url-pattern>
</servlet-mapping>

虽然你在浏览器输入的确实html,但是其实请求的是一个 Servlet
映射当然也是支持通配符的,比如 /* 就是映射到任何页面
但是只能有两种固定的格式:

  1. *. 开头 + 扩展名 [ 固定扩展名(*.jsp) ]
  2. / 开头并且以 /* 结尾 [固定路径]

/abc/*.html 是错误的,有歧义嘛~ ; 服务器不知道它是路径映射还是扩展映射

这样也就不可避免的会有存在匹配冲突的情况下,谁匹配更多(也就是更精确)就是那个,和正则类似,*的匹配优先级最低,所以*开头的都很低,一般是最后匹配

缺省的 Servlet 的映射为: / ,当找不到 Servlet 的时候找它
其实任何请求都是由 Servlet 进行处理的,当访问静态资源的时候其实就是因为找不到相关的 Servlet 映射,就交给系统缺省的 Servlet 映射负责处理,来找到那些静态的资源,如果静态资源也没用,那就只能映射到 404 页面了
如果覆盖了缺省的 Servlet ,那静态资源就访问不到了,只会访问自定义的缺省的 Servlet ,所以不要把 url-pattern 设置为 /

ServletContext

Web容器在启动的时候,会为每一个 web 应用都创建一个 ServletContext 对象,它代表当前的 web 应用,注意是当前的web应用不是 Servlet
ServletContext 在服务器启动时创建,关闭时销毁,既然它代表web应用,那么就有很多相应的方法,比如获取各种配置等,面向对象思想嘛~
上一篇 了解Tomcat 中已经说的很详细了
ServletConfig 对象中维护了 ServletContext 对象的引用,所以可以在 Servlet 中通过 this.ServletConfig.getServletContext() 获得 ServletContext 对象
当然其实也是直接可以通过 getServletContext 方法获取到的,因为继承的是 HttpServlet 在爷爷辈的 init 方法中已经进行了保存

1
2
3
4
5
6
7
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}

ServletContext 对象通常称为 context 域

转发&重定向

这个用的次数是非常之频繁的,先说明下它和重定向的主要区别:

  • 重定向:我没有,我给你个地址,让你去找别人要
  • 转发:我没有,我帮你找别人获取,然后再把你需要的资源给你

转发对于客户端来说只发一次请求,网址也不变,客户端甚至不会察觉,也不知道这个资源到底是谁的( 嗯,有点像反向代理呢 ,在我的公众号已经写过正向代理和反向代理是什么了)
用代码来表达就是:

1
2
3
4
RequestDispatcher rd = getServletContext().getRequestDispatcher("/index.jsp")
rd.forward(request,response);
// 一般的写法为
request.getRequestDispatcher("path").forward(request,response);

至于这两者的区别,最大的区别就是 ServletContext 转发时路径必须绝对路径,也就是 / 开头
使用 request 进行转发既可以使用绝对路径也可以使用相对路径(相对于 web 应用也就是比起上面可以不写 /),一般这种方式用的比较多,大多数是使用绝对路径的

当然如果需要携带参数需要 Request 域来实现,使用 setAttribute 设置自定义参数,下次再说,当然也可以使用 getParameter 方法来获得 URL 携带的数据

重定向就是用 sendRedirect 方法设置下路径就可以了,路径就是浏览器中的地址了,所以如果 / 开头就是指的此网站,后面多半需要加 web 应用的名,或者通过 request.getContextPath() 来获取此 web 应用的地址,然后再加 “/xxx.jsp”这样。

重定向(redirect)后:确认了要跳转的页面的 url,继续执行 redirect 下面的代码;执行完后,断开当前的与用户所发出的请求连接,即断开 request 的引用指向,因此 request 里存放的信息也会丢失。
转发(forward)后:确认了要转发的页面的地址,现在停止继续执行后面的代码,而是先执行转发后的那个 servlet 里的代码,执行完后再回来继续执行后面的代码;在这期间 check 和 success 共享一个 request 和 response 对象。

读取/存储问题

我们可以把一些信息存在 web.xml 文件中,方便修改和获取,在 web.xml 中也有两种形式,一种是属于 Servlet 的,一种是整个web应用的

1
2
3
4
5
6
7
8
9
10
11
12
13
<context-param>
<param-name>conData</param-name>
<param-value>Lolicon</param-value>
</context-param>

<servlet>
<servlet-name>HelloWorld</servlet-name>
<servlet-class>com.bfchengnuo.test.HelloWorld</servlet-class>
<init-param>
<param-name>data</param-name>
<param-value>is test data</param-value>
</init-param>
</servlet>

获取方式基本也相同,当然也可以进行设置:

1
2
3
4
// 获取 Servlet 中的配置的数据
String str = this.getServletConfig().getInitParameter("data");
// 获取 web 应用的配置的数据
String str2 = this.getServletContext().getInitParameter("conData");

设置初始化参数使用注解也是可以的,类似这样
@WebServlet(name="HelloWorld",urlPatterns={"/HelloWorld"},initParams={@WebInitParam(name="id",value="1"),@WebInitParam(name="name",value="Loli")})

properties

对于无逻辑性的数据,一般使用:.properties 来进行存储,内容格式非常简单一般就是一行一个 key=val 的形式,详细的介绍见 维基百科
Java中提供了专门的类来处理这种文件

1
2
3
4
5
InputStream resourceAsStream = this.getServletContext().getResourceAsStream("/WEB-INF/classes/a.properties");
Properties prope = new Properties();
prope.load(resourceAsStream);

String str = prope.getProperty("key");

如果读取资源文件的程序不是 Servlet 类,就只能通过类装载器去读,文件不能太大,因为会被加载进内容,太大会内存溢出
ParamTest.class.getClassLoader().getResourceAsStream(path)
类装载器的 path 相对于的是 src 目录,也就是包名的开始目录

由于类装载器只装载一次,在 JVM 没有重启的情况下,修改文件无效
解决方案是用传统方式读取,首先通过类装载器获取到文件的绝对路径,再 FileInputStream 读取
FileInputStream 的相对路径是相对的 JVM 的启动目录,所以最好传绝对路径,如果用 ServletContext 去读,相应的 / 就是 web 应用的目录,用类装载器的话相对的目录就是 src 下开始
获取路径的方法为:
HelloWorld.class.getClassLoader().getResourceAsStream("com/bfchengnuo/xx").getPath()

关于@WebServlet的补充

@WebServlet 主要属性列表:

属性名 类型 描述
name String 指定 Servlet 的 name 属性,等价于 <servlet-name>。如果没有显式指定,则该 Servlet 的取值即为类的全限定名。
value String[] 该属性等价于 urlPatterns 属性。两个属性不能同时使用。
urlPatterns String[] 指定一组 Servlet 的 URL 匹配模式。等价于 <url-pattern>标签。
loadOnStartup int 指定 Servlet 的加载顺序,等价于 <load-on-startup> 标签。
initParams WebInitParam[] 指定一组 Servlet 初始化参数,等价于 <init-param>标签。
asyncSupported boolean 声明 Servlet 是否支持异步操作模式,等价于 <async-supported>标签。
description String 该 Servlet 的描述信息,等价于 <description> 标签。
displayName String 该 Servlet 的显示名,通常配合工具使用,等价于 <display-name>标签。

例如初始化参数:

1
2
3
4
5
6
7
@WebServlet(name="HelloWorld",urlPatterns={"/HelloWroldServlet","/HelloWorld"},initParams={@WebInitParam(name="id",value="yeh"),@WebInitParam(name="name",value="Loli")})
class Test extends HttpServlet{}

// 分开配置
@WebServlet("hello")
@WebInitParam(name="id",value="yeh")
Public class Test extends HttpServlet{}

注意格式就好,第二种低版本的 Tomcat 好像不识别

参考

http://blog.csdn.net/zw_2011/article/details/7432839

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~