澳门新葡亰平台官网下载-平台游戏app > 前端 >
Servlet程序开发

本文实例讲述了JSP学习之Java Web中的安全控制。分享给大家供大家参考。具体如下:

1 Servlet简介

Servlet(服务端小程序)是使用Java语言编写的服务端小程序,可以像JSP一样,生成动态的Web页;

Servlet运行在服务器端,并由服务器调用执行,是一种按照Servlet标准开发的类;

Servlet程序是Java对CGI程序的实现,与传统的CGI程序的多线程处理操作不同的是,Servlet采用多线程的处理方式;

使得Servlet程序的运行效率比传统的CGI更高;

Servlet保留有Java的可移植性的特点,使得更易使用,功能更加强大;

Servlet程序将按照如下步骤进行处理:

  a 客户端(可能是Web浏览器)通过HTTP提出请求;

  b Web服务器接受到该请求并将其发送给Servle;如果这个Servlet尚未被加载,Web服务器将把它加载到Java虚拟机并执行它;

  c Servlet程序将接受该HTTP请求并执行某种处理;

  d Servlet会将处理后的结果想Web服务器返回应答;

  e Web服务器将收到的应答发回给客户端;

在整个Servlet程序中最重要的Servlet接口,此接口下定义了一个GenericServlet的子类;

但是一般不会直接继承此类,而是根据所使用的协议选择GenericServlet的子类继承;

eg : 采用HTTP协议处理,一般而言当需要使用HTTP协议操作时用户自定义的Servlet类都要继承HttpServlet类;

一、目标:

2 第一个Servlet程序

开发一个可以处理HTTP请求的Servlet程序,则肯定要继承HttpServlet类;

而且自定义的类至少还要覆写HttpServlet类中提供的doGet()方法;

HttpServlet类中的方法:

public void doGet(HttpServletRequest reg,HttpServletResponse resp)throws ServletException,IOException  负责处理所有的get请求;

Servlet程序本身也是按照请求和应答的方式进行的,所以在doGet()方法中定义了两个参数;

即HttpServletRequest和HttpServletResponse,用来接收和回应用户的请求;

HelloServlet.java : 第一个Servlet程序

package org.shi.servletdemo ;
import java.io.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;

public class HelloServlet extends HttpServlet {
    public void doGet(HttpServletRequest req,HttpServletResponse resp)
              throws ServletException,IOException{
        PrintWriter out = resp.getWriter() ;
        out.println("<html>") ;
        out.println("<head><title>第一个Servlet程序</title></head>") ;
        out.println("<body>") ;
        out.println("<h1>HELLO WORLD</h1>") ;
        out.println("</body>") ;
        out.println("</html>") ;
        out.close() ;
    }
}

以上代码首先从HttpServletResponse对象中取得一个输出流对象,然后通过打印流输出HTML元素;

一个Servlet程序编译完成后,实际上是无法立即访问的,因为所有的Servlet程序都是以.class的形式存在的;

所以需要在WEB-INFweb.xml文件中进行Servlet程序的映射配置;

配置web.xml文件

<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>org.shi.servletdemo.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/helloServlet</url-pattern>
</servlet-mapping>

以上配置表示,通过/helloServlet路径即可找到对应的<servlet>节点,并找到<servlet-class>所指定的Servlet程序的“包.类”名称;

每次修改web.xml文件后,都要重新启动服务器,这样新的配置才可以起作用;

在实际中通过Servlet完成页面的输出显示并不是一件很方便的事,这也是jsp存在的原因,Servlet在开发中并不能作为输出显示使用;

① 掌握登录之后的一般处理过程;
② 能够为每个页面添加安全控制;
③ 能够共享验证代码;
④ 使用过滤器对权限进行验证;
⑤ 能够对文件的局部内容进行验证;
⑥ 掌握安全验证码的基本实现方式;
⑦ 通过异常处理增强安全性。

3 Servlet与表单

Servlet本身也存在HttpServletRequest和HttpServletResponse对象的声明,也可以使用Servlet接收表单所提交的内容;

input.html : 定义表单

<html>
<head><title>定义表单</title></head>
<body>
<form action="InputServlet" method="post">
    请输入内容:<input type="text" name="info">
    <input type="submit" value="提交">
</form>
</body>
</html>

以上代码中,表单在提交时会提交到InputServlet路径上,由于提交方式是post,所以在编写Servlet程序时就得使用doPost()方法;

地址提交属于get提交方法;

在进行Servlet开发时,如果直接通过浏览器输入一个地址,对于服务器来讲就相当于客户端的发出的get请求,会自动调用doGet()处理;

InputServlet.java  : 接收用户请求

package org.shi.servletdemo ;
import java.io.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;
public class InputServlet extends HttpServlet{
    public void doGet(HttpServletRequest req,HttpServletResponse resp)
              throws ServletException,IOException{
        String info = req.getParameter("info") ;    // 接收请求参数
        PrintWriter out = resp.getWriter() ;
        out.println("<html>") ;
        out.println("<head><title>MLDNJAVA</title></head>") ;
        out.println("<body>") ;
        out.println("<h1>" + info + "</h1>") ;
        out.println("</body>") ;
        out.println("</html>") ;
        out.close() ;
    }
    public void doPost(HttpServletRequest req,HttpServletResponse resp)
              throws ServletException,IOException{
        this.doGet(req,resp) ;
    }
}

以上代码由于要处理表单,所以增加doPost()方法,但是由于要处理的操作代码主体和doGet()方法一样;

所以直接利用this.doGet(reg,resp)继续调用本类中的doGet()方法完成操作;

配置web.xml ,注意映射路径

<servlet>
    <servlet-name>input</servlet-name>
    <servlet-class>org.shi.servletdemo.InputServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>input</servlet-name>
    <url-pattern>/InputServlet</url-pattern>
</servlet-mapping>

二、主要内容:

4 Servlet生命周期

Servlet程序是运行在服务器端的一段Java程序,其生命周期收受到Web容器的控制;

生命周期包括加载程序、初始化、服务、销毁、卸载5个部分;

Servlet生命周期对应的方法:

图片 1

各个生命周期的作用如下:

A 加载Servlet

Web容器负责加载Servlet,当Web容器启动后或者是第一次使用这个Servlet时,容器会负责创建Servlet实例;

但是用户必须通过部署描述符(web.xml)指定Servlet的位置(Servlet所在的包.类名称);

成功加载后,Web容器会通过反射的方式对Servlet进行实例化;

B 初始化

当一个Servlet被实例化后,容器将调用init()方法初始化这个对象,初始化目的是为了让Servlet对象在处理客户端请求前完成一些初始化的操作;

如建立数据库的连接、读取资源文件信息等,如果初始化失败,则此Servlet将被直接卸载;

C 处理服务

当有请求提交时,Servlet将调用service()方法(常用的是doGet()和doPost())进行处理;

在service()方法中,Servlet可以通过ServletRequest接收客户的请求,利用ServletResponse设置响应信息;

D 销毁

当Web容器关闭或者检测到一个Servlet要从容器中被删除时,会自动调用destroy()方法,以便让该实例释放掉所占用的资源;

E 卸载

当一个Servlet调用完destroy()方法后,此实例将等待被垃圾回收器所回收;

如果需要再次调用此 Servlet时,会重新调用init()方法初始化;


正常情况下,Servlet只会初始化一次,而处理服务会调用多次,销毁也只调用一次;

如果一个Servlet长时间不使用,也会被容器自动销毁,而如果再次使用会重新进行初始化操作,及特殊情况下初始化会进行多次,销毁也进行多次;


LifeCycleServlet.java : 生命周期

