今天我们进入java web部分的学习。
一.首先,我们来看一下Servlet,什么是Servlet。
Servlet是sun公司提供的一门用于开发动态web资源的技术。
Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
1、编写一个Java类,实现servlet接口。
2、把开发好的Java类部署到web服务器中。
按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet
Servlet是一个Java编写的程序,此程序在WEB服务器端运行的,是按照Servlet规范编写的一个 java类。Servlet处理客户端的请求,并将处理结果以响应的方式返回给客户端。
二.Servlet的运行
首先我们给出结论,Servlet运行在web服务器(容器)中,有web服务器转载并实例化该对象。
①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
②装载并创建该Servlet的一个实例对象。
③调用Servlet实例对象的init()方法。
④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
这些将在下面的程序中进行验证。
三.分析Servlet源码
Servlet源码由以下两个包组成
javax.servlet
javax.servlet.http
两个包
先看javax.servlet
此包中定义了Servlet类都必须实现的接口
ServletConfig接口:在初始化过程中由Servlet容器调用
ServletCOntext接口:定义Servlet用户获取容器信息的方法
ServletRequest接口:向服务端请求信息
ServeltResponse接口:响应客户端请求
Servlet接口:所有的Servlet实现类必须实现的方法Service
类定义:
ServletInputStream:用于从客户端读取二进制数据 继承了抽象类InuptStream
ServletOutputStream:用于将二进制数据返回给客户端
GenricServlet–抽象类,定义一个通用的,独立于底层协议的Servlet
java.servlet.http
此包中定义了使用HTTP通信协议的所有Servlet该实现的接口,该继承的类:
HttpServletRequest接口:封装http请求
HttpServletRequest接口:封装http响应
HttpSession:用于表示客户端存储有关客户的信息
HttpSessionAttributeListener接口:实现了这个监听接口:
首先,我们来看一下GenricServlet这个抽象类,这个是Servlet的基类。所有的Servlet都继承了这个类。
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
private transient ServletConfig config;
/**
*
* Does nothing. All of the servlet initialization
* is done by one of the <code>init</code> methods.
*
*/
public GenericServlet() { }
/**
* Called by the servlet container to indicate to a servlet that the
* servlet is being taken out of service. See {@link Servlet#destroy}.
*
*
*/
public void destroy() {
}
/**
* Returns a <code>String</code> containing the value of the named
* initialization parameter, or <code>null</code> if the parameter does
* not exist. See {@link ServletConfig#getInitParameter}.
*
* <p>This method is supplied for convenience. It gets the
* value of the named parameter from the servlet's
* <code>ServletConfig</code> object.
*
* @param name a <code>String</code> specifying the name
* of the initialization parameter
*
* @return String a <code>String</code> containing the value
* of the initialization parameter
*
*/
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
/**
* Returns the names of the servlet's initialization parameters
* as an <code>Enumeration</code> of <code>String</code> objects,
* or an empty <code>Enumeration</code> if the servlet has no
* initialization parameters. See {@link
* ServletConfig#getInitParameterNames}.
*
* <p>This method is supplied for convenience. It gets the
* parameter names from the servlet's <code>ServletConfig</code> object.
*
*
* @return Enumeration an enumeration of <code>String</code>
* objects containing the names of
* the servlet's initialization parameters
*
*/
public Enumeration getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
/**
* Returns this servlet's {@link ServletConfig} object.
*
* @return ServletConfig the <code>ServletConfig</code> object
* that initialized this servlet
*
*/
public ServletConfig getServletConfig() {
return config;
}
/**
* Returns a reference to the {@link ServletContext} in which this servlet
* is running. See {@link ServletConfig#getServletContext}.
*
* <p>This method is supplied for convenience. It gets the
* context from the servlet's <code>ServletConfig</code> object.
*
*
* @return ServletContext the <code>ServletContext</code> object
* passed to this servlet by the <code>init</code>
* method
*
*/
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
/**
* Returns information about the servlet, such as
* author, version, and copyright.
* By default, this method returns an empty string. Override this method
* to have it return a meaningful value. See {@link
* Servlet#getServletInfo}.
*
*
* @return String information about this servlet, by default an
* empty string
*
*/
public String getServletInfo() {
return "";
}
/**
*
* Called by the servlet container to indicate to a servlet that the
* servlet is being placed into service. See {@link Servlet#init}.
*
* <p>This implementation stores the {@link ServletConfig}
* object it receives from the servlet container for later use.
* When overriding this form of the method, call
* <code>super.init(config)</code>.
*
* @param config the <code>ServletConfig</code> object
* that contains configutation
* information for this servlet
*
* @exception ServletException if an exception occurs that
* interrupts the servlet's normal
* operation
*
*
* @see UnavailableException
*
*/
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
/**
*
* A convenience method which can be overridden so that there's no need
* to call <code>super.init(config)</code>.
*
* <p>Instead of overriding {@link #init(ServletConfig)}, simply override
* this method and it will be called by
* <code>GenericServlet.init(ServletConfig config)</code>.
* The <code>ServletConfig</code> object can still be retrieved via {@link
* #getServletConfig}.
*
* @exception ServletException if an exception occurs that
* interrupts the servlet's
* normal operation
*
*/
public void init() throws ServletException {
}
/**
*
* Writes the specified message to a servlet log file, prepended by the
* servlet's name. See {@link ServletContext#log(String)}.
*
* @param msg a <code>String</code> specifying
* the message to be written to the log file
*
*/
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
/**
* Writes an explanatory message and a stack trace
* for a given <code>Throwable</code> exception
* to the servlet log file, prepended by the servlet's name.
* See {@link ServletContext#log(String, Throwable)}.
*
*
* @param message a <code>String</code> that describes
* the error or exception
*
* @param t the <code>java.lang.Throwable</code> error
* or exception
*
*
*/
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
/**
* Called by the servlet container to allow the servlet to respond to
* a request. See {@link Servlet#service}.
*
* <p>This method is declared abstract so subclasses, such as
* <code>HttpServlet</code>, must override it.
*
*
*
* @param req the <code>ServletRequest</code> object
* that contains the client's request
*
* @param res the <code>ServletResponse</code> object
* that will contain the servlet's response
*
* @exception ServletException if an exception occurs that
* interferes with the servlet's
* normal operation occurred
*
* @exception IOException if an input or output
* exception occurs
*
*/
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
/**
* Returns the name of this servlet instance.
* See {@link ServletConfig#getServletName}.
*
* @return the name of this servlet instance
*
*
*
*/
public String getServletName() {
return config.getServletName();
}
}
我们可以看到这个类继承了Servlet接口和ServletConfig接口,其中实现了Servlet中接口的
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public String getServletInfo();
唯独没有实现 public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;方法,
很明显这个方法是由我们子类实现的,我们知道,我们的Servlet目前都是面向Http协议的,而Servlet的设计者为了以后的扩展性,Servlet接口中service方法的参数设置为了ServletRequest req, ServletResponse res ,而不是HttpServletRequest和HttpServletResponse(该接口的实现类对Http协议请求数据和返回数据的封装,具体实现类在服务器中)。
那么为了面向Http协议,就出现了HttpServlet继承了GenricServlet。实现了service方法,一般我们编写的Servlet都是直接继承该类
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);
}
}
Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUTDELETE。URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网的资源,而HTTP中的GET,POST,PUT,DELETE就对应着对这个资源的查,改,增,删4 操作(这里先做个了解,以后讲到restful的时候会继续提到)。 用的较多的是GET和POST,GET一般用于获取/查询资源信息,而POST一般用于更新资源信息。
对于POST和GET方式的区别
1.GET用于信息获取,而且应该是安全的和幂等的。
(1).所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生
副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会
影响 资源的状态。
* 注意:这里安全的含义仅仅是指是非修改信息。
(2).幂等的意味着对同一URL的多个请求应该返回同样的结果。这里我再解释一下幂等这个概念:
幂等(idempotent、idempotence)是一个数学或计算机学概念,常见于抽象代数中。
幂等有一下几种定义:
对于单目运算,如果一个运算对于在范围内的所有的一个数多次进行该运算所得的结果
和进行一次该运算所得的结果是一样的,那么我们就称该运算是幂等的。比如绝对值运算就
是一个例子,在实数集中,有abs(a)=abs(abs(a))。
对于双目运算,则要求当参与运算的两个值是等值的情况下,如果满足运算结果与参与
运算的 两个值相等,则称该运算幂等,如求两个数的最大值的函数,有在在实数集中幂等,
即max(x,x) = x。
看完上述解释后,应该可以理解GET幂等的含义了。
但在实际应用中,以上2条规定并没有这么严格。引用别人文章的例子:比如,新闻站点的头
版不断更新。虽然第二次请求会返回不同的一批新闻,该操作仍然被认为是安全的和幂等的,
因为它总是返回当前的新闻。从根本上说,如果目标是当用户打开一个链接时,他可以确信从
自身的角度来看没有改变资源即可。
2.POST表示可能修改变服务器上的资源的请求。
继续引用上面的例子:还是新闻以网站为例,读者对新闻发表自己的评论应该通过POST实现,
因为在评论提交后站点的资源已经不同了,或者说资源被修改了。
上面大概说了一下HTTP规范中GET和POST的一些原理性的问题。但在实际的做的时候,
很多人却没有按照HTTP规范去做,导致这个问题的原因有很多,比如说:
1.很多人贪方便,更新资源时用了GET,因为用POST必须要到FORM(表单),这样会麻烦一点。
2.对资源的增,删,改,查操作,其实都可以通过GET/POST完成,不需要用到PUT和DELETE。
3.另外一个是,早期的Web MVC框架设计者们并没有有意识地将URL当作抽象的资源来看待和设计,
所以导致一个比较严重的问题是传统的Web MVC框架基本上都只支持GET和POST两种HTTP方法,
而不支持PUT和DELETE方法。
* 简单解释一下MVC:MVC本来是存在于Desktop程序中的,M是指数据模型,V是指用户界面,
C则是控制器。使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的
表现形式。
说完原理性的问题,我们再从表面现像上面看看GET和POST的区别:
1.GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数
据,参数之间以&相连,如:login.action?name=hyddd&password=idontknow&verify=%E4%
BD%A0%E5%A5%BD。如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是
中文/其他字符,则直接把字符串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD,其中
%XX中的XX为该符号以16进制表示的ASCII。
POST把提交的数据则放置在是HTTP包的包体中。
2.”GET方式提交的数据最多只能是1024字节,理论上POST没有限制,可传较大量的数据,
IIS4中最大为80KB,IIS5中为100KB”??!
以上这句是我从其他文章转过来的,其实这样说是错误的,不准确的:
(1).首先是”GET方式提交的数据最多只能是1024字节”,因为GET是通过URL提交数据,那么
GET可提交的数据量就跟URL的长度有直接关系了。而实际上,URL不存在参数上限的问题,
HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。
IE对URL长度的限制是2083 字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,
理论上没有长度限制,其限制取决于操作系统的支持。
注意这是限制是整个URL长度,而不仅仅是你的参数值数据长度。[见参考资料5]
(2).理论上讲,POST是没有大小限制的,HTTP协议规范也没有进行大小限制,说“POST数据量
存在80K/100K的大小限制”是不准确的,POST数据是没有限制的,起限制作用的是服务器
的处理程序的处理能力。
3.在ASP中,服务端获取GET请求参数用Request.QueryString,获取POST请求参数用
Request.Form。在JSP中,用request.getParameter(\”XXXX\”)来获取,虽然jsp中也有
request.getQueryString()方法,但使用起来比较麻烦,比如:传一个test.jsp?name=hyddd&
password=hyddd,用request.getQueryString()得到的是:name=hyddd&password=hyddd。
在PHP中,可以用
GET和
_POST分别获取GET和POST中的数据,而
REQUEST则可以获取GET和POST两种请求中的数据。值得注意的是,JSP中使用request和PHP中使用
_REQ
UEST都会有隐患,这个下次再写个文章总结。
4.POST的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的“安全”不是同个
概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的Security的含义,比如
:通过GET提交数据,用户名和密码将明文出现在URL上,因为(1)登录页面有可能被浏览器缓存,
(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用GET
提交数据还可能会造成Cross-site request forgery攻击。
总结一下,Get是向服务器发索取数据的一种请求,而Post是向服务器提交数据的一种请求,
在FORM(表单)中,Method默认为”GET”,实质上,GET和POST只是发送机制不同,并不是一个
取
下面,我们来看一下一次Http请求从发送到服务器给出响应的过程
变成流程图,如下
1、Web Client 向Servlet容器(Tomcat)发出Http请求
2、Servlet容器接收Web Client的请求
3、Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中
4、Servlet容器创建一个HttpResponse对象
5、Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse
对象作为参数传给 HttpServlet对象
6、HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息
7、HttpServlet调用HttpResponse对象的有关方法,生成响应数据
8、Servlet容器把HttpServlet的响应结果传给Web Client
前面都是引用的别人的资料,最后是该总结下自己的感悟,让我们来看看web容器具体是怎样调用Servlet的
package wangcc.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {
/**
* Constructor of the object.
*/
public TestServlet() {
super();
System.out.println("构造方法执行");
}
/**
* Destruction of the servlet. <br>
*/
public void destroy() {
super.destroy(); // Just puts "destroy" string in log
// Put your code here
System.out.println("Destory");
}
/**
* The doGet method of the servlet. <br>
*
* This method is called when a form has its tag value method equals to get.
*
* @param request
* the request send by the client to the server
* @param response
* the response send by the server to the client
* @throws ServletException
* if an error occurred
* @throws IOException
* if an error occurred
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
/**
* The doPost method of the servlet. <br>
*
* This method is called when a form has its tag value method equals to
* post.
*
* @param request
* the request send by the client to the server
* @param response
* the response send by the server to the client
* @throws ServletException
* if an error occurred
* @throws IOException
* if an error occurred
*/
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("Service执行");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
String name = request.getClass().getName();
HttpSession session = request.getSession();
System.out.println("HttpServlet实现类类名为" + name);
String sessionName = session.getClass().getName();
System.out.println("Httpsession实现类名:" + sessionName);
out.write(name);
// Cookie[] cookies = request.getCookies();
}
/**
* Initialization of the servlet. <br>
*
* @throws ServletException
* if an error occurs
*/
public void init() throws ServletException {
// Put your code here
System.out.println("init()方法执行");
String name = getServletConfig().getServletName();
System.out.println("ServletName:" + name);
String contextName = getServletConfig().getServletContext().getClass()
.getName();
System.out.println(contextName);
}
}
访问http://localhost:8080/MyHttpDemo/TestServlet
在控制台可以得到
构造方法执行
init()方法执行
ServletName:wangcc.servlet.TestServlet
org.apache.catalina.core.ApplicationContextFacade
Service执行
HttpServlet实现类类名为org.apache.catalina.connector.RequestFacade
Httpsession实现类名:org.apache.catalina.session.StandardSessionFacade
由输出的情况我们可以看出:
首先我们就假设web服务器是一个普通的java类。这个类的工作会在收到浏览器请求时被触发。
那我们来模拟一下浏览器发出请求到服务器给浏览器作为响应的过程。
1.浏览器发送一个请求给web服务器,web服务器接收这个请求,然后将该Http请求封装成HttpServletRequest的一个实现类的对象实例(具体的实现类当然也是在web服务器中),可以理解 为web服务器调用了一个封装Http请求的方法
2.然后会web服务器会创建一个HttpServletResponse的对象实例,不过这个对象实例现在的内容是空的。
3.此时,如果这个Servlet是我们第一次使用,则web服务器需要创建该对象,所以此时会调用构造方法,所以有了第一个行的输出。
4.接着就会调用init(ServletConfig config);方法,我们观察源码可知,init()无参方法在父类的init(ServletConfig config);方法中,我们再子类重写了init(),
所以调父类的init(ServletConfig config)时会调用子类的init(),动态绑定。
5,之后调用service方法,将相应参数传入
6.通过service方法将响应信息写入HttpServletResponse的对象实例
7.然后将该对象传给web服务器
8.web服务器将响应信息传给浏览器
其中init()方法中的代码,直接调用了父类的方法,
我们知道,当初始化子类的时候,我们会调用父类的构造方法先初始父类、
而当实例化子类时,我们也可以看做实例化了一个父类,但是这个父类在子类实例当中,子类能访问父类的所有方法和属性,除了静态的和private的
System.out.println(“init()方法执行”);
String name = getServletConfig().getServletName();
System.out.println(“ServletName:” + name);
String contextName = getServletConfig().getServletContext().getClass()
.getName();
System.out.println(contextName);
当我们写完这个小例子的时候,我们再去看看源码,似乎就能明白为什么要这样设计。接下来的几天会继续复习java web之servlet,如http协议,request响应头设置,filter,cookie ,session的重新梳理。以及将一些之前的知识串联,今天的博客写的顺序有点乱,周末找个时间改一下吧。