Servlet 是sun提供的一门专门用于开发动态web资源的技术,传统的步骤分为两步:
- 编写一个java类,实现servlet接口
不过一般是继承自 HttpServlet ,因为大多都是用于http,它默认已经实现了所有未实现的方法,需要那个覆盖那个即可 - 把开发好的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 | "/HelloWorld") ( |
这样就会输入 msg 的消息,我是用 IDEA 写的,在类名的上面加上标注(注解) @WebServlet("/HelloWorld")
意思是设置 servlet 对应的url地址
Servlet3.0 之后提供了注解(annotation),使得不再需要在 web.xml 文件中进行 Servlet 的部署描述,简化开发流程
JDK1. 5版本之后, JAVA提供了一种叫做 Annotation 的新数据类型,中文译为注解或标注,它的出现为铺天盖地的XML配置文件提供了一个完美的解决方案,让 JAVA EE 开发更加方便快速,也更加干净了
如果不想使用注解,还有另一种方式,就是在 web.xml 文件中进行手动配置
1 | <servlet> |
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 | <servlet-mapping> |
虽然你在浏览器输入的确实html,但是其实请求的是一个 Servlet
映射当然也是支持通配符的,比如 /*
就是映射到任何页面
但是只能有两种固定的格式:
*.
开头 + 扩展名 [ 固定扩展名(*.jsp) ]/
开头并且以/*
结尾 [固定路径]
像 /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 | public void init(ServletConfig config) throws ServletException { |
ServletContext 对象通常称为 context 域
转发&重定向
这个用的次数是非常之频繁的,先说明下它和重定向的主要区别:
- 重定向:我没有,我给你个地址,让你去找别人要
- 转发:我没有,我帮你找别人获取,然后再把你需要的资源给你
转发对于客户端来说只发一次请求,网址也不变,客户端甚至不会察觉,也不知道这个资源到底是谁的( 嗯,有点像反向代理呢 ,在我的公众号已经写过正向代理和反向代理是什么了)
用代码来表达就是:
1 | RequestDispatcher rd = getServletContext().getRequestDispatcher("/index.jsp") |
至于这两者的区别,最大的区别就是 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 | <context-param> |
获取方式基本也相同,当然也可以进行设置:
1 | // 获取 Servlet 中的配置的数据 |
设置初始化参数使用注解也是可以的,类似这样@WebServlet(name="HelloWorld",urlPatterns={"/HelloWorld"},initParams={@WebInitParam(name="id",value="1"),@WebInitParam(name="name",value="Loli")})
properties
对于无逻辑性的数据,一般使用:.properties
来进行存储,内容格式非常简单一般就是一行一个 key=val 的形式,详细的介绍见 维基百科
Java中提供了专门的类来处理这种文件
1 | InputStream resourceAsStream = this.getServletContext().getResourceAsStream("/WEB-INF/classes/a.properties"); |
如果读取资源文件的程序不是 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 | "HelloWorld",urlPatterns={"/HelloWroldServlet","/HelloWorld"},initParams={ (name="id",value="yeh"), (name="name",value="Loli")}) (name= |
注意格式就好,第二种低版本的 Tomcat 好像不识别
评论框加载失败,无法访问 Disqus
你可能需要魔法上网~~