package org.shi.servletdemo ;
import java.io.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;
public class LifeCycleServlet extends HttpServlet{
    public void init() throws ServletException{
        System.out.println("** 1、Servlet初始化 --> init()") ;
    }
    public void doGet(HttpServletRequest req,HttpServletResponse resp)
              throws ServletException,IOException{
        System.out.println("** 2、Servlet服务 --> doGet()、doPost()") ;
    }
    public void doPost(HttpServletRequest req,HttpServletResponse resp)
              throws ServletException,IOException{
        this.doGet(req,resp) ;
    }
    public void destroy(){
        System.out.println("** 3、Servlet销毁 --> destory()") ;
        try{
            Thread.sleep(3000) ;
        }catch(Exception e){}
    }/*
    public void service(ServletRequest req,
                             ServletResponse res)
                      throws ServletException,
                             IOException{
        System.out.println("************ 服务 **************") ;
    }
    */
}

以上代码如果覆写了service()方法,则对应的doGet()和doPost方法就不在起作用了,而是直接使用service()方法进行处理;

因为在HttpServlet类中已经将service()方法覆写,方法的主要功能就是区分不同的请求类型;

在web.xml中配置Servlet

<servlet>
    <servlet-name>life</servlet-name>
    <servlet-class>org.shi.servletdemo.LifeCycleServlet</servlet-class> 
    <load-on-startup>1</load-on-startup> <!--容器启动时,Servlet会自动进行初始化-->             
</servlet>
<servlet-mapping>
    <servlet-name>life</servlet-name>
    <url-pattern>/LifeServlet</url-pattern>
</servlet-mapping>

① 通过修改前面的登录功能,分别对管理员和普通用户的登录进行处理;
② 为管理员才能访问的页面添加控制;
③ 共享各个页面中的控制代码,使用专门的文件,然后在需要的时候调用;
④ 使用过滤器降低重复验证代码;
⑤ 通过标准标签库完成页面局部信息的安全控制;
⑥ 介绍安全验证码的基本实现方式;

5 取得初始化配置信息

JSP中有一个内置对象,通过此对象可以读取web.xml中配置的初始化参数;

此对象实际上是ServletConfig接口的实例;可以通过init()方法找到ServletConfig接口实例;

InitParamServlet.java :  读取初始化配置的信息

package org.lxh.servletdemo ;
import java.io.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;
public class InitParamServlet extends HttpServlet {
    private String initParam = null ;    // 用于保存初始化参数
    public void init() throws ServletException{
        System.out.println("*****************") ;
    }

    public void init(ServletConfig config) throws ServletException{
        System.out.println("#######################") ;
        this.initParam = config.getInitParameter("ref") ;    // 接收的初始化参数名称暂时为ref
    }

    public void doGet(HttpServletRequest req,
                     HttpServletResponse resp)
              throws ServletException,
                     IOException{
        System.out.println("** 初始化参数:" + this.initParam) ;
    }
    public void doPost(HttpServletRequest req,
                     HttpServletResponse resp)
              throws ServletException,
                     IOException{
        this.doGet(req,resp) ;

    }
}

在web.xml中配置初始化信息

<servlet>
        <servlet-name>initparam</servlet-name>
        <servlet-class>org.shi.servletdemo.InitParamServlet</servlet-class>
        <init-param>
            <param-name>ref</param-name><!--参数名-->
            <param-value>www.baidu.com</param-value><!--参数值-->
        </init-param>
 </servlet>
 <servlet-mapping>
        <servlet-name>initparam</servlet-name>
        <url-pattern>/InitParamServlet</url-pattern>
  </servlet-mapping>

在Servlet中初始化方法有init()和init(ServletConfig config)两个,如果两个初始化方法同时出现,则调用的是init(ServletConfig config)方法;

1、完善登录功能

6 取得其他的内置对象

通过Servlet程序可以取得session及applicatio的内置对象

正常情况下,管理员登录成功之后跳转到管理员默认工作界面;普通用户登录之后跳转到普通用户默认工作界面;用户登录失败后跳转到登录界面重新登录。
为了完成这个功能,需要编写管理员界面和普通用户界面。
管理员界面对应的文件为manager.jsp,代码如下:

6.1 取得HttpSession实例

Servlet程序中要想取得一个session对象,则可以通过HttpServletRequest接口完成;

取得HttpSession接口实例

public HttpSession getSession()  返回当前的session

public HttpSession getSession(boolean create) 返回当前的session,如果没有则创建一个新的session对象返回

HttpSessionDemoServlet.java : 取得HttpSession对象

