> 文档中心 > 看完这一篇你还不会JavaWeb吗

看完这一篇你还不会JavaWeb吗


JavaWeb

引言

相信小伙伴们大学实训的时候,会有过javaweb实训吧,反正我们那是,在没学习各大框架之前,我们都是学习javaweb的原生操作,相信原生基础好了,后面框架无非是提供便利,所以这里对javaweb的知识做一个个人小总结,这里就开始分段去更新了,希望小伙伴可以跟上,只要跟上了,那就可以说可以了,趋于保姆级教程了,所以希望对你有帮助,那看到这里先给博主点个赞吧,看完你再决定关不关注下博主;

目录结构

在这里插入图片描述

介绍

来自百度百科:

在这里插入图片描述

javaweb就是写服务端小程序嘛,我们知道前台没办法写java,那么很多逻辑处理就没办法,就需要与后端交互,交互的同时我们还要保存数据,好像一个桥梁,只是在其中做了逻辑处理,然后拿到数据库数据与前台人员进行交互,那这其实也是mvc的概念,M(model模型,数据层) 、V(view视图,交互层)、C(controller控制,逻辑处理层),后面我们再理解下这个概念;

环境准备

Tomcat

下载安装

官网:https://tomcat.apache.org/,apache下开源产品;这里我们使用tomcat9

在这里插入图片描述

在这里插入图片描述

解压安装即可;

认识tomcat

我这里已经解压了,一个是源码文件,一个是运行文件,这里就不看源码了

在这里插入图片描述

认识一下tomcat结构

在这里插入图片描述

修改端口:进入conf目录,里面server.xml,默认8080,我这里修改成了8081

在这里插入图片描述

启动

进入tomcat的bin目录,输入startup.bat,即可启动tomcat

在这里插入图片描述

这时候我们浏览器访问:http://localhost:8081/,ok访问成功

在这里插入图片描述

Eclipse

下载安装

官网:http://www.eclipse.org/downloads/

不过多细说了,安装很简单,同样解压即可使用,如果还是不会的可以去搜一下图文教程,这里我们主要使用;

还有jdk,如果没有装jdk,那也赶紧先下载一个jdk安装上;

环境准备

Eclipse配置tomcat

导航栏windows——》 preferences :

在这里插入图片描述

选择自己的tomcat版本:

在这里插入图片描述

在这里插入图片描述

Finish完成添加:

找到控制台server

在这里插入图片描述

如果没有server,这样找到server

在这里插入图片描述

选择other,server –> ok即可:

在这里插入图片描述

双击server,设置完一定要ctrl+s保存下:

在这里插入图片描述

**eclipse启动tomcat:**右键运行start
在这里插入图片描述

配置完成

这时候我们浏览器访问:http://localhost:8081/,ok访问成功
在这里插入图片描述

新建web项目

安装好eclipes,后双击打开,
在这里插入图片描述

设置下编码为utf-8:

在这里插入图片描述

在这里插入图片描述

右键新建一个web项目,这里没有我们选择other

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

接下来两次next

在这里插入图片描述

在这里插入图片描述

到这里一个项目就新建好了,检查下,是否使用的是你自己的jdk和tomcat,这样我们就新建好一个web项目了

在这里插入图片描述

HelloWorld

这里我们新建一个jsp页面,里面输入HelloWorld

在这里插入图片描述

点到项目,右键运行:

在这里插入图片描述

测试成功,这样我们就可以在目录下编写代码了:

在这里插入图片描述

mysql

官网:https://www.mysql.com/

不过多介绍了,看下百度百科的介绍吧:

在这里插入图片描述

下载安装

在这里插入图片描述

我们使用mysql8
在这里插入图片描述

选择安装包:

在这里插入图片描述

在这里插入图片描述

这里就不带着大家安装了,网上教程很多,安装一个软件嘛,不过这里还是看着别人的图文教程一步步走比较好,现在马上去搜索一个安装教程,安装完后马上回来哦!

再说一下,万事开头配置难,配好一个环境对开发很重要,并且在配置环境的时候可能会出各种错,如果解决不了,大家换mysql版本,多重装几次,一定可以的

navicat

navicat是用来管理数据的一个可视化工具:我们不想用命令行一直在操作mysql,所以我们使用一个可视化工具更方便管理数据库;

当然这个软件是收费的,我们可以破解一下,这里大家也可以使用别的可视化工具,sqlyog都可以;

这里还是不带着大家安装了,教程很多,很简单大家快去安装上,这里给大家方便,随便找了一个教程,还是那句话,都不难很简单,快去安装完回来:http://t.zoukankan.com/yudx-p-13522514.html

JDBC

你回来了,那么我们就正式进入JDBC的学习了,什么是JDBC,来自百度百科:

在这里插入图片描述

其实就是java操作数据库的规范,我们知道关系型数据库有很,比如sqlserver 、mysql等等,但是不同的数据库我们不能去学很多种操作吧,这个时候为方便就可以规定了jdbc协议,我们使用不同的数据库,只需要切换数据源就可以,操作规范都是一样的,当然就省事了;

那对于Jdbc我们学习这么几个,连接数据库,操作数据库,断开数据库,直接上代码,连接数据库:

环境准备

在使用jdbc之前我们首先引入mysql驱动依赖:

<dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <version>8.0.28</version></dependency>

数据库表,我这里创建了一张货物表(Good):

/*Navicat MySQL Data TransferSource Server  : 本机Source Server Version : 80011Source Host    : localhost:3306Source Database: huanyingTarget Server Type    : MYSQLTarget Server Version : 80011File Encoding  : 65001Date: 2022-04-20 17:36:09*/SET FOREIGN_KEY_CHECKS=0;-- ------------------------------ Table structure for good-- ----------------------------DROP TABLE IF EXISTS `good`;CREATE TABLE `good` (  `goodid` int(20) NOT NULL,  `goodname` varchar(20) NOT NULL,  `surplus` int(20) NOT NULL,  `goodprice` int(20) NOT NULL,  PRIMARY KEY (`goodid`)) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;-- ------------------------------ Records of good-- ----------------------------INSERT INTO `good` VALUES ('1', '黑色森林', '2', '15');INSERT INTO `good` VALUES ('2', '奶油蛋糕', '3', '13');INSERT INTO `good` VALUES ('3', '水果蛋糕', '2', '11');INSERT INTO `good` VALUES ('4', '冰淇凌蛋糕', '5', '13');INSERT INTO `good` VALUES ('9', '牛奶蛋糕', '34', '9');INSERT INTO `good` VALUES ('11', '肉松蛋糕', '13', '13');

查询操作

