JSP&SERVLET 学习

jefxff 153,607 2020-05-07

Servlet&Jsp

知识点

  1. Web 容器 :简单点理解: 容器就是一个用 Java 写的程序; 这个容器不仅持有对象, 而且还负责对象的生命周期和相关服务的连接; 抽象的理解: 可以将Web容器视为运行 Servlet/JSP 的 HTTP 服务器

  2. JSP和Servlet的关系: JSP文件会被Web容器翻译成Servlet的.Java源文件, 接着编译为.class文件, 然后加载到容器之中, 所以最后提供服务的还是Servlet实例(Instance); 而 JSP 主要是从网页编辑者的角度, 方便设计网页画面来解决问题; Servlet主要从事Java程序逻辑定义

  3. 关于 MVC/Model2

    • MVC: Model View Controller (模型, 试图, 控制器): 模型不会有画面相关的程序代码而是负责服务器上数据访问或业务逻辑; 视图负责画面相关逻辑由网页来实现; 控制器接送浏览器请求,知道某个操作必须调用那些模型
    • model2:
      • 控制器: 取得请求参数, 验证请求参数, 转发请求给模型, 转发请求给画面, 这些操作都是用代码来实现
      • 模型: 接受控制器的请求调用, 负责处理业务逻辑, 数据存取逻辑, 也使用代码来实现
      • 视图: 接受控制器的请求调用, 从模型提取运算后的结果, 根据需求呈现所需的画面

部署 Servlet 的两种方式

  • 方式一: 在 WEB-INF 目录下的 web.xml 文件的 <\web-app> 标签中增加如下内容
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>xyz.xmcs.servlet.MyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/Hello</url-pattern>
    </servlet-mapping>
  • 方式二: 在编写的 Servlet 代码的 @WebServlet(name="Hello", urlPatterns="/Hello") 中使用 urlPatterns={"/Hello"} 来直接指定, 效果和方式一样@WebServlet 告知容器, 这个Servlet的名称是Hello, 是由name属性指定的, 客户端请求的URL是"/Hello", 是由urlPatterns指定的

JSP页面元素

  • 脚本 Scriptlet:
    • <% out.print("这里可以定义局部变量, Java语句, 以及调用方法"); %>
    • <%! // 这里可以定义全局变量, 定义方法 %>
    • <%= // 输出表达式 %>
  • 指令
    -page指令: <%@ page ... %>
  • 注释: html注释\<!-- --\>, java注释 // /\*\*/, jsp注释 <%-- --%>

JSP 九大内置对象(自带的, 不需要 new 也能使用的对象)

1. out : 输出对象, 向客户端输出内容