package org.shi.servletdemo ;
import java.io.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;
public class HttpSessionDemoServlet extends HttpServlet { //继承HttpServlet
    public void doGet(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{
        HttpSession ses = req.getSession() ; //取得session
        System.out.println("SESSION ID --> " + ses.getId()) ;  //取得session Id
        ses.setAttribute("username","bugshi") ;     // 设置session属性
        System.out.println("username属性内容:" + ses.getAttribute("username")) ;
    }
    public void doPost(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{//处理服务
        this.doGet(req,resp) ; //调用doGet()
    }
}

配置web.xml文件

<servlet>
    <servlet-name>sessiondemo</servlet-name>
    <servlet-class>
        org.shi.servletdemo.HttpSessionDemoServlet
    </servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>sessiondemo</servlet-name>
    <url-pattern>/HttpSessionDemoServlet</url-pattern>
</servlet-mapping>

manager.jsp代码:

6.2 取得ServletContext实例

application内置对象是ServleContext接口的实例,表示Servlet上下文;

如果在一个Servlet中使用此对象,直接通过GenericServlet类提供的方法即可;

public ServletContext getServletContext() 取得ServletContext对象;

ServletContextDemoServlet.java  取得ServletContext对象

package org.shi.servletdemo ;
import java.io.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;
public class ServletContextDemoServlet extends HttpServlet {
    public void doGet(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{
        ServletContext app = super.getServletContext() ; //取得application
        System.out.println("真实路径:" + app.getRealPath("/")) ;
    }
    public void doPost(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{
        this.doGet(req,resp) ; //调用doGet()
    }
}

配置web.xml文件

<servlet>
    <servlet-name>applicationdemo</servlet-name>
    <servlet-class>                                  
      org.shi.servletdemo.ServletContextDemoServlet
    </servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>applicationdemo</servlet-name>
    <url-pattern>/ServletContextDemoServlet</url-pattern>
</servlet-mapping>

以上代码通过getServletContext()方法取得ServletContext实例后,将虚拟路径对应的真实路径进行输出;

<%@ page contentType="text/html;charset=gb2312"%>

7 Servlet跳转

从一个jsp或者一个HTML页面可以通过表单或者超链接跳转进行Servlet,那么从Servlet可以跳转到Servlet、jsp页面;

管理员操作界面

7.1 客户端跳转

Servlet中如果要进行客户端跳转,直接使用HttpServletResponse接口的sendRedirect()方法;

但是此跳转只能传递session及application范围的属性,而无法传递request范围的属性;

ClientRedirectDemo.java

package org.shi.servletdemo ;
import java.io.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;
public class ClientRedirectDemo extends HttpServlet {
    public void doGet(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{ //处理服务
        req.getSession().setAttribute("name","bugshi") ; //设置session属性
        req.setAttribute("info","HelloJAVA") ; //设置request属性
        resp.sendRedirect("get_info.jsp") ; //页面跳转
    }
    public void doPost(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{
        this.doGet(req,resp) ;
    }
}

配置web.xml文件

<servlet>
    <servlet-name>client</servlet-name>
    <servlet-class>
            org.shi.servletdemo.ClientRedirectDemo
    </servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>client</servlet-name>
    <url-pattern>/forward/ClientRedirectDemo</url-pattern>
</servlet-mapping>

get_info.jsp : 接收属性

<%@ page contentType="text/html" pageEncoding="GBK"%>
<html>
<head><title>接收属性</title></head>
<body>
<%    request.setCharacterEncoding("GBK") ;    %>
<h2>sesion属性:<%=session.getAttribute("name")%></h2>
<h2>request属性:<%=request.getAttribute("info")%></h2>
</body>
</html>

由于是客户端跳转,跳转后的地址栏会发生改变;

只能接收session属性范围的内容,而request范围属性的内容无法接收到,这也是request属性范围只能在服务器端跳转的原因;

普通用户界面对应的文件为commonuser.jsp,代码如下:

7.2 服务器端跳转

在Servlet中没有像jsp中的<jsp:forward />指令;

如果要执行服务器端的跳转,必须依靠RequestDispatcher接口完成;

RequestDispatcher接口提供的方法

public void forward(ServletRequest request,ServletResponse response) throws ServletException,IOException   页面跳转

public void include(ServletRequest request,ServletResponse response) throws ServletException,IOException   页面包含

使用RequestDispatcher 接口的forward()方法即可完成跳转功能;

如果要想使用此接口还需要ServletRequest接口提供的方法进行初始化

该方法为:

  public RequestDispatcher(String path)  取得RequestDispatcher接口的实例;

ServletRedirectDemo.java

package org.shi.servletdemo ;
import java.io.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;
public class ServerRedirectDemo extends HttpServlet {
    public void doGet(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{
        req.getSession().setAttribute("name","bugshi") ;
        req.setAttribute("info","HelloJAVA") ;
        RequestDispatcher rd = req.getRequestDispatcher("get_info.jsp") ;    // 准备好了跳转操作
        rd.forward(req,resp) ;    // 完成跳转
    }
    public void doPost(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{
        this.doGet(req,resp) ;
    }
}

配置web.xml文件

<servlet>
    <servlet-name>server</servlet-name>
    <servlet-class>
        org.shi.servletdemo.ServerRedirectDemo
    </servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>server</servlet-name>
    <url-pattern>/forward/ServerRedirectDemo</url-pattern>
</servlet-mapping>

服务器端跳转后,页面的路径不会发生改变,而且此时可以在跳转后的JSP页面接收到session及request范围的属性;

commonuser.jsp代码:

8 Web开发模式:Mode I 与Mode II

<%@ page contentType="text/html;charset=gb2312"%>

8.1 Mode I 

指在开发中将显示层、控制层、数据层的操作统一交个JSP或JavaBean来进行处理;

图片 2

处理情况分为两种:①一种为完全使用jsp进行开发,②另一种是使用JSP+JavaBean的模式进行开发

① 用户发出的请求交给JSP页面进行处理;如果是开发小型的Web程序,为了开发快速与遍历,通常将显示层和逻辑运算层都写在JSP页面中;

优点:

  开发速度快;程序设计人员不需要额外的编写JavaBean或Servlet,只需要专注开发jsp页面;

  小幅度修改代码较方便;因为没有使用到JavaBean或Servlet;修改程序时,直接修改JSP后,再交给Web容器重新编辑即可;

  而不同像写JavaBean或Servlet要先将Java源文件编译为类文件,在放到Web容器中;

缺点:

  程序可读性低;程序代码和网页标记都汇合在了一起,增加维护的困难度和复杂度;

  程序可重复利用性低;将所有的程序都直接写在了JSP页面中,并没有把常用的程序写成组件以增加重用性,造成程序代码过于繁杂,难以维护;

② 将显示操作都写入JSP页面,而业务层都写成JavaBean形式,将程序代码封装成组件;这样JavaBean将负责大部分的数据处理等(类似于DAO);

  如执行数据库操作等,再将数据处理后的结果返回至JSP页面显示; 

优点:

  程序可读性较高;大部分程序代码写在JavaBean中,不会和网页显示标记汇合在一起,后期维护时较为轻松;

  可重复利用性较高;核心业务代码使用JavaBean来开发,可重复使用此组件,大大减少重复性程序代码的操作;

缺点:

  没有流程控制;每一个JSP页面都需要检查请求的参数是否正确,条件判断,异常发生时的处理;

  所有的显示操作都与具体的业务代码紧密耦合在一起,日后维护困难;

普通用户界面

8.2 Mode II

在 Mode II中所有的开发都是以Servlet为主体展开的,由Servlet接收所有的客户端请求,然后根据请求调用相应的JavaBean;

将所有结果交给JSP完成,也就是MVC模式;

图片 3

MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开;

MVC设计被分成3个核心层,即模型层、显示层和控制层,他们各自处理自己的事务;

  显示层:主要负责接收Servlet传递的内容,并且调用JavaBean,将内容显示给用户;

  控制层:主要负责所有的用户请求参数,判断请求参数是否合法;根据请求的类型调用JavaBean执行操作并将最终的处理结果交给显示层进行显示;

  模型层:完成一个独立业务的操作组件,一般由JavaBean或EJB的形式进行定义的;

      EJB是一种分部组件技术,主要负责业务中心的编写,分为会话Bean,实体Bean和消息驱动Bean3种;

在MVC的设计模式中,最关键的部分是使用RequestDispatcher接口;因为内容都是通过此接口保存在JSP页面上进行显示的;

MVC处理流程图:

图片 4

当用户有请求提交时,所有的请求都会交给Servlet进行处理;

然后由Servlet调用JavaBean,将JavaBean的操作结果通过RequestDispatcher接口传递到JSP页面上;

这些要显示的内容只是在一次请求-回应中有效,所以在MVC设计模式中,所有的属性传递都是使用request属性范围,这样做可以提升操作性能

使用request传递属性原因在于保存范围越大占用的内容就越多

修改登录的Servlet,修改后的代码如下:

 9 实例操作:MVC设计模式应用

MVC登录程序流程:

图片 5

本程序中,用户输入的登录信息提交给Servlet进行接收;

Servlet接收到请求内容后首先对其合法性进行校验(输入的内容是否为空或者长度是否满足要求);

如果验证失败,则将错误信息传递给登录页面显示;如果数据合法,则调用DAO层完成数据库的验证;

根据验证的结构跳转到登录成功或登录失败的页面;

本程序为了操作便捷,将登录成功和失败都统一设置成登录页;


User     用户登录的VO操作类;

DataBaseConnection  负责数据库的连接和关闭操作;

IUserDAO  定义登录操作的DAO接口;

UserDAOImpl   DAO接口的真实实现类,完成具体的登录验证;

UserDAOProxy  定义代理操作,负责数据库的打开和关闭并且调用真实主题;

DAOFactroy  工厂类,取得DAO接口的实例;

LoginServlet  接收请求参数,进行数据验证,调用DAO完成具体的登录验证,并根据DAO的验证返回登录信息;

login.jsp   提供用户输入表单,可以显示用户登录成功或失败的信息;


数据库创建脚本

DROP TABLE IF EXISTS user;
CREATE TABLE user(
       userid  varchar(30)  PRIMARY KEY,
       name   varchar(30)  NOT NULL,
       password  varchar(32)  NOT NULL   
);

insert info user(userid,name,password)  values('admin','administrator','admin');

按照DAO设计模式,首先定义出VO类;

User.java  : 定义VO类

package org.shi.mvcdemo.vo ;
public class User {
    private String userid ;
    private String name ;
    private String password ;

    public void setUserid(String userid){
        this.userid = userid ;
    }
    public void setName(String name){
        this.name = name ;
    }
    public void setPassword(String password){
        this.password = password ;
    }
    public String getUserid(){
        return this.userid ;
    }
    public String getName(){
        return this.name ;
    }
    public String getPassword(){
        return this.password ;
    }
}

DatabaseConnection.java : 定义数据库连接类

package org.lxh.mvcdemo.dbc ;
import java.sql.* ;
public class DatabaseConnection {
    private static final String DBDRIVER = "org.gjt.mm.mysql.Driver" ;
    private static final String DBURL = "jdbc:mysql://localhost:3306/shi" ;
    private static final String DBUSER = "root" ;
    private static final String DBPASSWORD = "123456" ;
    private Connection conn = null ;
    public DatabaseConnection() throws Exception{
        try{
            Class.forName(DBDRIVER) ; //加载驱动
            this.conn = DriverManager.getConnection(DBURL,DBUSER,DBPASSWORD) ; //连接数据库
        }catch(Exception e){
            throw e ;
        }
    }
    public Connection getConnection(){ //取得数据库连接
        return this.conn ;
    }
    public void close() throws Exception{
        if(this.conn != null){
            try{
                this.conn.close() ;
            }catch(Exception e){
                throw e ;
            }
        }
    }
}

定义DAO接口时,只需定义一个登录验证方法;

IUserDAO.java : 定义DAO接口

package org.shi.mvcdemo.dao ;
import org.shi.mvcdemo.vo.User ;
public interface IUserDAO {
    // 现在完成的是登陆验证,那么登陆操作只有两种返回结果
    public boolean findLogin(User user) throws Exception ;
} 

现在的方法只需来执行查询操作,并采用findXxx命名形式;

UserDAOImpl.java:定义DAO实现类

package org.shi.mvcdemo.dao.impl ;
import org.shi.mvcdemo.vo.User ;
import org.shi.mvcdemo.dbc.* ;
import org.shi.mvcdemo.dao.* ;
import java.sql.* ;
public class UserDAOImpl implements IUserDAO {
    private Connection conn = null ; //定义数据库操作对象
    private PreparedStatement pstmt = null ;  //定义数据库连接对象
    public UserDAOImpl(Connection conn){
        this.conn = conn ;
    }
    public boolean findLogin(User user) throws Exception{
        boolean flag = false ;
        String sql = "SELECT name FROM user WHERE userid=? AND password=?" ;
        this.pstmt = this.conn.prepareStatement(sql) ;  //实例化操作
        this.pstmt.setString(1,user.getUserid()) ;
        this.pstmt.setString(2,user.getPassword()) ;
        ResultSet rs = this.pstmt.executeQuery() ;
        if(rs.next()){
            user.setName(rs.getString(1)) ;    // 取出一个用户的真实姓名
            flag = true ;
        }
        this.pstmt.close() ;
        return flag ;
    }
} 

在真实实现类中将通过输入的用户ID和密码进行验证,如果验证成功,则通过VO将用户的真实姓名取出并返回;

UserDAOProxy.java 定义DAO代理操作类

package org.shi.mvcdemo.dao.proxy ;
import org.shi.mvcdemo.vo.User ;
import org.shi.mvcdemo.dbc.* ;
import org.shi.mvcdemo.dao.* ;
import org.shi.mvcdemo.dao.impl.* ;
import java.sql.* ;
public class UserDAOProxy implements IUserDAO {
    private DatabaseConnection dbc = null ;
    private IUserDAO dao = null ;//定义DAO接口
    public UserDAOProxy(){
        try{
            this.dbc = new DatabaseConnection() ;
        }catch(Exception e){
            e.printStackTrace() ;
        }
        this.dao = new UserDAOImpl(dbc.getConnection()) ;
    }
    public boolean findLogin(User user) throws Exception{
        boolean flag = false ;
        try{
            flag = this.dao.findLogin(user) ;    // 调用真实主题,完成操作
        }catch(Exception e){
            throw e ;
        }finally{
            this.dbc.close() ;
        }
        return flag ;
    }
} 

DAOFactroy.java : 定义工厂类,取得DAO实例

package org.shi.mvcdemo.factory ;
import org.shi.mvcdemo.dao.* ;
import org.shi.mvcdemo.dao.proxy.* ;
public class DAOFactory {
    public static IUserDAO getIUserDAOInstance(){
        return new UserDAOProxy() ; //返回代理实例
    }
}

DAO操作完成只是数据库的操作,;

在Servlet中接收到客户端发来的输入数据,同时调用DAO,并根据DAO的结果返回相应的信息;

LoginServlet.java: 定义Servlet

package org.shi.mvcdemo.servlet ;
import java.io.* ;
import java.util.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;
import org.shi.mvcdemo.factory.* ;
import org.shi.mvcdemo.vo.* ;
public class LoginServlet extends HttpServlet {
    public void doGet(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{
        String path = "login.jsp" ;
        String userid = req.getParameter("userid") ;
        String userpass = req.getParameter("userpass") ;
        List<String> info = new ArrayList<String>() ;    // 收集错误
        if(userid==null || "".equals(userid)){
            info.add("用户id不能为空!") ;
        }
        if(userpass==null || "".equals(userpass)){
            info.add("密码不能为空!") ;
        }
        if(info.size()==0){    // 里面没有记录任何的错误
            User user = new User() ;
            user.setUserid(userid) ;
            user.setPassword(userpass) ;
            try{
                if(DAOFactory.getIUserDAOInstance().findLogin(user)){
                    info.add("用户登陆成功,欢迎" + user.getName() + "光临!") ;
                } else {
                    info.add("用户登陆失败,错误的用户名和密码!") ;
                }
            }catch(Exception e){
                e.printStackTrace() ;
            }
        }
        req.setAttribute("info",info) ; //保存错误信息
        req.getRequestDispatcher(path).forward(req,resp) ; //跳转
    }
    public void doPost(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{
        this.doGet(req,resp) ; //调用doGet()操作
    }
}

在 Servlet中,首先对接收的userid和userpass两个参数进行了验证;

如果没有输入参数或者输入的参数为空,则会在info对象中增加相应的错误信息;

当验证通过后,程序将调用DAO 进行数据层的验证,并根据DAO的返回信息结果来决定返回给客户端的信息;

login.jsp : 登录页

<%@ page contentType="text/html" pageEncoding="GBK"%>
<%@ page import="java.util.*"%>
<html>
<head><title>登录页</title></head>
<body>
<script language="javascript">
    function validate(f){
        if(!(/^w{5,15}$/.test(f.userid.value))){
            alert("用户ID必须是5~15位!") ;
            f.userid.focus() ;
            return false ;
        }
        if(!(/^w{5,15}$/.test(f.userpass.value))){
            alert("密码必须是5~15位!") ;
            f.userpass.focus() ;
            return false ;
        }
    }
</script>
<%
    request.setCharacterEncoding("GBK") ;
%>
<%
    List<String> info = (List<String>) request.getAttribute("info") ; //取得属性
    if(info != null){    // 有信息返回
        Iterator<String> iter = info.iterator() ;
        while(iter.hasNext()){
%>
            <h4><%=iter.next()%></h4>
<%
        }
    }
%>
<form action="LoginServlet" method="post" onSubmit="return validate(this)">
    用户ID:<input type="text" name="userid"><br>
    密&nbsp;&nbsp;码:<input type="password" name="userpass"><br>
    <input type="submit" value="登陆">
    <input type="reset" value="重置">
</form>
</body>
</html>

配置web.xml文件

<servlet>
    <servlet-name>login</servlet-name>
    <servlet-class>
            org.shi.mvcdemo.servlet.LoginServlet
    </servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>login</servlet-name>
    <url-pattern>/mvclogin/LoginServlet</url-pattern>
</servlet-mapping>

使用MVC开发后,JSP中的java代码逐步减少了;

JSP的功能就是将Servlet传递回的内容进行输出,使程序的代码更加清晰,分工更加明确;

在实际开发中,要记住JSP最好包含3中类型的代码:

接收属性:接收从Servlet传递过来的属性;

判断语句:判读传递到JSP页面的属性是否存在;

输出内容:使用迭代或者VO进行输出;

JSP页面中唯一允许导入的包只能是java.util包;

LoginProcess.java代码:

10 过滤器

 从使用上看,Servlet可以分为简单Servlet,过滤Servlet,监听Servlet;

JSP只是可以完成简单的Servlet;

package servlet;
import javabean.User;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginProcess extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
       doPost(request,response);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
       // 获取信息
       String username = request.getParameter("username");
       String userpass = request.getParameter("userpass");
       // 调用JavaBean
       User user = new User();
       user = user.findUserByName(username);
       String forward;
       if(user==null){
           forward="failure.jsp";
       }else if(user.getUserpass().equals(userpass)){
           if(user.getUsertype().equals("1")){
              forward="manager.jsp";
           }
           else{
              forward="commonuser.jsp";
           }
       }else{
           forward="failure.jsp";
       }
       RequestDispatcher rd = request.getRequestDispatcher(forward);
       rd.forward(request,response);
    }
}

10.1 过滤器

Filter是Servlet2.3之后增加的信息功能,但需要限制用户访问某些资源或者在处理请求时提前处理某些资源时,即可使用过滤器完成;

过滤器是以一种组件的形式绑定到Web应用程序中,与其他Web应用程序组件不同的的是,过滤器采用“链”的方式进行处理;

图片 6

未使用过滤器之前,客户端都是直接请求Web资源;

一旦加入了过滤器,所有的请求都是先交给过滤器处理,然后访问相应的Web资源。可以达到对某些资源的限制;

实现过滤器

在Servlet中,如果要定义一个过滤器,则直接然一个类实现javax.servlet.Filter接口即可;

此接口定义3个操作方法;

public  void init(FilterConfig filterConfig)throws ServletException  过滤器初始化化(容器启动时自动初始化)时调用,可以通过FilterConfig取得初始化参数;

public void doFilter(ServletRequest reg,ServletResponse res)throws IOException,ServletException 完成具体的过滤操作,通过FilterChain让请求继续向下传递;

public void destroy()  过滤器销毁使用

 在FilterChain接口中依然定义了一个同样的doFilter()方法;

因为在一个过滤器之后可能存在另一个过滤器,也可能是请求的最终目标(Servlet),这样就通过FilterChain形成了一个过滤连的操作;

SimpleFilter.java :定义了一个简单的过滤器

package org.shi.filterdemo ;
import java.io.* ;
import javax.servlet.* ;
public class SimpleFilter implements Filter {
    public void init(FilterConfig config)
          throws ServletException{
        // 接收初始化的参数
        String initParam = config.getInitParameter("ref") ;    
        System.out.println("** 过滤器初始化,初始化参数=" + initParam) ;
    }
    public void doFilter(ServletRequest request,
              ServletResponse response,
              FilterChain chain)
              throws IOException,
                     ServletException{
        System.out.println("** 执行doFilter()方法之前") ;
        chain.doFilter(request,response) ;//将请求继续传递
        System.out.println("** 执行doFilter()方法之后") ;
    }
    public void destroy(){
        System.out.println("** 过滤器销毁。") ; //销毁过滤
    }
}

以上代码中,SimpleFilter类实现了Filter接口,所以要覆写Filter接口中的3个方法;

在doFilter()方法中增加了两条输出语句,分别是在FilterChain调用doFilter()方法之前和方法之后;

因为过滤器采用了“链”的处理方式,所以两条语句都会执行;

配置web.xml文件

<filter>
    <filter-name>simple</filter-name>
    <filter-class>org.shi.filterdemo.SimpleFilter</filter-class>
    <init-param>
        <param-name>ref</param-name>
        <param-value>HELLOSHI</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>simple</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<url-pattern>表示一个过滤器的过滤位置,如果是“/*” 表示对于根目录下的一切操作都需要过滤;

过滤器中的初始化方法是在容器启动是自动调用加载;

并通过FilterConfig的getInitParameter()方法取出了配置的初始化参数,只初始化一次;

但是对于过滤器中的doFilter()方法实力上会调用两次,一次是在FilterChain操作之前,一次是是在FilterChain之后;

过滤器的应用

2、为每个界面添加安全控制

过滤器本身是属于一个组件的形式加入到应用程序中;例如,可以使用过滤器完成编码的过滤操作或者用户的登录验证;

上面的实例中登录成功后会跳转到管理员界面或者普通用户界面,但是如果用户直接输入管理员界面,就会跳过登录界面。例如用户可以直接输入:

实例一:编码过滤

编码过滤是必不可少的操作;按之前的做法,在每一个JSP或者Servlet中都重复编写“request.setCharacterEncoding("GBK")”的方式是不可取的;

会造成大量代码的重复,此时即可通过过滤器完成这种编码过滤;

EncodingFilter.java : 编码过滤器

package org.shi.filterdemo ;
import java.io.* ;
import javax.servlet.* ;
public class EncodingFilter implements Filter {
    private String charSet ;
    public void init(FilterConfig config)
          throws ServletException{
        // 接收初始化的参数
        this.charSet = config.getInitParameter("charset") ;    //取得初始化参数
    }
    public void doFilter(ServletRequest request,
              ServletResponse response,
              FilterChain chain)
              throws IOException,
                     ServletException{
        request.setCharacterEncoding(this.charSet) ;//设置统一编码
        chain.doFilter(request,response) ;
    }
    public void destroy(){
    }
}

以上代码中,初始化操作时,通过FilterConfig中getInitParameter()取得一个配置的初始化参数;

此参数的内容是一个指定的过滤编码,即可为所有的页面设置统一的请求编码;

配置web.xml文件

<filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.shi.filterdemo.EncodingFilter</filter-class>
     <init-param>
        <param-name>charset</param-name>
        <param-value>GBK</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

为了解决这个问题,在每个有安全限制的界面都应该增加安全控制。需要完成两项工作:

实例二:登录验证

登录验证最早的做法是通过验证session的方式完成,如果每一个页面都这样处理会造成大量代码的重复;

通过过滤器的方式即可避免这种重复的操作;

session本身属于HTTP协议的范畴,但是doFilter()方法中定义的是ServetRequest类型的对象;

如果取得session,则必须通过向下转型,将ServletRequest变为HttpServletRequest接口的对象,才能通过getSession()方法取得session对象;

LoginFilter.java : 登录验证过滤,假设的登录后保存的session属性名为userid

package org.shi.filterdemo ;
import java.io.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;
public class LoginFilter implements Filter {
    public void init(FilterConfig config)
          throws ServletException{
    }
    public void doFilter(ServletRequest request,
              ServletResponse response,
              FilterChain chain)
              throws IOException,
                     ServletException{
        // session属于http协议的范畴
        HttpServletRequest req = (HttpServletRequest) request ;
        HttpSession ses = req.getSession() ; //取得session
        if(ses.getAttribute("userid") != null) {
            // 已经登陆过了,则可以访问
            chain.doFilter(request,response) ;
        } else {
            request.getRequestDispatcher("login.jsp").forward(request,response) ; //跳转到登录页
        }
    }
    public void destroy(){
    }
}

配置web.xml文件

<filter>
    <filter-name>login</filter-name>
    <filter-class>org.shi.filterdemo.LoginFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>login</filter-name>
    <url-pattern>/filterdemo/*</url-pattern>
</filter-mapping>

login.jsp : 登录页

<%@ page contentType="text/html" pageEncoding="GBK"%>
<html>
<head><title>登录页</title></head>
<body>
<form action="login.jsp" method="post">
    用户名:<input type="text" name="uname"><br>
    密&nbsp;&nbsp;码:<input type="password" name="upass"><br>
    <input type="submit" value="登陆">
    <input type="reset" value="重置">
</form>
<%    // 直接通过一个固定的用户名和密码
    String name = request.getParameter("uname") ;
    String password = request.getParameter("upass") ;
    if(!(name==null || "".equals(name) || password==null || "".equals(password))){
        if("shi".equals(name) && "123456".equals(password)){
            // 如果登陆成功,则设置session属性范围。
            session.setAttribute("userid",name) ;
            response.setHeader("refresh","2;URL=welcome.jsp") ;
%>
            <h3>用户登陆成功,两秒后跳转到欢迎页!</h3>
            <h3>如果没有跳转,请按<a href="welcome.jsp">这里</a>!</h3>
<%
        } else {
%>
            <h3>错误的用户名或密码!</h3>
<%
        }
    }
%>
</body>
</html>

以上代码首先通过HttpServlet取得当前的session,然后判断在session范围内是否存在userid属性;

如果存在,则表示用户已经登录过;如果不存在,则跳转到login.jsp上进行登录;

当需要自动执行某些操作时可以通过过滤器完成;

① 在登录之后把用户的信息写入到session中;
② 在每个页面中,从session中获取信息进行验证;

11 监听器

监听器的主要功能是监听web的各种操作;当相关的事件触发后产生事件,并对此事件进行处理;

在登录之后把用户信息写入到session中,下面是修改后的LoginProcess.java代码:

在web中,对application、session和request 3中操作;

LoginProcess.java代码:

11.1 对application 监听

对application监听,实际上就是对ServletContext(Servlet 上下文)监听,主要使用ServletContextListener和ServletContextAttributeListener两个接口;

a 上下文件状态监听:ServletContextListener接口

对Servelt上下文监听可以使用javax.servlet.ServletContextListener接口;

该接口定义的方法如下:

public void  contextInitialized(ServletContextEvent sce)  容器启动时触发;

public void  contextDestroyed(ServletContextEvent sce)  容器销毁时时触发;

在上下文监听状态中,一旦触发了ServletContextListener接口中定义的事件后,可以通过ServletContextEvent进行事件的处理;

该事件定义方法如下:

public  ServletContext getServletContext()  取得ServletContext对象;

在ServletContextEvent类中只定义了一个getServletContext()方法;用户可以通过该方法取得一个ServletContext对象的实例;

ServletContextListenerDemo.java : 对Servlet上下文状态监听;

package org.shi.listenerdemo ;
import javax.servlet.* ;
public class ServletRequestListenerDemo implements ServletRequestListener {
    public void requestInitialized(ServletRequestEvent sre){
        System.out.println("** request初始化。http://" + 
            sre.getServletRequest().getRemoteAddr() + 
            sre.getServletContext().getContextPath()) ;
    }
    public void requestDestroyed(ServletRequestEvent sre){
        System.out.println("** request销毁。http://" + 
            sre.getServletRequest().getRemoteAddr() + 
            sre.getServletContext().getContextPath()) ;
    }
}

配置web.xml文件

<listener>
    <listener-class>
        org.shi.listenerdemo.ServletRequestListenerDemo
    </listener-class>
</listener>

以上代码的容器初始化和销毁操作中,分别通过ServletContextEvent事件对象取得ServletContext实例;

然后调用getContextPath()方法取得虚拟路径的名称,当容器启动和关闭时,后台将输出内容;

所有的Servlet程序都必须在web.xml文件中进行配置,如果要同时配置简单的Servlet、过滤器、监听器,建议如下步骤配置:

1 先配置过滤器;2 在配置监听器;3 最后配置Servlet;

package servlet;
import javabean.User;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginProcess extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
       doPost(request,response);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
       // 获取信息
       String username = request.getParameter("username");
       String userpass = request.getParameter("userpass");
       // 调用JavaBean
       User user = new User();
       user = user.findUserByName(username);
       // 得到session对象
       HttpSession session = request.getSession(true);
       String forward;
       if(user==null){
           forward="failure.jsp";
       }else if(user.getUserpass().equals(userpass)){
           if(user.getUsertype().equals("1")){
              // 在session对象中存储信息
              session.setAttribute("usertype","1");
              forward="manager.jsp";
           }
           else{
              session.setAttribute("usertype","0");
              forward="commonuser.jsp";
           }
       }else{
           forward="failure.jsp";
       }
       RequestDispatcher rd = request.getRequestDispatcher(forward);
       rd.forward(request,response);
    }
}

b 上下文属性监听 : ServletContextAttributeListener接口

对于Servlet上下文属性的操作监听,可以使用javax.servlet.ServletContextAttributeListener接口;

该接口定义方法如下:

public void attributeAdded(ServletContextAttribute Event scab)  增加属性时触发;

public void attributeRemoved(ServletContextAttribute Event scab)  删除属性时触发;

public void attributeReplaced(ServletContextAttribute Event scab)  替换属性(重复设置)时触发;

在上下文属性监听中,一旦触发了ServletContextAttributeListener接口中定义事件后,可以通过ServletContextAttributeEvent进行事件的处理;

ServletContextAttributeEvent事件定义的方法:

public String getName()  取得设置的属性名称;

publc Object getValue() 取得设置的属性内容;

ServletContextAttributeListenerDemo.java : 对上下文属性监听

package org.shi.listenerdemo ;
import javax.servlet.* ;
public class ServletRequestAttributeListenerDemo implements ServletRequestAttributeListener {
    public void attributeAdded(ServletRequestAttributeEvent srae){
        System.out.println("** 增加request属性 --> 属性名称:" + srae.getName() + ",属性内容:" + srae.getValue()) ;
    }
    public void attributeRemoved(ServletRequestAttributeEvent srae){
        System.out.println("** 删除request属性 --> 属性名称:" + srae.getName() + ",属性内容:" + srae.getValue()) ;
    }
    public void attributeReplaced(ServletRequestAttributeEvent srae){
        System.out.println("** 替换request属性 --> 属性名称:" + srae.getName() + ",属性内容:" + srae.getValue()) ;
    }
}

配置web.xml文件

 <listener>
        <listener-class>
            org.shi.listenerdemo.ServletRequestAttributeListenerDemo
        </listener-class>
   </listener>

以上代码中,在属性的增加、替换、删除上都编写了具体的监听操作,并且通过ServletContextAttributeEvent事件取得所有操作属性的名称和对应内容;

以commonuser.jsp为例介绍如何在每个文件中进行安全控制,下面是修改后的代码:

11.2 对Session监听

 在监听器中,针对于session的监听操作主要使用HttpSessionListener、HttpSessionAttrinuteListener、HttpSessionBandingListener接口;

a  session状态监听:HttpSessionListener接口

当需要创建和销毁session的操作监听时,可以实现javax.servlet.http.HttpSessionListener接口;

此接口定义方法如下 :

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

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

当session创建和销毁后,将产生HttpSessionEvent事件,此事件定义方法如下:

public HttpSession getSession()  取得当前的session;

HttpSessionListenerDemo.java: 对session监听

package org.shi.listenerdemo ;
import javax.servlet.http.* ;
public class HttpSessionListenerDemo implements HttpSessionListener {
    public void sessionCreated(HttpSessionEvent se){
        System.out.println("** SESSION创建,SESSION ID = " +se.getSession().getId() ) ;
    }
    public void sessionDestroyed(HttpSessionEvent se){
        System.out.println("** SESSION销毁,SESSION ID = " +se.getSession().getId() ) ;
    }
}

配置web.xml文件

<listener>
        <listener-class>
            org.shi.listenerdemo.HttpSessionListenerDemo
        </listener-class>
   </listener>

以上代码进行session的创建和销毁时,会将当前的sessionId输出;

当一个新用户打开一个动态页面时,服务器会为新用户分配session,并且触发HttpSessionListener接口中的sessionCreated()事件;

但是在用户销毁时却有两种不同的方式来触发sessionDestroyed()事件;

方式一:调用HttpSession接口的invalidate()方法,让一个session失效;

方式二:超过配置的session超时时间,session超过时间可以直接在web.xml中配置

<session-config>
        <session-timeout>5</session-timeout>
</session-config>

如果一个用户在5分钟后没有与服务器进行任何交互操作的话,那么服务器会认为此用户已离开,将自动将其注销;

如果没有配置超过时间,则默认的超过时间为30分钟;

2 session属性箭筒:HttpSessionAttributeListener接口

要对session的属性进行监听,则可以使用javax.servlet.http.HttpSessionAttributeListener接口完成;

HttpSessionAttributeListener接口定义的方法:

public void attributeAdded(HttpSessionBingEvent se)  增加属性时触发;

public void attributeRemoved(HttpSessionBindingEvent se)  删除属性时触发;

public void attributeReplaced(HttpSessionBindingEvent se)  替换属性时触发;

当属性进行操作是,将根据属性的操作触发HttpSessionAttributeListener接口中的方法;

每个操作方法都产生HttpSessionBandingEvent事件;

此事件定义的方法如下:

public HttpSession getSession()  取得session;

public String getName()  取得属性的名称;

public Object getValue()  取得属性的内容

HttpSessionAttributeListenerDemo.java : 对session的属性进行监听

package org.shi.listenerdemo ;
import javax.servlet.http.* ;
public class HttpSessionAttributeListenerDemo implements HttpSessionAttributeListener {
    public void attributeAdded(HttpSessionBindingEvent se){
        System.out.println(se.getSession().getId() + ",增加属性 --> 属性名称" + se.getName() + ",属性内容:" + se.getValue()) ;
    }
    public void attributeRemoved(HttpSessionBindingEvent se){
        System.out.println(se.getSession().getId() + ",删除属性 --> 属性名称" + se.getName() + ",属性内容:" + se.getValue()) ;
    }
    public void attributeReplaced(HttpSessionBindingEvent se){
        System.out.println(se.getSession().getId() + ",替换属性 --> 属性名称" + se.getName() + ",属性内容:" + se.getValue()) ;
    }
}

配置web.xml文件

<listener>
        <listener-class>
            org.shi.listenerdemo.HttpSessionAttributeListenerDemo
        </listener-class>
 </listener>

commonuser.jsp代码:

 3 session属性监听:HttpSessionBindingListener接口

web中提供了一个javax.servlet.http.HttpSessionBindingListener接口,通过此接口实现的监听程序可以不用配置直接使用;

HttpSessionBindingListener接口定义的方法:

public void valueBound(HttpSessionBindingEvent event)  绑定对象到session触发;

public void valueUnbound(HttpSessionBindingEvent event)  绑定对象到session触发;

以上两个方法都将产生HttpSessionBindingEvent事件;

通过此接口实现一个用户登录信息监听的操作;

LoginUser.java : 用户登录状态监听

package org.shi.listenerdemo ;
import javax.servlet.http.* ;
public class LoginUser implements HttpSessionBindingListener {
    private String name ;
    public LoginUser(String name){
        this.setName(name) ;
    }
    public void valueBound(HttpSessionBindingEvent event){
        System.out.println("** 在session中保存LoginUser对象(name = " + this.getName() + "),session id = " + event.getSession().getId()) ;
    }
    public void valueUnbound(HttpSessionBindingEvent event){
        System.out.println("** 从session中移出LoginUser对象(name = " + this.getName() + "),session id = " + event.getSession().getId()) ;
    }
    public String getName(){
        return this.name ;
    }
    public void setName(String name){
        this.name = name ;
    }
}

以上代码中保存了用户的登录名,由于此类实现了HttpSessionBindingListener接口;

一旦使用了session增加或删除本类对象时就会自动触发valueBound()和valueUnBound()操作;

session_bound.jsp : 向session中增加LoginUser对象

<%@ page contentType="text/html" pageEncoding="GBK"%>
<%@ page import="org.shi.listenerdemo.*"%>
<html>
<head><title>触发操作</title></head>
<body>
<%
    LoginUser user = new LoginUser("bushi") ;
    session.setAttribute("info",user) ;    // 直接保存LoginUser对象
%>
</body>
</html>

以上代码实例化了一个LoginUser对象,然后将此对象保存在session属性范围内;

这样监听器就会自动调用valueBound()操作进行处理;

session_unbound.jsp : 从session中删除LoginUser对象

<%@ page contentType="text/html" pageEncoding="GBK"%> 
<%@ page import="org.shi.listenerdemo.*"%> 
<html> 
    <head>
        <title>触发操作</title>
    </head>
     <body>
         <% session.removeAttribute("info") ; // 直接保存LoginUser对象 %>
     </body> 
</html>            
<%@ page contentType="text/html;charset=gb2312"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:if test="${usertype!=/"0/"}">
  <jsp:forward page="login.jsp"/>
</c:if>

11.3 对request监听

对request的监听,主要使用ServletRequestListener和ServletRequestAttributeListener接口;

a 请求监听状态:ServletRequestListener接口

当需要每次对用户的请求进行监听时,可以使用javax.servlet.ServletRequestListener接口;

此接口定义方法如下:

public void requestInitialized(ServletRequestEvent sre)  请求开始时调用;

public void requestDestroyed(ServletRequestEvent sre)  请求结束时调用;

ServletRequestListener接口一旦监听到事件后,将产生ServletRequestEvent事件的处理对象;

此事件定义方法如下:

public ServletRequest getServletRequest() 取得ServletRequest对象;

public ServletContext  getServletContext() 取得ServletContext对象;

ServletRequestListenerDemo.java : 对用户请求进行监听

package org.shi.listenerdemo ;
import javax.servlet.* ;
public class ServletRequestListenerDemo implements ServletRequestListener {
    public void requestInitialized(ServletRequestEvent sre){
        System.out.println("** request初始化。http://" + 
            sre.getServletRequest().getRemoteAddr() + 
            sre.getServletContext().getContextPath()) ;
    }
    public void requestDestroyed(ServletRequestEvent sre){
        System.out.println("** request销毁。http://" + 
            sre.getServletRequest().getRemoteAddr() + 
            sre.getServletContext().getContextPath()) ;
    }
}

配置xml文件:

<listener>
    <listener-class>
        org.shi.listenerdemo.ServletRequestListenerDemo
    </listener-class>
</listener>

b request属性监听:ServletRequestAttributeListener接口

对request范围属性的监听可以使用javax.servlet.ServletRequestAttributeListener接口;

此接口定义方法如下:

public void attributeAdded(ServletRequestAttributeEvent sare)  属性增加时调用;

public void attributeReplaced(ServletRequestAttributeEvent sare)  属性替换时调用;

public void attributeRemoved(ServletRequestAttributeEvent sare)  属性删除时调用;

加入监听器后,request属性的操作都会产生ServletRequestAttributeEvent事件;

此事件定义方法如下:

public String getName(); 取得设置的属性名称

public Object getValue() 取得设置的属性内容

ServletRequestAttributeListenerDemo.java : 监听request属性操作

package org.shi.listenerdemo ;
import javax.servlet.* ;
public class ServletRequestAttributeListenerDemo implements ServletRequestAttributeListener {
    public void attributeAdded(ServletRequestAttributeEvent srae){
        System.out.println("** 增加request属性 --> 属性名称:" + srae.getName() + ",属性内容:" + srae.getValue()) ;
    }
    public void attributeRemoved(ServletRequestAttributeEvent srae){
        System.out.println("** 删除request属性 --> 属性名称:" + srae.getName() + ",属性内容:" + srae.getValue()) ;
    }
    public void attributeReplaced(ServletRequestAttributeEvent srae){
        System.out.println("** 替换request属性 --> 属性名称:" + srae.getName() + ",属性内容:" + srae.getValue()) ;
    }
}

配置xml文件:

<listener>
        <listener-class>
            org.shi.listenerdemo.ServletRequestAttributeListenerDemo
        </listener-class>
 </listener>

request_attribute_add.jsp : 设置request属性

<%@ page contentType="text/html" pageEncoding="GBK"%>
<html>
<head><title>设置属性</title></head>
<body>
<%
    request.setAttribute("info","bugshi") ;
%>
</body>
</html>

所用的请求属性只能在一次服务器跳转中保存,如果要观察属性替换操作,则可以编写两次设置request属性的操作;

request_attribute_replace.jsp : 设置两次request属性

<%@ page contentType="text/html" pageEncoding="GBK"%>
<%@ page import="org.shi.listenerdemo.*"%>
<html>
<head><title>设置两次request属性</title></head>
<body>
<%
    request.setAttribute("info","www.baidu.cn") ;
    request.setAttribute("info","www.baidu.cn") ;
%>
</body>
</html>

request_attribute_remove.jsp : 删除属性

<%@ page contentType="text/html" pageEncoding="GBK"%>
<%@ page import="org.shi.listenerdemo.*"%>
<html>
<head><title>删除属性</title></head>
<body>
<%
    request.setAttribute("info","bugshi") ;
    request.removeAttribute("info") ;
%>
</body>
</html>

普通用户界面

12 监听器实例——在线人员统计

当用户登录成功后,会向session中增加一个用户的信息标记;

此时,将触发监听的事件,会向用户列表中增加一个新的用户名(用户列表可以通过Set保存);

当用户注销或者会话超时后,会自动从列表中删除此用户;

由于所有的用户都需要访问此用户列表,那么此列表的内容就必须保存在application范围中;

为了操作方便,所有的用户只要输入的用户名不为空,则就向列表中增加;

要完成在线列表的监听器,需要使用如下3个接口:

ServletContextListener接口:在上下文初始化时设置一个空的接口到application中;

HttpSessionAttributeListener接口:用户增加session属性时,表示新用户登录,从session中取出此用户的登录名,保存在列表中;

HttpSessionListener接口:当用户注销或会话超时时,将用户从列表中删除;

OnlineUserList.java : 在线用户监听

package org.shi.listenerdemo ;
import java.util.* ;
import javax.servlet.* ;
import javax.servlet.http.* ;
public class OnlineUserList implements ServletContextListener,HttpSessionAttributeListener,HttpSessionListener {
    private ServletContext app = null ;
    public void contextInitialized(ServletContextEvent sce){
        this.app = sce.getServletContext() ;
        this.app.setAttribute("online",new TreeSet()) ;    // 准备集合
    }
    public void contextDestroyed(ServletContextEvent sce){
    }
    public void attributeAdded(HttpSessionBindingEvent se){
        Set all = (Set) this.app.getAttribute("online") ;
        all.add(se.getValue()) ;
        this.app.setAttribute("online",all) ;
    }
    public void attributeRemoved(HttpSessionBindingEvent se){
        Set all = (Set) this.app.getAttribute("online") ;
        all.remove(se.getSession().getAttribute("userid")) ;
        this.app.setAttribute("online",all) ;
    }
    public void attributeReplaced(HttpSessionBindingEvent se){}
    public void sessionCreated(HttpSessionEvent se){}
    public void sessionDestroyed(HttpSessionEvent se){
        Set all = (Set) this.app.getAttribute("online") ;
        all.remove(se.getSession().getAttribute("userid")) ;
        this.app.setAttribute("online",all) ;
    }

}

配置xml文件:

<listener>
        <listener-class>
            org.shi.listenerdemo.OnlineUserList
        </listener-class>
</listener>
<session-config>
       <session-timeout>1</session-timeout>
</session-config>

login.jsp : 登录页

<%@ page contentType="text/html" pageEncoding="GBK"%>
<html>
<head><title>登录页</title></head>
<body>
<form action="login.jsp" method="post">
    用户ID:<input type="text" name="userid">
    <input type="submit" value="登陆">
</form>
<%
    String userid = request.getParameter("userid") ;
    if(!(userid==null || "".equals(userid))){
        session.setAttribute("userid",userid) ;
        response.sendRedirect("list.jsp") ;
    }
%>
</body>
</html>

以上采用了自提交的方式,表单直接提交到login页面上,然后判断用户ID是否为空;

如果不为空,则在session中增加一个新的属性标记,之后跳转到list.jsp页面;

list.jsp : 显示在线用户

<%@ page contentType="text/html" pageEncoding="GBK"%>
<%@ page import="java.util.*"%>
<html>
<head><title>显示在线用户</title></head>
<body>
<%

    Set all = (Set) this.getServletContext().getAttribute("online") ; //从application中取出所有的用户列表
    Iterator iter = all.iterator() ;
    while(iter.hasNext()){
%>
        <h3><%=iter.next()%></h3>
<%
    }
%>
</body>
</html>

list.jsp页面从application中将保存的集合列表读取出来,然后采用迭代的方式将所有的在线用户的用户ID输出;

当用户离开后且达到session的会话超时后,监听器会从列表中自动删除此用户;

 

这样,如果不登录而直接访问commonuser.jsp就会跳转到登录界面。

3、采用专门的文件进行验证

因为很多页面都要编写验证的代码,所以可以把这些代码放在一个文件中进行共享,需要的使用调用共享文件。下面仍然以commonuser.jsp为例介绍如何实现验证代码的共享。

使用专门的文件存放共享代码:

check.jsp代码:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:if test="${usertype!=/"0/"}">
  <jsp:forward page="login.jsp"/>
</c:if>

在需要验证的文件中导入这个专门的文件。以commonuser.jsp为例:

commonuser.jsp代码:

<%@ page contentType="text/html;charset=gb2312"%>
<%@ include file="check.jsp" %>

普通用户界面

使用include指令包含目标文件,在把JSP转换成Java文件的时候,会把目标文件的代码拷贝到当前文件。
再运行测试,结果是相同的。

4、使用过滤器对权限进行验证

把具有相同权限要求的文件放在相同的文件夹下,对文件夹的访问进行统一的过滤。

编写用于过滤的Servlet,代码如下:

CommonCheck.java代码:

package servlet;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CommonCheck extends HttpServlet implements Filter {
public void doFilter(ServletRequest arg0, ServletResponse arg1,
        FilterChain arg2) throws IOException, ServletException {
    // 得到session
    HttpSession session = ((HttpServletRequest)arg0).getSession(true);
    // 得到用户类型
    String usertype = (String)session.getAttribute("usertype");
    // 进行判断
    if(usertype==null || usertype.equals("1")){
        ((HttpServletResponse)arg1).sendRedirect("./../login.jsp");
    }
    // 继续调用其他的过滤器
    try{
        arg2.doFilter(arg0, arg1);
    }catch(Exception e){}
}
public void init(FilterConfig arg0) throws ServletException {
    // TODO Auto-generated method stub
}
}

配置过滤器,过滤器的配置与Servlet的配置非常类似,在web.xml中添加如下代码:

<filter>
  <filter-name>CommonCheck</filter-name>
  <filter-class>servlet.CommonCheck</filter-class>
</filter>
 <filter-mapping>
  <filter-name>CommonCheck</filter-name>
  <url-pattern>/commonuser/*</url-pattern>
 </filter-mapping>

url-pattern中使用/commonuser/*,这样只要访问commonuser这个文件夹,就会访问这个过滤器,如果用户没有登录,将不能访问目标文件。

测试:为了测试需要创建一个文件夹commonuser,把commonuser.jsp拷贝到commonuser文件中。

测试过程如下:

先直接访问:

然后在登录界面输入正确的用户名和口令,然后再次在地址栏中输入上面的地址,这时候会看到commonuser.jsp文件的内容。表示验证通过。

5、对文件局部内容的安全进行控制

前面介绍的都是文件级别的安全控制,有时候需要对文件中部分内容进行安全控制,例如物品信息列表这样的界面,如果当前用户是管理员,则可以在其中完成管理功能,而对于普通用户来说,而不可以,这就需要进行局部的控制。局部控制主要是通过标准标签库中的<c:if>标签来完成。

6、安全验证码的基本实现方式

为了增强网站的安全性,很多网站采用了很多安全措施。例如SSL方式的访问、U盾和口令卡(工商银行)、信息加密等。安全验证码是现在比较流行的有效的一个安全措施,能够有效的解决用户通过遍历所有可能的组合来破解密码的问题。
基本工作原理如下:每次客户端访问服务器的时候,服务器会生成验证码,以图形的形式显示给用户,同时在服务器上保留备份,用户在提交信息的时候需要把验证码同时提交道服务器,服务器接收到验证码之后与服务器端的验证码进行比较,如果相同则进行处理。如果不同,则让用户重新输入。因为每次都变化,所有用户如果想破解密码,首先要应付变化的安全验证码,所以加大了破解的难度。

7、通过异常处理增强安全性

有时候用户的攻击是根据网站所使用的服务器来进行了,因为很多服务器都有自己的bug。如果不能对异常进行有效的处理,错误信息会显示在客户端,从错误信息中可以让客户发现服务器的版本信息,这样就为用户的恶意攻击提供了便利条件。

例如,用于输入:

而abc.jsp是一个不存在的文件,这时候如果不进行处理,会在客户端显示服务器的信息。

如果能够对各种异常进行处理,不让用户看到你所使用技术和服务器,这样客户进行攻击的难度就加大了。

曾经有一个学生作了这样一件事情:使用JSP技术完成了一个网站,然后通过配置之后,客户端访问的时候,使用的文件后缀名都是php,给人的感觉就像是采用php技术编写的网站。

下一篇:十三、JSP动作