从零开始学 serlvet


一、hello servlet

写一个servlet,首先从tomcat中导包 jsp-api.jar + servlet-api.jar,写一个hello Servlet!

方法1.写一个java类继承genericServlet。并实现service方法。

方法2.写一个java类继承HttpSerlvet,并重写doPost() doGet()方法。

二、servlet的生命周期

任何一个servlet都包含init()、service()、destroy()方法。init在首次访问该servlet时进行初始化,而且只初始化一次。每次访问servlet都会调用service方法。在卸载servlet或结束项目时,调用destroy方法。如果想改变加载的优先级,需要更改

public class Servlet2 extends GenericServlet{
    @Override
    public void init() throws ServletException {
        System.out.println("init");
    }
    @Override
    public void destroy() {
        System.out.println("destroy");
    }
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("hello generic Servlet!");
        servletResponse.getWriter().print("Hello Servlet");
    }
}

index.jsp

<%@ page contentType="text/html;charset=utf-8" %>




hello


web.xml

<?xml version="1.0" encoding="UTF-8"?>

    
        a
        com.cskaoyan.Servlet2
        1
    
    
        a
        /helloServlet
    

第一次访问http://localhost:8080/helloServlet,出现init。这说明是在访问Servlet时再执行init()。且只执行一次。

以后多次访问只会执行Service()方法。

结束时会执行destroy()方法。

结果:

load-on-startup

但是,如果在web.xml的节点下,加入1,就会在web应用初始化时进行init。(也可以注释Servlet2类:@WebServlet(value = "/helloServlet",loadOnStartup = 1))

 结果:

 也就是说,load-on-start 作用是:当数值为正整数,在项目启动时就初始化,而不是在访问到该类再初始化。数值越小优先级越高。

load-on-startup 元素标记容器是否应该在web应用程序启动的时候就加载这个servlet,(实例化并调用其init()方法)。
它的值必须是一个整数,表示servlet被加载的先后顺序。
如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。

关于url-pattern的一些细节问题

1、在web.xml中的写法

    
        a
        com.cskaoyan.Servlet2
        1
    
    
        a
        /helloServlet
    

等价于

在标记的类上添加注解:

@WebServlet(value="/servlet1")或 @WebServlet(urlPatterns = {"/servlet1",“/sjdfk”})

2、关于urlPattern的映射问题:

一个servlet可以设置多个url-pattern吗?可以的。

多个servlet可以配置同一个url-pattern吗?不可以。

测试代码:

@WebServlet("/TestUrlPattern")
public class TestUrlPattern222 extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("/TestUrlPattern222");
        servletResponse.getWriter().write("/TestUrlPattern222");
    }
}
@WebServlet(urlPatterns = {"/TestUrlPattern","/TestUrlPattern2"})
public class TestUrlPattern extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("/TestUrlPattern");
        servletResponse.getWriter().write("/TestUrlPattern");
    }
}

运行报错:Caused by: java.lang.IllegalArgumentException: 名为 [TestUrlPattern]和 [TestUrlPattern222] 的servlet不能映射为一个url模式(url-pattern) [/TestUrlPattern]

说明了多个Servlet不同映射相同的 UrlPattern

 

3.关于UrlPattern中 / * 与 / 优先级

   servlet1:

@WebServlet("/abc/*")
public class TestUrlPriority extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("/abc/*");
        servletResponse.getWriter().write("/abc/*");
    }
}

   servlet2:

@WebServlet("/*")
public class TestUrlPriority2 extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("/*");
        servletResponse.getWriter().write("/*");
    }
}

   servlet3:

@WebServlet("/abc")
public class TestUrlPriority3 extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("/abc");
        servletResponse.getWriter().write("/abc");
    }
}

  servlet4:

@WebServlet("*.do")
public class TestUrlPriority4 extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("*.do");
        servletResponse.getWriter().write("*.do");
    }
}

测试结果:

/**
* 访问:/abc/a.html 结果: /abc/*
* /abc     /abc
* a.do        /*
* /xxx/yyy/a.do /*
*/

