eclipse2017支持的Servlet的版本_jsp进阶(servlet篇)

Servlet篇(jsp重点)

@author:杜勇帅

@email:[email protected]

-一.Servlet基础

一.Servlet概述

Servlet是运行在服务器端的java程序(类),作用是接收并处理客户端的请求,生成响应给客户端。

学习了Servlet后,jsp页面只负责展示数据,Servlet负责处理请求(调用Dao实现具体功能),并将数据转发到jsp页面上去展示(生成响应给客户端).

Servlet版本:有2.3-2.5版本,还有3.0及以上版本,只有3.0及以上版本才支持Servelt注解。@WebServlet,该注解在Servelt3.0及以上版本可以直接使用,用来标注一个类是一个Servlet. 使用Ecipse创建web项目时,Dynamic web module version就是Servlet版本,可以选择3.0以下版本和3.0版本

eclipse2017支持的Servlet的版本_jsp进阶(servlet篇)_第1张图片

二. Servle入门

入门程序,我们选择Servlet2.5版本,就不能使用注解,需要在web.xml中配置Servlet,才能让Servlet为我们处理请求,生成响应。

1).创建web项目,Sevlet版本选择2.5

eclipse2017支持的Servlet的版本_jsp进阶(servlet篇)_第2张图片

2)创建一个Servlet类,命名:XxxServlet,例如HelloServlet。Servlet类要继承HttpServlet

public class HelloServlet extends HttpServlet {
}

3)重写doGet和doPost()方法

public class HelloServlet extends HttpServlet {
    // 处理GET请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("处理Get请求");
    }

    // 处理Post请求
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("处理Post请求");
    }
}

4)在web.xml中配置Servlet


   
     helloservlet
     com.sxt.servlet.HelloServlet
   

    
       helloservlet
       /hello.action
    

4)发布项目,访问Servlet

在浏览器中的地址栏输入http://localhost:8088/0510_02_serlvetHello/hello.action,即可访问Servlet

注意:1.在web.xml中servlet-class是servlet的全限定类名(包名.类名)

2.url-pattern是servlet暴露给外界的访问路径,必须以"/"开头

三. Servlet运行原理

1).用户在地址栏上输入servlet的完整访问路径:

http://localhost:8088/0510_02_serlvetHello/hello.action

2)web服务器接收到请求servlet的完整url后,从项目根路径后面截取出/hello.action

3)Web服务器在web.xml中查找值为/hello.action的url-pattern节点,进而找到它的兄弟节点servlet-name

4)取出servlet-name节点中的值,即servlet的别名helloservlet

5)Web服务器在web.xml中查找别名和4)中的helloservlet相同的servlet-name节点

6)查找servlet-name的兄弟节点servlet-class,取出节点中的值,即servlet的全限定类名

com.sxt.servlet.HelloServlet。接下来服务器通过反射机制创建Servlet对象

7)servlet实例创建完成后,web服务器再根据客户端请求的方法来决定调用doGet()或doPost()方法处理客户端的请求并生成响应给客户端

四. 创建Servlet的第2种方式

刚才我们创建Servlet时,servlet版本号是2.5,不支持注解。

1)创建web项目时,选择Servlet3.0版本

2)创建Servlet类时,直接New->Web--->Servlet

eclipse2017支持的Servlet的版本_jsp进阶(servlet篇)_第3张图片

填写Servlet的类名

eclipse2017支持的Servlet的版本_jsp进阶(servlet篇)_第4张图片

修改Servlet的访问路径,默认是/servlet类名,要求改成/xxx.action

eclipse2017支持的Servlet的版本_jsp进阶(servlet篇)_第5张图片

3)生成的Servlet类如下,自带了一个注解@@WebServlet,并且重写了doGet()和doPost()

@WebServlet("/hello.action")
public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

注意:使用第2种方式创建Servlet,就不需要在web.xml文件中配置Servlet了,因为

@WebServlet("/hello.action")注解就已经明确的告诉web服务器servlet的url-pattern是/hello.action。用了Servlet的注解后就不能再在web.xml写配置Servlet了。即注解和web.xml配置只能用其中一种.

五.Servlet的生命周期

Servlet生命周期共经历了4个阶段:

1.实例化:第1次请求Servlet时,调用无参构造方法,web服务器创建Servlet的实例

2.初始化:第1次请求Servlet时,调用init()方法,只调用一次,在init()方法初始化Servlet中的资源

3.服务:每次请求都调用service()方法,该方法会根据请求方法类型来决定调用doGet()或doPost()

