学习JavaWeb(五)——jsp

前言:

承接前面的内容,我们来继续学习JavaWeb。

 

 

 

 


jsp

什么是jsp

jsp的全称是 java server pages,也就是java的服务器页面。

它的主要作用是代替 Servlet程序回传 html 页面的数据。

因为Servlet 程序回传 html页面是一件非常繁琐的事情,开发成本和维护成本都很高。

 

创建一个新项目,我们来体验一下

我们来看看servlet是怎么回传数据的

public class PrintHtml extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
                    throws ServletException, IOException {
        //通过响应的输出流回传html页面数据
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer=resp.getWriter();
        String content="<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>有意思</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "    <h3 align=\"center\" >大抵如此</h3>\n" +
                "</body>\n" +
                "</html>";
        writer.write(content);
    }

然后在web.xml中进行配置:

    <servlet>
        <servlet-name>PrintHtml</servlet-name>
        <servlet-class>com.atluoluo.servlet.PrintHtml</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>PrintHtml</servlet-name>
        <url-pattern>/test</url-pattern>
    </servlet-mapping>

这些都是我们上一篇内容中学的,很明显,Servlet回传很麻烦,仅仅是一点点html内容就要很复杂,这非常不利于处理和维护,现在,我们来看看jsp怎么处理:

我们右击web目录,创建一个jsp(我起名:a.jsp):

<%--
  Created by IntelliJ IDEA.
  User: lenovo
  Date: 2020/5/4
  Time: 15:20
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h3 align="center" >大抵如此</h3>
</body>
</html>

我们直接访问url+a.jsp,就可以看到和上面的效果一样。

 

jsp访问规则

jsp页面和html页面一样,都是存放在web目录下。访问也跟访问html页面一样。

  • html页面:访问地址=======》 http://ip:port/工程路径/a.html
  • jsp页面:访问地址是=======》 http://ip:port/工程路径/b.jsp

 

jsp的本质是什么

jsp页面本质上是一个Servlet程序。

当我们第一次访问jsp页面的时候,Tomcat服务器会帮我们把jsp页面翻译成为一个java源文件,并帮助我们把它编译成为字节码程序。通过CATALINA_BASE我们找到Tomcat的服务器目录并找到对应项目的文件,我们打开java源文件不难发现,里面的a_jsp.java中的主类继承了HttpJspBase,而其实HttpJspBase是HttpServlet类的子类。

也就是说,jsp翻译出来的java类,它间接继承了HttpServlet类,也就是说,翻译出来的是一个Servlet程序。

故:jsp页面本质上是一个Servlet程序。

它其实内部工作和我们的获取输出流一点一点输出是一样的,源文件你可以找到类似如下:

 

jsp的指令

page指令

观察jsp文件内容我们会发现,除了 注释 以及 下面的html内容 ,中间有这样的一条语句:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

jsp的page指令可以修改jsp页面中一些重要的属性,或者行为

属性如下:

  1. language:表示jsp翻译后是什么语言,暂时只支持java
  2. contentType:表示jsp返回的数据类型是什么。也是源码中response.setContentType()参数值。
    1. 设置响应体的mime类型以及字符集
    2. 设置当前jsp页面的编码(只能是高级的IDE才会生效,如果使用低级的工具,则需要设置pageEncoding属性设置当前页面的字符集)
  3. pageEncoding:表示当前jsp页面本身的字符集
  4. import:跟java源代码中一样,可以导包导类
  5. autoFlush(给out输出流使用):设置当out输出流缓冲区满了之后,是否自动刷新缓冲区,默认值是true
  6. buffer(给out输出流使用):设置out缓冲区的大小,默认是8kb
  7. errorPage:设置当jsp页面运行时出错自动跳转去的错误页面路径(必须是本地页面),这个路径一般以“/”打头,表示的请求地址是http://ip:port/工程路径/,映射到代码的web目录。
  8. isErrorPage:设置当前jsp页面是否是错误信息页面。默认是false。(如果是true,可以获取异常信息),即是否使用内置对象exception(这也是九大内置对象之一)
  9. session:设置访问当前jsp页面是否会创建HttpSession对象(在java源代码中),默认是true。
  10. extends:设置jsp翻译出来的java类默认继承谁。

 