结论:URL精准匹配优先级最高,/* 的优先级高于/ 高于 *.xxx

因此实际开发中存在以下问题:

4. @WebServleturl(Pattern= ”/“)存在的问题

问题:当@WebServleturl(Pattern= ”/“) 访问不到除了jsp以外所有的静态资源,当@WebServleturl(Pattern= ”/* “)访问不到所有的静态资源

原因:tomcat 中对静态资源文件有特定处理

在tomcat目录下web.xml文件,有以下字段:

    
   default / jsp *.jsp *.jspx

如果自己写了一个Servlet的,而它的@WebServleturl(Pattern= “/*” ),那么 /* 的优先级高于 *.xxx ,则所有请求被拦截(包括静态资源请求),需要自己手动处理所有请求。如果没有重写jsp和其他静态资源的处理方式,就会导致访问不到所有的静态资源。

同理:如果@WebServleturl(Pattern= “/”),那么 / 优先级低于 *.xxx, 则除了jsp页面,其他静态资源都被拦截。自己没有重写,则访问不到(例如html)

测试代码如下:(app1为项目名)

  Servlet1:

@WebServlet("/*")
//@WebServlet("/aabbcc")
public class SpecialServlet extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        servletResponse.getWriter().print("/*");
    }
}

  Servlet2:

@WebServlet("/")
public class SpecialServlet2 extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        servletResponse.getWriter().print("/");
    }
}

  测试结果:

访问:http://localhost:8080/app1/
结果: /*
访问:http://localhost:8080/app1/1.html
结果:/*

如果把servlet1的注解改为 @WebServlet("/aabbcc")
访问:http://localhost:8080/app1/
结果:正常显示jsp页面
访问:http://localhost:8080/app1/1.html
结果:/

  结论:/*优先级高于*.xxx,*.jsp与jsp页面匹配度更高(高于/)

Servlet运行的流程图

servlet的 servletContext 与 ServletConfig

servletContext获取全局参数,对应xml中的

servletConfig 获取初始化参数,对应xml中的

servletContext域(共享)

servletContext域是指在当前应用下,所有servlet共用一块内存空间,servletContext数据共享。

通俗理解: 一个应用里面只有一个context对象,即使有多个servlet,只能拿到同一个context对象。参考jsp的四个域 page context session application

@WebServlet("/servlet1")
public class Servlet1 extends GenericServlet {

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        this.getServletContext().setAttribute("name","servlet1");
    }
}
@WebServlet(value = "/servlet2")
public class Servlet2 extends GenericServlet{
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        //servletResponse.getWriter().print("Hello Servlet");
        String name = (String) getServletContext().getAttribute("name");
        servletResponse.getWriter().write(name);
        System.out.println(name);
    }
}

然后先访问:http://localhost:8080/servlet1   浏览器结果:无

再访问:http://localhost:8080/servlet2        浏览器结果:出现servlet1

 这说明servletcontext是共用的。

绝对路径与相对路径

在servlet中很少new File(),因为new File的默认路径为tomcat的bin目录。而我们关心的是部署目录。而使用getRealPath()和getServletContext()获取部署目录的绝对和相对路径。从而可以获得WEB-INF目录下的文件。而WEB-INF同级别文件可以通过浏览器  Http:localhost:8080/项目名/文件名  ,直接访问

@WebServlet("/servlet3")
public class servlet3 extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        File file = new File("1.html");
        System.out.println(file.exists());
        System.out.println(file.getAbsolutePath());

        String realPath = getServletContext().getRealPath("WEB-INF\\1.html");
        System.out.println(realPath);
        
        String contextPath = servletRequest.getServletContext().getContextPath();
        System.out.println(contextPath);

    }
} 

访问:http://localhost:8080/app1/servlet3

结果:

HTML页面中网络路径的写法

1.全路径:http://localhost:8080/app1/page/2.html

好不好呢?全路径如果使用这种硬编码格式来书写,肯定是不好的

企业开发:开发环境(个人主机)、测试环境、生产环境

如果要写的话,也可以,但是必须将这些可变的部分以配置文件的形式配置起来,从配置文件中读取

 

2.相对路径:img src ='1.jpeg'

http://localhost/app1/2.html

http://localhost/app1/TestRequest2

(过分依赖于当前的页面路径)

 

3./开头的路径:/项目应用名/资源路径

比如:/app1/TestRequest2(推荐的写法)

 

练习:用以上三种不同的写法,填写表单路径。浏览器访问页面为与WEB-INF同级page目录下的2.html

  public class TestRequest2:

@WebServlet("/TestRequest2")
//webservlet值拼接的是...的内容。---->  http:localhost:8080/app1/" ... "
public class TestRequest2 extends GenericServlet {
    @Override
    public void service(ServletRequest req, ServletResponse servletResponse) throws ServletException, IOException {// TO DO}
}

  page文件夹下的2.html




    
    222




username:
password:

java python c++

 

学习 Request

练习:写一个 Servlet 和 form表单,接收传入参数并打印在控制台。

提示:使用ServletRequest 的 getParameterNames() 、 getParameter()、getParameterValues() 方法

实现类:

@WebServlet("/TestRequest")
public class TestRequest2 extends GenericServlet {
    @Override
    public void service(ServletRequest req, ServletResponse servletResponse) throws ServletException, IOException {
//System.out.println(req.getParameter("username")); //System.out.println(req.getParameter("password")); //String hobbies = req.getParameter("hobby"); //参数一个一个获取太繁琐了,一个for搞定它 System.out.println("_________________________"); Enumeration parameterNames = req.getParameterNames(); while (parameterNames.hasMoreElements()) { String parameterName = parameterNames.nextElement(); //String parameter = req.getParameter(parameterName); String[] parameters = req.getParameterValues(parameterName); // Don't print submit if(parameterName.equals("submit")) continue; // The parameters maybe only one element or an Array. Use if statement to judge if(parameters.length==1){ System.out.println(parameterName+":"+parameters[0]); } else { System.out.println(parameterName+":"+Arrays.toString(parameters)); } } } }

  form表单:1.html




    
    111


username:
password:

java python c++

访问:http://localhost:8080/app1/1.html

结果:

username:123@qq.com
password:111
gender:male
hobby:[java, python, c++]

三、JavaBean

什么是javaBean

JavaBean: java里面的对象类。类似于Student类。成员变量是private,提供相应的get和set方法;一定要有无参构造函数.

练习:通过反射,将 form 表单中的参数封装到 user 类中。

。。。。。

转发与包含

servlet处理一个逻辑之后,需要跳转至一个页面,那么这个时候可以怎么做呢?

转发:源组件执行一部分逻辑之后,接下来将请求交给目标组件来继续处理,后面主动权交给目标组件

包含:源组件执行一部分逻辑之后,接下来将目标组件包含到自身之中来,一并做出响应,并没有移交主动权

代码层面的体现:

转发:(源组件)留头不留体(响应头、响应体)

包含:(源组件) 留头也留体(响应头、响应体)

----------------------------

特别注意:下文的TestForward类中关于req.getRequestDispatcher("/跳转目标");注意这个分发器的参数,可以是相对路径“跳转目标”,也可以"/跳转目标"。但绝对路径不行,因为它会帮你自动添加”/项目名“。全路径的报错结果如下:

 ------------------------------

forward 与 include 比较测试代码:

  TestForward:

@WebServlet("/TestForward")
public class TestForward extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //响应头
        resp.setHeader("Content-Type","text/html;charset=utf-8");
        //响应体
        resp.getWriter().print("TestForward");
        RequestDispatcher dispatcher = req.getRequestDispatcher("/TestForward2");
        dispatcher.forward(req,resp);//测试forward
//        dispatcher.include(req,resp);//测试include
        resp.getWriter().print("TestForward");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }
}

  TestForward2:

@WebServlet("/TestForward2")
public class TestForward2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().print("TestForward2");
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }
}

首先测试forward

浏览器访问:http://localhost:8080/app1/TestForward

浏览器结果:

 结论:对于forward:响应头setHeader成功,而响应体 resp.getWriter().print("TestForward");失败

而测试include:发现响应头与响应体都成功(结果就不再演示)。

测试2:登录后的转发页面

Login类:

@WebServlet("/login")
public class Login extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if(username.equals("admin")&&password.equals("admin")){
            req.getRequestDispatcher("/success.html").forward(req,resp);
        }else{
            req.getRequestDispatcher("/fail.html").forward(req,resp);
        }
    }
}

login.html




    
    login


username:
password:

success.html:




    
    success


  success

  

fail.html




    
    fail


fail


浏览器访问:http://localhost:8080/app1/login.html,即可查看结果。