4.销毁:正常关闭服务器时销毁Servlet实例,调用destory()

六.HttpServletRequest对象的方法

1.获取请求行信息的方法

//获取请求的方法类型
          String method = request.getMethod();
          System.out.println("请求方法:"+method);
          //获取请求的资源路径
          String uri = request.getRequestURI();
          //获取协议以及版本号
          String protocol = request.getProtocol();

2.获取请求参数的方法

getParameter(String name):获取一个请求参数的值

getParameterValue(String name):获取一组请求参数的值,针对checkbox元素

3.设置请求编码

setCharacterEncoding(“UTF-8”)

4.获取请求头的值

getHeader(String name):根据请求头的名字获取值

5.请求转发 .xxx代表要跳转的资源路径

request.getRequestDispatcher("xxx").forward(request, response);

它就相当于jsp页面上的/jsp:forward

6.setAttribute(String name,Object value):向request域对象好存储数据

  1. getAttribute(String name):获取request域中的数据

七HttpServletResponse对象的方法

1.addHeader(String name,String value):添加响应头

2.getHeader(String name):获取响应头的值

3.setHeader(String name,String value):设置响应头

4.setContentType(String contentType):设置响应的内容类型(Mime类型)

5.setCharacterEncoding(String encoding):设置响应的编码

6.getWriter():获取打印流

7.sendRedirect(String location):重定向,跳转到一个新的资源上,地址栏的url会变化

八.转发和重定向的区别

请求转发的过程只有1次请求,地址栏的url没有变化,可以继续使用request域中的数据;

重定向的过程有2次请求(产生了新的请求),url会改变,request域中的数据就丢失了.

请求转发只能访问服务器内部资源(当前web项目下的资源),重定向可以访问外部资源

如果我们在Servlet中使用request域对象存了一个数据,再跳转到jsp页面上时希望能用EL获取出request域中的数据,此时我们只能使用请求转发来跳转.如果是用session存的数据,使用转发或重定向到jsp页面,都能取到session中的数据。

九.路径的问题

在超链接或重定向中请求Servlet时,Servlet路径前的”/”参照(代表)web服务器的根路径,会丢失了web应用的根路径。

相对路径:以当前资源所在的路径为参考位置,建立出来的路径

绝对路径:在访问的资源路径前带上web项目的根路径,此时的路径叫做绝对路径

比如index.jsp在项目的根路径下,servlet的路径/user.action也是参照项目根路径

此时在index.jsp用超链接跳转到servlte中,href=”user.action”

重定向或超链接都会改变地址栏的url,都属于客户端的跳转,发生在客户端的跳转,”/”带来的问题就是丢失web应用的根路径,因为”/”参照web服务器的根路径.

解决客户端跳转时丢失项目根路径的问题:

在”/”前加上request.getContextPath()来得到项目根路径,在jsp页面上用EL表达式${pageContext.request.contextPath}来代替request.getContextPath().

请求转发是在服务器内部的跳转,是请求转发中去访问Servlet,servlet路径前的”/”参照web项目的根路径,例如下面的:

request.getRequestDispatcher("/response.action").forward(request, response);

不管是服务器内部跳转还是客户端的跳转,如果在访问Servlet时没有带”/”,都表示相对路径,相对路径是以当前web资源所在的位置建立出来的路径,即要跳转的资源必须和当前web资源在同一个位置(同一级目录下)

总之:在做客户端的跳转(请求转发以外的跳转)时,路径前”/”代表web服务器的根路径,要防止报404,可以使用绝对路径

十.把学生信息的crud项目改造成jsp+Servlet版本

改造项目的目的:让jsp和Servlet各司其职,jsp负责展示数据, Servlet负责处理请求,生成响应给客户端,最终消除jsp页面上所有的脚本代码

在MVC分层开发模式中,jsp充当View(视图),Servlet充当Controler(控制器)

A.改造全查询

1.把index.jsp页面上的请求路径改成Servlet的路径

1.在QueryAllServlet中处理全查询请求,将数据存起来,再转发到列表页面

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //创建StudentDao的对象
        StudentDao stuDao = new StudentDaoImpl();
        List list = stuDao.queryAll();
        //把集合保存到request域中
        request.setAttribute("stulist", list);
        //跳转到list.jsp页面
        request.getRequestDispatcher("list.jsp").forward(request, response);
    }