关于上面的autoFlush、buffer,我们可以做个试验,修改page指令:

<%@ page contentType="text/html;charset=UTF-8"
         autoFlush="false"
         buffer="1kb"
         language="java" %>

然后将<body>中的内容多复制一些,然后刷新页面,你就会看到报错信息:

JSP Buffer overflow

这个错误是因为缓冲区满了之后不能自动刷新。

include指令

这个指令也正是“动态包含”的关键指令,可以给页面指令包含其他页面的元素。

页面包含的。导入页面的资源文件。

<%@include file="test.jsp"%>

taglib指令

导入资源

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
//prefix:前缀,自定义的

 

 

jsp的常用脚本

声明脚本(很少使用)

    <%! 声明java代码  %>

可以给jsp翻译出来的java类定义属性和方法甚至是静态代码块。内部类等。

示例

在jsp文件中:

<body>
    <h3 align="center" >大抵如此</h3>
    <%--声明属性--%>
    <%!
        private Integer id;
        private String name;
        private static Map<String,Object> map;
    %>
    <%--声明static静态代码块--%>
    <%!
        static {
            map = new HashMap<String, Object>();
            map.put("key1","value1");
            map.put("key2","value2");
            map.put("key3","value3");
        }
    %>
    <%--声明类方法--%>
    <%!
        public int test(){
            return 12;
        }
    %>
    <%--声明内部类--%>
    <%!
        public static class A{
            private Integer id;
            private String abc="123";
        }
    %>
</body>

我们在<%!   %>中写的代码,都会在对应的java源文件中做添加:

表达式脚本(常用)

<%= 表达式代码 %>

表达式脚本的作用是在jsp页面上输出数据。

    <%--表达式--%>
    <%=12 %><br />
    <%=12.12 %><br />
    <%="我是字符串" %><br />
    <%= map%><br/>

在页面上即可正常输出。

表达式脚本的特点:

  1. 所有的表达式脚本都会被翻译到java源代码的_jspService()方法中
  2. 表达式脚本都会被翻译成为out.print()输出到页面上
  3. 由于表达式脚本翻译的内容都在_jspService()方法中,所以_jspService()方法中的对象都可以直接使用。
  4. 表达式中的表达式不能以分号结束

 

表达式脚本会在java源文件中翻译成:out.print(内容)

 

代码脚本

<%
         java语句
%>

代码脚本的作用是:可以在jsp页面中,编写我们自己需要的功能(写的是java语句)

代码脚本的特点是:

  1. 代码脚本翻译之后都在_jspService方法中
  2. 代码脚本由于翻译到_jspService()方法中,所以在_jspService()方法中的现有对象都可以直接使用
  3. 代码脚本还可以由多个代码脚本块组合完成一个完整的java语句
  4. 代码脚本还可以和表达式脚本一起组合使用在jsp页面上输出数据

 

代码脚本会在java源文件中翻译成:内容(直接翻译)

 

代码脚本与表达式脚本组合使用:

    <%
        for(int i=0;i<1000;i++){
    %>
    <%=i%><br/>
    <%
            System.out.println(i);
        }
    %>

灵活搭配可以很方便的使用

    <table border="2px" bgcolor="#f4a460"  align="center">
    <%
        for(int i=0;i<10;i++){
    %>
        <tr>
            <td>第<%=i%>行</td>
        </tr>
    <%
            System.out.println(i);
        }
    %>
    </table>

jsp中的三种注释

html注释

<!--注释内容-->

html注释会被翻译到java源代码中,在_jspService方法里,以out.write输出到客户端

 

java注释

java注释一般写在jsp代码脚本中,会被翻译到java源代码中

 

jsp注释

