Fork me on GitHub

Servlet编程

Servlet编程

1. servlet概念及相关接口简介

java Servlet是运行在web服务器或应用服务器上的程序,他是作为来自web浏览器或其他HTTP客户端的请求和HTTP服务器山的数据库或应用程序之间的中间层
什么是Servlet
是一个java类,继承自HttpServlet类
这个类在服务器端运行,用以处理客户端的请求
主要作用
1.读取客户端(浏览器)发送来的显式数据和隐式的HTTP请求数据,包括html文件,cookies,HTTP请求等信息
2.处理数据被返回生成结果。
3.发送显式数据和隐式HTTP响应到客户端

2.开发一个Servlet流程

步骤:
1.编写java类,继承自HttpServlet类
2.重写doGet和doPost方法
3.Servlet程序交给tomcat服务器运行
3.1.servlet程序的class码拷贝到WEB-INF/calsses目录,如果是IDE,会自动拷贝
3.2.在web.xml文件中进行配置
3.3.配置的另一种方法是在servlet程序开头写上这样的语句,表示访问路径@WebServlet("/first")
先看servlet的程序

/**
 * 运行在tomcat服务器端的servlet类
 */
@WebServlet(name = "FirstServlet")
public class FirstServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //向浏览器输出内容
        response.getWriter().write("This is first servlet!");
        //向控制台打印信息
        System.out.println("helloWorld");
    }
}

servlet程序写完之后还需要在WEB-INF目录下配置web.xml
配置如下:

    <!--配置一个servelt-->
    <!--servlet的配置-->
    <servlet>
        <!--servlet的内部名称,名字可以自定义,但是最好有意义-->
        <servlet-name>FirstServlet</servlet-name>
        <!--servlet的类全名:包名+简单类名-->
        <servlet-class>servers.FirstServlet</servlet-class>
    </servlet>

    <!--servlet的映射配置-->
    <servlet-mapping>
        <!--servlet的内部名称,一定要和上面的内部名称保持一致-->
        <servlet-name>FirstServlet</servlet-name>
        <!--servlet的映射路径(访问servlet的名称)-->
        <url-pattern>/first</url-pattern>
    </servlet-mapping>

3.servlet路径映射

当我们访问 http://localhost:8080/sayhello/first路径时,是如何调用servlet呢?
xml路径开始之前:
tomcat服务器启动时,首先加载webapps中的每一个web应用的web.xml配置文件
http:// :http协议
localhost : 在本地的hosts文件中查找是否存在该域名对应的IP地址
8080 : 找到tomcat服务器
/sayhello : 在tomcat的webapps目录下找到sayhello目录
/first 资源名称
访问到/first时开始执行:
1.在sayhello的web.xml中查找是否有匹配的url-pattern的内容(/first)
2.如果找到匹配的url-pattern,则使用当前的servlet-name的名称到web.xml文件中查询是否有相同的servlet配置
3.如果找到,则取出对应的servlet配置信息中的servlet-calss内容
4.通过反射:
4.1.构造FirstServlet的对象
4.2.然后调用FirstServlet里面的方法

Servlet的映射路径
路径有两种精准匹配和模糊匹配两种方式
精确匹配:

/first 访问 http://localhost:8080/sayhello/first