1.list.jsp页面只负责显示数据即可


            
                ${stu.sid }
                ${stu.sname }
                ${stu.sex }
                ${stu.age }
                
                修改--删除
                
            
        

B.改造添加功能

1)add.jsp上的表单要请求Servlet

姓名:
性别:
年龄:

2)Servlet中处理添加的请求

protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 1.获取表单中的所有请求参数的值
        // 解决post请求的中文乱码
        request.setCharacterEncoding("UTF-8");
        String sname = request.getParameter("sname");
        String sex = request.getParameter("sex");
        int age = Integer.parseInt(request.getParameter("age"));
        // 2.把参数的值封装成学生对象
        Student stu = new Student(sname, sex, age);
        // 3.调用dao里面的insert方法,完成添加
        StudentDao stuDao = new StudentDaoImpl();
        int i = stuDao.insert(stu);
        if (i > 0) {
            // 添加成功,重新请求全查询的Servlet
            response.sendRedirect("queryAll.action");
            }
    }

以上的做法是用一个Servlet只处理一个请求,当项目的模块很多时,会造成Servlet的数量很多很多。

解决办法:

在实际开发中,应该以模块为单位来建立servlet,即1个模块只创建一个Servlet,该Servlet负责处理此模块的所有请求.

一个Servlet处理某个模块的所有请求的技巧:

在访问Servlet时,多加一个参数method=xxx,xxx就是Servlet中的一个方法,xxx方法的签名要和doPost()的签名完全一致

例如:index.jsp上访问Servlet中的queryAll方法

在Servlet中添加一个queryAll*方法来处理全查询请求*

protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 解决post请求的中文乱码
        request.setCharacterEncoding("UTF-8");
        // 获取method参数值
        String method = request.getParameter("method");
        if ("queryAll".equals(method)) {
            this.queryAll(request, response);
        } else if ("add".equals(method)) {
            this.add(request, response);
        }
    }

    // 处理全查询请求
    protected void queryAll(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // System.out.println("m1方法被调用");
        // 创建StudentDao的对象
        StudentDao stuDao = new StudentDaoImpl();
        List list = stuDao.queryAll();
        // 把集合保存到request域中
        request.setAttribute("stulist", list);
        // 跳转到list.jsp页面
        request.getRequestDispatcher("list.jsp").forward(request, response);
    }

同理要处理添加功能,要把add.jsp上的表单的请求路径改成servlet路径

C.改造修改功能

1)把列表页面list.jsp上”修改”超链接改成servlet路径,带method参数

2)在Servlet中添加方法toUpdate处理准备修改请求

// 处理修改前的查询,为修改做准备
    protected void toUpdate(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取要修改的学生的id
        int id = Integer.parseInt(request.getParameter("sid"));
        // 根据id查询一个学生对象
        StudentDao stuDao = new StudentDaoImpl();
        Student stu = stuDao.queryById(id);
        // 存到request域中
        request.setAttribute("stu", stu);
        // 请求转发到update.jsp上去回显数据
        request.getRequestDispatcher("update.jsp").forward(request, response);

    }

3)在update.jsp上只回显数据,消除了jsp脚本,把表单的请求路径改成servlet的路径,发送修改的请求


     
    编号:
姓名:
性别:
年龄:

4)在Servlet中增加一个update方法,处理修改请求

// 处理修改请求
    protected void update(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 获取表单中的参数值
        String sname = request.getParameter("sname");
        String sex = request.getParameter("sex");
        int age = Integer.parseInt(request.getParameter("age"));
        // 获取sid
        int sid = Integer.parseInt(request.getParameter("sid"));
        // 把参数的值封装成学生对象
        Student stu = new Student(sname, sex, age);
        stu.setSid(sid);
        // 调用dao里的修改方法,完成修改
        StudentDao stuDao = new StudentDaoImpl();
        int i = stuDao.update(stu);
        if (i > 0) {
            // 修改成功,重新请求list.jsp
            response.sendRedirect("student.action?method=queryAll");
        }
    }

代码优化:在doPost()中使用反射思想根据获取到的方法名来创建Method对象,并调用servlet的同名方法

protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 解决post请求的中文乱码
        request.setCharacterEncoding("UTF-8");
        // 获取method参数值
        String method = request.getParameter("method");

        try {
            // 用反射来动态调用Servlet中的方法,方法名就是method,获取一个方法对象
            Method methodObj = this.getClass().getDeclaredMethod(method, HttpServletRequest.class,
                    HttpServletResponse.class);
            // 用当前类的实例来调用方法
            methodObj.invoke(this, request, response);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

D.改造删除功能

1.把列表页面list.jsp上”删除”超链接所调用的js函数中的路径改成Servlet路径

function deleteById(id){
          //使用confirm弹出一个确认取消对话框
         var flag=   confirm("是否确认删除此信息?");
          if(flag){
    //发请求给jsp页面去处理删除,相当于get请求, location.href:改变(设置)地址栏上的url
              location.href="student.action?method=delete&sid="+id;
          }
    }

2.在Servlet中添加一个delete方法来处理删除请求

// 处理删除请求
    protected void delete(HttpServletRequest request, 
    HttpServletResponse response)
            throws ServletException, IOException {
        int sid = Integer.parseInt(request.getParameter("sid"));
        // 调用Dao里的删除方法,完成删除
        StudentDao stuDao = new StudentDaoImpl();
        int i = stuDao.delete(sid);
        if (i > 0) {
            // 删除成功,重新请求全查询的Servlet
            response.sendRedirect("student.action?method=queryAll");
        }
    }

二.会话技术与Servle域对象

1.会话技术概述

因为http协议默认是无状态的,它记不住客户端的状态,无法区分请求是否是来自于同一个用户,会认为每个请求都是新的客户端发过来的请求。会话技术的作用就是实现在多个请求中共享数据,区分不同的客户端.

会话:会话是浏览器与服务器之间的一次通信,它包含浏览器与服务器之间的多次请求、响应过程,浏览器关闭后本次会话结束。

常用会话技术:Cookie和Session

Cookie:将数据存储在客户端本地,安全性不好

Session:将数据存储在服务器端,安全性很好,服务器压力大

2.Cookie

Cookie的基本使用:

第1:服务器端创建Cookie

//服务器端创建Cookie
        Cookie  cookie=new Cookie("username", "zhangsan");
        //服务器把Cookie发送给浏览器
        response.addCookie(cookie);
        //重定向到ReadServlet
        response.sendRedirect("read.action");

第2:服务器接收浏览器所携带的Cookie

//服务器端接收浏览器携带的Cookie
        Cookie[] cookies = request.getCookies();
        for(Cookie ck:cookies){
             //获取我们想要的那个Cookie,通过比较名称
             if(ck.getName().equals("username")){
                  System.out.println("名为username的Cookie的值:"+ck.getValue());
                  break;
             }
            //System.out.println("cookie名:"+ck.getName()+",cookie值:"+ck.getValue());
        }

Cookie的深入使用:

1.Cookie分为两类:会话级别的Cookie和持久级别的Cookie

会话级别的Cookie:把Cookie存储在浏览器的内存中,关闭浏览器后Cookie就销毁了

持久级别的Cookie:把Cookie存储在硬盘上,关闭后再次打开浏览器,这些cookie依然有效,直到超过设定的过期时间。

2.设置Cookie的有效时间:setMaxAge(int time):时间单位是秒

补充:1)在jsp页面上读取Cookie的值,语法:${cookie.cookie名.value} 名为username的Cookie的值:${cookie.username.value }

​ 2)多个不同浏览器是无法共享cookie的,因为每个浏览器存储cookie的路径不一样的(在硬盘上保存Cookie的txt文件的路径不同),所以无法做到不同浏览器共享cookie。

Cookie在项目中的使用场景:

由于Cookie只保存字符串数据,在实际项目中以下几个场合中会用到Cookie:

1)记住用户名密码,实现免登录

2).记住用户的偏好设置,例如网站的背景色(皮肤)

3).其它需要保存字符串数据的场景

3.Session

Sesssion的原理:第1次访问服务器时,服务器会为当前会话创建一个session对象,使用服务器端内存空间为客户端保存数据,同时给浏览器发送一个名为JSESSIONID的Cookie(相当于给你分配一个房间,再给你一把钥匙),浏览器再次访问服务器时,需要携带着名为jSessionID的Cookie去服务器上寻找属于本会话的Session. 名为JSESSIONID的Cookie的值是本次会话的唯一标识ID。

3.1Session存取数据

1).以key/value方式存入数据:session.setAttribute(String name,Object obj);

​ 2).根据key取出数据:session.getAttribute(String name);

​ 3).删除数据:session.removeAttribute(String name);

例如:

//向session中存数据
           session.setAttribute("book1", "西游记");
           session.setAttribute("book2", "红楼梦");
           //删除数据
           session.removeAttribute("book1");
           //获取数据
           String book1 = (String)session.getAttribute("book1");
           String book2 = (String)session.getAttribute("book2");
           System.out.println("book1:"+book1);   //没有,因为已经删除
           System.out.println("book2:"+book2);