<%--注释内容--%>

jsp注释可以注释掉一切,可以注释掉jsp中所有代码

 

jsp九大内置对象

在jsp页面中不需要创建,直接使用的对象

jsp中有九大内置对象我们可以直接拿来使用

  • request:HttpServletRequest类的请求对象。作用:一次请求访问的多个资源(转发)
  • response:HttpServletResponse类的对象。作用:响应对象
  • pageContext:PageContext类的jsp的上下文对象。作用:当前页面共享数据,还可以获取其他八个内置对象
  • session:HttpSession类的会话对象。作用:一次会话的多个请求间共享数据
  • application:ServletContext类的对象,作用:所有用户间共享数据
  • config:ServletConfig类的对象。作用:Servlet的配置对象
  • out:JSPWriter类的jsp输出流对象。作用:输出对象,数据输出到页面上
  • page:Object类的指向当前jsp的对象。作用:当前页面(Servlet)的对象 this
  • exception:Throwable类的异常对象(Page指令中声明为错误页面才会出现)

应用示例:

<table border="2px" bgcolor="#f4a460"  align="center">
<tr>
<td>客户端地址:<%=request.getRemoteHost()%>:<%=request.getRemotePort()%></td>
</tr>
<tr>
<td>服务器地址:<%=request.getLocalAddr()%>:<%=request.getLocalPort()%></td>
</tr>
</table>

 

 

jsp四大域对象

域对象是可以像Map一样存取数据的对象。

四个域对象功能一样。

不同的是他们对数据的存取范围。

  • pageContext(PageContextImpl类):当前jsp页面范围内有效
  • request(HttpServletRequest类):一次请求内有效
  • session(HttpSession类):一个会话范围内有效(打来浏览器访问服务器,直到关闭浏览器)
  • application(ServletContext类):整个web工程范围内都有效(只要web工程不停止,数据都在)

虽然四个域对象都可以存取数据,在使用上他们是有优先顺序的。

四个域在使用的时候,优先顺序分别是,他们从小到大的范围的顺序。

pageContext==》request==》session==》application

 

 

out输出和response.getWriter输出的区别

out是jsp九大内置对象中的输出流对象

response是jsp九大内置对象中的响应对象

你如果直接把他们放在代码脚本中输出内容,其实是看上去没有区别的:

    <%
        response.getWriter().write("response输出1<br/>");
        out.write("out输出1<br/>");
    %>

如果你将上面代码顺序反一下,你会发现页面还是先输出response。

由于jsp翻译之后底层源代码都是使用out来进行输出,所以一般情况下,我们在jsp页面中统一使用out来进行输出,避免打乱页面输出内容的顺序。

而说到out的输出方式,又有两个方法:print、write

这两个方法都可以正常输出字符串,但是注意write输出int型会自动将int转变为ascii码对应的那个字符,而print就不会这样。

write(int c)的源代码:
    public void write(int c) throws IOException {
        synchronized (lock) {
            if (writeBuffer == null){
                writeBuffer = new char[WRITE_BUFFER_SIZE];
            }
            writeBuffer[0] = (char) c;
            write(writeBuffer, 0, 1);
        }
    }

 

jsp常用标签

静态包含

一个单独的jsp页面只维护一份,改一处,其他的都统一修改,这就可以使用静态包含来实现。

这样的目录结构下:

main.jsp

<body >
    头部信息 <br />
    主体内容 <br />
    <%--静态包含
        file属性指定你要包含的jsp页面的路径
        地址中第一个斜杠表示为http://ip:port/工程路径/
        映射到代码的web目录
    --%>
    <%@ include file="/include/footer.jsp" %>
</body>

footer.jsp

<body>
    页脚信息 <br />
</body>

然后我们访问:http://localhost:8080/jsp_war_exploded/include/main.jsp

就可以看到:

源代码的实现原理是将 被包含的页面内容原封不动地拷贝到静态包含的位置去输出从而实现的,即:

  1. 静态包含不会翻译被包含的jsp页面
  2. 静态包含其实是把被包含的jsp页面的代码拷贝到包含的位置执行输出

 

