Servlet(一)
Servlet接口定义所有servlet必须实现的方法。一个servlet是运行在web服务器中的一个小java程序,servlet通过HTTP协议接收并响应web客户端发来的请求。这个接口中定义的方法包括初始化servlet,服务请求,以及remove servlet,这些方法即servlet的生命周期方法,调用顺序如下:
1)某个创建servlet对象的时候,调用初始化方法: void init(ServletConfig config)
2)客户端发送请求的时候,service方法被执行:void service(ServletRequest req, ServletResponse res)
3)某个servlet对象被摧毁的时候,调用destroy方法: void destroy()
sun公司定义Servlet接口的两个默认实现类,分别为javax.servlet.GenericServlet和javax.servlet.http.HttpServlet
HttpServlet指能够处理HTTP请求的servlet,在原有的Servlet接口上添加一些与HTTP协议处理方法,比Servlet接口的功能更为强大,因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法
Servlet的运行过程
Servlet程序是由web服务器调用,web服务器接收到客户端的Servlet访问请求后:
1)web服务器首先检查是否已经装载并创建了该Servlet的实例对象,如果是,则直接执行第4步,否则执行第2步。
2)装载并创建该Servlet的一个实例对象。
3)调用Servlet实例对象的init()方法。
4)创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP相应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
5)web应用程序被停止或重新启动前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
注意点:
客户端是通过URL地址访问服务器中的资源,所以servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成
<web-app>
<servlet>
<servlet-name>AnyName</servlet-name>
<servlet-class>HelloServlet</servlet-class> //设置Servlet的完整类名
</servlet>
<servlet-mapping>
<servlet-name>AnyName</servlet-name>
<url-pattern>/demo/hello.html</url-pattern>//用于指定Servlet的对外访问路径
</servlet-mapping>
</web-app>
同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值可以是同一个Servlet的注册名。在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:一种格式是:“*.扩展名”;另一种格式是:以“/”开头,并以“/*”结尾。例如:
<servlet-mapping>
<servlet-name>AnyName</servlet-name>
<url-pattern>*do</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>AnyName</servlet-name>
<url-pattern>/action/*</url-pattern>
</servlet-mapping>
Servlet是一个供其他java程序(Servlet引擎)调用的java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才销毁。
在Servlet的整个生命周期中,Servlet的init方法只被调用一次,而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doGet或doPost方法。
如果再<servlet>元素中配置一个<load-on-startup>元素,那么web应用程序在启动时,就会装载并创建Servlet的实例对象,以及调用Servlet实例对象的init()方法。该技术可以用来为web写一个initServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。
<servlet>
<servlet-name>...</servlet-name>
<servlet-class>...</servlet-class>
<load-on-startup>1<load-on-startiup>
</servlet>
如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的url的访问请求都将交给缺省Servlet处理,即缺省Servlet用于处理所有其他Servlet都不处理的访问请求。
在<tomcat安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaulServlet的Servlet,并将这个Servlet设置为了缺省Servlet。当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet
假设现在在工程名为test的WebRoot目录下新建一个1.html,在web.xml文件中并没有配置<servlet-mapping>,即在工程中没有Servlet映射成1.html,然后访问:http://localhost:8080/test/1.html时,这时候这个请求就交给缺省的Servlet,缺省的Servlet收到请求后,会首先看一下web应用下面有没有这个1.html,如果有则读取并返回到浏览器,如果没有则返回一个错误页面。
当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因为Servlet只有一个实例化对象,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。那么如何解决Servlet中的线程安全问题呢?
1) 如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。该接口没有任何方法,它起到了一个标志的作用。对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线成并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立Servlet实例对象。
2)使用synchronized 关键字,synchronized能保证一次只有一个线程可以访问被保护的区段,从而可以通过同步块操作来保证线程的安全。
3)尽量避免在Servlet里使用实例变量,只要在Servlet里面的任何方法里面都不使用共有的实例变量,那么该Servlet就是线程安全的。
以上三种方式中,实现SingleThreadModel接口可以解决问题,但是并不是真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。另外,创建多个Servlet实例对象也会引起大量的开销。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为过时的了。
同样,如果在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大下降。因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。所以在实际的开发中也应避免或最小化 Servlet 中的同步代码。
ServletConfig对象
在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数,当Servlet配置初始化参数后,web容器在创建servlet实例对象时,会自动将这些参数封装到ServletConfig对象中,并在调用Servlet的init方法时,将ServletConfig对象传递给Servlet。进而,程序员通过ServletConfig对象就可以得到当前Servlet的初始化参数信息。该对象的getInitParameter(String name)用来获得指定参数名的参数值,getInitParameterNames()用来获得所有参数名
配置文件里进行如下配置:
<servlet> <servlet-name>ServletConfigDemo</servlet-name> <servlet-class>servletConfig.ServletConfigDemo</servlet-class> <init-param> <param-name>category</param-name> <param-value>book</param-value> </init-param> <init-param> <param-name>school</param-name> <param-value>tongji</param-value> </init-param> <init-param> <param-name>name</param-name> <param-value>java</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>ServletConfigDemo</servlet-name> <url-pattern>/ServletConfigDemo</url-pattern> </servlet-mapping>
在ServletConfigDemo.java中的代码如下:
public class ServletConfigDemo1 extends HttpServlet { ServletConfig config = null; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String value = config.getInitParameter("category");//获取指定的初始化参数 resp.getOutputStream().write((value + "<br/>").getBytes()); Enumeration e = config.getInitParameterNames();//获取所有参数名 while(e.hasMoreElements()){ String name = (String) e.nextElement(); value = config.getInitParameter(name); resp.getOutputStream().write((name + "=" + value + "<br/>").getBytes()); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } @Override public void init(ServletConfig config) throws ServletException { this.config = config; //初始化时会将ServletConfig对象传进来 } }
注:实际开发中,并不需要重写init方法,以上代码中重写init方法是为了说明config对象的传递过程。其实在父类的init方法中已经实现了该config的传递了,我们只要直接调用getServletConfig()就可以得到config对象,即在doGet方法中直接通过下面的调用方式获得ServletConfig对象:
ServletConfig config = this.getServletConfig();
那么ServletConfig对象有什么作用呢?一般主要用于以下情况:
1)获得字符集编码;
2)获得数据库连接信息;
3)获得配置文件,查看struts案例的web.xml文件等。
ServletContext对象
web容器启动时,它会为每个web应用程序都创建一个对应的ServletContext对象,它代表当前web应用。在ServletConfig接口中有getServletContext方法用来获得ServletContext对象;ServletContext对象中维护ServletContext对象的引用,也可以直接获得ServletContext对象。所以开发人员在编写Servlet时,可以通过下面两种方式获得ServletContext对象:
this.getServletConfig().getServletContext(); this.getServletContext();
一般直接获得即可。
一个web应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯,ServletContext对象通常也被称为context域对象。有如下主要方法:
getResource(String path); //方法获得工程里的某个资源 getResourceAsStream(String path); //通过路径获得跟资源相关联的流 setAttribute(Sring name, Object obj); //方法往ServletContext里存对象,通过MAP集合来保存。 getAttribute(String name); //方法从MAP中取对象 getInitParameter(String name); //获得整个web应用的初始化参数, //这个跟ServletConfig获取参数不同,这是在<context-param></context-param>中定义的,config对象里的getInitParameter方法获得的是具体某个servlet的初始化参数。 getNamedeDispatcher(String name); //方法用于将请求转给另一个servlet处理,参数表示要转向的servlet。 //调用该方法后,要紧接着调用forward(ServletRequest request, ServletResponse response)方法 getServletContextName(); // 获得web应用的名称。
ServletContext应用有哪些呢?
1)多个Servlet通过ServletContext对象实现数据共享(见下面的Demo1和Demo2)
2)获取web应用的初始化参数(见Demo3)
3)实现Servlet的转发(见Demo4和Demo5)
4)利用ServletContext对象读取资源文件(xml或者properties)(见Demo6)
Demo1:往context域中存入数据
public class ServletContextDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String data = "adddfdf"; ServletContext context = this.getServletConfig().getServletContext(); context.setAttribute("data", data);//将数据写到ServletContext } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
Demo2:从context域中读取数据
public class ServletContextDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext context = this.getServletContext(); String data = (String) context.getAttribute("data");//通过键值从ServletContext中获取刚才存入的数据 resp.getOutputStream().write(data.getBytes()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
Demo3:获取整个web应用的初始化参数
public class ServletContextDemo3 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext context = this.getServletContext(); String url = context.getInitParameter("url");//获取整个web应用的初始化参数,参数是在<context-param></context-param>中定义的 resp.getOutputStream().write(url.getBytes()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
Demo4:实现转发
public class ServletContextDemo5 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext context = this.getServletContext(); RequestDispatcher rd = context.getRequestDispatcher("/ServletContextDemo5"); rd.forward(req, resp);//将请求转发给ServletContextDemo5.java处理 } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
Demo5:
public class ServletContextDemo5 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getOutputStream().write("ServletDemo5".getBytes());//处理ServletDemo4传过来的请求 } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
Demo6:读取资源文件
public class ServletContextDemo6 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //test1(resp); //test2(resp); //test3(resp); //test4(); } //读取文件,并将文件拷贝到e:\根目录,如果文件太大,只能用servletContext,不能用类装载器 private void test4() throws FileNotFoundException, IOException { String path = this.getServletContext().getRealPath("/WEB-INF/classes/db.properties"); String filename = path.substring(path.lastIndexOf("\\")+1); InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties"); byte buffer[] = new byte[1024]; int len = 0; FileOutputStream out = new FileOutputStream("e:\\" + filename); while((len = in.read(buffer)) > 0){ out.write(buffer, 0, len); } } //使用类装载器读取源文件(不适合装载大文件) private void test3(HttpServletResponse resp) throws IOException { ClassLoader loader = ServletContextDemo6.class.getClassLoader(); InputStream in = loader.getResourceAsStream("db.properties"); Properties prop = new Properties(); prop.load(in); String driver = prop.getProperty("driver"); resp.getOutputStream().write(driver.getBytes()); } private void test2(HttpServletResponse resp) throws FileNotFoundException, IOException { String path = this.getServletContext().getRealPath("/WEB-INF/classes/db.properties");//获取绝对路径 FileInputStream in = new FileInputStream(path);//传统方法,参数为绝对路径 Properties prop = new Properties(); prop.load(in); String driver = prop.getProperty("driver"); resp.getOutputStream().write(driver.getBytes()); } //读取web工程中资源文件的模板代码(源文件在工程的src目录下) private void test1(HttpServletResponse resp) throws IOException { InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties"); ////注:源文件若在工程的WebRoot目录下,则上面参数路径直接为"/db.properties",因为WebRoot即代表web应用 Properties prop = new Properties(); prop.load(in);//先装载流 String driver = prop.getProperty("driver"); String url = prop.getProperty("url"); String username = prop.getProperty("username"); String password = prop.getProperty("password"); resp.getOutputStream().write(driver.getBytes()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
参考:
https://blog.csdn.net/eson_15/article/details/51245629
浙公网安备 33010602011771号