美文网首页
JSP与Servlet

JSP与Servlet

作者: AxisX | 来源:发表于2020-02-07 22:04 被阅读0次

    1.JSP

    1.1 JSP简介

    JSP(全称Java Server Pages),是运行在服务端的动态网页开发技术,以java语言为脚本语言,Jsp网页为整个服务器端的java库单元提供了一个接口来服务于HTTP的应用程序。它使用Jsp标签在HTML网页中插入java代码,标签通常以<%开头以%>结束。

    1.2 开发环境

    下载java JDK并对环境变量进行配置,并下载tomcat服务器,配置相应的环境变量%CATALINA_HOME%。在cmd中用如下语句,开启和停止tomcat服务。

    %CATALINA_HOME%\bin\startup.bat //开启
    %CATALINA_HOME%\bin\shutdown  //停止
    

    通过访问 http://localhost:8080/ 便可以使用 Tomcat 自带的一些 web 应用了

    image.png
    Tomcat的目录:bin存放可执行文件(startup.bat shutdown.bat),conf存放配置文件(如server.xml),lib存放tomcat依赖的jar文件,log存放日志文件,temp存放临时文件,webaspps存放可执行的项目(即我们的项目存放地),work存放由jsp翻译成的java以及编译成的class文件。

    在Tomcat文件夹下的\webapps\ROOT文件夹下建一个jsp文件,内容如下,打开浏览器访问地址 http://localhost:8080/test.jsp即可看到输出内容tomcat test。

    <%@ page contentType="text/html;charset=UTF-8" %> //为了使页面正常显示中文
    <%
    out.print("tomcat test");
    %> 
    

    如果使用IDEA进行项目开发,可参照https://www.jianshu.com/p/315697f8511c进行配置,但是要注意jdk和tomcat版本的匹配。

    IDEA运行jsp项目
    按照图中所有的过程都显示了finish,打开对应的url,即可看到"Tomcat test"字样。如果过程中遇见问题,除了Jdk和tomcat版本匹配问题,如果卡在编译过程中还需要考虑是否需要更改java.security。

    1.3 jsp生命周期

    JSP生命周期就是从创建到销毁的整个过程,类似于servlet生命周期,区别在于JSP生命周期还包括将JSP文件编译成servlet。

    jsp生命周期

    (1)编译阶段
    浏览器请求Jsp页面,Jsp引擎先插是否需要编译该文件,如果需要就将jsp翻译成java ,即将其转换为servlet文件,servlet容器编译servlet源文件,生成servlet类(class文件)。
    (2)初始化阶段
    加载与JSP对应的servlet类,创建其实例,并调用它的初始化方法——jspInit()方法
    (3)执行阶段
    当JSP网页完成初始化后,JSP引擎将会调用_jspService()方法。调用与JSP对应的servlet实例的服务方法。该方法对每个request请求产生与之对应的response,如GET、POST、DELETE等。

    void _jspService(HttpServletRequest request, HttpServletResponse response){
       // 服务端处理代码
    }
    

    (4)销毁阶段
    调用与JSP对应的servlet实例的销毁方法——jspDestroy()。初始化方法和销毁方法都可以进行复写。

    1.4 jsp语法

    脚本程序的语法格式如下,每种语法都有与其对应的xml格式。

    <%-- 代码 --%>
    <% 代码片段 %>   => <% out.println("你的 IP 地址 " + request.getRemoteAddr()); %>
    //xml写法
    <jsp:scriptlet>
       代码片段
    </jsp:scriptlet>
    
    <%-- 声明 --%>
    <%! declaration;%>  =>  <%! int a, b, c; %> 
    //xml写法
    <jsp:declaration>
       代码片段
    </jsp:declaration>
    
    <%-- 表达式 --%>
    <%= 表达式 %> =>  <%= (new java.util.Date()).toLocaleString()%>
    //xml写法
    <jsp:expression>
       表达式
    </jsp:expression>
    
    <%-- 指令--%>
    <%@ directive attribute="value" %>
    //xml写法
    <jsp:directive.page attribute="value" />
    
    <%-- 条件语句--%>
    <% if (day == 1 | day == 7) { %>
          <p>今天是周末</p>
    <% } else { %>
          <p>今天不是周末</p>
    <% } %>
    
    <%-- 循环语句--%>
    <%for ( fontSize = 1; fontSize <= 3; fontSize++){ %>
       <font color="green" size="<%= fontSize %>">
       test
       </font><br />
    <%}%>
    
    

    结合一个简单的注册表单,来说明一下上述jsp语法的具体应用。

    //test.jsp
    <form action="deal.jsp">
          用户名:<input type="text" name="username"/><br/>
          密码:<input type="password" name="password"/><br/>
          年龄:<input type="text" name="age"/><br/>
          爱好:
          <input type="checkbox" name="hobby" value="足球"/>足球
          <input type="checkbox" name="hobby" value="篮球"/>篮球
          <input type="checkbox" name="hobby" value="乒乓球"/>乒乓球
          <input type="submit" value="注册"/>
        </form>
    
    //deal.jsp
    <%
            request.setCharacterEncoding("utf-8");//针对post方式设置编码方式,get方式在xml中设置URIEncoding
            String name=request.getParameter("username");//获取字段名为username的值
            String pwd=request.getParameter("password");
            Integer age=Integer.parseInt(request.getParameter("age"));
            String[] hobbies=request.getParameterValues("hobby");//获取字段名为hobby的多个值(多checkbox)
    
        %>
        注册成功,信息如下:<br/>
        姓名:<%=name%><br/>//表达式形式输出获取的值
        密码:<%=pwd%><br/>
        年龄:<%=age%><br/>
        爱好:
        <%
            for(String hobby:hobbies){//代码段循环输出获取的hobby
                out.print(hobby+"&nbsp;");
            }
        %>
    

    注册页点击注册前后的效果分别如图左和图右所示:


    效果图

    这个简单的例子并没有对传入的参数进行类型和值得判断,只作为简单demo便于理解。

    1.5 隐式对象(九种)

    JSP隐式对象也叫内置对象,是不需要new也能使用的自带对象。


    JSP所支持的九大隐式对象

    request用于请求对象、response用于响应对象,session是会话对象,application是全局对象,pageContext是JSP页面容器,config是配置对象(存放服务器配置信息)、out为输出对象、page输出当前JSP页面对象(相当于java中的this)、exception是异常对象。这里通过demo重点说一下前四个。

    (1)request:存储客户端向服务器端发送的请求信息,常用方法如下,demo可见上节jsp语法部分

    String getParameter(String name):根据请求的字段名key,返回字段值value
    String[] getParameterValues(String name):根据请求的字段名key,返回多个字段值value(checkbox)
    void setCharacterEncoding("UTF-8"):设置请求编码
    getRequestDispatcher("b.jsp").forward(request.response):请求转发的方式跳转页面A->B
    ServletContext getServerContext():获取项目的Servlet Context对象。
    

    (2)response:响应对象,常用方法包含

    void addCookie(Cookie cookie):服务器端向客户端增加cooide对象
    void sendRedirect(String location) throws IOException:页面跳转的一种方法(重定向)
    重定向:地址栏改变,第一次请求数据不保留(将跳转地址响应给客户端,客户端在向跳转地址发送请求(第二次))
    void getRequestDispatcher(String location):页面跳转的另一种方法(请求转发)
    请求转发:地址栏不变,保留第一次请求时的数据(直接将请求转发给跳转地址)
    void setContentType(String type):设置服务器端响应的代码
    

    登录示例:

    //index.jsp
     <form action="deal.jsp" method="post">
          用户名:<input type="text" name="username"/><br/>
          密码:<input type="password" name="password"/><br/>
          <input type="submit" value="登录"/>
    </form>
    
    //deal.jsp
    <%
            request.setCharacterEncoding("utf-8");
            String name=request.getParameter("username");
            String pwd=request.getParameter("password");
            if(name.equals("admin")&&pwd.equals("123")){
    //          response.sendRedirect("success.jsp");//页面跳转:重定向导致数据丢失
                request.getRequestDispatcher("success.jsp").forward(request,response);//页面跳转:请求转发,可以获取到数据并且地址栏没有改变
            }
            else {
                out.print("用户名或密码错误");
            }
    %>
    
    //success.jsp
        登录成功!<br/>
        欢迎您:
        <%
            String name=request.getParameter("username");
            out.print(name);
        %>
    

    (3)Session
    Cookie是由服务器端产生,发送给客户端保存,所以存在于客户端,不是内置对象。它相当于本地缓存的作用,可以提高访问服务器端的效率,但安全性较差。它由name=value组成,对应javax.servlet.http.Cookie类。内部包含,public Cookie(String name, String value)、String getName()获取name、 String getValue()获取value、void setMaxAge(int expiry)设置最长有效期,等函数。
    服务器用response.addCookie(Cookie cookie)准备给客户端的cookie,页面跳转时同时发送cookie,客户端通过request.getCookie()获取cookie。服务器端增加cookie,利用response对象,客户端获取对象是利用request对象。不能直接获取某一个单独对象,只能一次将所有cookie拿到。

    客户端登录,服务器端生成cookie发送给客户端,跳转页面显示相应内容的demo如下:

    //index.jsp
      <%
          Cookie cookie1=new Cookie("name","admin");
          Cookie cookie2=new Cookie("password","123");
          response.addCookie(cookie1);
          response.addCookie(cookie2);
          response.sendRedirect("success.jsp");
      %>
    
    //success.jsp
        <%
            Cookie[] cookies=request.getCookies();
            for(Cookie cookie:cookies){
                out.print(cookie.getName()+"--"+cookie.getValue()+"<br/>");
            }
        %>
    

    session则被称为会话,从开启浏览器到关闭页面称为一次会话,所以存在同一会话共享机制(会话结束前,无论在该站点下的任何页面都可以取得session)。当客户端发送请求时,服务器端会产生session对象(用于保存该客户端的信息)和对应唯一的sessionID(用于区分其他的session),然后服务器端会产生一个cookie,并且该cookie的name=JSESSIONID,value=服务器sessionID的值,并在发送响应时传给客户端。 所以客户端的cookie中包含JSESSIONID,和服务器的session一一对应。客户端第二次或者第N次请求服务端时,服务端会先用客户端cookie中的JSESSIONID与服务器端sessionID进行匹配,如果匹配成功(cookie.jsessionID=session.sessionID)说明用户曾登陆过,可以直接默认登录。

    Session方法如下

    String getID():获取SessionID
    boolean isNew():判断是否是新用户(第一次访问)
    void invalidate():使session失效(退出登录、注销)
    void setAttribute()、Object getAttribute()
    void setMaxIncativeInterval(秒数):设置最大有效非活动时间
    int getMaxIncativeInterval()
    invalidate():使session失效
    removeAttribute():使某一个属性失效
    

    登录过后的用户信息被session记录,下次访问页面可以直接读取用户信息,不用登录的demo如下:

    //index.jsp
    <form action="deal.jsp" method="post">
         用户名:<input type="text" name="username"/><br/>
         密码:<input type="password" name="password"/><br/>
         <input type="submit" value="登录"/>
       </form>
    
    //deal.jsp
    <%
       request.setCharacterEncoding("utf-8");
       String name=request.getParameter("username");
       String pwd=request.getParameter("password");
       if(name.equals("admin")&&pwd.equals("123")){
           session.setAttribute("username",name);
           session.setAttribute("password",pwd);
           session.setMaxInactiveInterval(20);
           request.getRequestDispatcher("success.jsp").forward(request,response);
       }
       else{
           response.sendRedirect("index.jsp");
       }
    %>
    
    //success.jsp
    <%
               String name=(String)session.getAttribute("username");
               if (name!=null){
                   out.print(name);
               }
               else{
                   response.sendRedirect("index.jsp");
               }
    
           %>
    

    简而言之,客户端第一次请求服务器端时,如果服务器端发现此请求没有JSESSIONID就会为其创建一个拥有JSESSIONID的cookie。服务器端在第一次响应客户端时,会发送一个JSESSION的cookie。虽然cookie不是内置对象,但服务器端会自动new一个JESSIONID的cookie。如果我们想获得JSESSIONID,可以用如下代码:

    <%
                Cookie[] cookies=request.getCookies();
                for (Cookie cookie:cookies){
                    if (cookie.getName().equals("JESSIONID")){
                        System.out.println("JESSIONID"+cookie.getValue());
                    }
                }
    %>
    

    (4)application
    application是全局对象,主要方法如下:
    String getContextPath():虚拟路径
    String getRealPath(String name):虚拟路径所对应的绝对路径

    另外,pageContext(也称page对象,当前页面有效)、request(同义词请求有效)、session(同一次会话有效)、application(全局有效/整个项目有效)可以统称为四种范围对象(从小到大排列),这四个对象都有共同的方法:
    Object getAttribute(String name)
    void setAttribute(String name,Object obj),例如setAttribute("a","b")如果a对象之前不存在,则创建一个a对象,如果a已经存在,就将a的值改为b
    void removeAttribute(String name)

    1.6 JDBC

    JDBC(Java DateBase Connectivity),可以为多种关系型数据库(DBMS,如oracle、mysql、sqlServer等)提供统一的访问方式,用Java来操作数据库。JDBC操控JDBC DriverManager从而可以操作其他数据库驱动程序,进而对数据库进行操作。
    JDBC API:提供了各种操作访问接口,
    (1)DriverManager(管理jdbc驱动)
    (2)Connection(连接)(由DriverManager产生)
    (3)Statement和PrepareStatement(增删改.executeUpdate()、查.executeQuery())、CallableSatatement(调用数据库中的存储过程/存储函数)(由Connection产生)
    (4)ResultSet(返回的结果集)(由Statement产生),包含next()函数,光标下移判断是否有下一条数据,还有pervious()函数,光标上移。getXXX()函数,获取具体字段值。

    JDBC访问数据库:首先导入驱动(加载具体的驱动类)、与数据库建立连接、发送sql并执行、处理结果集(查询)。不同类型数据库对应的驱动如下:

    Oracle:ojdbc-x.jar,具体的驱动类为oracle.jdbc.OracleDriver
    mySQL:mysql-connector-java-x.jar,具体的驱动类为com.mysql.jdbc.Driver
    SqlServer:sqljdbc-x.jar,具体的驱动类com.microsoft.sqlserver.hdbc.SQLServerDriver
    

    JDBC的流程demo如下:

    import java.sql.*;
    
    public class JDBCDemo {
       static final String URL="jdbc:oracle:thin:@localhost:1521:ORC";
       static final String USERNAME="scott";
       static final String PWD="tiger";
    
       public static void update(){//增删改
           //a.导入驱动,加载具体的驱动类
           Connection connection=null;
           Statement stmt=null;
           ResultSet resultSet=null;
           try{
               Class.forName("oracle.jdbc.OracleDriver");
               //b.与数据库建立连接
               connection=DriverManager.getConnection(URL,USERNAME,PWD);
    
    
               //c.发送sql并执行
               // (1)Statement方式
               stmt=connection.createStatement();
               String sql="insert into Users values('zhangsan',18)";
               String query="select age,name from Users";
               resultSet=stmt.executeQuery(query);//resultSet为指向行的数据,.getXXX()获取对应类型数据
               int count=stmt.executeUpdate(sql);//执行增删改,返回值表示增删改了几条
    
               // (2)PrepareStatement方式(sql语句提前,可用?替代值),推荐使用此方法
               //在多运行条件下,sql编译一次,数据被执行N次。而Statement方式方式sql也需要被执行N次
               //此方式可以防止sql注入
               String sql2="insert into Users values(?,?)";
               PreparedStatement pstmt=connection.prepareStatement(sql2);//预编译
               pstmt.setString(1,"zhangsan");//set方法替换占位符
               pstmt.setInt(1,18);
               int count2=pstmt.executeUpdate();
    
               //(3)CallableSatatement方式(调用存储过程(无返回值,out代替)或存储函数(有返回值))
               //(?,?,?)前两个参数相加,第三个参数为输出参数,输出两个的和
               CallableStatement cstmt=connection.prepareCall("call addTwoNum(?,?,?)");
               cstmt.setInt(1,10);//set处理参数
               cstmt.setInt(2,10);
               cstmt.execute();
               //设置输出参数的类型
               cstmt.registerOutParameter(3,Types.INTEGER);
               int result=cstmt.getInt(3);
    
               //d.处理结果
               if(count>0){
                   System.out.println("操作成功");
               }
               while(resultSet.next()){//next表示下移,判断下移之后是否有元素,有则为true,否则为false
                   int age=resultSet.getInt("age");
                   String name=resultSet.getString("name");
                   //上面两行数据等价于如下两行,此对应编号从1开始,但推荐使用字段名而不是下标查询
                   //int age=resultSet.getInt(1);
                   //String name=resultSet.getString(2);
    
                   System.out.println(name+""+age);
               }
           }
           catch(ClassNotFoundException | SQLException e){
               e.printStackTrace();
           }
           finally{
               try{
                   if(resultSet!=null) resultSet.close();
                   if(stmt!=null)stmt.close();//对象.方法,如果对象为空会报空指针
                   if(connection!=null) connection.close();
                   //先开的后关,后开的先关
               }
               catch (SQLException e){
                   e.printStackTrace();
               }
           }
       }
       public static void main(String[] args){
           update();
       }
    }
    

    一般对于大型数据,我们可以采取JDBC以字符串形式存储路径,然后IO流读取。对于Oracle的CLOB(大文本数据,如小说,在Mysql中称为text)/BLOB(二进制数据)类型数据,在发送sql时,先通过FileInputStream/File读取,然后通过InputStreamReader对参数处理,但要注意转换流可能需要处理编码方式,最后通过SetCharacterStream进行传值(此步可以指定file.length,设置存储小说的长度)

    1.7 JSP访问数据库

    结合上节所讲的JDBC内容,本节加入jsp,作为完整的登录判断demo进行演示。本文以Mysql数据库为例,所需驱动可以从官网下载相应版本https://downloads.mysql.com/archives/c-j/,需要注意的是在IDEA中需要引入该驱动,下载该驱动后,加入到工程的Lib中,并且需要加入到tomcat的lib文件下,否则依旧会报错 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver!

    //登录页面index.jsp
       <form action="deal.jsp" method="post">
         用户名:<input type="text" name="username"/><br/>
         密码:<input type="password" name="password"/><br/>
         <input type="submit" value="登录"/>
       </form>
    

    登录页面设置用户名和密码的输入框,点击登录调用deal.jsp,该文件会接收username和password的传参,并且利用JDBC驱动调用数据库进行查询,如果有返回结果则登录成功,否则登录失败。

    //处理页面deal.jsp
    <%@ page import="java.sql.*" %>
    <%
           final String URL="jdbc:mysql://localhost:3306/javaweb";
           final String USERNAME="root";
           final String PWD="";
           Connection connection=null;
           Statement stmt=null;
           ResultSet resultSet=null;
           try{
               Class.forName("com.mysql.jdbc.Driver");
               //b.与数据库建立连接
               connection=DriverManager.getConnection(URL,USERNAME,PWD);
               stmt=connection.createStatement();
               String name=request.getParameter("username");
               String pwd=request.getParameter("password");
               String sql="select * from users where name='"+name+"' and password='"+pwd+"'";
               ResultSet rs=stmt.executeQuery(sql);
               if(rs.next()){
                   out.println("登录成功");
               }
               else{
                   out.print("登录失败");
               }
           }
           catch(ClassNotFoundException | SQLException e){
               e.printStackTrace();
           }
           finally{
               try{
                   if(stmt!=null)stmt.close();//对象.方法,如果对象为空会报空指针
                   if(connection!=null) connection.close();
                   //先开的后关,后开的先关
               }
               catch (SQLException e){
                   e.printStackTrace();
               }
           }
       %>
    

    如果将连接数据库部分的代码放入一个java文件中,将deal.jsp简化成如下形式:

       <%
           String name=request.getParameter("username");
           String pwd=request.getParameter("password");
           LoginDao dao=new LoginDao();
           int result=dao.login(name,pwd);
           if(result>0){
               out.print("登录成功");
           }
           else{
               out.print("登录失败");
           }
       %>
    

    而java部分代码类LoginDao部分代码如下(省略了数据库连接和查询部分,可见上述deal.jsp完整代码自行提炼):

    public class LoginDao {
       public int login(String name, String password) {
           String URL = "jdbc:oracle:thin:@localhost:1521:ORC";
           String USERNAME = "scott";
           String PWD = "tiger";
       }
    }
    

    这样将Jsp登录操作的代码转移到LoginDao.java,其中的LoginDao.java就称为javaBean

    1.8 JavaBean

    通过上述的例子我们可以看出,javaBean能够减轻jsp的复杂度,提高代码的复用率(任何的登录操作都可以通过调用该类执行)。所以,如果满足,该类是一个Public修饰的类,且无参数构造,所有的属性都是private,并且提供set/get,如果是boolean则get可以替换成is,那么该类就是JavaBean。
    在使用层面,javaBean也可以分为两类,
    一种是封装业务逻辑的javaBean(LoginDao.java),将JDBC部分的代码封装进Login.java
    一种是封装数据的javaBean(如Person.java、Student.java)。
    Login login=new Login(name,pwd)用Login对象封装两个数据(用户名和密码),对应着数据库的表。
    封装业务逻辑的javaBean主要用于操作封装数据的javaBean

    1.9 EL(表达式语言)

    EL,Expression Language,可以替代JSP页面中的java代码。jsp中使用java显示数据需要处理null或进行强制类型转换等,代码和html掺杂,而EL可以较好的解决这些问题。
    EL操作符如下:

    //点操作符(使用方便)
    ${域对象.域对象中的属性.属性.属性.级联属性}
    如
    ${requestScope.student.address.schoolAddress}
    //中括号操作符(功能强大,可以包含特殊字符(. 、 -),可以获取变量值,可以访问数组)
    ${requestScope.student["address"]["schoolAddress"]}
    
    //获取map属性实例:
    Map<String,Object>map=new HashMap<>();
    map.put("cn","中国");
    map.put("us","美国");
    request.setAttribute("map",mao);
    
    ${requestScope.map.cn}
    

    EL表达式的隐式对象
    (1)作用域访问对象(EL域对象)
    *Scope对象:pageScope,requestScope,sessionScope,applicationScope变量用来访问存储在各个作用域层次的变量。举例来说,如果需要显式访问在requestScope层的student变量,就可以参照上述demo。
    如果不指定域对象,则会默认根据从小到大的顺序(pageScope->requestScope->sessionScope->applicationScope)依次取值。假如有个session对象,EL写为 ${sessionScope.sessionKey},我们写成 ${sessionKey}也可以找到正确的值。
    (2)参数访问对象
    参数访问对象用于获取表单数据(如,request.getParameter(等于 ${param})、request.getParameterValues(等于 ${paramValue})、超链接数据等。

       <form action="TestServlet" method="post">
               用户名:<input type="text" name="username"/><br/>
               密码:<input type="password" name="password"/><br/>
         <input type="submit" value="登录"/>
       </form>
    

    对于这个表单,我们就可以通过 ${param.username}来获取值。

    (3)JSP隐式对象(pageContext)
    在JSP中可以通过pageContext获取其他的JSP隐式对象。如果要在EL中使用JSP隐式对象可以通过此方式间接获取。例如 ${pageContext.request},同样,此方式也可以级联获取 ${pageContext.request.serverPort}

    1.10 过滤器(拦截器)

    在请求和响应时都会处理,需要过滤器放行。要想将一个普通的class变成一个具有特定功能的类(过滤器等),要么继承父类,要么实现一个接口,要么增加一个注解。
    过滤器首先要实现一个Filter接口,其init()、destory()原理、执行机制类似Servlet,通过chain.doFilter()放行。要说明的一点是过滤器中的doFilter方法参数时ServletRequest,在Servlet中的方法参数是HttpServletRequest。设置拦截器还要配置相应的web.xml。

    //过滤器TestFilter.java
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import java.io.IOException;
    
    @WebFilter(filterName = "TestFilter")
    public class TestFilter implements Filter {
        public void destroy() {}
    
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
            System.out.println("拦截请求");
            chain.doFilter(req, resp);//放行
            System.out.println("拦截响应");  
        }
    
        public void init(FilterConfig config) throws ServletException {}
    }
    
    //web.xml
        <filter>
            <filter-name>TestServlet</filter-name>
            <filter-class>TestFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>TestServlet</filter-name>
            <url-pattern>/TestServlet</url-pattern>//拦截TestServlet的请求
        </filter-mapping>
    

    如果要拦截一切请求,可以写成<url-pattern>/*</url-pattern>

    dispatcher:
    还可以通过dispatcher限定拦截的对象。
    REQUEST:拦截HTTP请求get\post
    FORWARD:只拦截通过请求转发的请求
    INCLUDE:只拦截通过request.getRequestDispatcher("").include() 、通过<jsp: include page="...">此种方式发出的请求
    ERROR:只拦截<error-page>发出的请求

    1.11 监听器

    上文提到过四个范围对象:PageContext、request、session、application。监听器主要监听后三个对象。其对应的监听器分别为:request:ServletRequestListenersession:HttpSessionListenerapplication:ServletContextListener,每个监听器各自提供监听开始和结束方法。属于监听对象的创建和销毁。另外,监听器也可以监听对象属性的变更。

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.ServletRequestEvent;
    import javax.servlet.ServletRequestListener;
    import javax.servlet.annotation.WebListener;
    import javax.servlet.http.HttpSessionAttributeListener;
    import javax.servlet.http.HttpSessionEvent;
    import javax.servlet.http.HttpSessionListener;
    import javax.servlet.http.HttpSessionBindingEvent;
    
    @WebListener()
    public class TestListener implements ServletRequestListener,ServletContextListener,
            HttpSessionListener, HttpSessionAttributeListener {
        
        public TestListener() {
        }
    
        public void contextInitialized(ServletContextEvent sce) {
            System.out.println("contextInitialized");
        }
    
        public void contextDestroyed(ServletContextEvent sce) {
            System.out.println("contextDestroyed");
        }
    
        public void sessionCreated(HttpSessionEvent se) {
            /* Session is created. */
            System.out.println("sessionCreated");
        }
    
        public void sessionDestroyed(HttpSessionEvent se) {
            System.out.println("sessionDestroyed");
        }
    
        public void attributeAdded(HttpSessionBindingEvent sbe) {
            System.out.println("attributeAdded");
        }
    
        public void attributeRemoved(HttpSessionBindingEvent sbe) {
            System.out.println("attributeRemoved");
        }
    
        public void attributeReplaced(HttpSessionBindingEvent sbe) {
            System.out.println("attributeReplaced");
        }
    
        @Override
        public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
            System.out.println("requestDestroyed");
        }
    
        @Override
        public void requestInitialized(ServletRequestEvent servletRequestEvent) {
            System.out.println("requestInitialized");
        }
    }
    

    根据输出信息发现其执行顺序如下:

    contextInitialized
    requestInitialized
    sessionCreated
    requestDestroyed
    requestInitialized
    

    另外对于session的监听,还可以根据session的状态来进行
    比如,session对象的绑定和解绑采用HttpSessionBindingListener,如session.setAttribute("a",xxx)将对象a绑定到session中。session.removeAttribute("a")将对象a从session中解绑。而session对象的钝化和活化采用HttpSessionActivationListener。钝化可以理解为将内存中的session存入到硬盘中,而活化则是从硬盘到内存的过程。示例如下:

    //index.jsp
    <%@ page import="test.BeanListener"%>
     <%
          BeanListener bean=new BeanListener();
          session.setAttribute("bean",bean);//绑定
      %>
    

    要注意的是,直接在jsp文件中引入class会报错,需要在classes文件夹下先创建一个文件夹,再逐级引入。

    //BeanListener 
    package test;
    import javax.servlet.http.HttpSessionBindingEvent;
    import javax.servlet.http.HttpSessionBindingListener;
    
    public class BeanListener implements HttpSessionBindingListener {
        public BeanListener() {
        }
        @Override
        public void valueBound(HttpSessionBindingEvent httpSessionBindingEvent) {//绑定
            System.out.println("绑定Bean对象(将Bean对象增加到session域中),绑定的对象为:"+this+",sessionID"+ httpSessionBindingEvent.getSession().getId());
        }
    
        @Override
        public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) {//解绑
            System.out.println("解绑Bean对象(将Bean对象从session域中移除),解绑的对象为:"+this+",sessionID"+ httpSessionBindingEvent.getSession().getId());
        }
    }
    

    1.12 JNDI

    JNDI:java命名与目录接口,将某一个资源(对象),以配置文件(tomcat/conf/context.xml)的形式写入。

    //web.xml
    <Environment name="jndiName" value="jndiValue" type="java.lang.String" />
    
    //index.jsp
      <%
          Context ctx=new InitialContext();
          String testjndi=(String) ctx.lookup("java:comp/env/jndiName");
          out.print(testjndi);
      %>
    

    数据库连接池
    打开、关闭数据库比较消耗性能,所以设置数据库连接池。服务器端的交互不再直接和数据库而是与数据库连接池。常见连接池包含:tomcat-dbcp、dbcp、c3p0、druid。而数据源(javax.sql.DataSource)可以管理数据库连接池。
    tomcat-dbcp使用方式如下:
    a.类似jndi,需要在context.xml中配置数据库,配置实例如下:

    <Resource name="student" auth="Container" type="javax.sql.DataSource" maxActive="400" maxIdle="20" maxWait="5000" username="system" password="sa" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/javaweb">
    

    同时需要在web.xml中配置,指定数据源

    <resource-ref>
        <res-ref-name>student</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
    

    之前写JDBC写访问数据库的连接,在运用数据库连接池后,JDBC的connection指向数据源,那么首先我们要获得数据源,通过Context ctx=new InitialContext();,然后查找对应的数据源,ctx.lookup("java:comp/env/jndiName");

    2. Servlet

    2.1 Servlet简介

    Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。


    Servlet架构

    Servlet读取客户端(浏览器)发送得到显示数据(表单等)、隐式(cookie等),然后处理并产生结果,这个过程可能访问数据库,接着发送数据或HTTP响应到客户端(浏览器)

    2.2 Servlet生命周期

    Servlet 通过调用 init () 方法进行初始化->Servlet 调用 service() 方法来处理客户端的请求->Servlet 通过调用 destroy() 方法终止(结束)。最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。这和上面所述的Jsp生命周期类似。
    其中。init 方法和destroy方法被设计成只调用一次。前者在第一次创建 Servlet 时被调用,后者在Servlet生命周期结束时被调用。而对于Service()方法,每次服务器接收到一个Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。

    2.3 Servlet实例

    前端页面,设置一个链接,地址为TestServlet,因为链接的方式为get,为了更好的演示再创建一个空form,method为post。

    //index.jsp
        <a href="TestServlet">TestServlet</a><br/>
        <form action="TestServlet" method="post">
          <input type="submit" value="登录"/>
        </form>
    

    首先编写一个类TestServlet继承HttpServlet,重写doGet()和doPost()方法。

    //TestServlet.java
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    public class TestServlet extends HttpServlet{
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Exception{
            System.out.println("doGet");
        }
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Exception{
            System.out.println("doPost");
        }
    }
    

    接着编写web.xml中servlet的映射关系。要注意的是这个TestServlet.java程序放在了src中,而在写Url-pattern的相对路径时,是相对于web文件夹目录的。因为web和src都是根目录,所以Url-pattern为/TestServlet。

    //web.xml
    <servlet>
            <servlet-name>TestServlet</servlet-name>
            <servlet-class>TestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>TestServlet</servlet-name>
            <url-pattern>/TestServlet</url-pattern>
        </servlet-mapping>
    

    最终效果如下,点击链接会显示doGet,点击button会显示doPost。


    Servletdemo演示图

    上面这个demo说明了Servlet的流程,首先发送请求->寻找对应的<url-pattern>,然后根据<servlet-mapping>中的<servlet-name>去匹配<servlet>中的<servlet-name>然后寻找<servlet-class>,将请求交给该<servlet-class>执行。

    这里还有一个IDEA使用Servlet的技巧:
    首先,假如我们想编写一个Servlet类名为TestServlet2.可以直接在src文件夹下new->Create New Servlet。设置好它的名称后,自动生成的代码如下:


    IDEA自动生成TestServlet2

    并且,它自动帮助我们在class前声明了@WebServlet(name="TestServlet2"),这样我们就不用再去web.xml里面声明,如果我们需要url-pattern,直接在后面加参数声明@WebServlet(name="TestServlet2",urlPatterns = "/TestServlet2"),然后就可以直接调用了。这也是Servlet3.0版本之后的一个区别,即Servlet3.0开始不需要再在web.xml中配置,可以通过在Servlet类的定义处上编写注解@WebServlet的方式调用。这个注解后面还可以增加初始化参数initParams等但是这个注解只隶属于某一个具体的Servlet,因此无法为整个web容器设置初始化参数,所以如果是3.0的方式想要设置web容器的初始化参数,扔需要配置web.xml。
    简单来说demo中的<a href=“TestServlet2”>这个地址会自动与@WebServlet中的值进行匹配,如果匹配成功就是该注解所对应的类。

    2.4 Servlet AP

    Servlet由两个软件包组成,一个对应HTTP协议,一个对应非HTTP协议的其他软件包。Servlet适用于任何通信协议。我们所用的一般是位于javax.servlet.http包中的类和接口,是基础的HTTP协议。
    Servlet传递链如下:Servlet+ServletConfig+Serializable=>GenericServlet=>httpServlet=>自定义Servlet。
    一些重点概念如下:
    ServletConfig:接口
    ServletContext getServletContext:获取Servlet上下文对象,可以创建application。
    String getInitParameter(String name):在当前Servlet范围内,获取名为name的参数值(初始化参数)
    ServletContext中的常见方法(application):
    getContextPath():相对路径
    getRealPath():绝对路径
    setAttribute()、getAttribute()
    String getInitParameter(String name):在当前Web容器范围内,获取名为name的参数值(初始化参数)

    3. JSP与Servlet

    3.1 二者关系——MVC模式

    Servlet通过HttpServletResponse对象在java代码中动态输出HTML内容,JSP则是在HTML静态页面中嵌入Java代码,Java代码被动态执行后生成HTML内容。鉴于各自特点,Servlet能够很好地组织业务逻辑代码,但其生成的动态HTML可读性差,JSP则避免了这种劣势,但HTML中掺杂了太多业务逻辑。所以二者基于MVC模式进行了结合。MVC(Model-View-Controller),这种架构模式把系统分为三部分,模型、视图和控制器。MVC的运作流程如下:


    MVC模式

    Web浏览器发送HTTP请求到服务端,被Controller(Servlet)获取并进行处理(例如参数解析、请求转发)Controller(Servlet)调用核心业务逻辑—Model部分,获得结果Controller(Servlet)将逻辑处理结果交给View(JSP),动态输出HTML内容动态生成的HTML内容返回到浏览器显示。

    3.2 MVC模式实现登录demo

    index.jsp位于View视图层展示登录页,TestServlet(即为controller)实现登录控制,LoginDao位于模型层,实现登录功能。前面说到模型分为封装逻辑的模型(实现功能)和封装数据的模型(实体类)。整体流程为:index.jsp调用TestServlet,TestServlet再调用LoginDao。

    //index.jsp登录表单
        <form action="TestServlet" method="post">
                用户名:<input type="text" name="username"/><br/>
                密码:<input type="password" name="password"/><br/>
          <input type="submit" value="登录"/>
        </form>
    

    表单的action为TestServlet控制器,该控制器要调用LoginDao模型实现登录功能。登录功能实现的基础就是JDBC,仿照上文所讲的例子,LoginDao的代码如下。可以看到我们需要从View中接收username和password,将二者进行数据封装,封装类称为Login。

    //LoginDao.java
    import java.sql.*;
    
    public class LoginDao {
        //模型层:用于处理登录(登录功能,即查询数据)
        public static int login(Login login){
            int result=-1;
            final String URL="jdbc:mysql://localhost:3306/javaweb";
            final String USERNAME="root";
            final String PWD="";
            Connection connection=null;
            PreparedStatement pstmt=null;
            ResultSet resultSet=null;
            try{
                Class.forName("com.mysql.jdbc.Driver");
                connection=DriverManager.getConnection(URL,USERNAME,PWD);
                String sql="select count(*) from users where name=? and password=?";
                pstmt=connection.prepareStatement(sql);
                pstmt.setString(1,login.getName());
                pstmt.setString(2,login.getPassword());
                resultSet=pstmt.executeQuery();
                if(resultSet.next()){
                    result=resultSet.getInt(1);
                }
                if (result>0){
                    return 1;
                }
                else{
                    return 0;
                }
    
            }
            catch (ClassNotFoundException e){
                e.printStackTrace();
                return -1;
            }
            catch (SQLException e){
                e.printStackTrace();
                return -1;
            }
            catch (Exception e){
                e.printStackTrace();
                return -1;
            }
            finally {
                try{
                    if (resultSet!=null)resultSet.close();
                    if (pstmt!=null)pstmt.close();
                    if (connection!=null)connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                catch (Exception e){
                    e.printStackTrace();
                }
    
            }
        }
    }
    

    程序登录可能有多种情况,登录成功和登录不成功。其中不成功的情况分为用户名和密码错误、系统错误。所以因为有三种可能,设置LoginDao函数类型为int,根据返回值判断最终情况。
    Login类,有name和password两个参数,分别设置它的构造函数(有参和无参两种),再设置set和get。

    //Login.java
    public class Login {
        //用于传递用户名和密码,属于封装数据的模型(实体类)
        String name;
        String password;
    
        public Login(String name, String password) {
            this.name = name;
            this.password = password;
        }
        public Login(){
        }
        public void setName(String name) {
            this.name = name;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
        public String getName() {
            return name;
        }
    
        public String getPassword() {
            return password;
        }
    }
    

    最后完成控制器TestServlet的代码。它先将数据进行封装,然后调用名为LoginDao的Model进行校验,然后接收LoginDao的返回值(该值代表登录结果),最后根据登录结果进行处理。

    //TestServlet.java
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    
    //控制器层(Controller),用于接受view请求,并分发给Model处理
    public class TestServlet extends HttpServlet{
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            //接收用户名密码
            req.setCharacterEncoding("utf-8");
            String name=req.getParameter("username");
            String pwd=req.getParameter("password");
            Login login=new Login(name,pwd);//存放用户名密码=>javaBean
            //调用模型层的登录功能
            int result=LoginDao.login(login);
            if(result>0){
                resp.sendRedirect("success.jsp");
            }
            else if (result==0){
                System.out.println("用户名或密码错误");
                resp.sendRedirect("index.jsp");
            }
            else{
                System.out.println("系统错误");
                resp.sendRedirect("index.jsp");
            }
        }
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            doGet(req,resp);
        }
    }
    

    3.3 三层架构

    三层架构与MVC设计模式目标一致,都是为了解耦合并提高代码复用,但是二者对于项目的理解角度不同。三层分别为:表示层(USL,User Show Layer:视图层)、业务逻辑层(BLL,Business Logic Layer:Service层)、数据访问层(DAL,Data Access Layer:Dao层)。上层依赖于下层(持有成员变量或需要有下层才能成立)

    MVC vs 三层架构

    参考资料

    Jsp完整参数:
    https://www.runoob.com/jsp/jsp-tutorial.html
    具体的web.xml配置:
    https://www.cnblogs.com/hafiz/p/5715523.html
    demo来源:
    https://www.bilibili.com/video/av29086718?

    相关文章

      网友评论

          本文标题:JSP与Servlet

          本文链接:https://www.haomeiwen.com/subject/mftdxhtx.html