package dao;import java.sql.*;public class TesterDao {public static void main(String[] args) {// 四大属性String DRIVER = "com.mysql.jdbc.Driver";String URL = "jdbc:mysql://localhost:3306/huanying?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8";String NAME = "root";String PASSWORD = "root";// 三大对象Connection con = null; // 连接对象PreparedStatement ps = null; // sql语句编译对象ResultSet rs = null; // 返回结果集,查询对象// 连接mysqltry {Class.forName(DRIVER);con = DriverManager.getConnection(URL, NAME, PASSWORD);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}// 编译sqly语句,String sql = "select * from good";try {     //con.createStatement()  这个不安全,不使用// 从数据库连接中获取sql语句ps = con.prepareStatement(sql);// 执行sql,并返回结果集,查询使用executeQuery,增删改使用executeUpdaters = ps.executeQuery();// 便利取出结果集内容while (rs.next()) {System.out.println(rs.getInt(1)); // 从第一列返回System.out.println(rs.getString(2)); // 从第二列返回System.out.println(rs.getInt(3)); // 从第三列返回System.out.println(rs.getInt(4)); // 从第四列返回}} catch (Exception e) {e.printStackTrace();}// 最后关闭连接,从123=》321这个顺序关闭try {if (rs != null) {rs.close();}if (ps != null) {ps.close();}if (con != null) {con.close();}} catch (Exception e) {}}}

结果

在这里插入图片描述

增删改操作

修改我们只需要改变一个地方就可以,1、连接数据库,2、执行sql,返回结果,3、关闭连接,这里我们主要做第三步

package dao;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;public class Test02Dao {public static void main(String[] args) {// 四大属性String DRIVER = "com.mysql.jdbc.Driver";String URL = "jdbc:mysql://localhost:3306/huanying?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8";String NAME = "root";String PASSWORD = "root";// 三大对象Connection con = null; // 连接对象PreparedStatement ps = null; // sql语句编译对象ResultSet rs = null; // 返回结果集,查询对象// 连接mysqltry {Class.forName(DRIVER);con = DriverManager.getConnection(URL, NAME, PASSWORD);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}// 编译sql语句,添加一条数据String sql = "insert into good values(12,'草莓蛋糕',22,23)";try {// 从数据库连接中获取sql语句ps = con.prepareStatement(sql);// 执行sql,并返回结果集,查询使用executeQuery,增删改使用executeUpdate//增删改只返回影响数据库行数,int类型     //删改都一样,尝试下int result = ps.executeUpdate();if (result==1) {System.out.println("添加数据成功");}} catch (Exception e) {e.printStackTrace();}// 最后关闭连接,从123=》321这个顺序关闭try {if (rs != null) {rs.close();}if (ps != null) {ps.close();}if (con != null) {con.close();}} catch (Exception e) {}}}

运行结果

在这里插入图片描述

sql预编译

那我们知道,这样写的sql语句是死的,但是如果靠字符串拼接,那就很不安全,这里我们可以实现sql预编译,通过?去充当占位符那样,这里我们使用修改尝试下

package dao;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;public class Testr03Dao {public static void main(String[] args) {// 四大属性String DRIVER = "com.mysql.jdbc.Driver";String URL = "jdbc:mysql://localhost:3306/huanying?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8";String NAME = "root";String PASSWORD = "root";// 三大对象Connection con = null; // 连接对象PreparedStatement ps = null; // sql语句编译对象ResultSet rs = null; // 返回结果集,查询对象// 连接mysqltry {Class.forName(DRIVER);con = DriverManager.getConnection(URL, NAME, PASSWORD);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}// 编译sql语句,添加一条数据String sql = "update good set surplus=? where goodid=?";//两个从前台获取的值int surplus=99;int goodid=12;try {// 从数据库连接中获取sql语句ps = con.prepareStatement(sql);//补充完sql ps.setObject(1, surplus);   //点一下,有很多set类型的方法object更好用,两个参数:从左开始第几个?,填充什么值ps.setObject(2, goodid);// 执行sql,并返回结果集,查询使用executeQuery,增删改使用executeUpdate//增删改只返回影响数据库行数,int类型int result = ps.executeUpdate();if (result==1) {System.out.println("修改数据成功");}} catch (Exception e) {e.printStackTrace();}// 最后关闭连接,从123=》321这个顺序关闭try {if (rs != null) {rs.close();}if (ps != null) {ps.close();}if (con != null) {con.close();}} catch (Exception e) {}}}

运行结果

在这里插入图片描述

DBCP

介绍

来自百度百科:

在这里插入图片描述

dbcp大家不要多想就是一个池化概念,什么操作都没有,就是在jdbc的基础上多了些配置;

池化技术,百度百科介绍的很清楚,这里我在给大家比喻一下,好比你从一个房间进出,你需要不停的开关门,进出很多次是不是很烦,麻烦对不对,这个时候如果我们一直把门打开这,这样进出不就很方便,门卫,对吧,他会自己去管理这门什么时候关什么时候一直开着,所以就很方便,不理解没关系,不妨碍我们使用;

环境改变

想要使用dbcp需要导入dbcp的三个jar包:

commons-pool2-2.4.2.jar,commons-logging-1.2.jar,commons-dbcp2-2.1.1.jar

maven依赖:

<dependency>    <groupId>org.apache.commons</groupId>    <artifactId>commons-dbcp2</artifactId>    <version>${dbcp.version}</version></dependency> 

创建一个配置文件,db.properties文件用来放置配置文件,这个是在网上找的比较全点

#全部的参数参考,org.apache.commons.dbcp2.BasicDataSourceFactory里的ALL_PROPERTIESdriverClassName=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://localhost:3306/huanying?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8username=rootpassword= #initialSize=10#最大连接数量maxActive=50#maxIdle=20#minIdle=5#maxWait=60000#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。connectionProperties=useUnicode=true;characterEncoding=gbk;serverTimezone=UTC#指定由连接池所创建的连接的自动提交(auto-commit)状态。defaultAutoCommit=true#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLEdefaultTransactionIsolation=READ_UNCOMMITTED

创建DBCPUtil工具类:

package dao;import java.io.IOException;import java.io.InputStream;import java.sql.*;import java.util.Properties;import javax.sql.DataSource;import org.apache.commons.dbcp.BasicDataSourceFactory;// 封装Dbcp连接池的工具类// 该类中提供了获取连接和关闭连接的通用方法public class DbcpUtil {// 使用ThreadLocal来解决线程同步问题.让每个线程都使用各自的连接对象// ThreadLocal 可以被其中的Connection做成多份拷贝,让不同的线程对象使用private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();private static DataSource ds;// 静态代码块会在程序执行之前,被自动的执行static {InputStream in = DbcpUtil.class.getResourceAsStream("db.properties");Properties pro = new Properties();try {pro.load(in);ds = BasicDataSourceFactory.createDataSource(pro);} catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}}// 获取连接的时候,把方法中遇到的异常通过throws抛出去,抛给调用者// 通过异常在方法和方法之间传递信息public static Connection getConnectio() throws SQLException {Connection conn = threadLocal.get();// 如果conn等于null 或者conn已经关闭if (conn == null || conn.isClosed()) {conn = ds.getConnection();threadLocal.set(conn);}return conn;}public static void closeConnection() {Connection conn = threadLocal.get();try {// 如果conn不等于null 并且没有关闭if (conn != null && !conn.isClosed()) {conn.close();}} catch (SQLException e) {e.printStackTrace();} finally {threadLocal.set(null);}}}

查询数据库

package dao;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;public class DBCPTester01 {public static void main(String[] args) {// 三大对象Connection con = null; // 连接对象PreparedStatement ps = null; // sql语句编译对象ResultSet rs = null; // 返回结果集,查询对象try {// 1、获取连接对象,改变了con = DbcpUtil.getConnectio();// 2、执行sql语句// 编译sql语句,String sql = "select * from good";// 从数据库连接中获取sql语句ps = con.prepareStatement(sql);// 执行sql,并返回结果集,查询使用executeQuery,增删改使用executeUpdaters = ps.executeQuery();// 便利取出结果集内容while (rs.next()) {System.out.println(rs.getInt(1)); // 从第一列返回System.out.println(rs.getString(2)); // 从第二列返回System.out.println(rs.getInt(3)); // 从第三列返回System.out.println(rs.getInt(4)); // 从第四列返回}} catch (Exception e) {e.printStackTrace();}finally {//最后关闭连接,这里的关闭不是像jdbc直接关闭了连接,而是放回来连接池DbcpUtil.closeConnection();}}}

运行结果

在这里插入图片描述

我们发现知识配置一下,知识获得连接和关闭连接的方式变了,其他都没变,很简单吧,主要就是理解其中的概念;

BaseDao

我们发现是不是操作数据库总共分三步:1、连接mysql 2、执行sql语句 3、关闭数据库,我们发现其实只有第二步是我们一直使用在改变的,第一步和第三步,完全没有必要每次写那么多代码,我们都是学过面向对象的人了,那我们就可以对这个进行下简单的封装;

对于增删改方法,我们发现PreparedStatement对象都是执行的executeUpdate方法,那就简单了,增删改我们封装到一个方法里:

public int executeUpdate(String sql, Object... param) {    int result = 0;    try { // 通过DBCP获取连接 con = DbcpUtil.getConnectio(); ps = con.prepareStatement(sql); for (int i = 0; i < param.length; i++) {     ps.setObject(i, param[i]); } result = ps.executeUpdate();    } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace();    } finally { DbcpUtil.closeConnection();    }    return result;}

但是对于查询方法的封装,对于每张表都不一样,通过sql查询的结果也都不一样,所以我们封装到sql执行把ResultSet结果集对象交出给自定义处理:

public ResultSet executeQuery(String sql, Object... param) {try {con = DbcpUtil.getConnectio();ps = con.prepareStatement(sql);if (param != null) {for (int i = 0; i < param.length; i++) {ps.setObject(i++, param[i]);}}rs = ps.executeQuery();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}return rs;}

为什么这里我没有关闭连接呢,你尝试下,是不是就报错了,因为数据都在ResultSet对象里,如果我们关闭连接就是ResultSet关闭,当然会报错,并且数据取不出来;

在这里插入图片描述

最后完整的BaseDao,我这里只是做了下简单的封装,并且可能也封装的不好,大家根据自己的业务,到时候会有更好的封装,方便使用:

package Dao;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;public class BaseDao {// 三大对象Connection con = null; // 连接对象PreparedStatement ps = null; // sql语句编译对象ResultSet rs = null; // 返回结果集,查询对象public int executeUpdate(String sql, Object... param) {int result = 0;try {// 通过DBCP获取连接con = DbcpUtil.getConnectio();ps = con.prepareStatement(sql);for (int i = 0; i < param.length; i++) {ps.setObject(i+1, param[i]);}result = ps.executeUpdate();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {DbcpUtil.closeConnection();}return result;}public ResultSet executeQuery(String sql, Object... param) {try {con = DbcpUtil.getConnectio();ps = con.prepareStatement(sql);if (param != null) {for (int i = 0; i < param.length; i++) {ps.setObject(i++, param[i]);}}rs = ps.executeQuery();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}return rs;}}

测试

package Dao;import java.sql.SQLException;import java.util.ArrayList;import java.util.List;public class Test01Dao extends BaseDao {// 修改一个good的名字public int updateTest(String sql, String goodName, int goodId) {return this.executeUpdate(sql, goodName, goodId);}// 查询allpublic void queryTest(String sql) {this.executeQuery(sql);try {while (rs.next()) {System.out.println(rs.getInt(1)); // 从第一列返回System.out.println(rs.getString(2)); // 从第二列返回System.out.println(rs.getInt(3)); // 从第三列返回System.out.println(rs.getInt(4)); // 从第四列返回}} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}finally{DbcpUtil.closeConnection();}}public static void main(String[] args) {Test01Dao test01Dao = new Test01Dao();// test01Dao.queryTest("select * from good");int res = test01Dao.updateTest("update good set goodname=? where goodid=?", "hello", 17);if (res == 1) {System.out.println("修改成功");}}}

查询全部结果,测试成功:

在这里插入图片描述

执行修改,这里只是执行了修改,删除和增加还用演示吗,不用了吧:

结果,测试成功:

在这里插入图片描述

MVC

介绍

mvc同样也是一种概念,来自百度百科:

在这里插入图片描述

其实就是下面的逻辑图

在这里插入图片描述

在编码上我们将自己的代码也做成了这层;

Servlet

介绍,来自百度百科:

在这里插入图片描述

服务器程序,servlet只是一直接口,凡是继承了这个接口的都是servlet,但是我们一般只使用httpServlet进行http请求,所以现在一般说的servlet指的是继承了httpServlet的类,我们可以写程序通过servlet映射出去可以访问;

在这里插入图片描述

下面我们来了解下servlet;

生命周期

首先就是servlet生命周期:

在这里插入图片描述

三个生命周期,init()、service()、destroy();

init() 方法

init 方法被设计成只调用一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。因此,它是用于一次性初始化,就像 Applet 的 init 方法一样。

service() 方法

service() 方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。

destroy() 方法

destroy() 方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。

在调用 destroy() 方法之后,servlet 对象被标记为垃圾回收。destroy 方法定义如下所示:

  public void destroy() {    // 终止化代码...  }

那这里我们主要使用service(),但是我们不去实现这个方法,而是通过service() 方法由容器调用,service 方法在适当的时候调用 doGet、doPost、doPut、doDelete 等方法。所以,您不用对 service() 方法做任何动作,您只需要根据来自客户端的请求类型来重写 doGet() 或 doPost() 即可。

我们主要实现doGet和doPos方法体,接收用户请求;

快速上手

我们来快速新建一个servlet来看看,右键新建servlet:

在这里插入图片描述

那一个servlet我们就创建好了,这是代码自己生成的,我们来看下他给我们创建的都是什么代码以及怎么使用:

在这里插入图片描述

package servlet;import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** * Servlet implementation class HelloServlet * 这里的注解,就是我们的请求路径 */@WebServlet("/HelloServlet")//继承HttpServlet ,而HttpServlet实现了servlet接口public class HelloServlet extends HttpServlet { //序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。private static final long serialVersionUID = 1L;    /**     * @see HttpServlet#HttpServlet()     */    public HelloServlet() { //构造方法 super();    }/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) * doGet方法体 */protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// TODO Auto-generated method stub //默认给请求的响应,这里我们注释了,写我们自己的响应//response.getWriter().append("Served at: ").append(request.getContextPath()); response.getWriter().print("hello World");}/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) * doPost方法体 */protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// TODO Auto-generated method stub //调用了doGet方法,这里我们可以这么使用,这样我们都使用doPostdoGet(request, response);}}

我们运行项目并访问HelloServlet请求,ok测试成功:

在这里插入图片描述

这是注解的方式配置请求路径,我们可以通过web.xml配置请求路径:

我们新建项目时的web.xml

在这里插入图片描述

打开,我们可以通过xml配置请求路径:

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://xmlns.jcp.org/xml/ns/javaee"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"id="WebApp_ID" version="3.1"><display-name>xuexiriji</display-name><welcome-file-list><welcome-file>index.html</welcome-file><welcome-file>index.htm</welcome-file><welcome-file>index.jsp</welcome-file><welcome-file>default.html</welcome-file><welcome-file>default.htm</welcome-file><welcome-file>default.jsp</welcome-file></welcome-file-list>  <servlet>  <servlet-name>HelloServlet</servlet-name>  <servlet-class>servlet.HelloServlet</servlet-class></servlet>      <servlet-mapping>  <servlet-name>HelloServlet</servlet-name>  <url-pattern>/hello</url-pattern></servlet-mapping></web-app>

我们把WebServlet注解注释掉,再次运行,访问配置的hello路径,访问成功:

在这里插入图片描述

四大作用域

优先级从小到大

  • PageContext : 页面作用域范围有效,jsp页面内置对象
  • Requset : 一次请求作用域范围有效
  • Session :一次会话作用域范围有效
  • ServletContext :一次服务作用域,web应用加载起来,就一直在,管理多个servlet

下面总结使用方法:

其实四大作用域不管哪个对象最常用的,90%都是这三个getAttribute()、setAttribute()、removeAttribute()

PageContext

pageContext对象是javax.servlet.jsp.PageContext类的实例对象,用来代表整个JSP页面。它代表页面上下文,该对象主要用于访问 JSP 之间的共享数据,使用pageContext可以访问page、request、session、application范围的变量。

方 法 说 明
forward(java.lang.String.relativeUtlpath) 把页面转发到另一个页面
getAttribute(String name) 获取参数值
getAttributeNames(int scope) 获取某范围的参数名称的集合,返回值为java.util.Enumeration对象
getException() 返回exception对象
getRequest() 返回request对象
getResponse() 返回response对象
getSession() 返回session对象
getOut() 返回out对象
getApplication() 返回application对象
setAttribute() 为指定范围内的属性设置属性值
removeAttribute() 删除指定范围内的指定属性

在页面使用中我们很少使用,在jsp中我们再使用几次;

HttpRequest

//设置请求编码格式requset.setCharaterEncoding("utf-8");//获取请求参数requset.getParameter();//获取属性request.getAttribute();//设置属性request.setAttribute();//请求转发request.getRequestDispatcher("/login.jsp").forward(request, response);

HttpRespones

//设置响应编码response.setContentType( "text/ html; charset=utf-8");//设置响应状态重定向,一般不这么用response.setStatus(response.SC_MOVED_TEMPORARILY);response.setHeader("Location", site);    //我们通常使用这个进行重定向response.sendRedirect()// 设置刷新自动加载的事件间隔为 5 秒response.setIntHeader("Refresh", 5);//获得网页写对象PrintWriter out = response.getWriter();out.print()  //像屏幕输出数据,一般不这么用,我们后面直接用jsp

这里需要注意:

一般情况下的页面跳转就用sendRedirect , 用的不是同个request对象,而另一个则是

request.getRequestDispatcher()是请求转发,前后页面共享一个request ;response.sendRedirect()是重新定向,前后页面不是一个request。request.getRequestDispather();返回的是一个RequestDispatcher对象。RequestDispatcher.forward()是在服务器端运行;HttpServletResponse.sendRedirect()是通过向客户浏览器发送命令来完成.所以RequestDispatcher.forward()对于浏览器来说是“透明的”;而HttpServletResponse.sendRedirect()则不是。

ServletContext

servletContext,是Servlet中最大的一个接口,呈现了web应用的Servlet视图。

ServletContext实例是通过 getServletContext()方法获得的,由于HttpServlet继承GenericServlet的关系,GenericServlet类和HttpServlet类同时具有该方法。

没有太多东西,主要是还是这三个:getAttribute()、setAttribute()、removeAttribute()

Cookie

前言

因为cookie、session、token本身就是很容易混淆的东西,这里不放在一块了,所以我们先来理解下cookie是什么,怎么去简单的使用下, 看前点个赞,养成好习惯;👍

书接上回:
【JavaWeb】-Tomcat、Eclipse使用项目搭建(一)
【JavaWeb】-mysql、jdbc、dbcp使用(二)
【JavaWeb】-MVC、Servlet学习(三)

介绍

cookie是啥,来自百度百科:

在这里插入图片描述

我觉得多少都了解cookie吧,因为是不是有时候好网站都在拦截Cookie或者我们见过Cookie,在哪里呢?

打开浏览器,右键点击检查,这里存放了很多Cookie,Cookie就是与服务器沟通时候的自己带的一些信息,其实就是用户名和密码,我们判断用户是否登录,以及用户登录网站留下的信息。

这些相信大家都知道,我们通常第一次登录网站需要输入账号密码,那第二次登陆的时候就都会留下我们第一次的信息,那下面我们要说的三个东西都是实现这项功能的技术,那么我们顺其往下,我们先来学习Cookie;

在这里插入图片描述

环境准备

那这次我们做一下环境准备:大家平常都在哪里白嫖这种网页模板呀,博主反正找到的都不好看,如果有请在博主评论区里艾特我一下,在线卑微! 找一个好看的登录模板;

这里我们使用就是保存用户登录账号和名称做演示:

我们首先设置下过滤编码创建一个filter过滤全部编码为utf-8:

package filter;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.annotation.WebFilter;/** * Servlet Filter implementation class CharasetFilter */@WebFilter("/*")public class CharasetFilter implements Filter {/** * Default constructor. */public CharasetFilter() {// TODO Auto-generated constructor stub}/** * @see Filter#destroy() */public void destroy() {// TODO Auto-generated method stub}/** * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) */public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {request.setCharacterEncoding("utf-8");response.setContentType("text/html;charset=utf-8");System.out.println("经过过滤");chain.doFilter(request, response);}/** * @see Filter#init(FilterConfig) */public void init(FilterConfig fConfig) throws ServletException {// TODO Auto-generated method stub}}

我们创建一个MyLoginServlet.java:

package servlet;import java.io.IOException;import java.net.URLEncoder;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** * Servlet implementation class MyLoginServlet */@WebServlet("/MyLoginServlet")public class MyLoginServlet extends HttpServlet {private static final long serialVersionUID = 1L;/** * @see HttpServlet#HttpServlet() */public MyLoginServlet() {super();// TODO Auto-generated constructor stub}/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse *      response) */protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {//String name = ;String pass = request.getParameter("password");Cookie name = new Cookie("name", URLEncoder.encode(request.getParameter("username"), "UTF-8"));Cookie passCookie = new Cookie("pass", pass);//设置时间24小时name.setMaxAge(60*60*24);passCookie.setMaxAge(60*60*24);response.addCookie(name);response.addCookie(passCookie);response.sendRedirect("index.jsp");//request.getRequestDispatcher("ToIndexServlet").forward(request, response);}/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse *      response) */protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// TODO Auto-generated method stubdoGet(request, response);}}

我们的index.jsp页面,发现了没有我们的jsp页面可以写java代码,后面我们细说jsp:

主界面<%Cookie[] cookies = request.getCookies();if (cookies != null) {// 我们可以遍历下我们的cooike看看for (Cookie cookie : cookies) {out.println(cookie.getName() + "
");out.println(cookie.getValue() + "
");out.println("==============
");}}%>这里是主界面

这里我们不跟数据库打交道,随便的登录信息即可,登录页面登录:

在这里插入图片描述

通过开发者模式,我们查看网络请求,我们看到了服务器给我们的响应请求设置了cookie:

在这里插入图片描述

登录进入index.jsp页面,我们发现我们所有携带的cookie就都在这里了:

在这里插入图片描述

当我们关闭浏览器再次进入,我们发现我们的信息还在,因为上面我们设置了cookie有效时间为24小时,所以我们退出之后登录信息依然存在,这样通过cookie保存信息我们就完成了:

在这里插入图片描述

这里我们也同样可以根据浏览器开发者看到cookie信息:
在这里插入图片描述

这里发现我们cookie除了设置时间,还可以设置很多信息(来自菜鸟教程):

Servlet Cookie 方法

以下是在 Servlet 中操作 Cookie 时可使用的有用的方法列表。

序号 方法 & 描述
1 public void setDomain(String pattern) 该方法设置 cookie 适用的域,例如 runoob.com。
2 public String getDomain() 该方法获取 cookie 适用的域,例如 runoob.com。
3 public void setMaxAge(int expiry) 该方法设置 cookie 过期的时间(以秒为单位)。如果不这样设置,cookie 只会在当前 session 会话中持续有效。
4 public int getMaxAge() 该方法返回 cookie 的最大生存周期(以秒为单位),默认情况下,-1 表示 cookie 将持续下去,直到浏览器关闭。
5 public String getName() 该方法返回 cookie 的名称。名称在创建后不能改变。
6 public void setValue(String newValue) 该方法设置与 cookie 关联的值。
7 public String getValue() 该方法获取与 cookie 关联的值。
8 public void setPath(String uri) 该方法设置 cookie 适用的路径。如果您不指定路径,与当前页面相同目录下的(包括子目录下的)所有 URL 都会返回 cookie。
9 public String getPath() 该方法获取 cookie 适用的路径。
10 public void setSecure(boolean flag) 该方法设置布尔值,表示 cookie 是否应该只在加密的(即 SSL)连接上发送。
11 public void setComment(String purpose) 设置cookie的注释。该注释在浏览器向用户呈现 cookie 时非常有用。
12 public String getComment() 获取 cookie 的注释,如果 cookie 没有注释则返回 null。

我们也可以手动删除cookie信息:

在这里插入图片描述

这样我们的name就没有了:

在这里插入图片描述

那这里就是cookie的使用,我们发现cookie是不是有点儿不安全,可以看到账号和密码,如果这些信息被别人一下看到,或者拦截一下,那不就太明文了,对吧,通过cookie我们是将我们的存储信息交给了客户端,那我们如果交给服务端去做呢?,下面就到了session了,我们看看什么是session;

Session

HTTP 是一种"无状态"协议,这意味着每次客户端检索网页时,客户端打开一个单独的连接到 Web 服务器,服务器会自动不保留之前客户端请求的任何记录。

我们刚才已经看到了其实,我们访问一个网站其实每次都会发出一个会话也就是session,都会有一个sessionid,我们可以通过session将登录信息存储在服务端;

我们还是通过之前的方法,但是这次我们不使用cookie使用session;

// 当参数为空或者true时,获得session,若没有则新建;当参数为false时,获得session,若没有也不会新建HttpSession session = request.getSession(true); 默认是ture

修改后的MyLoginServlet.java:

package servlet;import java.io.IOException;import java.net.URLEncoder;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.annotation.WebServlet;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import com.mysql.cj.Session;/** * Servlet implementation class MyLoginServlet */@WebServlet("/MyLoginServlet")public class MyLoginServlet extends HttpServlet {private static final long serialVersionUID = 1L;/** * @see HttpServlet#HttpServlet() */public MyLoginServlet() {super();// TODO Auto-generated constructor stub}/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse *      response) */protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {/*//String name = ;String pass = request.getParameter("password");//这里可以通过URLEncoder编码Cookie name = new Cookie("name", URLEncoder.encode(request.getParameter("username"), "UTF-8"));Cookie passCookie = new Cookie("pass", pass);//设置时间24小时name.setMaxAge(60*60*24);passCookie.setMaxAge(60*60*24);response.addCookie(name);response.addCookie(passCookie);response.sendRedirect("index.jsp");//request.getRequestDispatcher("ToIndexServlet").forward(request, response);*///获取数据并存入sessionHttpSession session = request.getSession(false);session.setAttribute("name", request.getParameter("username"));session.setAttribute("pass", request.getParameter("password"));//设置sesion过期时间,默认一次会话结束,也就是浏览器关闭,设置24小时session.setMaxInactiveInterval(60*60*24);Cookie login = new Cookie("JSESSIONID", session.getId());login.setMaxAge(60*60*60);response.addCookie(login);response.sendRedirect("index.jsp");}/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse *      response) */protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// TODO Auto-generated method stubdoGet(request, response);}}

index.jsp:

主界面<%/* Cookie[] cookies = request.getCookies();if (cookies != null) {// 我们可以遍历下我们的cooike看看for (Cookie cookie : cookies) {out.println(cookie.getName() + "
");out.println(cookie.getValue() + "
");out.println("==============
");}} */out.print(session.getAttribute("name")+"
");out.print(session.getAttribute("pass")+"
");%>这里是主界面

这时候我们登录访问,登录上去之后,通过cookie报错下来JSESSIONID,这样我们将信息存储在session,用户只拥有一个session这样我们可以更好的保证安全性;

在这里插入图片描述

我们发现登录上之后sessionid就被存入到cookie,这样我们再访只需要携带sessionid交给后端,后端通过sessionid拿到session就可以拿到存储在session中的信息,这样就把东西都交给了后端,安全性就得到了提高;

我们看下session常用方法,来自菜鸟教程:

序号 方法 & 描述
1 public Object getAttribute(String name) 该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null。
2 public Enumeration getAttributeNames() 该方法返回 String 对象的枚举,String 对象包含所有绑定到该 session 会话的对象的名称。
3 public long getCreationTime() 该方法返回该 session 会话被创建的时间,自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。
4 public String getId() 该方法返回一个包含分配给该 session 会话的唯一标识符的字符串。
5 public long getLastAccessedTime() 该方法返回客户端最后一次发送与该 session 会话相关的请求的时间自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。
6 public int getMaxInactiveInterval() 该方法返回 Servlet 容器在客户端访问时保持 session 会话打开的最大时间间隔,以秒为单位。
7 public void invalidate() 该方法指示该 session 会话无效,并解除绑定到它上面的任何对象。
8 public boolean isNew() 如果客户端还不知道该 session 会话,或者如果客户选择不参入该 session 会话,则该方法返回 true。
9 public void removeAttribute(String name) 该方法将从该 session 会话移除指定名称的对象。
10 public void setAttribute(String name, Object value) 该方法使用指定的名称绑定一个对象到该 session 会话。
11 public void setMaxInactiveInterval(int interval) 该方法在 Servlet 容器指示该 session 会话无效之前,指定客户端请求之间的时间,以秒为单位。

session跟踪

这里我们session跟踪使用的是cookie,session跟踪有三种方式,如果浏览器禁用了cookie呢

隐藏表单

我们还可以使用:隐藏的表单字段、URL重写,但是都有相应的问题

<input type="hidden" name="jssesionid" value="7D8449A7EF434DE66DED63227E02AC1E">

表单提交问题;

url重写

这个是浏览器禁用cookie之后比较常用的:

例如,http://w3cschool.cc/file.htm;sessionid=12345;,session 会话标识符被附加为 sessionid=12345,标识符可被 Web 服务器访问以识别客户端。

HttpServletResponse接口定义了两个用于URL重写的方法:

encodeURL方法,用于超链接和form表单的action属性中设置的URL进行重写encodeRedirectURL 方法 用于对传递给HttpServletResponse.sendRedirect方法的URL进行重写

其实就是在末尾给你拼接了sessionid,我们访问的时候只要服务端没有清除会话也就是session这样我们通过这样的形势还是会被服务端识别,找到session就可以找到信息:

在这里插入图片描述

说完session的使用之后,那我们发现,用户不用存储任何东西,我们都session数据存储在服务器端,一个用户那不用说,但是上万个用户呢,这样我们的服务器肯定是很耗费资源的,单独拿出一台服务器存储session挂了怎么办,我们又不愿意浪费服务器怎么办;下面我们就可以提到token;

Token

介绍

目前已经有很多都用到了token,那如果我们说了解cookie和session那token绝对是个新概念,其实我也是后面才了解,而且也不是很了解,这里既然都说到了,就拿目前自己了解的说下吧;

我们首先看看token是什么(来自百度百科):

在这里插入图片描述

就是信息加密技术,我们这里叫做令牌,作为交互对话的唯一身份标识,当用户第一次过来登录过后,我们验证身份会给用户发一个令牌,也就是token;

当用户再来访问的时候,只需要携带令牌,可以存放在cookie或请求头header中,这里注意我们session是通过session跟踪只能在cookie而令牌可以放到请求头中,这样可以防止一些攻击,大家可以自行去百度更多的文章去理解,这里我也不会这些攻击,等哪天会了再来跟大家说吧,这样我们与我们这里已经存在的令牌做对比,就可以得知用户是不是合法用户;

我们发现其实token跟session差不多,那其实token也可以理解成session,但是我们可以通过加密把信息全部存储在token中,服务端只需要留下token令牌只需要进行比对,需要的时候拿出token中的信息,这样我们就可以解放了服务端,同时信息的存储又交给了客户端;哈哈,是不是来来回回,还是来来回回,大家说会不会下一个技术出现,就又还给服务端了呢;

这样token虽然省出了空间,但是又要把用户的信息解密出来又是利用了cpu,那我们知道没办法,想要时间就浪费空间,想要空间就得浪费时间,就是这样

那token怎么实现呢,其实就是把信息加密成令牌就可以了,如果自己愿意做,自己可以做,那这里我们不做,就了解下jwt;

jwt

那什么又是jwt呢?

  • JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。

  • 一个 JWT 实际上就是一个字符串,它由三部分组成,头部、载荷与签名。前两部分需要经过 Base64 编码,后一部分通过前两部分 Base64 编码后再加密而成。

三部分组成:

header(头部)

{    'typ':'jwt',    #类型是jwt    'alg':'HS256'   #加密算法,这里还有很多加密算法,大家自行了解} 

payload(载荷)

{    'sub':'42342342342',    'name':'john',    'admin':ture}iss:发行人exp:到期时间sub:主题aud:用户nbf:在此之前不可用iat:发布时间jti:JWT ID用于标识该JWT

signature(签名)

这里是我们做一个签名数据,放在我们后端,对数据进行加密,如果别别人知道了,那这些token数据无疑全部暴露;

添加依赖

<dependency>    <groupId>io.jsonwebtoken</groupId>    <artifactId>jjwt</artifactId>    <version>0.9.1</version></dependency>

编写测试代码

package Test;import java.util.Date;import java.util.UUID;import io.jsonwebtoken.*;public class TestJwt {// 这是我们后端对数据的加密签名,密钥private String signature = "xuexiriji";public String encryption() {// 通过建造者模式创建一个jwt构造器JwtBuilder jwtBuilder = Jwts.builder();// 过期时间24小时long time = 1000 * 60 * 60 * 24;// 设置头部String token = jwtBuilder// 头部header.setHeaderParam("typ", "JWT").setHeaderParam("alg", "HS256")// 载荷payload,也就是数据.claim("name", "root").claim("pass", "root")// 主题.setSubject("user")// 有效时间,重当前算起,到什么时候.setExpiration(new Date(System.currentTimeMillis() + time))// 再设置一个id.setId(UUID.randomUUID().toString())// signature,加一个签名.signWith(SignatureAlgorithm.HS256, this.signature)// 拼接.compact();System.out.println(token);return token;}public void parse(String token){JwtParser jwtParser = Jwts.parser();Jws<Claims> claimsJws =  jwtParser.setSigningKey(this.signature).parseClaimsJws(token);Claims claims = claimsJws.getBody();System.out.println(claims);//获取载荷信息System.out.println(claims.get("name"));System.out.println(claims.get("pass"));//可以获取idSystem.out.println(claims.getId());//获取subjectSystem.out.println(claims.getSubject());//有效期System.out.println(claims.getExpiration());}public static void main(String[] args) {TestJwt testJwt = new TestJwt();testJwt.parse(testJwt.encryption());}}

输出内容

我们通过生成可以看到生成的由三部分组成,通过.分隔,前两个分别对应header和payload的base64加密,最后一个是通过前两个内容加上密钥也就是signature再一次加密:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoicm9vdCIsInBhc3MiOiJyb290Iiwic3ViIjoidXNlciIsImV4cCI6MTY1MTU0ODI0MSwianRpIjoiZWYxM2RhMTktYzM1ZS00MjNiLTkxZTEtZGYxMTE5MjI4MWI0In0.Kf7pwcDhRRnhZrZRJYodDsB3DJmh05ege-Qri7iCNdc这是我们输出的数据{name=root, pass=root, sub=user, exp=1651548241, jti=ef13da19-c35e-423b-91e1-df11192281b4}rootrootef13da19-c35e-423b-91e1-df11192281b4userTue May 03 11:24:01 CST 2022

我们不妨做给简单的tokenUtil:

package util;import java.util.Date;import java.util.Map;import java.util.UUID;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jws;import io.jsonwebtoken.JwtBuilder;import io.jsonwebtoken.JwtParser;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;public class TokenUtil {// 这是我们后端对数据的加密签名,密钥private static String signature = "xuexiriji";// 过期时间24小时private static long time = 1000 * 60 * 60 * 24;public static String getToken(Map<String, Object> map) {// 通过建造者模式创建一个jwt构造器JwtBuilder jwtBuilder = Jwts.builder();// 设置头部String token = jwtBuilder// 头部header.setHeaderParam("typ", "JWT").setHeaderParam("alg", "HS256")// 载荷payload,也就是数据.setClaims(map)// 主题.setSubject("user")// 有效时间,重当前算起,到什么时候.setExpiration(new Date(System.currentTimeMillis() + time))// 再设置一个id.setId(UUID.randomUUID().toString())// signature,加一个签名.signWith(SignatureAlgorithm.HS256, signature)// 拼接.compact();return token;}public static Claims parseToken(String token) {JwtParser jwtParser = Jwts.parser();Jws<Claims> claimsJws =  jwtParser.setSigningKey(signature).parseClaimsJws(token);Claims claims = claimsJws.getBody();/*System.out.println(claims);//获取载荷信息System.out.println(claims.get("name"));System.out.println(claims.get("pass"));//可以获取idSystem.out.println(claims.getId());//获取subjectSystem.out.println(claims.getSubject());//有效期System.out.println(claims.getExpiration());*/return claims;}}

token尝试

我们了解了jwt的加密这里我们尝试下jwt对登录信息的保存,做个小例子:

改变我们的MyLoginServlet:

package servlet;import java.io.IOException;import java.net.URLEncoder;import java.util.HashMap;import java.util.Map;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.annotation.WebServlet;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.apache.commons.collections.map.HashedMap;import com.mysql.cj.Session;import com.sun.org.apache.bcel.internal.generic.NEW;import Dao.BaseDao;import util.TokenUtil;/** * Servlet implementation class MyLoginServlet */@WebServlet("/MyLoginServlet")public class MyLoginServlet extends HttpServlet {private static final long serialVersionUID = 1L;/** * @see HttpServlet#HttpServlet() */public MyLoginServlet() {super();// TODO Auto-generated constructor stub}/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse *      response) */protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String name = request.getParameter("username");String pass = request.getParameter("password");Map<String, Object> map = new HashMap<String, Object>();map.put("name", name);map.put("pass", pass);//生成token,使用token放入请求头中,那就是前后端分离的项目了,或者前端使用异步方式请求,带上请求头String token = TokenUtil.getToken(map);//将token放入cookieresponse.addCookie(new Cookie("token", token));//将token放入请求头//response.addHeader("token", token);response.sendRedirect("index.jsp");}/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse *      response) */protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// TODO Auto-generated method stubdoGet(request, response);}}

index.jsp页面

主界面<% Cookie[] cookies = request.getCookies();if (cookies != null) {// 我们可以遍历下我们的cooike看看for (Cookie cookie : cookies) {if(cookie.getName().equals("token")){Claims user = TokenUtil.parseToken(cookie.getValue());out.print(user.get("name")+"
");out.print(user.get("pass")+"
");}}} %>这里是主界面

效果

在这里插入图片描述

在这里插入图片描述

我们发现,这里我们并没有存储用户信息而是将信息放到了token中进行存储,我们只需要后面拿过来token然后通过密钥解析,就可以拿到用户的信息;

我们这里还可以用请求头测试下:

改变下代码:

MyLoginServlet.java

@WebServlet("/MyLoginServlet")public class MyLoginServlet extends HttpServlet {protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String name = request.getParameter("username");String pass = request.getParameter("password");Map<String, Object> map = new HashMap<String, Object>();map.put("name", name);map.put("pass", pass);//生成token,使用token放入请求头中,那就是前后端分离的项目了,或者前端使用异步方式请求,带上请求头String token = TokenUtil.getToken(map);//将token放入cookie//response.addCookie(new Cookie("token", token));//将token放入请求头response.addHeader("token", token);//response.sendRedirect("index.jsp");}/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse *      response) */protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// TODO Auto-generated method stubdoGet(request, response);}}

index.jsp页面

主界面<%/*  Cookie[] cookies = request.getCookies();if (cookies != null) {// 我们可以遍历下我们的cooike看看for (Cookie cookie : cookies) {if(cookie.getName().equals("token")){Claims user = TokenUtil.parseToken(cookie.getValue());out.print(user.get("name")+"
");out.print(user.get("pass")+"
");}}} */if (request.getHeader("token") != null) {Claims user = TokenUtil.parseToken(request.getHeader("token"));out.print(user.get("name") + "
");out.print(user.get("pass") + "
");}%>这里是主界面

我们再次运行执行登录请求,打开检查,我们发现已经从请求头中响应了过来:

在这里插入图片描述

这里我们用postman测试下,通过请求头测试,测试成功:

在这里插入图片描述

而用户只要是携带了token,我们后端有这个token只需要判断有没有就知道这个用户是否登录过;这里我们放在了cookie中,token我们还可以放在请求头中,可以防止很多攻击,对于前后端项目来说,很好,但是这里尽量不要存放用户敏感信息,因为token也不是安全的;

包括token如果存放在请求头中,这里我们没有办法刷新用户的token只能是到时间之后自动过期;

具体使用场景看具体业务,这里只是简单的了解下,并没有什么业务代码,也是对于自己的理解,好就这样吧!

jsp

介绍

JSP 与 PHP、ASP、ASP.NET 等语言类似,运行在服务端的语言。

前面我们知道了servlet,其实jsp页面最后就是被转换为servlet的html页面,这里我们可以看下:

新建一个test.jsp页面,前面虽然我们不知道但是也应用了

Insert title here<%out.print("

hello world

");%>

我们运行一下:

在这里插入图片描述

是不是就输出了我们输出的

hello world

,在很早之前没有jsp这门技术,都是通过servlet在servlet通过这样输出,然后将html页面输出到页面,像这样,但是是不是很不方便,就是因为不方便技术才会进步嘛

我们可以直接写html然后同时在html里面也可以写java代码,其实最后就会产生了jsp这门技术,而其实也是通过jsp页面转化成功servlet这样,那过多的话就不说了,我们直接看知识点和如何使用;

在这里插入图片描述

jsp语法

我们先来学习jsp语法,怎么在jsp页面写java代码;

我们想要写java代码可以用以下形式用括起来里面写java代码:

以下把菜鸟教程当教科书,做教科书参考:

比如我们上面所写的:

<%out.print("

hello world

");%>

设置编码

如果我们要在页面正常显示中文,我们需要在 JSP 文件头部添加以下代码:

我们在新建jsp页面的时候,会自动添加上,前提是设置了eclipse设置了jsp的编码格式为utf-8

window —-> perferences:

在这里插入图片描述

声明变量

比如我们声明一个变量;

其实也可以在里面声明,但是鉴于规范可以在声明

如何在html里面输出表达式:

其实也可以在里面输出,但是这里不是用System.out中输出,这样是向控制台输出,而是用jsp内置对象out向屏幕输出:

就像刚才的,这样是向屏幕输出,而过使用了System.out就会向你的控制台输出;

<%out.print("

hello world

");%>

那为什么又有一个这样的输出呢,,你不要觉得不好,这个贼还用,比如这样,我们通过servlet传递到前台的值,可以直接作为input的value,特别好用;

<input value="" />

还有一些其他的,都在这里了

语法 描述
JSP注释,注释内容不会被发送至浏览器甚至不会被编译
HTML注释,通过浏览器查看网页源代码时可以看见注释内容
<% 代表静态 <%常量
%> 代表静态 %> 常量
在属性中使用的单引号
" 在属性中使用的双引号

指令

其实也不是什么,接着我们的理解,我们比如要使用utli包里面的东西是不是要导包啊,这时候可不是就行了,这里我们叫指令,没几个,看看吧:

     //导包包含其他文件引入标签库的定义,可以是自定义标签

加大内置对象

和我们在servlet中使用差不多,这里比如out我们也使用过了:

对象 描述
request HttpServletRequest类的实例
response HttpServletResponse类的实例
out PrintWriter类的实例,用于把结果输出至网页上
session HttpSession类的实例
application ServletContext类的实例,与应用上下文有关
config ServletConfig类的实例
pageContext PageContext类的实例,提供对JSP页面所有对象以及命名空间的访问
page 类似于Java类中的this关键字
exception exception 类的对象,代表发生错误的 JSP 页面中对应的异常对象

生命周期

那没那么多复杂的可难的,我们看一下生命周期,以下代码来自菜鸟教程:

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><html><head><title>test</title></head><body><%!   private int initVar=0;  private int serviceVar=0;  private int destroyVar=0;%>  <%!  public void jspInit(){    initVar++;    System.out.println("jspInit(): JSP被初始化了"+initVar+"次");  }  public void jspDestroy(){    destroyVar++;    System.out.println("jspDestroy(): JSP被销毁了"+destroyVar+"次");  }%><%  serviceVar++;  System.out.println("_jspService(): JSP共响应了"+serviceVar+"次请求");  String content1="初始化次数 : "+initVar;  String content2="响应客户请求次数 : "+serviceVar;  String content3="销毁次数 : "+destroyVar;%><h1>菜鸟教程 JSP 测试实例</h1><p><%=content1 %></p><p><%=content2 %></p><p><%=content3 %></p></body></html>

在这里插入图片描述

我们把它当成servlet使用就可以了本质也就是servlet,jsp最后就是编译成一个个servlet类;

jstl

介绍

在这里插入图片描述

其实就是在对jsp页面编写使用的再一次简化,很简单,了解下:

引入依赖

需要连个jar包:jstl.jarstandard.jar

常用的就是c标签了,jsp页面上添加:,这时候我们就可以使用C标签了

因为通过java编写输出比如很多列表的循环,我们通过jsp页面去写,会是这样的效果:

在这里插入图片描述

那这里的循环还是很小一段,假如我们会有很多html代码,还得去写什么这样不管不好写,而且代码可读性太差,那通过jstl我们可以这样,只需要一个标签将html代码围住继续写,这样不光代码可读性提高了,而且是不是很方便了呢:

在这里插入图片描述

C语法

一个表格,我觉得一看就懂吧,就是将我们上面所用的servlet做成了标签这种类似html的标签,提高了可读性,简化了代码,这里推荐大家去菜鸟教程上观看,以及分析,这里就不做过多解释了:

在这里插入图片描述

小实例(附源码)

javaweb到这里就差不多了,我们做一个小示例,完成一些基本操作;

这里我给大家写了两个页面,实在没有什么素材,只能靠自己给大家写两个了;

同时这里是源码,是整个JavaWeb系列,因为直接从创建文件开始,就用的一个项目,从上面到下面所有的源码都在里面了,所以如果你直接看的这个博客,推荐你拿到这个项目,找到我的主页,从头到尾看一遍,所以说你真幸运直接有了大结局,当然了,这才只是个开始,后面还有更多:

全部源码,百度网盘

链接:https://pan.baidu.com/s/1_eQZgTuKHkgZ4RwODV_ylw?pwd=tyzt 提取码:tyzt

这里我实在不想做一个系统了,因为之后几乎都是重复的操作,大家如果把这个全部理解,做个系统简简单单,什么什么管理系统,无非是再加个侧边栏,然后对几张表的操作,加上关系啥的,这里我在网上找的一个模板,也教大家怎么去拿到这种网页,变成自己使用;

这是我们进行操作的页面:
在这里插入图片描述

这是修改时候的页面,一会儿呢就改成我们想要的那种:

在这里插入图片描述

实现效果

我们先来看一下实现效果:

  • 了解下大致的功能

    1、登录功能,这里没有建用户表,大家创建一个用户表一个查询方法就实现,最简单的功能2、对货物表的CRUD3、条件查询 -搜索功能4、带条件查询的分页

登录页面:

在这里插入图片描述

对单表的所有操作页面,一页显示5个:

在这里插入图片描述

编辑和新增货物页面:

在这里插入图片描述

在这里插入图片描述

搜索功能,搜索带上分页:

在这里插入图片描述

好,大家可以拿到本地运行下,下面对代码进行下解释

讲解

首先就是代码的写的,这里拿最开始,我们先尝试着,把代码数据库中的数据显示到页面上:

首先就是数据库:

创建货物表:

SET FOREIGN_KEY_CHECKS=0;-- ------------------------------ Table structure for good-- ----------------------------DROP TABLE IF EXISTS `good`;CREATE TABLE `good` (  `goodid` int(20) NOT NULL AUTO_INCREMENT,  `goodname` varchar(20) NOT NULL,  `surplus` int(20) NOT NULL,  `goodprice` int(20) NOT NULL,  PRIMARY KEY (`goodid`)) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;-- ------------------------------ Records of good-- ----------------------------INSERT INTO `good` VALUES ('1', '黑色森林', '2', '15');INSERT INTO `good` VALUES ('2', '奶油蛋糕', '3', '13');INSERT INTO `good` VALUES ('3', '水果蛋糕', '2', '11');INSERT INTO `good` VALUES ('4', '冰淇凌蛋糕', '5', '13');INSERT INTO `good` VALUES ('9', '牛奶蛋糕', '34', '9');INSERT INTO `good` VALUES ('11', '肉松蛋糕', '13', '13');INSERT INTO `good` VALUES ('12', '草莓蛋糕', '99', '23');INSERT INTO `good` VALUES ('13', '苹果蛋糕', '32', '12');INSERT INTO `good` VALUES ('14', '香蕉蛋糕', '32', '12');INSERT INTO `good` VALUES ('15', '火龙果蛋糕', '43', '43');INSERT INTO `good` VALUES ('16', '香橙蛋糕', '65', '31');INSERT INTO `good` VALUES ('17', '苹果', '43', '54');INSERT INTO `good` VALUES ('18', '菠萝', '32', '32');INSERT INTO `good` VALUES ('19', '橙子', '435', '32');INSERT INTO `good` VALUES ('20', '花椒', '43', '65');INSERT INTO `good` VALUES ('21', '大蒜', '23', '54');INSERT INTO `good` VALUES ('22', '鸡蛋', '32', '43');INSERT INTO `good` VALUES ('23', '西红柿', '32', '43');

写一个pojo对象

package pojo;public class Good {private int goodId;private String goodName;private int surplus;private int goodPrice;public int getGoodId() {return goodId;}public void setGoodId(int goodId) {this.goodId = goodId;}public String getGoodName() {return goodName;}public void setGoodName(String goodName) {this.goodName = goodName;}public int getSurplus() {return surplus;}public void setSurplus(int surplus) {this.surplus = surplus;}public int getGoodPrice() {return goodPrice;}public void setGoodPrice(int goodPrice) {this.goodPrice = goodPrice;}public Good(int goodId, String goodName, int surplus, int goodPrice) {super();this.goodId = goodId;this.goodName = goodName;this.surplus = surplus;this.goodPrice = goodPrice;}public Good() {super();}@Overridepublic String toString() {return "Good [goodId=" + goodId + ", goodName=" + goodName + ", surplus=" + surplus + ", goodPrice=" + goodPrice+ "]";}}

GoodDao接口:

package Dao;import java.util.List;import pojo.Good;public interface GoodDao {public List<Good> selectGoodAll();public Good  selectGoodById();public List<Good> selectGoodBySearch(String condition);public int updateGood(Good good);public int insertGood(Good good);public int deleteGood(int goodId);}

GoodDaoImpl实现类:

package Dao.impl;import java.util.ArrayList;import java.util.List;import Dao.BaseDao;import Dao.GoodDao;import pojo.Good;import util.DbcpUtil;public class GoodDaoImpl extends BaseDao implements GoodDao {@Overridepublic List<Good> selectGoodAll() {List<Good> list = new ArrayList<Good>();String sql = "select * from good";this.executeQuery(sql);try {while (rs.next()) {Good good = new Good(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getInt(4));list.add(good);}} catch (Exception e) {e.printStackTrace();} finally {DbcpUtil.closeConnection();}return list;}@Overridepublic Good selectGoodById() {Good good = new Good();String sql = "select * from good where goodid = ?";this.executeQuery(sql);try {good.setGoodId(rs.getInt(1));good.setGoodName(rs.getString(2));good.setSurplus(rs.getInt(3));good.setGoodPrice(rs.getInt(4));} catch (Exception e) {e.printStackTrace();} finally {DbcpUtil.closeConnection();}return good;}@Overridepublic List<Good> selectGoodBySearch(String condition) {List<Good> list = new ArrayList<Good>();String sql = "select * from good where goodname like ?";this.executeQuery(sql);try {while (rs.next()) {Good good = new Good(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getInt(4));list.add(good);}} catch (Exception e) {e.printStackTrace();} finally {DbcpUtil.closeConnection();}return list;}@Overridepublic int updateGood(Good good) {String sql = "UPDATE good SET goodName=?,surplus=?,goodPrice='? where goodId=?";return this.executeUpdate(sql, good.getGoodName(),good.getSurplus(),good.getGoodPrice(),good.getGoodId());}@Overridepublic int insertGood(Good good) {String sql = "insert into good (goodName,surplus,goodPrice)VALUES(?,?,?)";return this.executeUpdate(sql, good.getGoodName(),good.getSurplus(),good.getGoodPrice());}@Overridepublic int deleteGood(int goodId) {String sql = "delete from buy where goodid=?";return this.executeUpdate(sql, goodId);}}

这里为了方便就不写service层了,我们直接写servlet,首先就是:

GoodGetAllservlet 实现doGet方法

protected void doGet(HttpServletRequest request, HttpServletResponse response)    throws ServletException, IOException {    GoodDao goodDao = new GoodDaoImpl();    List<Good> goods =  goodDao.selectGoodAll();    request.setAttribute("goods", goods);    request.getRequestDispatcher("good.jsp").forward(request, response);}

good.jsp页面将我们的数据渲染上去

货物编号 货物名称 剩余数量 货物价格 操作
${good.goodId} ${good.goodName} ${good.surplus} ${good.goodPrice} 正常

这样我们就成功把数据渲染上去了,运行页面,我们可以看到我们数据库里的信息已经渲染到页面了,那这里就是我们主要的一个流程实现方式,我们的代码就是这么一层层写的,通过这个方式你是不是可以再创建几个servlet把增删改也写一下呢;这些写完,我们来看下搜索功能;

搜索功能

搜索功能也是通过传递参数,具体实现在这里,通过keyword然后传递给servlet:

在这里插入图片描述

这里我直接是带条件的搜索了,这里最后一定要注意,我们把keyword放进了session,不然分页的时候你点击下一页就会没有搜索条件喽

在这里插入图片描述

很简单,利用了,sql语句中的模糊查询;

分页功能

分页比较困难些,这里为了方便,我们创建了一个对页脚进行面向对象操作,进行了下封装,这里可能比较难理解一点,其实我们这里只需要关心两个数据

  • 我们的数据记录条数,这取决于我们可以分多少页,我这里一页5条,24条记录,就是24/5+1=5页,多少记录,才能知道多少页数,这都是要给到下面值得

  • 第二个就是通过前端传过来,现在是第几页,只有知道是第几页我们才能知道从哪里开始重新渲染数据;
    在这里插入图片描述

    然后上一页下一页+1-1的操作;

这里渲染是重点,也能会晕哦,这个关系一定要弄清,上手尝试下就理解了:

  • 首页
  • 上一页
  • 如果总页数小于3页,我们全部显示出来 <c:if test="${pi.totalpage
  • ${i.index}
  • ${i.index}
  • 如果总页数大于三页 3 }"> 如果小于总页数-2的页数,我们就从当前页开始往后显示3页 <c:if test="${pi.curpage
  • ${i.index}
  • ${i.index}
  • ...
  • 如果已经接近末尾了,我们直接从当前页显示到末尾 = pi.totalpage-2}">
  • ...
  • ${i.index}
  • ${i.index}
  • 下一页
  • 尾页

小结

到这里我们的JavaWeb知识点就学的差不多了,可能之后你就要开启框架之旅了,但是不管学习任何框架,都是通过底层去网上抽取的,不断地接管不断地封装,然后配置,但是对于学习一个框架,我们的底层原理一定要夯实,框架原理都是很简单的

我们不一定要去看源码,但是我们后面的能力不是在使用框架上,而是我们对框架的改错找错能力,框架都是人写的,也会有性能bug的地方,这时候就是我们对于原理,以及原生JavaWeb的熟练了,以及怎么通过框架实现我们对框架的自定义div,所以一定要动手把这一部分练习的很熟练,这样我们之后的学习可以说已经成功一半了,好了,当然了,这只是一点儿,也只是一个开始;

这么一大篇还不赶紧收藏起来,后来的小伙伴直接看到这个,那真的幸运了,把源码下载起来,回到之前的跟篇走完,希望你会有收获的,那到这里就求三连,点个关注吧

多动手、多思考,
一定不要眼高手低