模糊匹配:

  1. /* http://localhost:8080/sayhello/任意路径
    2./itcast/* http://localhost:8080/day10/sayhello/任意路径
    3.*.后缀名 例:×.do http://localhost:8080/sayhello/任意路径.do

注意:
1.url-pattern要么以 / 开头,要么以开头。 例如, itcast是非法路径。
2.不能同时使用两种模糊匹配,例如 /itcast/
.do是非法路径
3.当有输入的URL有多个servlet同时被匹配的情况下:
3.1 精确匹配优先。(长的最像优先被匹配)
3.2 以后缀名结尾的模糊url-pattern优先级最低!!!

4.缺省servlet

servlet的缺省路径(/)是在tomcat服务器内置的一个路径,该路径对应的是一个DefaultServlet(缺省Servlet)。这个缺省的Servlet的所用是用于解析web应用的静态资源文件

案例:URL输入http://localhost:8080/sayhello/index.html 如何读取文件????
1.首先到sayhello应用下的web.xml文件查找是否有匹配的url-pattern
2.如果没有匹配的url-pattern,则交给tomcat的内置DefaultServlet处理
3.DefaultServlet程序到sayhello应用的根目录下查找是否存在一个名称为index.html的静态文件
4.如果找到静态文件,则读取该文件内容,返回给浏览器
5.如果找不到该文件,则返回404错误页面
结论:先找动态资源,再找静态资源,不要轻易在web.xml使用/*,如果要使用,推荐加上后缀

5.servlet生命周期(重点)

5.1.引入

在之前接触的类中,可以使用new方法来创建一个对象,可以调用对象的方法,可以使对象等于null来消除对象。但是在servlet这个类中,我们只是重写了doGet等方法,并没有自己实现这个类的对象创建,初始化,调用,消除。
那么servlet这个类的生命周期是怎么样的呢?创建,初始化,调用,消除又是怎么实现的呢?

5.2.Servlet重要的四个生命周期方法

构造方法:创建servlet对象的时候调用。默认情况下,第一次访问servlet的时候创建servlet对象。只调用1次,证明servlet对象在tomcat是单实例的
init方法:创建完servlet对象的时候调用。只调用1次
service方法:每次发出请求时调用。请求几次调用几次
destory方法:销毁servlet对象的时候调用。停止服务器或者重新部署web应用时销毁servlet对象,只调用1次

代码示例:

/**
 * 测试servlet的生命周期的四个重要方法
 */
@WebServlet(name = "LifeDemo")
public class LifeDemo extends HttpServlet {
    //1.构造方法
    public LifeDemo(){
        System.out.println("1.LifeDemo被创建了");
    }
    
    //2.inti方法
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("2.init方法被调用");
    }
    
    //3.service方法
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("3.service方法被调用");
    }
    
    //4.destory方法
    @Override
    public void destroy() {
        System.out.println("4.servlet对象销毁");
    }
}

5.3.伪代码演示servlet的生命周期

servlet是通过反射来调用的,这里使用伪代码模拟过程
tomcat内部代码运行
1.通过映射找到servlet-class的内容(字符串:servers.FirstServlet)
2.通过反射构造FirstServlet对象
2.1.得到字节码对象
Class clazz = class.forName("servers.FirstServlet");
2.2.调用无参的构造方法来构造对象
Object obj = clazz.newInstance(); ---1.servlet的构造方法被调用
3.创建ServletConfig对象,通过反射调用init方法
3.1.得到方法对象
Method m = clazz.getDeclareMethod("init",ServletConfig.class);
3.2.调用方法
m.invoke(obj,config); --2.servlet的init方法被调用
4.创建request,response对象,通过反射调用service方法
4.1 得到方法对象
Methodm m =clazz.getDeclareMethod("service",HttpServletRequest.class,HttpServletResponse.class);
4.2 调用方法
m.invoke(obj,request,response); --3.servlet的service方法被调用
5.当tomcat服务器停止或web应用重新部署,通过反射调用destroy方法
5.1 得到方法对象
Method m = clazz.getDeclareMethod("destroy",null);
5.2 调用方法
m.invoke(obj,null); --4.servlet的destroy方法被调用

5.4.用时序图来演示servlet的生命周期

5.5.Servlet的自动加载

默认情况下,第一次访问servlet的时候创建servlet对象。如果servlet的构造方法或init方法中执行了比较多的逻辑代码,那么导致用户第一次访问sevrlet的时候比较慢。为了解决这个问题,我们可以设置在web应用第一次被访问的时候,就执行servlet的构造方法和init方法
改变servlet创建对象的时机: 提前到加载web应用的时候!!!
方法:
在servlet的配置信息中,加上一个即可!!

<servlet>
    <servlet-name>LifeDemo</servlet-name>
    <servlet-class>gz.itcast.c_life.LifeDemo</servlet-class>
    <!-- 让servlet对象自动加载 -->
    <load-on-startup>1</load-on-startup> 
  </servlet>

注意: 整数值越大,创建优先级越低!!

6.有参的init方法和无参的init方法

有参的init方法是tomcat的生命周期方法。
无参的init方法使用提供给开发者重写该方法的地方
不要覆盖有参的方法,只能覆盖无参的方法

/**
 * init有参和无参方法的区别
 */
