servlet总结
Table of Contents
1 Servlet的概念
要理解Servlet,首先得从最早的网页开始讲起。最早时,网页是静态的,我们通过url定位到一个静态的html网页,然后,服务器把这个网页传输回去,这样就完成了一次访问。但是,这时的网页是静态的,甚至是单文件的。后来,为了把相同类型的元素之间的属性归类,只需要定义一次,所以,这种类型的元素可以共享属性,于是就发明了css,把元素的属性归类集中定义。但是,html语言是固定的。它本身没有任何控制语句,没有if判断,也没有循环。不能动态的生成网页。比如,根据输入的行数改变表格的行数等,这个只通过html是无法实现的,它必须借助于另外一门有控制语句的语言。这些语言有很多,比如JavaScript、Python、Tcl等等。通过这样语言动态的输出html的元素,可以动态的生成网页。我们说到的这些都还是前端的,就是我们访问一个网页的时候,把html、css、还有js以及一些多媒体文件一起返回。其实从服务器的角度来说还是静态的,虽然,我们的网页确实是动态生成的了,但这是在客户端,通过浏览器执行js等前端语言动态生成的html。
那么,从服务器的角度来看,怎样才算是动态的呢?那就是,当我们在url中使用不同的参数提交给服务器时,服务器可以根据参数,动态的生成html。那才能算是从服务器端实现了动态网页。
那这个是如何实现的呢?最早的实现叫CGI(common gateway interface),这些CGI的实现语言有很多种,早期Sun公司Tcl做CGI很流行,后来,Sun公司收购了Java。逐渐改为使用Java作为CGI的实现语言。
而Servlet是使用Java实现的一个CGI程序的封装。它把一些常见的处理流程封装起来,使用者只需继承它,修改几个重要的处理函数就可以了。所以,Servlet就是一个Java版的后台处理程序,它能理解客户发送过来的请求。并能根据请求进行处理,然后,返回处理结果。而我们平时使用的最多的,应该是Servlet基于http的协议的一个实现,叫HttpServlet。就是说客户发送http协议的请求,HttpServlet能够理解,并响应请求。
2 HttpServlet的使用
Java现在流行的构建工具是gradle,所以,我们这里也使用gradle来进行展示。关于各种构建工具的比较参见:
http://www.cnblogs.com/yangwen0228/p/6534655.html
2.1 程序结构如下:
Directory tree ============== [-] src `--[-] main |--[-] java | `--[-] cn | `--[-] shinysw | `--[-] servlet | `----- Servlet1.java `--[-] webapp `--[-] WEB-INF `----- web.xml
2.2 build.gradle配置文件如下:
apply plugin: 'war' apply from: 'https://raw.github.com/akhikhl/gretty/master/pluginScripts/gretty.plugin' dependencies { compile 'javax.servlet:javax.servlet-api:3.1' }
2.3 Servlet1.java文件如下:
package cn.shinysw.servlet; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Servlet1 extends HttpServlet { private String message; public void init() throws ServletException { message = "Hello World"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<h1>" + message + "</h1>"); } public void destroy() { // do nothing. } }
我们这里的Servlet1就是继承于HttpServlet的,只要有这个HttpServlet在,那么任何向它的请求就能由它的定义函数来进行处理了。它的几个主要处理函数分别是:
- init()
用于初始化变量或者状态。
- doGet()
用于处理http中的get请求。
- doPost()
用于处理http中的post请求。
- destroy()
用于结束时的状态改变或者资源回收等。
2.4 web.xml文件如下:
<web-app> <servlet> <servlet-name>servletdemo</servlet-name> <servlet-class>cn.shinysw.servlet.Servlet1</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletdemo</servlet-name> <url-pattern>/haha</url-pattern> </servlet-mapping> </web-app>
这个文件,是实现url到servlet的具体类之间的映射关系的关键。我们通过<servlet>定义一个具体的处理容器。这个容器一般包括两部分:
- 具体类 -> servlet 之间的关联
<servlet> <servlet-name>servletdemo</servlet-name> <servlet-class>cn.shinysw.servlet.Servlet1</servlet-class> </servlet>
- url -> servlet 之间的关联
<servlet-mapping> <servlet-name>servletdemo</servlet-name> <url-pattern>/haha</url-pattern> </servlet-mapping>
首先,我们通过定义一个<servlet>把具体的HttpServlet的处理类 cn.shinysw.servlet.Servlet1
绑定到 servletdemo
上面,这个名称可以是任意的,只要不与其他的冲突就可以了。然后,我们通过一个<servlet-mapping>把一个url-pattern /haha
绑定到 servletdemo
上面,这里的url-pattern可以是glob形式的匹配模式,比如 /*
/test*
等。这样,当我们在浏览器中输入服务器的ip+端口号+项目名称+url-pattern能够匹配到的url,都会发送给绑定的类进行处理了。这里,假设我们使用jetty或者tomcat启动这个war,程序使用默认端口8080,那么当我们访问 http://localhost:8080/servletdemo/haha 时,服务器就会把这个http请求发送给绑定的 cn.shinysw.servlet.Servlet1
这个类进行处理。
注意,这里url-pattern,不管前面是否带有 /
,都是相对于项目url的,所以, /haha
和 haha
效果一样。对于Jetty容器,可以省略掉/,但是tomcat不能省略,省略掉会报错,无法启动。
3 http GET 请求
当我们在url后面,添加?key1=value1&key2=value2这种形式的请求时,就是发送GET请求。这种请求,由于参数都是可见的,所以,当参数包含密码,以及敏感信息等时,这种方式是不合适的。另外,这种方式,参数最多只能包含1024个字节,所以,长度比较长的参数也不适合用GET请求。相反,像淘宝的item,这种希望能够通过收藏夹收藏链接,下次直接定位到item等,就特别适合于使用GET,将item信息存储在url中。
另一种发送GET请求的方法是使用form,我们在webapp中创建一个get.html,在其中输入:
<!doctype html> <html> <body> <form action="para" method="GET"> first name: <input name="first_name" type="text" value="yang"/> <br/> <input type="submit" value="Submit"/> </form> </body> </html>
其中的action指向的就是我们在web.xml中定义的url-pattern。
注意:这个action的起始地址是相对于当前项目对应的url,不应带 /
。比如这个例子,是相对于 http://localhost:8080/servletdemo
的。所以,action="para",最终调用的url是 http://localhost:8080/servletdemo/para
。而假设我们使用action="/para",则最终调用的url是 http://localhost:8080/para
,则会出现404错误。
同样,我们创建一个新的Servlet类来处理这个请求:
package cn.shinysw.servlet; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Servlet2 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println(request.getParameter("first_name")); response.setContentType("text/html"); PrintWriter out = response.getWriter(); String title = "Http GET paramater:"; out.println("<!doctype html>"); out.println("<html>"); out.println("<body bgcolor="#241341">"); out.println("<h1>" + "" + title + "</h1>"); out.println("<p>" + request.getParameter("first_name") + "</p>"); out.println("</body>"); out.println("</html>"); } }
然后,在web.xml中增加一个mapping:
<servlet> <servlet-name>s2</servlet-name> <servlet-class>cn.shinysw.servlet.Servlet2</servlet-class> </servlet> <servlet-mapping> <servlet-name>s2</servlet-name> <url-pattern>/para</url-pattern> </servlet-mapping>
4 http POST 请求
由于GET请求的参数是可见的,所以,为了安全,我们常常需要使用POST请求,POST请求常使用form的形式,和GET一样的。
在webapp下创建post.html文件:
<!doctype html> <html> <body> <form action="post" method="POST"> first name: <input name="first_name" type="text" value="yang"/> <br/> <input type="submit" value="Submit"/> </form> </body> </html>
同样,我们创建一个新的Servlet类来处理这个请求:
package cn.shinysw.servlet; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Servlet3 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println(request.getParameter("first_name")); response.setContentType("text/html"); PrintWriter out = response.getWriter(); String title = "Http GET paramater:"; out.println("<!doctype html>"); out.println("<html>"); out.println("<body bgcolor="#241341">"); out.println("<h1>" + "" + title + "</h1>"); out.println("<p>" + request.getParameter("first_name") + "</p>"); out.println("</body>"); out.println("</html>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
然后,在web.xml中增加一个mapping:
<servlet> <servlet-name>s3</servlet-name> <servlet-class>cn.shinysw.servlet.Servlet3</servlet-class> </servlet> <servlet-mapping> <servlet-name>s3</servlet-name> <url-pattern>post</url-pattern> </servlet-mapping>
5 Filter
Filter顾名思义就是做过滤工作的。实际上,可以将它理解为一种装饰器。当需要的时候,可以在操作之前添加一些操作行为。这些操作完全可以放在每个单独的servlet当中去。之所以弄出一个Filter来,完全是因为这些工作大多数是共用的,如果每个servlet当中去写一个,就会重复。所以,干脆把它单独出来,通过配置的方式,哪些页面需要这些处理的,就加一下。然后,根据处理的内容,还可以分类,filter1,filter2…,哪些页面需要几个filter,自己去组合,组合的顺序,是在web.xml中的定义顺序决定的。
<filter> <filter-name>f1</filter-name> <filter-class>cn.shinysw.servlet.ServletFilter1</filter-class> <init-param> <param-name>test_param</param-name> <param-value>Initialization Paramter Demo</param-value> </init-param> </filter> <filter-mapping> <filter-name>f1</filter-name> <url-pattern>*</url-pattern> </filter-mapping>
6 ErrorHandler
<error-page> <expecption-type>java.lang.Throwable</expecption-type> <location>/errorpage</location> </error-page>
其中的location必须带 /
,但这个location不管带不带 /
都是相对于项目url的。
7 Cookie
Cookie是服务器将一些常用的信息保存在浏览器上的一种方法。好比,一家餐馆,它想做活动,发给顾客一张凭证,每吃一次盖一个章,盖满5次送一顿饭。而这张凭证,相当于是餐馆给顾客发的一个cookie。cookie的好处就是,餐馆不需要保管这些凭证,只需要顾客自己保管好就行了。缺点是,顾客可能自己伪造印章,自己盖章,安全性较差。相应的,后面会介绍session,session是与cookie相反的一种存储信息的方式。它相当于,同样是餐馆做活动,这次它不发凭证给顾客,而是给顾客办个会员卡,分配一个会员号。以后,每次来只需要会员号就行了,吃饭的次数保存在餐馆的记录本(数据库/服务器)里面。这种方式的优点是,由于数据是在餐馆里面,所以安全性比较高。缺点是,需要餐馆来进行数据的管理,增加管理成本,特别是涉及到多家连锁餐馆的时候,这些数据是集中保存还是分布式保存,分布式保存时如何同步等,会比较麻烦。
package cn.shinysw.servlet; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ServletCookie extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); response.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); Cookie cookie = null; Cookie[] cookies = null; Boolean flagExistVisit = false; cookies = request.getCookies(); for (Cookie c : cookies) { if (c.getName().equals("visit_times")) { c.setValue("" + ((new Integer(c.getValue())).intValue() + 1)); response.addCookie(c); cookie = c; flagExistVisit = true; break; } } response.addCookie(new Cookie("first_name", "yang")); for (Cookie c : cookies) { if (c.getName().equals("first_name")) { c.setMaxAge(0); response.addCookie(c); out.println(c.getName() + ": " + c.getValue() + "<br/>"); break; } } if (!flagExistVisit) { cookie = new Cookie("visit_times", "1"); cookie.setMaxAge(60*60*24); response.addCookie(cookie); } out.println("中文 访问次数 visit times: " + cookie.getValue()); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
这里顺便测试了一下中文的输出,由于默认编码是iso-8859的,所以,中文是毫无疑问会乱码的。那么,添加:
response.setCharacterEncoding("utf-8")
之后,是否能输出中文了呢?发现,还是乱码的,并且,检查java源文件的编码也是utf-8。那么,只有可能是javac在编译的时候,用的不是utf-8编码的。一般,编译器都会使用本地系统的编码,我们在windows系统中,当然就是使用gb2312了或者cp936。将文件编码改为gb2312, response.setCharacterEncoding("gb2312")
,然后,编译运行,果然就能正确地输出中文了。当然,为了更通用,我们还是改为utf-8编码。那么,就需要改变javac的编码了。我们在build.gradle添加:
compileJava.options.encoding = 'utf-8'
8 原理总结
- Servlet是一种规范,称为Servlet规范,是J2EE规范的一部分。
- Servlet规范定义了Servlet相关的一组接口、其实现是由Servlet容器开发商来实现,类似于JDBC驱动。
- Servlet的也是类,其对象是通过Servlet容器来创建,Servlet只能在Servlet容器中运行。打个比方说:容器是青山,Servlet是松柏。
- 当客户端请求Servlet时,容器会做两件事情:
a. Servlet容器会将请求自动组装为一个ServletRequest对象,并自动产生一个ServletResponse对象,这两个对象一并传递给Servlet的service(request,response)方法。
b. 在该Servlet对象上调用service(request,response)方法来处理并响应用户的请求。
- 用户无法直接调用Servlet的方法,也无法去创建Servlet的实例。