3.2 Session的生命周期

1.创建:第一次请求Servlet且调用request.getSession()方法时创建session;如果是请求jsp页面,会自动创建session(除非你把session禁用,在page指令上加一个属性 session="false" )

2.服务:每次请求服务器端的资源,都共享同一个session,为客户端服务(给客户端存储重要的数据)

3.销毁:三种”死法”

A)服务器正常关闭

B)session过期

有效期的设置有2种方式

1).在web.xml中配置session的有效期,单位是分钟


    
      20
    

2).在程序中手动设置session有效时间,单位是秒

session.setMaxInactiveInterval(10*60);

在程序中设置有效期优先级要高于web.xml中配置的有效期;如果两边都没有设置session有效期,则默认有效期是30分钟

C)手动销毁

调用session.invalidate()销毁session

3.3 Session的登录案例

处理登录请求的LoginServlet主要代码如下:

// 获取请求参数
        String username = request.getParameter("username");
        String userpwd = request.getParameter("userpwd");
        //假登录判断,假设正确用户名是admin,密码是12345
        if("admin".equals(username)&&"12345".equals(userpwd)){
              //把用户信息封装到用户对象中,
            User user=new User(username, userpwd, "女");
            //把用户对象存到session中
            HttpSession session = request.getSession(); 
            session.setAttribute("user", user);
            //跳转到后台主页面
            response.sendRedirect(request.getContextPath()+ "/view/main.jsp");
        }else{
            //登录失败,跳转到登录页面
            //错误信息存到request域对象中
            request.setAttribute("errorMsg", "用户名或密码错误!");
            //转发到登录页面
            request.getRequestDispatcher("login.jsp").forward(request, response);
        }

处理安全退出请求的LogoutServlet的主要代码如下:

HttpSession session = request.getSession();
        // 安全退出,即注销,将session销毁
        session.invalidate();
        //跳转到登录页面
        response.sendRedirect( request.getContextPath()+"/login.jsp");

总结:

Session在项目中用来保存与用户会话相关的重要数据,比如登录成功后需要在多个页面中共享用户信息,此时就用session来保存用户对象;比如购物车(购物车中的数据就相当于购物小票中的一条数据)

| 商品id | 商品名 | 价格 | 数量 | 金额 | | ------ | ------ | ---- | ---- | ---- | | 1 | 篮球 | 50 | 2 | 100 | | 2 | 足球 | 40 | 1 | 40 |

​ 总金额:140

3.4 Session的问题及解决办法

关闭浏览器后,再次重启浏览器,取不出session中的数据了,可能原因:

1)服务器端的session已经销毁

2)客户端名为JSESSIONID的Cookie销毁了

实际原因是2,因为服务器端在创建了session后给客户端发送过去的名为JSESSIONID的Cookie是会话级别的,在重启浏览器再次访问时就无法拿着”钥匙”去找”房间”

解决办法:把名为JSESSIONID的Cookie变成持久级别的Cookie,再发给浏览器

//把名为JSESSIONID的Cookie变成持久级别的
           Cookie cookie=new Cookie("JSESSIONID",session.getId());
           cookie.setMaxAge(24*60*60);
           cookie.setPath(request.getContextPath());
           //发送给浏览器
           response.addCookie(cookie);

三:Servlet进阶

1 ServletConfig

在Servlet创建时有时需要初始化一些配置信息,但又不能写死,怎么办呢?此时可以借助ServletConfig对象,用它来读取配置信息。使用步骤:

1.1 在web.xml中配置Servlet的初始化参数

比如在RegisterServlet中需要初始化一个name和一个age,就写到web.xml中

eclipse2017支持的Servlet的版本_jsp进阶(servlet篇)_第6张图片

1.2 Servlet中获取配置信息

在Servlet中重写init()方法,在init()方法中调用ServletConfig对象的getInitParameter()方法获取配置的初始化参数

eclipse2017支持的Servlet的版本_jsp进阶(servlet篇)_第7张图片

2 Servlet过滤器

2.1 为什么需要过滤器

场景1: 编写servlet,接收参数:

​ request.getParameter() /getParameterValues()

如果不做任何处理,就出现中文乱码问题。

解决乱码问题: request.setCharacterEncoding("utf-8");

问题:如果在项目中的每一个servlet都加上request.setCharacterEncoding("utf-8");

