看完这一篇你还不会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.jar
和standard.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,所以一定要动手把这一部分练习的很熟练,这样我们之后的学习可以说已经成功一半了,好了,当然了,这只是一点儿,也只是一个开始;
这么一大篇还不赶紧收藏起来,后来的小伙伴直接看到这个,那真的幸运了,把源码下载起来,回到之前的跟篇走完,希望你会有收获的,那到这里就求三连,点个关注吧
多动手、多思考,
一定不要眼高手低