@WebServlet(name = "InitDemo")
public class InitDemo extends HttpServlet {
    /**
     * 有参数的init方法
     * 该方法是servlet的生命周期方法,一定会被tomcat服务器调用
     * 这个init方法不能被覆盖
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("有参的init方法");
    }
    
    /**
     * 无参数的init方法
     * 该方法是servlet的编写初始化代码的方法
     * 是sun公司设计出来专门给开发这进行覆盖的,然后在里面编写servlet的出事逻辑代码方法
     * @throws ServletException
     */
    @Override
    public void init() throws ServletException {
        System.out.println("无参的init方法");
    }
}

7.Servlet线程安全

从上面的示例可以知道,Servlet是单实例,但是servlet又能被多客户端访问,所以又是一个多线程的。所以servlet是一个单实例多线程的

如果多线程同时访问了servlet对象的共享数据(成员变量)可能会引发线程安全问题
解决方法
1.把使用到共享数据的代码块进行同步(使用synchronized关键字进行同步)
2.建议在servlet类中尽量不要使用成员变量,如果确实要使用成员,必须同步。而且尽量缩小同步代码块的范围(建议哪里使用成员变量,就同步哪里!!),以避免因为同步导致并发效率降低

/**
 * servlet的多线程并发问题
 */
@WebServlet("/thread")
public class ThreadDemo extends HttpServlet {

    int count=1;

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

        //多线程锁
        synchronized (ThreadDemo.class) {//锁对象必须唯一,建议使用字节码
            resp.getWriter().write("你现在是当前网站的第" + count + "个访客");

            count++;
        }
    }
}

Servlet中的对象
HttpServletRequest 请求对象:获取请求信息
HttpServletResponse 响应对象: 设置响应对象
ServletConfig对象 servlet配置对象
ServletContext对象; servlet的上下文对象

8.servletConfig对象

8.1.作用

servletConfig对象:主要是用于加载servlet的初始化对象

8.2.对象创建和得到

创建时机:在创建完servlet对象之后,在调用init方法之前
得到对象:直接从有参数的init方法得到

8.3.servlet的初始化参数配置

ServletConfig的API:
java.lang.String getInitParameter(java.lang.String name) 根据参数名获取参数值
java.util.Enumeration getInitParameterNames() 获取所有参数
ServletContext getServletContext() 得到servlet上下文对象
java.lang.String getServletName() 得到servlet的名称

配置文件:

    <servlet>
        <servlet-name>ServletConfigDemo</servlet-name>
        <servlet-class>servers.ServletConfigDemo</servlet-class>
        <!-- 初始参数: 这些参数会在加载web应用的时候,封装到ServletConfig对象中 -->
        <init-param>
            <param-name>name</param-name>
            <param-value>rocco</param-value>
        </init-param>
    </servlet>

ServletConfig的代码

/**
 * 测试ServletConfig对象
 */
@WebServlet(name = "ServletConfigDemo")
public class ServletConfigDemo extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //读取servlet的初始参数
        String name = this.getServletConfig().getInitParameter("name");

        response.getWriter().write("hello");
        System.out.println(name);

    }
}

9. ServletContext对象

9.1.引入

ServletContext对象,叫做Servlet的上下文对象。表示一个当前的web应用环境,一个web应用中只有一个ServletContext对象

9.2.对象创建和得到

创建时间:加载web应用时创建该对象
得到对象:从ServletConfig的getServletContext方法得到

9.3.ServletContext对象的核心API

得到ServletContext对象:
ServletContext context = this.getServletContext();

得到web应用路径
String getContextPath(); 得到当前web的应用路径

得到web应用的初始化参数(全局)
config参数是当前servlet的参数,只能由当前servlet对象获得
Context参数是全局参数,通过ServletContext对象,所有的都可以获得
全部参数的写法:

    <!--配置web应用参数-->
    <context-param>
        <param-name>AAA</param-name>
        <param-value>AAA's value</param-value>
    </context-param>
    <context-param>
        <param-name>BBB</param-name>
        <param-value>BBB's value</param-value>
    </context-param>
    <context-param>
        <param-name>CCC</param-name>
        <param-value>CCC's value</param-value>
    </context-param>

String getInitParameter(String name); 得到web应用指定的初始化参数
Enumeration geetInitParameterNames();得到web应用所有的初始化参数