显示代码重复啰嗦。能不能把这部分公共代码抽取出去,放在一个地方执行????

场景2: 登录 -> 输入信息 -> 登录成功 -> 看到用户主页(欢迎xxx回来。。。)

如果用户不登录,直接访问主页,将跳转到登录页面

在其他需要登录才能访问的页面中,同样也需要加上验证用户是否登录成功的代码。

用于验证用户是否登录成功代码:

用于验证用户是否登录成功代码:                         
user= session.getAttribute("user");
             if(user==null){                                        //  跳转到登录页面
            }

问题: 能不能把这部分公共验证用户是否登录成功代码抽取出去,在一个地方执行??

结论: 以上两种场景出现的问题,都可以使用过滤器(Filter)解决!!!!

2.2 过滤器简介

过滤器就是Filter,它是Servlet技术中最激动人心的技术之一,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp,Servlet, 静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前和之后,对访问的请求和响应进行拦截

eclipse2017支持的Servlet的版本_jsp进阶(servlet篇)_第8张图片

2.3 Filter是如何实现拦截的?

Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪些web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:

1) 调用目标资源之前,让一段代码执行

2) 决定是否调用目标资源(即是否让用户访问web资源)

web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,如果调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。

3)调用目标资源之后,让一段代码执行

2.4 Filter开发流程

Filter开发分为二个步骤:

第1步:编写java类实现Filter接口,并实现其doFilter方法。

public class FilterTest implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub    
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        // TODO Auto-generated method stub
          System.out.println("我是调用目标资源前执行的代码");
          chain.doFilter(request, response);//放行,调用web资源,
          System.out.println("调用目标资源后执行的代码");
    }
    @Override
    public void destroy() {
        // TODO Auto-generated method stub
    }
}