2. request : 请求对象, 存储 "客户端向服务器端发送的请求信息";

  • 常用方法:

    • String getParameter(String name): 根据请求的字段名, 返回字段值;
    • String[] getParameterValues(String name): 根据请求的字段名key(input标签的name属性值), 返回多个字段值value(input标签的value属性值)
    • void setCharacterEncoding("UTF-8") : 设置post请求的编码为 UTF-8
    • getRequestDispatcher("b.jsp").forward(request, response): 请求转发的方式跳转页面 (当前页面跳转到b.jsp)
    • getServerContext(): 获取项目的ServerContext对象
  • get 提交方式: 在form标签中通过method属性指定

    ; 在地址栏直接通过 "?参数名1=参数值1&参数名2=参数值2" 方式指定; 超链接(<\a href="url">) 方式指定

  • get 和 post 方式的区别: get 方式在地址栏显示请求信息,(地址栏只能容纳 4-5KB); 如果请求数据存在大文件,则使用 post; 文件上传操作必须使用 post

  • 统一get请求的编码 request:

    1. 统一每个变量的编码(了解就好) name = new String(name.getBytes("iso-8859-1), "utf-8")
    2. 修改 server.xml 文件, 更改tomcat默认的 get提交方式编码(在端口号的标签后面添加 URIEncoding="URF-8")
  • 统一post方式的编码 : request.setCharacterEncoding("UTF-8");

3. response : 响应对象:

  • 常用方法:

    • void addCookie(Cookie cookie) : 服务端向客户端增加Cookie对象
    • void sendRedirect(String location) throws IOException : 页面跳转的一种方式 (重定向)
    • void setContentType(String type) : 设置服务端响应的编码 (设置服务端的contentType类型)
  • 重定向(response.sendRedirect("success.jsp");)和请求转发(request.getRequestDispatcher("success.jsp").forward(request, response);)的区别:

    • 请求转发的地址栏不变, 重定向的地址栏会改变

    • 请求转发会保留第一次请求时的数据, 重定向不保留第一次请求时的数据

    • 请求转发请求的次数是一次, 重定向请求的次数是两次

    • 可以这样理解, 请求转发(只有一次请求一次响应)是服务器内部的跳转, 而重定向(有两次请求两次响应)是返回给客户端让客户端重新发起一次请求

    • 请求转发的跳转在服务端, 重定向的转发在客户端

4. session (服务端的);

  • 作用: 提高访问服务端的效率, 但是安全性较差
  • Cookie: key=value; 是由 javax.servlet.http.Cookie这个类产生
  • 常用方法:
    • public Cookie(String key, String value)
    • String getName() 获取key
    • String getValue() 获取value
    • void setMaxAge(int expiry); 设置最大有效期(单位是秒)
  • 服务端准备Cookie给客户端:
    • response.addCookie(Cookie cookie)
  • 服务端发送Cookie给客户端:
    • 直接页面跳转或重定向就可以发送Cookie给客户端
  • 客户端获取Cookies:
    • request.getCookies();
  • 注意:
    • 服务端增加Cookie是通过response.addCookie(Cookie cookie); 客户端获取Cookie是通过 request.getCookies()
    • 不能直接获取某一条单独的Cookie对象, 只能一次性获取全部的Cookies对象
    • 通过F12调试工具发现, 除了自己设定的Cookie对象外, 还有一个name为JSESSIONID的cookie
    • cookie 中只保存英文或数字, 否则需要编解码
    • cookie 不是内置对象, 要使用必须使用 new Cookie(String String); 但是, 服务端会自动生成一个name=JSESSIONID的Cookie对象, 并将其返回给客户端
4.2 session: 会话
  • 常见的会话(同一次会话共享session): 浏览网站(从打开--到关闭); 购物(浏览--付款--退出); 电子邮件(浏览--写邮件--退出)
  • session的运行机制:
    • 客户端第一次请求服务端时, (首先会JSESSIONID匹配SESSIONID)服务端会产生一个session对象(用户保存该客户的信息),并且每个session都有一个唯一的sessionid(用于区分其他的session); 服务端也会产生一个cookie, 并且该cookie的name=JSESSIONID,value等于sessionid的值, 然后服务端会在响应客户端的同时将该cookie发送给客户端, 至此客户端就有了一个cookie(JSESSIONID);因此, 客户端的cookie就可以和服务端的session一一对应(JSESSIONID--sessionID)
    • 客户端第二次或第N次请求服务端时, 服务端会先用客户端cookie中的JSESSIONID去匹配服务端session的sessionID, 如果匹配成功说明不是第一次登录, 就无需登录, 否则就按照第一次请求;
  • session注意点:
    • session存储再服务端; 在同一个用户(客户)请求时共享
    • 实现机制:客户第一次请求时产生一个sessionid, 并复制给cookie的JSESSIONID,然后发给客户端, 最终 session的sessionid和cookie的jsessionid完成一一对应的关系
  • session常用的方法:
    • getId(): 获取 sessionId
    • boolean isNew() : 判断是否是第一次访问(新用户)
    • void invalidate() : 使session实现 (退出登录, 注销)
    • void setAttribute()
    • Object getAttribute()
    • void setMaxInactiveInterval(秒): 设置最大有效非活动时间
    • int getMaxInactiveInterval() : 获取最大的有效非活动时间
  • request作用域的问题:
    • 问题引出: 在session的练习中, 通过 getMaxInactiveInterval() 设定了session的最大有效非活动时间为10秒, 但是在10秒后,地址栏回车后浏览器报错; 但是使用F5刷新却没问题
    • 原因: request数据只有在同一次请求有效; 地址栏回车相当于发出了第二次请求, 而F5刷新时, 浏览器会自动重复刚才的行为(即提交信息, 获得Jsessionid)
  • 练习: 超市门口顾客和存包处
  • Session 和 Cookie 的区别:
    • Session保存在服务端(Cookie保存在客户端), 所以Session的安全性比较高;
    • Session保存的内容是Object类型的, 而Cookie保存的内容是String类型的

5. application : 全局对象

  • 常用方法:
    • String getContextPath() : 获取虚拟路径
    • String getRealPath(String name) : 获取虚拟路径相对的绝对路径

6. config : 配置对象(服务器配置信息)

7. page : 当前JSP页面对象(相当于Java中的this)

8. exception : 异常对象

9. pageContext : JSP页面容器


JSP四种范围对象(小 --> 大)

  • pageContext : JSP页面容器 ; 范围是: 当前页面有效(页面跳转后无效)
  • request : 请求对象 ; 范围是: 同一次请求有效(请求跳转有效, 但是重定向后会无效(重定向有两次请求,而不是同一次))
  • session : 会话对象 ; 范围是: 同一次会话有效(无论怎么跳转都有些, 关闭/切换浏览器后无效(从登录到退出期间都有效))
  • application : 全局对象 ; 范围是: 全局有效,整个项目运行期间有效(切换浏览器任然有效), 关闭服务, 其他项目无效 (多个项目共享, 重启任然有效:JNDI)
  • 以上4个对象共有的方法:
    • Object getAttribute(String name): 根据属性名, 返回属性值
    • void setAttribute(String name, Object obj) : 设置属性值(新增, 修改)
    • void setAttribute("a", "b") : 如果a对象之前不存在, 则新建一个a对象; 如果a之前存在, 则将a的值改为b
    • void removeAttribute(String name) : 根据属性名, 删除对象
  • 以上4个对象, 通过 setAttribute()赋值, 通过 getAttribute()取值,
  • 以上4个范围对象, 尽量使用最小的范围(范围越大, 开销越大)

JDBC : Java Database Connectivity(java数据库连接)

JDBC API 主要功能:

  • DriverManager: 管理jdbc驱动
  • Connection: 获取数据库连接
  • Statement(PreparedStatement) : 增删改查
  • CallableStatement : 调用数据库中的存储过程/存储函数
  • ResultSet : 返回的结果集

JDBC 访问数据库的过程:

  • 加载具体的数据库驱动驱动类
  • 与数据库建立连接
  • 发送sql, 执行
  • 处理结果集(查询)

数据库驱动

  • (数据库名)Oracle;(驱动jar) ojdbc-x.jar; (具体驱动类)oracle.jdbc.OracleDriver; (连接字符串)jdbc:oracle:thin:@localhost:1521:ORCL
  • (数据库名)MySQL;(驱动jar) mysql-cinnector-java-x.jar; (具体驱动类)com.mysql.cj.jdbc.Driver; (连接字符串)jdbc:mysql://localhost:3306/数据库实例名
  • (数据库名)SqlServer;(驱动jar) sqljdbc-x.jar; (具体驱动类)com.microsoft.sqlserver.jdbc.SQLServerDriver; (连接字符串)jdbc:microsoft:sqlserver:localhost:1433;databasename=数据库实例名
  • 使用JDBC操作数据库时, 如果对数据库进行了更换, 只需要更换具体的驱动类, 连接字符串, 用户名,密码即可

Connection 产生操作数据库的对象

  • Connection 产生 Statement对象: createStatement()
  • Connection 产生 preparedStatement对象(预编译的SQL): prepareStatement()
  • Connection 产生 CallableStatement对象(调用存储过程): prepareCall()

Statement操作数据库

  • 增删改 : executeUpdate()
  • 查询: executeQuery()

ResultSet : 保存结果集

  • next()光标下移, 查看是否有下一条数据
  • previous() 光标上移
  • getXxx(字段名|位置): 获取字段值

preparedStatement 操作数据库

  • public interface PreparedStatement extends Statement; 所以 Statement 有的方法他都有, 包括 executeUpdate 和 executeQuery
  • PreparedStatement 比父类的强大之处在于有一堆 setXxx() 的赋值语句

PreparedStatement 和 Statement 在使用时的区别:

  • 都可以进行增删改查
  • Statement: stmt.executeUpdate(sql);
  • PreparedStatement: sql(可能存在占位符?); 在创建 PreparedStatement 对象时, 将sql预编译 prepareStatement(sql); 执行时 pstmt.executeUpdate();
  • PreparedStatement 使用起来编码更加简便, 避免了字符串的拼接
  • 提高性能
  • 安全, 可以有效防止SQL注入(Statement有SQL注入的可能)例如输入: abc ' or 1=1 --

CallableStatement: 调用数据库中的存储过程/存储函数

  • 调用方法: connection.prepareCall(参数:存储过程或存储函数)

    • 参数格式:
      • 存储过程(无返回值return, 用 Out参数代替): {call 存储过程名(参数列表)}
      • 存储函数(有返回值): {? = call 存储函数名(参数列表)}
  • JDBC 调用存储过程的步骤

    • 产生调用存储过程的对象(CallableStatement) cstmt = connection.prepareCall("...");
    • 通过 setXxx()处理输出参数值 cstmt.setInt(1, 30);
    • 通过 registerOutParameter(...) 设置输出参数的类型
    • 通过 cstmt.execute() 执行
    • 通过 getXxx() 接受输出值 (cstmt.execute()的返回值)
  • JDBC 调用存储函数: 与调用存储过程的区别

    • 区别1主要是在存储函数中第一个问号是用于接收数据的参数

处理 CLOB(Oracle),TEXT(MySQL)

  • 处理稍大型的数据
  • 存储路径 如(D盘下的一本大型小说): path = D:\all.txt; 通过 JDBC 存储文件的路径; 获取时获取该路径,然后根据IO操作处理
  • 存 (oracle)数据库中 CLOB 类型
    • 先通过 PreparedStatement 预编译的 ? 代替小说的内容(占位符)
    • 再通过 pstmt.setCharacterStream(2,reader, (int)file.length());将上一步的?替换为小说流
  • 取 (oracle)数据库中 CLOB 类型
    • 通过 Reader reader = rs.getCharacterStream("NOVEL"); 将clob类型的数据保存到Reader对象中
    • 将Reader通过Writer输出即可

处理 BLOB 二进制类型

  • 存取与CLOB步骤基本一致, 主要区别:
    • BLOB 存取用的方法是: setBinaryStream/getBinaryStream
    • BLOB 因为存取的是二进制文件,所以用字节流; 而 CLOB/TEXT因为是操作字符, 所以采用的都是字符流

JSP 访问数据库(一般不用)

  • jsp 就是在html中嵌套的Java代码, 因此 Java 代码可以写在jsp中(<% Java代码 %>)
  • 导包操作: 将需要的jar复制到WEB-INF/lib目录中即可(前提是在新建项目时指定了WEB-INF/lib为JARs目录 )
  • 将普通的Java代码整合到jsp中:
      1. 注意导包: 在 这个page标签中导包 <%@ page import="java.sql.*"%>
      1. 普通Java代码中的访问修饰符在JSP代码中不可用, 需要将普通代码中的访问修饰符删除

JavaBean

  1. 刚才将 jsp 中的登录操作Java代码转移到 LoginDao.java中, 其中LoginDao类就称之为 JavaBean
  2. JavaBean的作用:
    • 减轻jsp的复杂度
    • 简化代码. 提高代码的复用(以后任何地方的登录操作, 都可以调用LoginDao实体类)
  3. JavaBean(就是一个Java类)的定义(需满足一下两点):
    • public 修饰的类, public 无参构造
    • 所有属性(如果有)都是private修饰的, 并且属性提供了 setter/getter方法.(如果是boolean, 则getter 可以替换成 is)
  4. 使用层面, JavaBean分为两大类:
    • 封装业务逻辑的JavaBean (如: LoginDao.java) 封装逻辑
      • 可以将jsp中的JDBC代码,封装到Login.java类中(Login.java)
      • 封装业务逻辑的JavaBean用于操作一个封装数据的JavaBean
    • 封装数据的JavaBean (如: Person.java, student.java) 封装数据
      • 对用的是数据库中的一张表, Login login = new Login(name, pwd); 即用Login对象封装了2个数据(用户名, 密码)

Servlet 认识

java 类必须符合一定的规范,才是Servlet:

  • 必须继承 javax.servlet.http.HeetServlet
  • 必须重写其中的 doGet() 或 doPost() 方法
    • doGet(): 接受并处理所有get提交方式的请求
    • doPost(): 接受并处理是所有post提交方式的请求
  • 配置 web.xml

Servlet 配置

  • servlet:2.5 配置 web.xml的 两个标签
    • 需要注意: 起的作用是中间的桥梁, 获取请求后, 会根据 找到对应的 对应的 Servlet 服务; 所以中的两个标签的值必须一致
  • servlet:3.0 配置 @webServlet注解 或者配置 web.xml的 两个标签;
    • 需要注意: web.xml 中的配置会覆盖了 @WebServlet 中的配置
  • servlet2.5 请求流程: 请求 --> -(根据)-> 中的 -(匹配)-> 中的 -(寻找到)-> -(将请求交由)-> 对应的 servlet响应服务
  • servlet3.0 请求流程: 请求地址和 @WebServlet 注解中的 urlPattern值匹配, 如果匹配成功, 则说明请求的就是该注解所对应的类

Servlet 生命周期, 5个阶段

  • 加载 --> 初始化(init()) --> 服务(service()) --> 销毁(destroy()) --> 卸载
  • 加载及卸载: 是Servlet容器自动处理, 不需要程序员干预;
  • 初始化: init(), 该方法会在 Servlet 被加载并实例化时执行; 该方法只会在Servlet第一次被加载时执行(且只执行一次)
    • 但是servlet2.5可以通过配置 web.xml(标签中添加: 1 ), 将init() 方法修改为开启Tomcat服务就被加载; (其中的数字1,代表的是多个Servlet的执行顺序)
    • 在 servlet3.0 版本中通过在 @WebServlet注解中添加 loadOnStartup = 1 来指定在开启Tomcat服务即加载该Servlet的init()初始化服务; (其中的数字1,代表的是多个Servlet的执行顺序)
  • 服务: service(); doGet(), doPost(); 调用几次就执行多少次
  • 销毁: destroy() Servlet被系统(JVM)回收时执行

Servlet API

Servlet 软件包

  1. Servlet 有两个软件包; 对应于HTTP协议的软件包 HTTPServlet, 对应于非HTTP协议的其他软件包
  2. 需要关注学习的是: 位于 javax.servlet.http 包下的类和接口, 是基础的HTTP协议

Servlet 继承关系

  1. 自定义Servlet类 -(继承了)-> HttpServlet类 -(继承了)-> GenericServlet抽象类 -(实现了)-> Servlet接口, ServletConfig接口, Serializable接口

  2. ServletConfig:接口

    • getServletName() : 获取Servlet的名字
    • getServletContext() : 获取Servlet上下文对象, 该方法的返回值就可用于创建 application 对象
    • getInitParameter(String name) : 在当前Servlet范围内, 获取名为 name 的参数值(初始化参数);
      • (2.5版本)其值是在 web.xml 中的 标签中通过 XXX-NameXXX-Value 的方式设置的
      • (3.0版本) 在 @WebServlet注解中通过 initParams={@WebInitParam(name="ServletParamName30", value="ServletParamValue30")} 指定; 此注解只属于这个Servlet, 而不是Web容器
  3. ServletContext 接口中常用的方法

    • getRealPath(): 获取绝对路径
    • getContextPath() : 获取相对路径
    • setAttribute()/getAttribute()
    • getInitParameter(String name); (ServletConfig接口和ServletContext接口中均出现的方法, 前者范围小于后者) ; 在 Web容器范围内, 获取名为 name 的参数值(初始化参数);
      • (2.5版本)其值是在 web.xml 中在 标签中, 通过 XXX-NameXXX-Value 的方式设置的
  4. HttpServlet 类

    • HttpServletRequest中的方法:(同 jsp内置对象中的 Request中的方法) 如: setAttribute(), getCookies(), getMethod()
    • HttpServletResponse中的方法:(同 jsp内置对象中的 Response中的方法)
  5. 继承关系说明

    • Servlet 接口中的方法都是抽象方法
    • GenericServlet 抽象类是对Servlet类的简单实现或空实现
    • 空实现的好处就是如果只需要接口的某一个两个功能, 则可以继承其空实现类, 而重写所需要的方法即可, 别的不需要的方法就不需要实现(因为在空实现类里面已经实现过了)

MVC设计模式

MVC 概念

  • M : Model, 模型: 一个功能
  • V : View , 视图: 前端页面(可用html, css, js, jsp等前端技术实现), 用于展示, 以及与用户交互
  • C : Controller, 控制器: 接受请求, 将请求跳转到模型进行处理; 处理完毕之后, 再将处理结果返回给请求处; 一般用Servlet实现控制器

MVC 案例

  • View视图层: login.jsp
  • Controller控制器: LoginController.java 登录控制器
  • Model模型层: LoginDao.java
  • 在 login.jsp 中获取到Uname和Upwd之后, 在 Login.java(对应的是数据库中的login表), 将Uname和Upwd组装成Login实例, 在LoginServlet中将Login实例传递给模型LoginDao, LoginDao执行后, 再将请求结果返回给LoginServlet, LoginServlet根据LoginDao返回的结果, 再跳转对应的页面

三层架构

  1. 三层架构和MVC设计模式的目标一致, 都是为了解耦, 提高代码的复用; 二者的区别是对项目的理解角度不同
  2. 三层的组成
    • 表示层(USL, User Show Layer; 称之为视图层)
      • 前台: 对应于MVC中的View, 用于和用户交互, 界面显示 (JSP, JS, CSS, Jquery等等实现) 代码位置位于 web 里面
      • 后台: 对应于MVC中的Controller, 用于控制跳转, 调用业务部逻辑层 (servlet, SpringMVC, Struts2) 位于 src下面的 xxx.servlet包
    • 业务逻辑层(BLL, Business Logic Layer; 称之为 service层)
      • 用于接收表示层的请求调用
      • 组装数据访问层, 带有逻辑性的操作(增删改查, 删=查+删)
      • 代码一般位于 xxx.service包中 (也叫, xxx.manager, xx.bll等等)
    • 数据访问层(DAL, Data Access Layer; 称之为 Dao层)
      • 直接访问数据库的操作, 原子性的操作(增删改查)
      • 代码一般位于 Xxx.Dao包中
  3. 三层的关系是:
    • 上层将请求传递给下层, 下层处理后, 返回给上层
    • 上层依赖于下层, (依赖: 代码理解就是: 持有成员变量(Servlet 持有 Service的成员变量, 所以可以说Servlet依赖于Service); 或者理解为: 有A的前提是必须现有B(先有数据库,才可能有Dao层, 所以可以说 Dao依赖于数据库 ))
    • 一般情况下, 一个servlet最好对应一个功能, 如果是增删改查, 最好写4个Servlet

三层优化 - 加入接口

  1. 加入接口: 面向接口编程; 即 先接口再实现类
  2. 接口实现类命名规则
    • 接口(Interface): 命名: IStudentService, IStudentDao (I + 实体类 + 层名称); 接口所在的包: xxx.service
    • 实现类(Implements): 命名: StudentServiceImpl, StudentDaoImpl (实体类 + 层名称 + Impl); 实现类所在的包: xxx.service.impl
  3. 实施以上措施后, 在调用实现类实例时, 采用多态的形式: 接口 x = new 实现类();

三层优化 - 加入 DBUtils

  1. DButis 通用的数据库帮助类, 可以(减少代码冗余)简化Dao层的代码量
  2. 使用的方法就是 "方法的重构", 将写好的代码中的相同的, 可以提炼出来的代码, 提炼出来, 单独写一个方法

分页

  1. 分页的实现是靠SQL的, 要实现分页, 就要知道从哪里开始, 到哪里结束

# JavaWeb