域对象有关的方法
域对象:作用是用于保存数据,获取数据。可以在不同的动态资源之间共享数据
servlet是运行在服务器上的程序,如何在不同的servlet的程序之间传递参数的呢?常规做法是通过重定向后面附带参数来传递,例如:response.sendRedirect("/Servlet2?name=eric") 但是这样有一个问题,就是只能传递字符串的参数,且参数被暴露

最好的解决方案是使用域对象,域对象可以共享任何类型的数据
ServletContext其实就是一个域对象
ServletContext域对象:作用范围在整个web应用中有效!!!

域对象的一些操作
void setAttribute(java.lang.String name, java.lang.Object object) --保存数据
Object getAttribute(java.lang.String name) --获得数据
void removeAttribute(java.lang.String name) --删除数据

所有域对象:
HttpServletRequet域对象
ServletContext域对象
HttpSession 域对象
PageContext域对象

转发
转发和重定向的效果是一样的

RequestDispatcher getRequestDispatcher(java.lang.String path) --转发(类似于重定向)

转发和重定向的区别
1.转发:

  • a.地址栏不会改变
  • b.转发只能转发到当前web应用内的资源
  • c.在转发过程中,可以把数据保存到request域对象中

2.重定向

  • a.地址栏会改变,变成重定向到的新地址
  • b.重定向可以跳转到当前web应用,或其他web应用,甚至是外部域名网站
  • c.不能再重定向的过程中,把数据保存到request中

结论:如果要使用request域对象进行数据共享,只能使用转发技术

其他一些方法
通常在一个java路径中,我们用 . 表示项目文件所在的地址,但是在java web项目中,点 . 表示tomcat下bin里的startup.sh所在的目录。所以在java web中不能使用点 . 来查找文件
但是可以使用 “/” 来查找, / 表示WEB-INF所在的目录地址,通过这个找到资源文件

java.lang.String getRealPath(java.lang.String path) --得到web应用的资源文件
java.io.InputStream getResourceAsStream(java.lang.String path) 得到资源文件,但是返回的是一个输入流,不需要再转换

/**
 * 测试ServletContext对象的核心API
 */
@WebServlet(name = "ServletContextDemo")
public class ServletContextDemo extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //1.得到ServletContext对象
        //下面两种方法都可以,推荐使用第二种
//        ServletContext context = this.getServletConfig().getServletContext();
        ServletContext context = this.getServletContext();//推荐使用

        //2.得到web应用路径
        //web应用路径:部署到tomcat服务器上运行的web应用名称
        String contextPath = context.getContextPath();
        System.out.println(contextPath);

        //案例:请求重定向
        response.sendRedirect(contextPath+"/index.html");

        //3.得到web应用参数
        String aaa = context.getInitParameter("AAA");//获取指定的一个
        Enumeration<String> enums = context.getInitParameterNames();//得到所有

        //4.域对象的一些操作
        //保存到域对象中
        context.setAttribute("name","tom");
        //从域对象中获得数据
        Object name = context.getAttribute("name");
        //从域对象中删除数据

        //5.转发,效果就是跳转页面
        //不能转发到项目以外的路径
        RequestDispatcher rd = context.getRequestDispatcher("/index.html");
        rd.forward(request,response);
        
                /**
         * 6.读取web应用下的资源文件
         */

        //在java项目中,点 . 表示java项目所在的地址
        //java web项目中,点 . 表示tomcat的bin目录,所以不能使用这种相对路径
//        File file = new File("./src/db.properties");
//        FileInputStream in = new FileInputStream(file);

        //正确获取
        //方法1.getRealPath
        // “/”表示WEB-INF所在目录地址
        String path = this.getServletContext().getRealPath("/WEB-INF/classes/db.properties");
        File file = new File(path);
        FileInputStream in = new FileInputStream(file);

        //方法2: 得到资源文件,返回的是输入流
        InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");

        Properties prop = new Properties();
        //读取资源文件
        prop.load(in);

        String user = prop.getProperty("user");
        String password = prop.getProperty("password");
        System.out.println(user+"="+password);
    
    }
}


posted @ 2016-12-09 21:34  洋葱源码  阅读(447)  评论(0编辑  收藏  举报