动态包含

修改上面的main.jsp文件:

<body >
    头部信息 <br />
    主体内容 <br />
    <%--动态包含
        file属性指定你要包含的jsp页面的路径
        地址中第一个斜杠表示为http://ip:port/工程路径/
        映射到代码的web目录
    --%>
    <%--
        page:指定你要包含的jsp路径
    --%>
    <jsp:include page="/include/footer.jsp"></jsp:include>
</body>

最后的效果其实是和静态包含效果一样的

那么静态动态有什么区别呢?

  • 动态包含会把包含的jsp页面也翻译成java代码(生成一个文件了)
  • 动态包含底层代码使用如下代码去调用被包含的jsp页面执行输出
org.apache.jasper.runtime.JspRuntimeLibrary.include(
request, response, "/include/footer.jsp", out, false);
  • 动态包含还可以传递参数

main.jsp

<jsp:include page="/include/footer.jsp">
    <jsp:param name="username" value="bbj" />
    <jsp:param name="passwd" value="123456" />
</jsp:include>

footer.jsp

<%=request.getParameter("passwd")%>

 

动态包含的底层原理

请求转发

以前我们使用请求转发是怎么做呢?

request.getRequestDispatcher(path:"/test.jsp").forward(request,response)

现在我们不需要再用 代码脚本,可以直接使用一个jsp标签:

<jsp:forward page="include/main.jsp"></jsp:forward>

 

 

jsp经典练习

九九乘法表

<head>
    <title>Title</title>
    <style type="text/css">
        #table{
            width: 900px;
            border-style: double groove dashed
        }
    </style>
</head>
<body bgcolor="#a9a9a9">
    <h1 align="center">九九乘法口诀表</h1>
    <table id="table" align="center">
    <% for(int i=1;i<=9;i++){ %>
        <tr>
        <% for(int j=1;j<=i;j++){ %>
            <td>

                <%= j+"*"+i+"="+ i*j%>
            </td>
        <%
                }
        %>
        </tr>
    <%
        }
    %>
    </table>
</body>

可见表达式脚本和代码脚本的jsp文件的可读性非常差,以后会学习EL表达式带代替这种脚本

输出10个学生信息到表格中

<head>
    <title>Title</title>
    <style type="text/css">
        table{
            border: 1px black groove ;
            width: 800px;
            /*合并边框*/
            border-collapse: collapse;
        }
        td,th{
            border: 1px black solid;
        }
        td{
            text-align: center;
        }
    </style>
</head>
<body bgcolor="#f5f5dc">
    <%
        List<Student> studentList=new ArrayList<Student>();
        for(int i=0;i<10;i++){
            int t=i+1;
            studentList.add(new Student(t,"name"+t,18+t,"phone"+t));
        }
    %>
    <table>
        <tr>
            <th>编号</th>
            <th>姓名</th>
            <th>年龄</th>
            <th>电话</th>
            <th>操作</th>
        </tr>
    <% for (Student student : studentList) { %>
            <tr>
                <td><%=student.getId()%></td>
                <td><%=student.getName()%></td>
                <td><%=student.getAge()%></td>
                <td><%=student.getPhone()%></td>
                <td>删除、修改</td>
            </tr>
    <% } %>
    </table>
</body>

其中的Student类是一个pojo包的类,有四个属性:id、name、age、phone

 

“输出10个学生信息到表格中”结合请求转发

上面的表,我们可以结合请求转发来较为完整的完成一个“搜索”。

这里我建立了一个类来操作mysql:

新建包com.atluoluo.dao.impl,这里新建一个接口和一个实现类

ConnectSQL.java

public interface ConnectSQL {
    //得到结果
    public ResultSet searchResult(String table,String keyword);
}

SQLControl.java

public class SQLControl implements ConnectSQL {
    // MySQL 8.0 以上版本 - JDBC 驱动名及数据库 URL
    //声明JDBC驱动名称
    static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";