第2步:在 web.xml 文件中使用和元素对编写的filter类进行注册,并设置它所能拦截的资源。


      filtertest
      com.sxt.filter.FilterTest


     filtertest
     
     /*

测试:访问index.jsp时被过滤器拦截了

eclipse2017支持的Servlet的版本_jsp进阶(servlet篇)_第9张图片

2.5 FilterChain

​ 在doFilter()方法中有个参数是FilterChain对象,FilterChain就是过滤器链,我们在一个web应用中可以开发多个Filter,这些Filter组合起来称之为一个FilterChain。

web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有其它filter,如果有,则调用下一个filter,直到filter调用完毕,最后调用目标资源。

2.6 Filter的生命周期

Filter生命周期有3个方法,除了doFilter是每次拦截web资源都调用外,还有下面两个方法:

A. init(FilterConfig filterConfig)throws ServletException:

和我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(注:filter对象只会创建一次,init方法也只会执行一次 )

开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

B.destroy():

在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。

2.7 Filter的常见应用

1.字符编码过滤器

表单以POST方式提交请求到Servlet时,可以在过滤器中统一处理中文乱码问题

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        // 统一设置请求和响应的字符编码
        // 1.设置请求的编码
        HttpServletRequest req = (HttpServletRequest) request;
        req.setCharacterEncoding("UTF-8");
        // 2.设置响应的编码
        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setContentType("text/html;charset=UTF-8");
        // 3.放行,让目标资源能够访问
         chain.doFilter(request, response);
    }

过滤器配置:


        encoding
        cn.sxt.filter.EncodingFilter
    
    
        encoding
        /*
    
  1. 登录权限验证过滤器

假设WEB-INF里的页面都需要登录才能访问,则过滤器可以这么设计:

1).首先登录Servlet处理登录逻辑,控制页面跳转

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取表单参数
        String username = request.getParameter("username");
        // 密码
        String userpwd = request.getParameter("userpwd");
        // 假登录
     if ("admin".equals(username) && "12345".equals(userpwd)) {
            // 登录成功
            User user = new User();
            user.setUsername(username);
            user.setUserpwd(userpwd);
            // 保存到session中          request.getSession().setAttribute("user", user);
            // 跳转到后台的页面
            request.getRequestDispatcher("WEB-INF/jsp/main/index.jsp").forward(request, response);
        } else {
            // 登录失败
    response.sendRedirect(request.getContextPath()+"/login.jsp");       }
    }

2).过滤器CheckLoginFilter:

public class CheckLoginFilter implements Filter {
    //保存初始化参数的值
    private String passResource;
   // 在初始化方法中获取过滤器的初始化参数的值
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub
         passResource = filterConfig.getInitParameter("passResource");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        // 把父接口对象强转成子接口的
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        HttpSession session = req.getSession();

        // 1)判断请求的url是否是特殊资源(无需进行登录验证),如果是,直接放行
        // 获取当前请求的资源的路径
        String uri = req.getRequestURI();
        // login.action,authCode.action
        String[] arr = passResource.split(",");
        for (String str : arr) {  
            if (uri.endsWith(str)) {
                // 放行
                chain.doFilter(request, response);
                return;
            }
        }
         // 2) url不是特殊资源,需要做登录验证
         Object obj = session.getAttribute("user");
        if (obj == null) {
            // 未登录,跳转到登录页面
            resp.sendRedirect(req.getContextPath() + "/login.jsp");
        } else {
            // 登录过了,放行
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
    }
}

3).配置过滤器拦截Servlet请求,并对登录和验证码的请求放行


    
        loginFilter
        cn.sxt.filter.CheckLoginFilter
        
        
            passResource
            
            login.action,authCode.action
        
    
    
        loginFilter
        *.action
    

3 Servlet监听器

3.1 概述

​ 监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听的对象发生上述事件后,监听器某个方法将立即被执行。被监听的对象,俗称 “事件源”

​ 监听器是Servlet 2.3新增的功能,Servlet监听器是Servlet规范中定义的一种特殊类,用于监听ServletContext、HttpSession和ServletRequest域对象的创建与销毁事件,以及监听这些域对象中属性发生变化的事件。

监听器所监听的对象:

1、ServletContext:在jsp中叫application,整个应用只存在一个

2、HttpSession:session,针对每一个会话

3、ServletRequest:request,针对每一个客户请求

监听内容:创建、销毁、属性改变事件

监听作用:可以在事件发生前、发生后进行一些处理,一般可以用来统计在线总人数和在线用户、统计网站访问量等。

3.2 监听器的基本使用

创建步骤:

1、创建一个实现监听器接口的类

如: class XXX implements XXXListener

2、配置web.xml文件,注册监听器



    完整类名

监听器的启动顺序:按照web.xml的配置顺序来启动

3.3监听器的分类

3.3.1 按照监听的对象划分

1)、用于监听应用程序环境对象ServletContext的事件监听器,实现ServletContextListener(监听ServletContext对象创建及销毁事件)、ServletContextAttributeListener(监听ServletContext对象的属性改变)接口

2)、用于监听用户会话对象HttpSeesion的事件监听器,实现HttpSessionListener、HttpSessionAttributeListener接口

3)、用于监听请求消息对象ServletRequest的事件监听器,实现ServletRequestListener、ServletRequestAttributeListener接口

3.3.2按照监听事件划分

A、监听域对象自身的创建和销毁事件的监听器

根据监听对象不同分别实现ServletContextListener、HttpSessionListener、ServletRequestListener接口。

1.监听ServletContext的创建和销毁:

实现ServletContextListener 接口,ServletContext创建时调用

​ contextInitialized方法,销毁时调用contextDestroyed方法

public void contextInitialized(ServletContextEvent sce)//ServletContext创建时调用

public void contextDestroyed(ServletContextEvent sce)//ServletContext销毁时调用

主要用途:

1)初始化的工作:初始化对象、初始化数据(如:加载驱动、读取属性文件的数据, 初始化web应用的菜单数据等);

2)加载初始化的配置文件: 如spring框架的配置文件;

3)任务调度(定时器):监听某项行为定时执行相应的功能;

在web.xml中可以配置web项目初始化信息,在contextInitialized方法中进行初始化参数的读取


    参数名
    参数值

自定义ServletContext监听器

public class MyServletContextListener implements ServletContextListener {
    // 当ServletContext对象创建时,执行contextInitialized()
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("servletContext域对象创建好了");
        ServletContext servletContext = sce.getServletContext();
        String name = servletContext.getInitParameter("name");
        // 读取属性文件conn.properties
        Properties p = new Properties();
        try {
            p.load(MyServletContextListener.class.getResourceAsStream(name));
            String url = p.getProperty("url");
String user= p.getProperty("user");
            // 保存url值
            servletContext.setAttribute("url", url);
            servletContext.setAttribute("user", user);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 当ServletContext对象销毁时,执行contextDestroyed()
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("servletContext域对象销毁了");
    }
}

监听器配置:


    cn.sxt.listener.MyServletContextListener
 

2.监听HttpSession的创建和销毁:

实现HttpSessionListener 接口,两个方法如下:

public void sessionCreated(HttpSessionEvent se)//session创建时调用

public void sessionDestroyed(HttpSessionEvent se)//session销毁时调用

主要用途:统计在线人数、记录访问日志等

自定义Session监听器,统计网站在线人数

public class MySessionlistener implements HttpSessionListener {
    // 计数器,存储在线人数
    private int count = 0;

    // 监听到session创建时,调用sessionCreated()方法
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("session创建了");
        synchronized (MySessionlistener.class) {
            count++;
            // 把人数存到ServletContext对象中
            ServletContext context = se.getSession().getServletContext();
            context.setAttribute("count", count);
        }
    }

    // 监听到session销毁时,调用sessionDestroyed()方法,表示用户离线
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("session销毁了");
        synchronized (MySessionlistener.class) {
            count--;
            // 把人数存到ServletContext对象中
            ServletContext context = se.getSession().getServletContext();
            context.setAttribute("count", count);
        }
    }
}

3.监听ServletRequest的创建和销毁:requestInitialized和requestDestroyed方法

public void requestInitialized(ServletRequestEvent sre)//request创建时调用

public void requestDestroyed(ServletRequestEvent sre)//request销毁时调用

主要用途:读取request参数,记录访问历史

实例:

public class MyRequestListener implements ServletRequestListener {
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        // 读取请求参数
        String value = sre.getServletRequest().getParameter("city");
        System.out.println(value);
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("request销毁");
            }
}

B、监听域对象中的属性发生改变的事件监听器

根据监听对象不同分别实现ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener接口。

实现方法:attributeAdded、attributeRemoved、attributeReplaced

4 注解

注解是有特殊含义的,它可以代替web.xml文件里的配置信息

4.1 @WebServlet

@WebServlet注解用在Servlet上,在servlet3.0以后不需要在web.xml中配置一个Servlet了,在类名上写注解就行,如@WebServlet(“/test.action”).

注解支持属性,语法: @WebServlet(属性名=值 ),例如

@WebServlet( urlPatterns={"/user.action","/test.action"} )
public class UserServlet extends HttpServlet {
}

4.2 @WebListener

这个是对监听器的注解,用了它后就不需要在web.xml注册监听器了

@WebListener
   public class MySessionListener implements HttpSessionListener{
 }

4.3 @WebFilter

这个是对过滤器的注解,写在Filter类上就好,就不需要在web.xml注册过滤器了。如果过滤器拦截的资源只有一种,urlPatterns属性的值可以是字符串,如果拦截的资源有多种,则属性值就必须写成数组

//如果过滤器拦截的资源只有一种,urlPatterns属性的值可以是字符串,如果拦截的资源有多种,则属性值就必须写成数组
@WebFilter(urlPatterns = { "/view/*","*.action" })
public class MyFilter implements Filter {

4.4 @WebInitParam

用来配置初始化参数的注解,可以使用在@WebFilter和@WebServlet注解上,作为它们的initParams属性的值

@WebFilter(urlPatterns = { "/view/*","*.action" }, 
           initParams={@WebInitParam(name="city",value="武汉"),@WebInitParam(name="username",value="张三")})
public class MyFilter implements Filter {
}

4.5 @MultipartConfig

该注解主要是为了辅助 Servlet 3.0 中 HttpServletRequest 提供的对上传文件的支持。该注解标注在 Servlet 上面,以表示该 Servlet 希望处理的请求的 MIME 类型是 multipart/form-data。另外,它还提供了若干属性用于简化对上传文件的处理

@WebServlet("/sf")

@MultipartConfig

public class SingleFileUpload extends HttpServlet {

}

5 错误页面配置

在web.xml文件中,除了可以通过welcome-file设定欢迎页面外,还可以通过error-page标签来设定错误页面

5.1 资源找不到的错误页面配置


    404
     /404.jsp
  

5.2 服务器内部错误的错误页面配置

当服务器内部出错时,会跳到默认的错误页面,我们可以用自己配置的错误页面来代替默认的页面


    500
     /500.jsp
  

补充:基于注解的过滤器的调用顺序

如果多个过滤器都是用注解版本,则调用的顺序取决于过滤器类名的头字母,与字母排序有关,如果头字母相同,再去比较后面的字母和数字,谁排在前面,就先调用谁。

例如:Filter01和Filter02都是用的注解,先调用Filter01

下一章:Json解析与Ajax

你可能感兴趣的:(eclipse2017支持的Servlet的版本_jsp进阶(servlet篇))