    //sql连接对象
    Connection conn = null;

    PreparedStatement pstmt;
    ResultSet rs;

    // 数据库的用户名与密码,需要根据自己的设置
    static final String USER = "root";
    static final String PASS = "964939451";

    public SQLControl(String database){
        //定义JDBC的url地址
        String DB_URL = "jdbc:mysql://localhost:3306" +
                "/"+database+"?useSSL=false&serverTimezone=UTC";
        try{
            // 注册 JDBC 驱动
            Class.forName(JDBC_DRIVER);

            // 打开链接
            System.out.println("连接数据库...");
            //连接数据库url
            conn = DriverManager.getConnection(DB_URL,USER,PASS);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    public void closeConn(){
        try {
            rs.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try{
            if(pstmt!=null)
                pstmt.close();
        }catch(SQLException se2){
        }// 什么都不做
        try{
            if(conn!=null) conn.close();
        }catch(SQLException se){
            se.printStackTrace();
        }
    }
    @Override
    public ResultSet searchResult(String table,String keyword) {
        pstmt = null;
        rs = null;
        try {
            //SQL语句
            String sql;
            sql = "SELECT * FROM "+table+" WHERE name=?";
            //执行SQL语句,并得到结果
            pstmt = conn.prepareStatement(sql);
            //给?赋值
            pstmt.setString(1, keyword);

            //执行查询
            rs = pstmt.executeQuery();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            return rs;
        }
    }
}

这样,新建一个SQLControl 类的对象即可建立连接,searchResult 成员方法可以在表中检索某个对象,closeConn 可以关闭一些连接,释放资源。

几个注意的点:

  • mysql版本大于8.0,所以使用的驱动什么的都比较讲究,详情请见以前这篇文章
  • 我自己写的时候遇到的坑,mysql对应jar包加到该项目的lib目录之外,另外还需要在Tomcat的根目录的lib中放上你的mysql对应jar包。
  • 另外,关闭资源时要先关闭ResultSet等对象,最后关闭Connection对象。

数据库的连接就搞定了。

可以通过另一个servlet代码来测试一下:

public class SearchStudentServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
                   throws ServletException, IOException {
        SQLControl sqlControl=new SQLControl("jspTest");
        ResultSet rSet=sqlControl.searchResult("user","Tom");
        try {
            while (rSet.next()) {
                int id = rSet.getInt("id"); // 获取第一个列的值 编号id
                String name = rSet.getString("name"); // 获取第二个列的值
                System.out.println(id+":"+name);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            sqlControl.closeConn();
        }
    }
}

配置一下web.xml,访问之后控制台正常打印消息即可。

//result set这个sql结果集合一定要先next(光标会滚动一次)一次才能用,可以理解为是一个下标从1开始的集合。

接下来我们来模拟搜索功能检索。

下面是我做的数据表

+—-+——–+—–+——–+
| id | name | age | phone |
+—-+——–+—–+——–+
| 1   | Tom   |  14 | 123456 |
| 2  |   Cat    |  31 | 234234 |
| 3  | Beddy | 16 | 67234 |
| 4  | luoluo | 20 | 657634 |
| 5  |   Tom  | 17  | 62334 |
| 6  |  Jack  |  23  | 623454 |
| 7  |   Tom | 27  | 234234 |
+—-+——–+—–+——–+
7 rows in set (0.00 sec)

 

好,接下来我们来实现一个简单的社工库:

index.jsp

<html>
  <head>
    <title>社工库</title>
    <style type="text/css">
      h1{
        position: absolute;
        top: 20%;
        left: 40%;
      }
      span{
        position: absolute;
        top: 35%;
        left: 27%;
      }
      #tips{
        position: absolute;
        top: 35%;
        left: 51%;
      }
      #sub{
        position: absolute;
        top: 45%;
        left:52%;
        width: 200px;
        height: 60px;
        color: black;
        text-anchor: middle;
        background-color: floralwhite;
      }
    </style>
  </head>
  <body bgcolor="#ffe4b5">
      <form action="http://localhost:8080/jsp_war_exploded/search"
            method="get">
          <h1>外国小朋友社工库</h1>
          <span>请输入外国小朋友的姓名:</span>
          <input type="text" id="tips" value="输入姓名" name="name" />
          <input type="submit" id="sub" value="搜索"/>
      </form>
  </body>
</html>

其中form表单指向的http://localhost:8080/jsp_war_exploded/search就是SearchStudentServlet类

SearchStudentServlet.java

public class SearchStudentServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                           throws ServletException, IOException {
        //获取请求的参数
        String searchName=req.getParameter("name");
        //发sql语句查询信息
        List<Student> studentList=new ArrayList<Student>();
        SQLControl sqlControl=new SQLControl("jspTest");
        ResultSet rSet=sqlControl.searchResult("user",searchName);
        try {
            while (rSet.next()) {
                int id = rSet.getInt("id"); // 获取第一个列的值 编号id
                String name = rSet.getString("name"); // 获取第二个列的值
                int age = rSet.getInt("age");
                String phone = rSet.getString("phone");
                //将找到的信息添加进入集合中
                studentList.add(new Student(id,name,age,phone));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            sqlControl.closeConn();
        }
        //保存查看到的结果
        req.setAttribute("stuList",studentList);
        //请求转发到showStudent.jsp页面(设置域数据)
        req.getRequestDispatcher("/a.jsp").forward(req,resp);
    }
}

OK其他也没啥了,然后启动Tomcat访问即可

 

Listener 监听器

Listener监听器它是JavaWeb的三大组件之一,三大组件:

  1. Servlet程序
  2. Filter过滤器
  3. Listener监听器

Listener监听器和Servlet一样,是JavaEE的规范,就是接口。

监听器的作用是:监听某种事物的变化,然后通过回调函数,反馈给客户(程序)去做一些相应的处理。

ServletContextListener监听器

ServletContextListener 它可以监听 ServletContext 对象的创建和销毁。

ServletContext(也叫Servlet上下文)对象在web工程启动的时候创建,在web工程停止的时候销毁。

如何使用ServletContextListener监听器监听 ServletContext对象

  1. 编写一个类去实现 ServletContextListener
  2. 实现其两个回调方法
  3. 到web.xml 中去配置监听器

应用示例:

创建一个包com.atluoluo.listener,创建一个类 MyServletContextListenerImpl.java

public class MyServletContextListenerImpl 
                 implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext对象被创建了");
    }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext对象被销毁了");
    }
}

然后在web.xml中进行配置

<listener>
<listener-class>
com.atluoluo.listener.MyServletContextListenerImpl
</listener-class>
</listener>

然后启动项目,就可以看见“对象被创建的提示”

 

 

 


MVC开发模式

jsp演变历史

  1. 早期只有servlet,只能使用response输出标签数据,非常麻烦
  2. 后来又有了jsp,简化了Servlet的开发,如果过度使用jsp,在jsp中即写大量代码,又写html表,造成难于维护,难于分工协作
  3. 再后来,java的web开发,借鉴经典的mvc开发模式,使得程序的设计更加合理性

MVC

  • M:Model,模型(JavaWeb中的JavaBean)
    • 完成具体的业务操作,如:查询数据库、封装对象
  • V:View,视图(JavaWeb中的jsp)
    • 展示数据
  • C:Controller,控制器(JavaWeb中的Servlet)
    • 获取用户的输入
    • 调用模型
    • 将数据交给视图进行展示

jsp中仅仅只做数据的展示,不要再在jsp中处理业务逻辑了。

优缺点

优点:

  1. 耦合性低,方便维护,可以利于分工协作
  2. 重用性高

缺点:

  1. 使得项目架构变得复杂,对开发人员要求高

 

 

 

 

 


商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

 

 

 

发表评论