尚硅谷全新JavaWeb教程,企业主流javaweb技术栈【学习笔记】上_尚硅谷企业级javaweb的文本-CSDN博客
尚硅谷全新JavaWeb教程,企业主流javaweb技术栈【学习笔记】下-CSDN博客
一 部分代码: https://download.csdn.net/download/qq_43668996/89066620
二 部分代码: https://download.csdn.net/download/qq_43668996/89094028
十二 案例开发-日程管理-第二期
12.1 项目搭建
12.1.1数据库准备
在navicat 中创建schedule_system数据库并执行如下语句
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- --------------------------------------------------
-- 创建日程表
-- --------------------------------------------------
DROP TABLE IF EXISTS `sys_schedule`;
CREATE TABLE `sys_schedule`(
`sid` int NOT NULL AUTO_INCREMENT,
`uid` int NULL DEFAULT NULL,
`title` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`completed` int(1) NULL DEFAULT NULL,
PRIMARY KEY(`sid`) USING BTREE
)ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- --------------------------------------------------
-- 插入日程数据
-- --------------------------------------------------
-- 创建用户表
--
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`uid` int NOT NULL AUTO_INCREMENT,
`username` varchar(10)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_Ci NULL DEFAULT NULL,
`user_pwd` varchar(100)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_Ci NULL DEFAULT NULL,
PRIMARY KEY(`uid`)USING BTREE,
UNIQUE INDEX `username`(`username`)USING BTREE
)ENGINE = InnoDB CHARACTER SET= utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- --------------------------------------------------
-- 插入用户数据
-- --------------------------------------------------
INSERT INTO `sys_user` VALUES(1,'zhangsan','e10adc3949ba59abbe56e057f20f883e');
INSERT INTO `sys_user` VALUES(2,'lisi','e10adc3949ba59abbe56e057f20f883e');
SET FOREIGN_KEY_CHECKS =1;
12.1.2 在idea中创建项目
schedule_system 添加依赖
添加组件
对于找不到Add Framework Support 的可以参考 找不到Add Framework Support-CSDN博客
并放到tomcat上
12.1.3 为idea与数据库建立连接
输入用户和密码,此处出现了问题,按提示选择Download下载即可
接着点击 Test Connextion ,出现成功提示选择Apply即可。如果不成功多试几次
选择 数据库旁边的 。。of .. 选择你要展示的数据库即可
12.1.4 准备实体类
- 实体类的类名和表格名称应该对应(对应不是一致,数据库中—形式名称在java类中应为驼峰式)
- 其体类的属性名和表格的列名应该对应
- 每个属性都必须是私有的
- 每个属性都应该具备getter setter
- 必须具备无参造器
- 应该实现序列化按口(缓存 分布式项目数据传递可能会将对象序列化)
- 应该重写类的hashcodefiequals力法
- tostring 是否重写都可以
使用Lombok帮助我们生成这些内容 getter setter 全参构造无参构造equals hashcode Lombok 使用步骤
- 步骤 1 检查idea是否已经安装 Lombok
在Setting中查找Installed 中是否有Lombok。如果没有,在Installed 左边的Marketplate 中安装
- 步骤 2 检查是否勾选了 enable annotation processer
- 步骤 3 导入 Lombok依赖
下载lombok包 https://download.csdn.net/download/qq_43668996/89094503 ,将jar包放到自行创建的lib包下,并将包添加为项目的library
- 步骤 4 在实体类上添加注解
@AllArgsConstructor //添加了全参构造
@NoArgsConstructor //添加了无参构造
@Data //添加 getter setter equals hashcode
@Getter
@Setter
@EqualsAndHashCode
@ToString
创建pojo包,专门放实体类
包下创建类 SysUser
定义属性,添加注释
package com.atguigu.schedule.pojo;
import lombok.*;
import java.io.Serializable;
import java.util.Objects;
@AllArgsConstructor //添加了全参构造
@NoArgsConstructor //添加了无参构造
@Data //添加 getter setter equals hashcode
//@Getter
//@Setter
//@EqualsAndHashCode
//@ToString
public class SysUser implements Serializable {
private Integer uid;
private String username;
private String userPwd;
}
按照同样方法创建SysSchedule 实体类
package com.atguigu.schedule.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class SysSchedule implements Serializable {
private Integer sid;
private Integer uid;
private String title;
private Integer completed;
}
12.1.5 准备Dao包
封装对数据库表格增删改查的类。
结构如下:
创建对数据库表进行增删改查操作的接口
package com.atguigu.schedule.dao;
import com.atguigu.schedule.pojo.SysSchedule;
import java.util.List;
//介绍一下当前类...
//作者:
//时间:
public interface SysScheduleDao {
// 用于向数据中增加一条日程记录
// @param schedule 日程数据以sysSchedule实体类对象形式入参
// @return 返回影响数据库记录的行数,行数为0说明增加失败,行数大于0说明增加成功
int addSchedule(SysSchedule schedule);
// 查询所有用户的所有日程
//@return 将所有日程放入一个:List<SysSchedu
//
List<SysSchedule> findAll();
}
package com.atguigu.schedule.dao;
import com.atguigu.schedule.pojo.SysSchedule;
import com.atguigu.schedule.pojo.SysUser;
//Data access object 数据访问对象
//该类中用于定义针对表格的CRUD (增删改查 )的方法
public interface SysUserDao {
int addUser(SysUser user);
}
在impl文档中创建以上接口的实现类
package com.atguigu.schedule.dao.impl;
import com.atguigu.schedule.dao.BaseDao;
import com.atguigu.schedule.dao.SysScheduleDao;
import com.atguigu.schedule.pojo.SysSchedule;
import java.util.List;
public class SysScheduleDaolmpl extends BaseDao implements SysScheduleDao {
@Override
public int addSchedule(SysSchedule schedule) {
String sql = "insert into sys_schedule values(DEFAULT,?,?,?)";
int rows = baseUpdate(sql, schedule.getUid(), schedule.getTitle(), schedule.getCompleted());
return rows;
}
public List<SysSchedule> findAll(){
String sql ="select sid,uid,title,completed from sys_schedule";
List<SysSchedule>scheduleList = baseQuery(SysSchedule.class, sql);
return scheduleList;
}
}
package com.atguigu.schedule.dao.impl;
import com.atguigu.schedule.dao.SysUserDao;
import com.atguigu.schedule.pojo.SysUser;
public class SysUserDaolmpl implements SysUserDao {
@Override
public int addUser(SysUser user) {
return 0;
}
}
创建对数据库表格增删改查的基础类 BaseDao 。
package com.atguigu.schedule.dao;
import com.atguigu.schedule.util.JDBCUtil;
import java.lang.reflect.Field;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class BaseDao {
//公共的查询方法返回的是单个对象
public <T> T baseQueryObject(Class<T> clazz, String sql, Object... args) {
T t = null;
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
int rows = 0;
try {
// 准备语句对象
preparedStatement = connection.prepareStatement(sql);
// 设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
// 执行 查询
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
t = (T) resultSet.getObject(1);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
JDBCUtil.releaseConnection();
}
return t;
}
//公共的查询方法 返回的是对象的集合
public <T> List<T> baseQuery(Class clazz, String sql, Object... args) {
List<T> list = new ArrayList<>();
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
int rows = 0;
try {
// 准备语句对象
preparedStatement = connection.prepareStatement(sql);
// 设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
// 执行 查询
resultSet = preparedStatement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
//将结果集通过反射封装成实体类对象
while (resultSet.next()) {
//使用反射实例化对象
Object obj = clazz.getDeclaredConstructor().newInstance();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnLabel(i);
Object value = resultSet.getObject(columnName);
//处理datetime类型字段和java.util.Data转换问题
if (value.getClass().equals(LocalDateTime.class)) {
value = Timestamp.valueOf((LocalDateTime) value);
}
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(obj, value);
}
list.add((T) obj);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
JDBCUtil.releaseConnection();
}
return list;
}
//通用的增制改方法
public int baseUpdate(String sql, Object... args) {
// 获取连接
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement = null;
int rows = 0;
try {
// 准备语句对象
preparedStatement = connection.prepareStatement(sql);
// 设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
//执行 增删改 executeUpdate
rows = preparedStatement.executeUpdate();
//释放资源(可选)
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
JDBCUtil.releaseConnection();
}
return rows;
}
}
12.1.6 准备util包,存放工具类
JDBCUtil 实现连接数据库操作
package com.atguigu.schedule.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtil{
private static ThreadLocal<Connection> threadLocal =new ThreadLocal<>();
private static DataSource dataSource;
//初始化连接池
static {
//可以帮助我们读取.properties配置文件
Properties properties = new Properties();
InputStream resourceAsStream = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
properties.load(resourceAsStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*1 向外提供连接池的方法*/
public static DataSource getDataSource() {
return dataSource;
}
/*2 向外提供连接的方法*/
public static Connection getConnection(){
Connection connection =threadLocal.get();
if(null== connection){
try {
connection =dataSource.getConnection();
}catch (SQLException e){
throw new RuntimeException(e);
}
threadLocal.set(connection);
}
return connection;
}
/*定义一个归还连接的方法(解除和ThreadLocal之间的关联关系)*/
public static void releaseConnection(){
Connection connection =threadLocal.get();
if(null != connection){
threadLocal.remove();
//把连接设置回自动提交的连接
try{
connection.setAutoCommit(true);
//自动归还到连接池
connection.close();
}catch(SQLException e){
throw new RuntimeException(e);
}
}
}
}
12.1.7 测试
package test;
import com.atguigu.schedule.dao.BaseDao;
import com.atguigu.schedule.pojo.SysUser;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
public class TestBaseDao {
private static BaseDao baseDao;
@BeforeClass //在测试代码运行前先执行一次这个代码。要求方法为静态
//BeforeMethod 表示每次执行一次方法就执行一次代码
public static void initBaeDao(){
baseDao = new BaseDao();
}
@Test
public void testBaseQueryObject(){
// 查询用户数最
String sql ="select count(1) from sys_user";
Long count =baseDao.baseQueryObject(Long.class, sql);
System.out.println(count);
}
@Test
public void testBaseQuery() {
String sql = "select uid,username,user_pwd userPwd from sys_user";
List<SysUser> sysUserList = baseDao.baseQuery(SysUser.class, sql);
sysUserList.forEach(System.out::println);
}
@Test
public void testBaseUpdate() {
String sql = "insert into sys_schedule values(DEFAULT,?,?,?)";
int rows = baseDao.baseUpdate(sql,1, "学习JAVA", 0);
System.out.println(rows);
}
}
package test;
import com.atguigu.schedule.dao.SysScheduleDao;
import com.atguigu.schedule.dao.impl.SysScheduleDaolmpl;
import com.atguigu.schedule.pojo.SysSchedule;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
public class TestSysScheduleDao {
private static SysScheduleDao scheduleDao;
@BeforeClass
public static void initscheduleDao(){
scheduleDao =new SysScheduleDaolmpl();
}
@Test
public void testAddSchedule(){
int rows =scheduleDao.addSchedule(new SysSchedule(null,2,"学习数据库",1) );
System.out.println(rows);
}
@Test
public void testFindAll(){
List<SysSchedule> scheduleList = scheduleDao.findAll();
scheduleList.forEach(System.out::println);
}
}
12.1.8 service层结构准备
package com.atguigu.schedule.service;
public interface SysScheduleService {
}
package com.atguigu.schedule.service;
//该接口定义了以sys_user表格为核心的业务处理功能
public interface SysUserService {
}
package com.atguigu.schedule.service.impl;
import com.atguigu.schedule.service.SysScheduleService;
public class SysScheduleServiceimpl implements SysScheduleService {
}
package com.atguigu.schedule.service.impl;
import com.atguigu.schedule.service.SysUserService;
public class SysUserServiceimpl implements SysUserService {
}
12.1.9 controller层结构准备
package com.atguigu.schedule.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
public class BaseController extends HttpServlet {
protected void service(HttpServletRequest req, HttpServletResponse resp) throws
ServletException,
IOException{
String requestURI =req.getRequestURI();///schedule/add
String[]split =requestURI.split( "/");
String methodName =split[split.length-1];
// 11使用反射通过方法名获取下面的方法
Class aclass =this.getClass();
// 获取方注
try {
Method declaredMethod = aclass.getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
declaredMethod.setAccessible(true);
declaredMethod.invoke( this,req,resp);
}catch(Exception e){
e.printStackTrace();
}
}
}
package com.atguigu.schedule.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
//曾加日程的请求 schedule/add
//查询日程的请求 schedule/find
//修改日程的请求 schedule/update
//删除日程的请求 schedule/remove
@WebServlet("/schedule/*")
public class SysScheduleController extends BaseController {
protected void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("add");}
}
package com.atguigu.schedule.controller;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
@WebServlet("/user/*")
public class SysUserController extends BaseController {
}
12.1.10 MD5Util 加密工具类
package com.atguigu.schedule.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException ;
public final class MD5Util{
public static String encrypt(String strSrc){
try {
char hexChars[]={ '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
byte[]bytes =strSrc.getBytes();
MessageDigest md=MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j= bytes.length;
char[]chars=new char[j* 2];
int k =0;
for(int i=0;i<bytes.length;i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
}catch(NoSuchAlgorithmException e){
e.printStackTrace();
throw new RuntimeException("\"MD5加密出错\"");
}
}
}
package test;
import com.atguigu.schedule.util.MD5Util;
import org.junit.Test;
public class TestMD5Util {
@Test
public void testEncrypt(){
String encrypt = MD5Util.encrypt( "123456");
System.out.println(encrypt);
}
}
12.1.11 准备视图相关文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<script>
//校验名字
function checkUsername(){
//定义正则字符规则
var usernameReg = /^[a-zA-Z0-9]{5}$/
//获取用户名
var username = document.getElementById("usernameInput").value
//获取提示框元素
var ts = document.getElementById("usernameInputSpan")
if(!usernameReg.test(username)){
ts.innerText = "请输入五位字母数字"
return false
}
ts.innerText = "OK"
return true
}
//校验密码
function checkUserPwd(){
//定义正则字符规则
var userPwdReg = /^[0-9]{6}$/
//获取密码元素
var userPwd = document.getElementById("userPwd").value
//获取提示框元素
var ts = document.getElementById("userPwdSpan")
if(!userPwdReg.test(userPwd)){
ts.innerText = "请输入六位数字"
return false
}
ts.innerText = "OK"
return true
}
//表单在提交时,校验用户名和密码格式,格式OK才会提交
function checkForm(){
var result1 = checkUsername()
var result2 = checkUserPwd()
return result1&&result2
}
</script>
<style>
.tb{
border: 5px solid rgb(13, 154, 69);
margin: auto;
width: 300px;
border-radius: 3px;/*
border-color: green;
border-width: 3px; */
}
.ipt{
border: 0px ;
}
td{
border-color: rgb(4, 156, 4);
border-width: 1px;
border-style: solid;
}
.btn1{
background-color: antiquewhite;
font-family: 幼圆;
}
.btn2{
background-color: antiquewhite;
color: blue;
font-family: 幼圆;
}
.buttonC{text-align: center;}
#usernameInputSpan, #userPwdSpan {
color: rgb(235, 84, 84);
font-size: xx-small;
}
</style>
</head>
<body>
<h2 style="color: #000;text-align: center; font-family: 'Courier New', Courier, monospace;">欢迎登陆</h2>
<h3 style="color: #000;text-align: center; font-family: 'Courier New', Courier, monospace;">请登录</h3>
<form method="post" action="login.html" onsubmit="return checkForm()">
<table class="tb" cellspacing="1px">
<tr class="ltr">
<td >请输入账号</td>
<td >
<input class="ipt" type="text" id="usernameInput" onblur="checkUsername()"/>
<span id="usernameInputSpan"></span>
</td>
</tr>
<tr class="ltr">
<td>请输入密码</td>
<td >
<input class="ipt" type="password" id="userPwd" onblur="checkUserPwd()" />
<span id="userPwdSpan"></span>
</td>
</tr>
<tr class="ltr" style="margin: auto;">
<td colspan="3" class="buttonC">
<input class="btn1" type="submit" value="登录">
<input class="btn1" type="reset" value="重置">
<button class="btn2"> <a href="regist.html">注册</a></button>
</td>
</tr>
</table>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>账号有误,请求<a href="/login.html">重新登陆</a></h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>密码有误,请求<a href="/login.html">重新登陆</a></h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<script>
//校验名字
function checkUsername(){
//定义正则字符规则
var usernameReg = /^[a-zA-Z0-9]{5}$/
//获取用户名
var username = document.getElementById("usernameInput").value
//获取提示框元素
var ts = document.getElementById("usernameInputSpan")
if(!usernameReg.test(username)){
ts.innerText = "请输入五位字母数字"
return false
}
ts.innerText = "OK"
return true
}
//校验密码
function checkUserPwd(){
//定义正则字符规则
var userPwdReg = /^[0-9]{6}$/
//获取密码元素
var userPwd = document.getElementById("userPwd").value
//获取提示框元素
var ts = document.getElementById("userPwdSpan")
if(!userPwdReg.test(userPwd)){
ts.innerText = "请输入六位数字"
return false
}
ts.innerText = "OK"
return true
}
//校验密码
function checkUserPwdCon(){
//获取密码元素
var userPwd = document.getElementById("userPwd").value
var userPwdCon = document.getElementById("userPwdCon").value
//获取提示框元素
var ts = document.getElementById("userPwdSpanCon")
if(userPwd!=userPwdCon){
ts.innerText = "密码不一致"
return false
}
ts.innerText = "OK"
return true
}
//表单在提交时,校验用户名和密码格式,格式OK才会提交
function checkForm(){
var result1 = checkUsername()
var result2 = checkUserPwd()
var result3 = checkUserPwdCon()
return result1&&result2&&result3
}
</script>
<style>
.tb{
border: 5px solid rgb(13, 154, 69);
margin: auto;
width: 300px;
border-radius: 3px;/*
border-color: green;
border-width: 3px; */
}
.ipt{
border: 0px ;
}
td{
border-color: rgb(4, 156, 4);
border-width: 1px;
border-style: solid;
}
.btn1{
background-color: antiquewhite;
font-family: 幼圆;
}
.btn2{
background-color: antiquewhite;
color: blue;
font-family: 幼圆;
}
.buttonC{text-align: center;}
#usernameInputSpan, #userPwdSpan, #userPwdSpanCon {
color: rgb(235, 84, 84);
font-size: xx-small;
}
</style>
</head>
<body>
<h2 style="color: #000;text-align: center; font-family: 'Courier New', Courier, monospace;">欢迎注册</h2>
<h3 style="color: #000;text-align: center; font-family: 'Courier New', Courier, monospace;">请注册</h3>
<form method="post" action=" login.html" onsubmit="return checkForm()">
<table class="tb" cellspacing="1px">
<tr class="ltr">
<td >请输入账号</td>
<td >
<input class="ipt" type="text" id="usernameInput" onblur="checkUsername()"/>
<span id="usernameInputSpan"></span>
</td>
</tr>
<tr class="ltr">
<td>请输入密码</td>
<td >
<input class="ipt" type="password" id="userPwd" onblur="checkUserPwd()" />
<span id="userPwdSpan"></span>
</td>
</tr>
<tr class="ltr">
<td>请确认密码</td>
<td >
<input class="ipt" type="password" id="userPwdCon" onblur="checkUserPwdCon()" />
<span id="userPwdSpanCon"></span>
</td>
</tr>
<tr class="ltr" style="margin: auto;">
<td colspan="3" class="buttonC">
<!-- <input class="btn1" type="submit" value="注册 " onclick=""> -->
<input class="btn1" type="reset" value="重置">
<button class="btn2"> <a href="login.html">注册</a></button>
</td>
</tr>
</table>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>注册失败,用户名可能已被占用,请求<a href="/regist.html">重新注册</a></h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>注册成功,请求<a href="/login.html">登陆</a></h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>以下是您的所有目录</h1>
。。。。。。。。。。。。
</body>
</html>
..........待补充代码...........
第六章 会话_过滤器_监听器
一 会话
1.1 会话管理概述
1.1.1为什么需要会话管理
HTTP是无状态协议
- 无状态就是不保存状态,即无状态协议(stateles),HTTP协议自身不对请求和响应之间的通信状态进行保存,也就是说,在HTTP协议这个级别,协议对于发送过的请求或者响应都不做持久化处理
- 简单理解:浏览器发送请求,服务器接收并响应,但是服务器不记录请求是否来自哪个浏览器,服务器没记录浏览器的特征,就是客户端的状态
举例: 张三去一家饭馆点了几道菜,觉得味道不错,第二天又去了,对老板说,还点上次的那几道菜
- 无状态: 老板没有记录张三是否来过,更没有记录上次他点了那些菜,张三只能重新再点一遍
- 有状态: 老板把每次来吃饭的用户都做好记录,查阅一下之前的记录,查到了张三之前的菜单,直接下单
1.1.2 会话管理实现的手段
Cookie和Session配合解决
- cookie是在客户端保留少量数据的技术,主要通过响应头向客户端响应一些客户端要保留的信息
- session是在服务端保留更多数据的技术,主要通过HttpSession对象保存一些和客户端相关的信息
- cookie和session配合记录请求状态
举例: 张三去银行办业务
- 张三第一次去某个银行办业务,银行会为张三开户(Session),并向张三发放一张银行卡(cookie)
- 张三后面每次去银行,就可以携带之间的银行卡(cookie),银行根据银行卡找到之前张三的账户(session)
1.2 Cookie
1.2.1 Cookie概述
cookie是一种客户端会话技术,cookie由服务端产生,它是服务器存放在浏览器的一小份数据,浏览器以后每次访问该服务器的时候都会将这小份数据携带到服务器去。
- 服务端创建cookie,将cookie放入响应对象中,Tomcat容器将cookie转化为set-cookie响应头,响应给客户端
- 客户端在收到cookie的响应头时,在下次请求该服务的资源时,会以cookie请求头的形式携带之前收到的cookie
- cookie是一种键值对格式的数据,从tomcat8.5开始可以保存中文,但是不推荐
- 由于cookie是存储于客户端的数据,比较容易暴露,一般不存储一些敏感或者影响安全的数据
应用场景举例
1.记录用户名
当我们在用户名的输入框中输入完用户名后,浏览器记录用户名,下一次再访问登录页面时,用户名自动填充到用户名的输入框,
2.保存电影播放进度
在网页上播放电影的时候,如果中途退出浏览器了,下载再打开浏览器播放同一部电影的时候,会自动跳转到上次退出时候的进度,因为在播放的
时候会将播放进度保存到cookie中
1.2.2 Cookie使用
servletA向响应中增加Cookie
package com.atguigu.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/servletA")
public class ServletA extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException {
// 创建cookie
Cookie cookie1 =new Cookie( "keya", "valua");
Cookie cookie2 =new Cookie("keyb", "valueb");
//将cookie放入response对象
resp.addCookie(cookie1);
resp.addCookie(cookie2);
}
}
servletB从请求中读取Cookie
package com.atguigu.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import javax.sql.rowset.serial.SerialException;
@WebServlet("/servletB")
public class ServletB extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException {
Cookie[] cookies =req.getCookies();
//请求中的多个cookie会进入该数组 请求中如果没有cookie cookies数组是为null
if(null != cookies) {
for(Cookie cookie :cookies)
System.out.println(cookie.getName()+"="+cookie.getValue());
}
}
// 获取请求中携带的cookie
}
1.2.3 cookie的时效性
默认情况下Cookie的有效期是一次会话范围内,我们可以通过cookie的setMaxAge()方法让Cookie持久化保存到浏览器上
- 会话级Cookie
- 服务器端并没有明确指定Cookie的存在时间
- 在浏览器端,Cookie数据存在于内存中
- 只要浏览器还开着,Cookie数据就一直都在
- 浏览器关闭,内存中的Cookie数据就会被释放
- 持久化Cookie
- 服务器端明确设置了Cookie的存在时间
- 在浏览器端,Cookie数据会被保存到硬盘上
- Cookie在硬盘上存在的时间根据服务器端限定的时间来管控,不受浏览器关闭的影响
- 持久化Cookie到达了预设的时间会被释放
cookie.setMaxAgel(int expiry) 参数单位是秒,表示cookie的持久化时间,如果设置参数为0,表示将浏览器中保存的该cookie删除
- servletA设置一个Cookie为持久化cookie
package com.atguigu.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/servletA")
public class ServletA extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException {
// 创建cookie
Cookie cookie1 =new Cookie( "keya", "valua");
cookie1.setMaxAge(60*5);//设置保存时效
cookie1.setPath("/servletB");//设置cookie的提交路径。此处为仅在 /servletB请求时提交
Cookie cookie2 =new Cookie("keyb", "valueb");
//将cookie放入response对象
resp.addCookie(cookie1);
resp.addCookie(cookie2);
}
}
1.2.4 Cookie的提交路径
访问互联网资源时不能每次都需要把所有cookie带上。访问不同的资源时,可以携带不同的cokie,我们可以通过cokie的 setPath(string path) 对cookie的路径进行设置
package com.atguigu.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/servletA")
public class ServletA extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException {
// 创建cookie
Cookie cookie1 =new Cookie( "keya", "valua");
cookie1.setMaxAge(60*5);//设置保存时效
cookie1.setPath("/servletB");//设置cookie的提交路径。此处为仅在 /servletB请求时提交
Cookie cookie2 =new Cookie("keyb", "valueb");
//将cookie放入response对象
resp.addCookie(cookie1);
resp.addCookie(cookie2);
}
}
1.3 Session
1.3.1 Httpsession概述
HttpSession是一种保留更多信息在服务端的一种技术,服务器会为每一个客户端开辟一块内存空间,即sesion对象,客户端在发送请求时,都可以使用自己的session.这样服务端就可以通过session来记录某个客户端的状态了
- 服务端在为客户端创建session时,会同时将session对象的id,即JSESSIONID以cookie的形式放入响应对象
- 后端创建完session后,客户端会收到一个特殊的cookie,叫做JSESSIONID
- 客户端下一次请求时携带JSESSIONID,后端收到后,根据JSESSIONID找到对应的session对象
- 通过该机制,服务端通过session就可以存储一些专门针对某个客户端的信息了
- session也是域对象(后续详细讲解)
应用场景
1 记录用户的登录状态
用户登录后,将用户的账号等敏感信息存入session
2 记录用户操作的历史
例如记录用户的访问痕迹,用户的购物车信息等临时性的信息
1.3.2 Httpsession的使用
用户提交form表单到senvet,携带用户名,ervietA获取session 将用户名存到sesion,用户再请求其他任意serviet,获取之间存储的用户
- 定义表单页,提交用户名,提交后
<form action="servletA" method="post">
用户名:
<input type="text" name="username">
<input type="submit” value="提交">
</form>
- 定义Servlet1,将用户名存入session
package com.atguigu.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/servlet1")
public class Servlet1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//按收消求中username参数
String username =req.getParameter("username");
//获得session对象
HttpSession session=req.getSession();
// 判断请求中有没有一个特殊的cookie ISESSIONID 值 **** ***
//1 有
//根据ISESSIONID找对应session对象
//1 找到了
//返国之前的session
// 2 没找到
// 创建一个新的session返国,并且向response对象中存放一个ISESSIONID的cookie
//2 没有
// 创建一个新的session返回,并且向response对象中存放一个ISESSIONID 的cookie
System.out.println(session.getId());
System.out.println(session.isNew());
//指usernamefsession
session.setAttribute("username",username);
// 客户端响应信息
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write( "成功");
}
}
- 响应中收到了一个JSESSIONID的cookie
- 定义其他Servlet2,从session中读取用户名
package com.atguigu.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
@WebServlet("/servlet2")
public class Servlet2 extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException {
// 获得session
HttpSession session = req.getSession();
System.out.println(session.getId());
System.out.println(session.isNew());
// 读session中存储的用户名
String username=(String) session.getAttribute( "username");
System.out.println("serlvet2 got username:"+username);
}
}
1.3.3 Httpsession时效性
为什么要设置session的时效
- 用户量很大之后,Session对象相应的也要创建很多。如果一味创建不释放,那么服务器端的内存迟早要被耗尽。
- 客户端关闭行为无法被服务端直接侦测,或者客户端较长时间不操作也经常出现,类似这些的情况,就需要对session的时限进行设置了
默认的session最大闲置时间(两次使用同一个session中的间隔时间)在tomcat/conf/web.xml配置为30分钟
我们可以自己在当前项目的web.xml对最大闲置时间进行重新设定
也可以通过HttpSession的API 对最大闲置时间进行设定
//设置最大闲置时间
session.setMaxInactiveInterval(60);
也可直接让session失效
//直接让session失效
session.invalidate();
1.4 三大域对象
1.4.1 域对象概述
域对象:一些用于存储数据和传递数据的对象,传递数据不同的范围,我们称之为不同的域,不同的域对象代表不同的域,共享数据的范围也不同
- web项目中,我们一定要熟练使用的域对象分别是 请求域,会话域,应用域
- 请求域对象是HttpServletRequest,传递数据的范围是一次请求之内及请求转发
- 会话域对象是Httpsession,传递数据的范围是一次会话之内,可以跨多个请求
- 应用域对象是Servletcontext,传递数据的范围是本应用之内,可以跨多个会话
生活举例: 热水器摆放位置不同,使用的范围就不同
- 摆在张三工位下,就只有张三一个人能用
- 摆在办公室的公共区,办公室内的所有人都可以用
- 摆在楼层的走廊区,该楼层的所有人都可以用
三大域对象的数据作用范围图解
1.4.2 域对象的使用
域对象的API
API | 功能 |
---|---|
void setAttribute(String name,String value) | 向域对象中添加/修改数据 |
Object getAttribute(String name); | 从域对象中获取数据 |
removeAttribute(String name); | 移除域对象中的数据 |
API测试
新建demo07,设置依赖并部署到tomcat上
- ServletA向三大域中放入数据
package com.atguigu.servlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//向请求域存汝数据
req.setAttribute("request","requestMessage");
//向会话域存数据
HttpSession session =req.getSession();
session.setAttribute("session","sessionMessage");
// 向应用域存数据
ServletContext application=getServletContext();
application.setAttribute("application", "applicationMsessage");
//获取请求域数据(在一次请求(请求域)中进行)
String reqMessage =(String) req.getAttribute( "request");
System.out.println("请求域:"+reqMessage);
// 请求转发(也在一个请求域中)
req.getRequestDispatcher("servletB").forward(req,resp);
}
}
- ServletB从三大域中取数据
package com.atguigu.servlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取谱求域中的数据
String reqMessage =(String) req.getAttribute( "request");
System.out.println("请求域:"+reqMessage);
//获取会话域中的聂据
HttpSession session=req.getSession();
String sessionMessage =(String)session.getAttribute( "session");
System.out.println("会话域:"+sessionMessage);
//获取应用域中的数据
ServletContext application = getServletContext();
String appMessage =(String)application.getAttribute( "application");
System.out.println("应用域:"+appMessage);
}
}
- 请求转发时,请求域可以传递数据 请求域内一般放本次请求业务有关的数据,如:查询到的所有的部门信息
- 同一个会话内,不用请求转发,会话域可以传递数据 会话域内一般放本次会话的客户端有关的数据,如:当前客户端登录的用户
- 同一个APP内,不同的客户端,应用域可以传递数据`应用域内一般放本程序应用有关的数据 如:Spring框架的IOC容器
二 过滤器
2.1过滤器概述
Filter,即过滤器,是JAVAEE技术规范之一,作用目标资源的请求进行过滤的一套技术规范,是Java web项目中 最为实用的技术之一
- Filter接口定义了过滤器的开发规范,所有的过滤器都要实现该接口
- Filter的工作位置是项目中所有目标资源之前,容器在创建HtpServletRequest和HttpservletResponse对象后,会先调用Filter的dofilter方法
- Filter的doFilter方法可以控制请求是否继续,如果放行,则请求继续,如果拒绝,则请求到此为止,由过滤器本身做出响应
- Filter不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理
- Filter是GOF中责任链模式的典型案例
- Filter的常用应用包括但不限于: 登录权限检查,解决网站乱码,过滤敏感字符,日志记录,性能分析.……
生活举例: 公司前台,停车场安保,地铁验票闸机
- 公司前台对来访人员进行审核,如果是游客则拒绝进入公司,如果是客户则放行,客户离开时提醒客户不要遗忘物品
- 停车场保安对来访车辆进行控制,如果没有车位拒绝进入,如果有车位,发放停车卡并放行,车辆离开时收取请车典
- 地铁验票闸机在人员进入之前检查票,没票拒绝进入,有票验票后放行,人员离开时同样验票
过滤器开发中应用的场景
- 日志的记录
- 性能的分析
- 乱码的处理
- 事务的控制
- 登录的控制
- 跨域的处理
- .............
过滤器工作位置图解
API | 目标 |
---|---|
default public void init(FilterConfig filterConfig) | 初始化方法,由容器调用并传入初始配置信息filterConfig对象 |
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) | 过滤方法,核心方法,过滤请求,决定是否放行,响应之前的其他处理等 |
default public void destroy() | 都在该方法中销毁方法,容器在回收过滤器对象之前调用的方法 |
2.2 过滤器使用
目标:开发一个日志记录过滤器
- 用户请求到达目标资源之前,记录用户的请求资源路径
- 响应之前记录本次请求目标资源运算的耗时
- 可以选择将日志记录进入文件,为了方便测试,这里将日志直接在控制台打印
定义一个过滤器类,编写功能代码
package com.atguigu.filters;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.LogRecord;
//日志过滤器
//记录请求的历史 将日志打印到控制台
// 1 实现Filter按口. jakarta.servlet.下的Filter
// 2 重写过滤方法
// 3 配置过滤器
// web.xml
// 注解
public class LoggingFilter implements Filter {
private SimpleDateFormat dateFormat =new SimpleDateFormat( "yyyy-MM-dd HH: mm:ss");
// 过滤请求的和响应的方法
// 1 请求到达目标资源之前,先经过该方法
// 2 该方法有能力控制请求是否继续向后到达目标资源可以在该方法内直按向客户端做响应处理
// 3 请求剑达目标资源后,响应之前,还会经过该方法
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 1 请求到达目标资源之前的功能代码
// 判断是否登录
// 校验权限是否满足
// 2 放行代码
// 3 响应之前 HttpservletResponse 转换为响应报文之前的功能代码
//请求到达目标资源之前的代码
//参数父转子
HttpServletRequest request =(HttpServletRequest)servletRequest;
HttpServletResponse response =(HttpServletResponse)servletResponse;
// 请求到达目标资源之前打印日志 yyyy-MM-dd HH:mm:ss ***被访问了
String requestURI= request.getRequestURI();
String dateTime =dateFormat.format(new Date());
String beforeLoggin = requestURI+"在"+dateTime+"被访问了";
System.out.println(beforeLoggin);
long t1= System.currentTimeMillis();
// 放行
filterChain.doFilter(servletRequest,servletResponse);
//响应之前的功能代码
long t2= System.currentTimeMillis();
//响应之前的功能代码***资源在yyyy-MM-dd HH:mm:ss 的请求 耗时 毫秒
String afterLogging = requestURI+"资源在"+dateTime+"的请求耗时:"+(t2-t1)+"毫秒";
System.out.println(afterLogging);
}
}
在xml文件中配置过滤器以及映射
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!--
配置过滤器-->
<filter>
<filter-name>loggingFilter</filter-name> <!--别名-->
<filter-class>com.atguigu.filters.LoggingFilter</filter-class>
</filter>
<!--配置过滤器的过港资源规则 路径 servlet-name-->
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<!--
url-pattern 根据谱求的资源路径 对指定的请求进行过滤
/* 过滤全部资源
/a/* 过滤以a开头的资源
*.html 过滤以html为后级的资源
/servlet1 对servlet1请求进行过滤
servlet-name 根据请求的servlet的别名,最指定的servlet资源进行过滤
一个filter-mapping中可以同时存在多个url-pattern 和 servlet-name
-->
<!-- <url-pattern> /* </url-pattern>-->
<!-- <servlet-name>/servlet11</servlet-name>-->
<url-pattern> /* </url-pattern>
</filter-mapping>
</web-app>
2.3 过滤器声明周期
过滤器作为web项目的组件之一,和serviet的生命周期类似,略有不同,没有servlet的load-on·startup的配置,默认就是系统启动立刻构造
阶段 | 对应方法 | 执行时机 | 执行次数 |
---|---|---|---|
创建对象 | 构造器 | web应用启动时 | 1 |
初始化方法 | void init(FilterConfig filterConfig) | 构造完毕 | 1 |
过滤请求 | void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) | 每次请求 | 多次 |
销毁 | default void destroy() | web应用关闭时 | 1 |
package com.atguigu.filters;
import jakarta.servlet.*;
import java.io.IOException;
public class LifeCycleFilter implements Filter {
// 1 构港 构遽器 项目启动 1
//2 初始化 init 构造完毕 1
//3 过滤 doFilter 每次请求 多次
//4 销毁 destory 服务关闭 1
public LifeCycleFilter(){
System.out.println("构造");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始化");
System.out.println(filterConfig.getInitParameter("dateTimePatter"));
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("过滤方法");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("销毁方法");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<filter>
<filter-name>lifeCycleFilter</filter-name>
<filter-class>com.atguigu.filters.LifeCycleFilter</filter-class>
<init-param>
<param-name>dateTimePattern</param-name>
<param-value>yyyy-MM-dd HH:mm:ss</param-value>
</init-param>
</filter>
<!--配置过滤器的过港资源规则 路径 servlet-name-->
<filter-mapping>
<filter-name>lifeCycleFilter</filter-name>
<!--
url-pattern 根据谱求的资源路径 对指定的请求进行过滤
/* 过滤全部资源
/a/* 过滤以a开头的资源
*.html 过滤以html为后级的资源
/servlet1 对servlet1请求进行过滤
servlet-name 根据请求的servlet的别名,最指定的servlet资源进行过滤
一个filter-mapping中可以同时存在多个url-pattern 和 servlet-name
-->
<!-- <url-pattern> /* </url-pattern>-->
<!-- <servlet-name>/servlet11</servlet-name>-->
<url-pattern> /* </url-pattern>
</filter-mapping>
</web-app>
2.4 过滤器链的使用
一个web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链
- 过滤器链中的过滤器的顺序由filter-mapping顺序决定
- 每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的
- 如果某个Filter是使用ServletName进行匹配规则的配置,那么这个Filter执行的优先级要更低
图解过滤器链
2.5 注解方式配置过滤器
xml配置
<!--配置filter,并为filter起别名-->
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.atguigu.filters.LoggingFilter</filter-class>
<!--配置filter的初始参数-->
<init-param>
<param-name>dateTimePattern</param-name>
<param-value>yyyy-MM-dd HH:mm:ss</param-value>
</init-param>
</filter>
<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<!--通过映射路径确定过滤资源-->
<url-pattern>/servletA</url-pattern>
<!--通过后缀名确定过滤资源-->
<url-pattern>*.html</url-pattern>
<!--通过servlet别名确定过滤资源-->
<servlet-name>servletBName</servlet-name>
</filter-mapping>
将xml配置转换为注解方式实现
@WebFilter(
filterName = "loggingFilter"
initParams = {@WebInitParam(name="dateTimePattern",value="yyyy-MM-dd HH :mm:ss")},
urlPatterns ={"/servletA","*.html"},
servletNames ={"servletBName"}
}
三 监听器
3.1 监听器概述
监听器:专门用于对域对象对象身上发生的事件或状态改变进行监听和相应处理的对象
- 监听器是GOF设计模式中,观察者模式的典型案例
- 观察者模式: 当被观察的对象发生某些改变时,观察者自动采取对应的行动的一种设计模式
- 监听器使用的感受类似JS中的事件,被观察的对象发生某些情况时,自动触发代码的执行
- 监听器并不监听web项目中的所有组件,仅仅是对三大域对象做相关的事件监听
监听器的分类
- web中定义八个监听器接口作为监听器的规范,这八个接口按照不同的标准可以形成不同的分类
- 按监听的对象划分
- application域监听器 ServletContextListener SeryletContextAttributel istener
- session 域监听器 HttpSessionlistener HttpSessionAttributelistener HttpSessionBindingListener HttpsessionActivationlistener
- request 域监听器 ServletRequestListener ServletRequestAttributeListener
- 按监听的事件分
- 域对象的创建和销毁监听器 ServletContextListener HttpSessionListener ServletRequestListener
- 域对象数据增删改事件监听器 ServletcontextAttributeListener HttpSessionAttributeListener ServletRequestAtributeListener
- 其他监听器 HttpSessionBindingListener HttpSessionActivationListener
3.2 监听器的六个主要接口
监听器的配置方式
- 在xml文件中
<listener>
<listener-class>com.atguigu.listener.MyApplicationListener</listener-class>
</listener>
- 在类前注解 @WebListener
3.2.1 application域监听器
ServletContextListener 监听ServletContext对象的创建与销毁
方法名 | 作用 |
---|---|
contextInitialized(ServletContextEvent sce) | ServletContext创建时调用 |
contextDestroyed(ServletContextEvent sce) | ServletContext销毁时调用 |
- ServletContextEvent对象代表从Servletcontext对象身上捕获到的事件,通过这个事件对象我们可以获取到ServletContext对象
ServletContextAttributeListener 监听ServletContext中属性的添加、移除和修改
方法名 | 作用 |
---|---|
attributeAdded(ServletContextAttributeEvent scab) | 向ServletContext中添加属性时调用 |
attributeRemoved(ServletContextAttributeEvent scab | 从ServletContext中移除属性时调用 |
attributeReplaced(ServletContextAttributeEvent scab) | 当ServletContext中的属性被修改时调用 |
- ServletContextAttributeEvent对象代表属性变化事件,它包含的方法如下
方法名 | 作用 |
---|---|
getName() | 获取修改或添加的属性名 |
getValue() | 获取被修改或添加的属性值 |
getServletContext() | 获取ServletContext对象 |
package com.atguigu.listener;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebListener;
import java.util.EventListener;
@WebListener
public class MyApplicationListener implements ServletContextListener, ServletContextAttributeListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext application =sce.getServletContext();
System.out.println(application.hashCode()+"应用域初始化了");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContext application =sce.getServletContext();
System.out.println(application.hashCode()+"应用域销毁了");
}
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
ServletContext application=scae.getServletContext();
String key=scae.getName();
Object value =scae.getValue();
System.out.println(application.hashCode()+"应用域增加了"+key+":"+value);
}
@Override
public void attributeRemoved(ServletContextAttributeEvent scae) {
ServletContext application=scae.getServletContext();
String key=scae.getName();
Object value =scae.getValue();
System.out.println(application.hashCode()+"应用域移除了"+key+":"+value);
}
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
ServletContext application=scae.getServletContext();
String key=scae.getName();
Object value =scae.getValue(); //获取旧值
Object newvalue =application.getAttribute(key);//获取新值
System.out.println(application.hashCode()+"应用域修改了"+key+":"+value+"为"+newvalue);
}
}
3.2.2 session域监听器
package com.atguigu.listener;
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.http.HttpSessionBindingEvent;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
@WebListener
public class MySessionListener implements HttpSessionListener, HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
// 任何一个session域中增加了数据都会触发该方法的执行
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
// 任何一个session域中移除了数据都会触发该方法的执行
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
// 任何一个session域中修改了数据都会触发该方法的执行
}
@Override
public void sessionCreated(HttpSessionEvent se) {
//任何一个session域对象的创建都会触发该方法的执行
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
//任何一个session域对象的销毁都会触发该方法的执行
}
}
3.2.3 request城监听器
package com.atguigu.listener;
import jakarta.servlet.ServletRequestAttributeEvent;
import jakarta.servlet.ServletRequestAttributeListener;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.annotation.WebListener;
@WebListener
public class MyRequestListner implements ServletRequestListener, ServletRequestAttributeListener {
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
// 任何一个request域中增加了数据都会触发该方法的执行
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
// 任何一个request域中移除了数据都会触发该方法的执行
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
// 任何一个request域中修改了数据都会触发该方法的执行
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// 任何一个request域销毁了都会触发该方法的执行
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
// 任何一个request域初始化了都会触发该方法的执行
}
}
3.3 session域的两个特殊监听器
3.3.1 session绑定监听器
HttpSessionBindingListener 监听当前监听器对象在Session域中的增加与移除
方法名 | 作用 |
---|---|
valueBound(HttpSessionBindingEvent event) | 该类的实例被放到Session域中时调用 |
valueUnbound(HttpSessionBindingEvent event) | 该类的实例从Session中移除时调用 |
HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:
方法名 | 作用 |
---|---|
getName() | 获取当前事件涉及的属性名 |
getValue() | 获取当前事件涉及的属性值 |
getSession() | 获取触发事件的HttpSession对象 |
package com.atguigu.listener;
import jakarta.servlet.http.HttpSessionBindingEvent;
import jakarta.servlet.http.HttpSessionBindingListener;
public class SessionBindingListener implements HttpSessionBindingListener {
@Override
public void valueBound(HttpSessionBindingEvent event) {
// 当前监听器实例放入某个session中作为数据 绑定
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
// 当前监听器实例放入某个session中作为数据 解绑定
}
}
package com.atguigu.servlet;
import com.atguigu.listener.SessionBindingListener;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/servlet1")
public class Servlet1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//应用域中汝入数据
ServletContext application=this.getServletContext();
application.setAttribute( "keya","value");
//创建session
HttpSession session = req.getSession();
SessionBindingListener sbl = new SessionBindingListener();
session.setAttribute("sbl",sbl);
session.removeAttribute("sbl");
//手动销毁session
session.invalidate();
}
}
3.3.2 钝化活化监听器
HttpSessionActivatonListener 监听某个对象在Session中的序列化与反序列化。
方法名 | 作用 |
---|---|
sessionWillPassivate(HttpSessionEvent se) | 该类实例和Session一起钝化到硬盘时调用 |
sessionDidActivate(HttpSessionEvent se) | 该类实例和Session一起活化到内存时调用 |
- HttpSessionEvent对象代表事件对象,通过getsession()方法获取事件涉及的HttpSession对象。
什么是钝化活化
- session对象在服务端是以对象的形式存储于内存的,session过多,服务器的内存也是吃不消的
- 而且一旦服务器发生重启,所有的session对象都将被清除,也就意味着session中存储的不同客户端的登录状态丢失
- 为了分摊内存 压力并且为了保证session重启不丢失,我们可以设置将session进行钝化处理
- 在关闭服务器前或者到达了设定时间时,对session进行序列化到磁盘,这种情况叫做session的钝化
- 在服务器启动后或者再次获取某个session时,将磁盘上的session进行反序列化到内存,这种情况叫做session的活化
如何配置钝化活化
- 在web日录下.添加 META-INF下创建Context.xml
- 在文件中配置钝化
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="d:\mysession">/Store>
</Manager>
</Context>
使用
package com.atguigu.listener;
import jakarta.servlet.http.HttpSessionActivationListener;
import jakarta.servlet.http.HttpSessionEvent;
public class MyActivationListner implements HttpSessionActivationListener {
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
//session对象即将钝化之前执行
}
@Override
public void sessionDidActivate(HttpSessionEvent se) {
//session对象活化完毕之后执行
}
}
package com.atguigu.servlet;
import com.atguigu.listener.MyActivationListner;
import com.atguigu.listener.SessionBindingListener;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/servlet1")
public class Servlet1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//应用域中汝入数据
ServletContext application=this.getServletContext();
application.setAttribute( "keya","value");
//创建session
HttpSession session = req.getSession();
SessionBindingListener sbl = new SessionBindingListener();
session.setAttribute("sbl",sbl);
session.removeAttribute("sbl");
//为session设置钝化活化监听
MyActivationListner myActivationListner = new MyActivationListner();
session.setAttribute("lis",myActivationListner);
//手动销毁session
session.invalidate();
}
}
四 案例开发-第三期
4.1 过滤器控制登录校验
需求说明:未登录状态下不允许访问showShedcheduleController相关增删改外理,重定向到login.html登录成功后可以自由访问
首先对SysUserController.java文件中login修改一下,将成功登录的信息存入session
protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1按收用户名制密码
String username=req.getParameter("username");
String userPwd =req.getParameter("userPwd");
//2 调用服务层方法,根据用产名武询用户信息
SysUser loginUser =userService.findByUsername(username);
if(null == loginUser){
//跳转到用户名行识提示贝
resp.sendRedirect("/logingUsernameError.html");
}else if(! MD5Util.encrypt(userPwd).equals(loginUser.getUserPwd())){
//3 判断密码是否匹配]
// 跳转到密码有误提不贝
resp.sendRedirect( "/logingUserPwdError.html");
}else{
// 發录成功之后,将發录的用户信息入session
HttpSession session =req.getSession();
session.setAttribute("sysUser",loginUser);
//4 跳转到首灭
resp.sendRedirect( "/showSchedule.html");
}
}
接着设置Filter文件对文件访问进行过滤,文件通过 @WebFilter(urlPatterns = {"/showSchedule.html","/schedule/*"}) 来指定,也可以用servketname取代urlPatterns进行指定
package com.atguigu.schedule.filter;
import com.atguigu.schedule.pojo.SysUser;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter(urlPatterns = {"/showSchedule.html","/schedule/*"})
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//参数父转子
HttpServletRequest request =(HttpServletRequest)servletRequest;
HttpServletResponse response =(HttpServletResponse)servletResponse;
//获得session此对象
HttpSession session=request.getSession();
// 从session域中获得登录的用户对象
SysUser sysUser=(SysUser)session.getAttribute("sysUser");
//判断用户对象是否为空
if(null == sysUser){
// 没登录 到login.html
response.sendRedirect( "/login.html");
}else {
//登录过 放行
filterChain.doFilter(request,response);
}
}
}
五 Ajax
5.1什么是ajax
- AJAX=Asynchronous JavaScript and XML(异步的JavaScript和XML)。
- AJAX不是新的编程语言,而是一种使用现有标准的新方法。
- AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。
- AJAX不需要任何浏览器插件,但需要用户允许 JavaScript 在浏览器上执行。
- XMLHttpRequest 只是实现 Ajax的一种方式
ajax工作原理:
- 简单来说,我们之前发的请求通过类似 form表单标签,a标签 这种方式,现在通过 运行js代码动态决定什么时候发送什么样的请求
- 通过运行JS代码发送的请求浏览器可以不用跳转而面,我们可以在,JS代码中决定是否要跳转而面
- 通过运行JS代码发送的请求,接收到返回结果后,我们可以将结果通过dom编程渲染到页面的某些元素上,实现局部更新
5.2 如何实现ajax请求
原生javascript方式进行ajax(了解):
根据操作使得信息出现在当前页的指定位置,或者跳转页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
function getMessage(){
//实例化一个XMLHttpRequest
var request = new XMLHttpRequest();
// 设置xmlHttpRequest对 象的回调两数
// request.readstate 1 2 3 4
// request.status 响应状态码 响应行状态码
request.onreadystatechange=function () {
if (request.readyState == 4 && request.status == 200){
//接收响应结果,处理结果
//request.responseText 后端响应回来的响应体中的数据
console.log(request.responseText)
//将信息放到指定位置
// var inputEle = document.getElementById("message")
// inputEle.value = request.responseText
//跳转页面
window.location.href = "http://www.atguigu.com"
}
}
//发送请求方式和请求资源路径
request.open("GET","/hello?username=zhangsan")
//发送请求
request.send()
}
</script>
</head>
<body>
<button onclick="getMessage()">按钮</button>
<input type="text" id="message"/>
</body>
</html>
package com.atguigu.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收参数
String username = req.getParameter("username");
//做出响应
resp.getWriter().write("hello"+username);
}
}
六 案例开发-日程管理-第四期
6.1 注册提交前校验用户名是否占用功能
为表格信息填充后。鼠标离开(onblur)绑定方法,添加占用校验。将信息发送到/user/chekUsernameUsed进行校验
function checkUsername(){
//定义正则字符规则
var usernameReg = /^[a-zA-Z0-9]{5}$/
//获取用户名
var username = document.getElementById("usernameInput").value
//获取提示框元素
var ts = document.getElementById("usernameInputSpan")
if(!usernameReg.test(username)){
ts.innerText = "请输入五位字母数字"
return false
}
// 格式正确,通过之后,继续校验用户名是否被占用
var request =new XMLHttpRequest();
//设置回调函数 设置响应回来的信息如何处理
request.onreadystatechange=function(){
if(request.readyState ==4 && request.status == 200){
ts.innerText = request.responseText
}
}
//设置请求方式和请求的资源路径
request.open("GET","/user/chekUsernameUsed?username="+username)
//发送请求
request.send()
// ts.innerText = "OK"
// return true
}
在对应位置书写占用判定方法
protected void chekUsernameUsed(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收用户名
String username =req.getParameter("username");
//调用服务层业务处理方法查询该用于名是否有对应的用户
SysUser sysUser =userService.findByUsername(username);
// 如果有 响应 占用
// 如果没有 响应 可用
String info="可用";
if(null != sysUser){
info="已占用";
}
resp.getWriter().write(info);
}
上述做法存在一定问题
1 响应乱码问题
2 响应信息格式不规范,处理方式不规范
后端响应回来的信息应当有一个统一的格式,前后端共同遵守
响应一个JSON串 { "code":"100/200/300/400/41" 业务状态码 本次请求的业务是否成功?如果失败了,是什么原因失败了?不是响应原因
"message":"业务状态码的 补充说明/描述”
"dat": { } 本次响应的数据 成功/不成功 List<Schedule>...
}
3 校验不通过,无法阻止表单提交的问题 未来使用VUE axios 结合pomise处理
6.2 JSON格式问题
定义相应类
package com.atguigu.schedule.common;
/**
* "code":"100/200/300/400/41" 业务状态码 本次请求的业务是否成功?如果失败了,是什么原因失败了?不是响应原因
* "message":"业务状态码的 补充说明/描述”
* "dat": { } 本次响应的数据成功/不成功 List<Schedule>...
* }
* @param <T>
*/
public class Result <T>{
private Integer code;
private String message;
private T date;
public Result() {
}
protected static <T> Result<T> build (T date) {
Result<T> result = new Result<T>();
if (date!=null)
result.setDate(date);
return result;
}
public static <T> Result<T> build (T body,Integer code,String message) {
Result<T> result = build(body);
result.setCode(code);
result.setMessage(message);
return result;
}
public static <T> Result<T> build (T body,ResultCodeEnum resultCodeEnum) {
Result<T> result = build(body);
result.setCode(resultCodeEnum.getcode());
result.setMessage(resultCodeEnum.getMessage());
return result;
}
public static <T> Result<T> ok (T date) {
Result<T> result = build(date);
return build(date,ResultCodeEnum.SUCCESS);
}
public Result <T> message (String msg) {
this.setMessage(msg);
return this;
}
public Result <T> code (Integer code) {
this.setCode(code);
return this;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getDate() {
return date;
}
public void setDate(T date) {
this.date = date;
}
}
定义响应类对应的枚举类
package com.atguigu.schedule.common;
public enum ResultCodeEnum {
SUCCESS( 200,"success"),
USERNAEM_ERROR( 501,"usernameError"),
PASSWORD_ERROR( 503,"passwordError"),
NOTLOGIN( 504,"notlogin"),
USERNAME_USED( 505,"usernameUsed");
private Integer code;
private String message;
// private ResultCodeEnum(Integer code , String message){
// this.code= code;
// this.message = message;
// }
private ResultCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getcode(){
return code;
}
public String getMessage(){
return message;
}
}
修改 public class SysUserController extends BaseController {}中的chekUsernameUsed方法。使用定义的响应类返回信息
protected void chekUsernameUsed(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收用户名
String username =req.getParameter("username");
//调用服务层业务处理方法查询该用于名是否有对应的用户
SysUser sysUser =userService.findByUsername(username);
// 如果有 响应 占用
// 如果没有 响应 可用
Result result = Result.ok(null);
//String info="可用";
if(null != sysUser){
// info="已占用";
result = Result.build(null, ResultCodeEnum.USERNAME_USED);
}
// 将result对象转换为JSON串响应给客户端
// objectMapper
ObjectMapper objectMapper =new ObjectMapper();
String info =objectMapper.writeValueAsString(result);
//告诉客户端响应给你的是一个JSON串
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().write(info);
}
将regist.html中的 checkUsername 方法修改,增加对回调函数响应信息处理
function checkUsername(){
//定义正则字符规则
var usernameReg = /^[a-zA-Z0-9]{5}$/
//获取用户名
var username = document.getElementById("usernameInput").value
//获取提示框元素
var ts = document.getElementById("usernameInputSpan")
if(!usernameReg.test(username)){
ts.innerText = "请输入五位字母数字"
return false
}
// 格式正确,通过之后,继续校验用户名是否被占用
var request =new XMLHttpRequest();
//设置回调函数 设置响应回来的信息如何处理
request.onreadystatechange=function(){
if(request.readyState ==4 && request.status == 200){
// ts.innerText = request.responseText
var result = JSON.parse(request.responseText)
if(result.code != 200){
ts.innerText = "不可用"
}else {
ts.innerText = "可用"
}
}
}
//设置请求方式和请求的资源路径
request.open("GET","/user/chekUsernameUsed?username="+username)
//发送请求
request.send()
// ts.innerText = "OK"
// return true
}
6.3 WebUtil工具类
可以将对象转换为JSON串响应给客户端写成工具类
package com.atguigu.schedule.util;
import com.atguigu.schedule.common.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class WebUtil {
private static ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
}
public static void writeJason(HttpServletResponse resp, Result result){
// 将result对象转换为JSON串响应给客户端
// objectMapper
resp.setContentType("application/json;charset=UTF-8");
try {
String info =objectMapper.writeValueAsString(result);
//告诉客户端响应给你的是一个JSON串
resp.getWriter().write(info);
}catch (Exception e){
e.printStackTrace();
}
}
}
这样可以在public class SysUserController extends BaseController {}中直接使用 WebUtil.writeJason(resp,result);
protected void chekUsernameUsed(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收用户名
String username =req.getParameter("username");
//调用服务层业务处理方法查询该用于名是否有对应的用户
SysUser sysUser =userService.findByUsername(username);
// 如果有 响应 占用
// 如果没有 响应 可用
Result result = Result.ok(null);
//String info="可用";
if(null != sysUser){
// info="已占用";
result = Result.build(null, ResultCodeEnum.USERNAME_USED);
}
// 将result对象转换为JSON串响应给客户端
// objectMapper
// ObjectMapper objectMapper =new ObjectMapper();
// String info =objectMapper.writeValueAsString(result);
// //告诉客户端响应给你的是一个JSON串
// resp.setContentType("application/json;charset=UTF-8");
// resp.getWriter().write(info);
WebUtil.writeJason(resp,result);
}
第七章 前端工程化
一、前端工程化开篇
1.1 什么是前端工程化
前端工程化 是使用 软件工程的方法 来 单独 解决 前端 的开发流程中 模块化、组件化、规范化、自动化 的问题,其主要目的为了提高效率和降低成本。
1.2 前端工程化实现技术栈
前端工程化实现的技术栈有很多,我们采用ES6+nodejs+npm+Vite+VUE3+router+pinia+axios+Element-plus组合来实现
- ECMAScript6 VUE3中大量使用ES6语法
- Nodejs 前端项目运行环境
- npm 依赖下载工具
- vite 前端项目构建工具
- VUE3 优秀的渐进式前端框架
- router 通过路由实现页面切换
- pinia 通过状态管理实现组件数据传递
- axios ajax异步请求封装技术实现前后端数据交互
- Element-plus 可以提供丰富的快速构建网页的组件仓库
前后端分离模式将前端后端放在不同app中
1 开发分离
2 部署分离
二 ECMA6Script
2.1.es6的介绍
ECMAScript6,简称ES6,是Javascript语言的一次重大更新。它于2015年发布,是原来的ECMAScript标准的第六个版本。ES6带来了大量的新特性,包括箭头函数、模板字符串、let和const关键字、解构、默认参数值、模块系统等等,大大提升了Javascript的开发体验。`由于VUE3中大量使用了ES6的语法,所以ES6成为了学习VUE3的门槛一
ES6对JavaScript的改进在以下几个方面:
- 更加简洁:ES6引入了一些新的语法,如箭头函数、类和模板字符申等,使代码更加简洁易懂。
- 更强大的功能:ES6引入了一些新的AP1、解构语法和迭代器等功能,从而使得JavaScript更加强大。
- 更好的适用性:ES6引|入的模块化功能为Javascript代码的组织和管理提供了更好的方式,不仅提高了程序的可维护性,还让Javascript更方便地应用于大型的应用程序。
总的来说,ES6在提高Javascript的核心语言特性和功能方面取得了很大的进展,由于ES6已经成为了Javascipt的标准,它的大多数新特性都已被现在浏览器所支持,因此现在可以放心地使用ES6来开发前端应用程序。
2.2 es6的变量和模板字符串
ES6 新增了 1et 和 const ,用来声明变量,使用的细节上也存在诸多差异
- let 和var的差别
- let 不能重复声明
- let有块级作用域,非函数的花括号遇见let会有块级作用域,也就是只能在花括号里面访问。
- let不会预解析进行变量提升
- let 定义的全局变量不会作为window的属性
- let在es6中推荐优先使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
// 1 let 不能重复声明
// var i = 10
// var i = ""
// let j =10
// let j = "" let 不能重复声明,此处为错误写法
// 2 let有块级作用域,非函数的花括号遇见let会有块级作用域,也就是只能在花括号里面访问。
// {
// var i = 10
// let j = 11
// }
// console.log(i)
// console.log(j)//此处出现错误,let有块级作用域,离开块不生效
// 3 let不会预解析进行变量提升
// console.log(a)//此处输出为undefined
// var a = 10
// console.log(j)//此处报错,let不会预解析
// let j = 10
// 4 let 定义的全局变量不会作为window的属性
// var a=10 // a会变成window对象的属性
// let b=10
// console.log(window.a)
// console.log(window.b)
// 5 let在es6中推荐优先使用
// 6 const 就是不可修改的let(像final修饰的变量),且必须初始化数值
// let a=10
// a= 20
// const c =10
// const PI =3.14
// c = 11 //错误,const不可修改
// const techers =["张老师","王老师","李老师"]
// //techers =["","","”]//错误,const不可修改
// techers.push("陈老师")//不改变指向元素,向里面添加可用
/*模板字符串字符串换行和 字符串拼接问题*/
/*"" '' 处理字符串不支持多行 */
let city ='北京'
let info ="<ul>"+
"<li>"+city+"</li>"+
"<li></li>"+
"</u1>"
let str = `<ul>
<li></li>
<li></li>
<li>${city}</li>
<li></li>
<li></li>
</ul>`
console.log(str)
</script>
</head>
<body>
</body>
</html>
2.3 es6的解构表达式,
ES6 的解构赋值是一种方便的语法,可以快速将数组或对象中的值拆分并赋值给变量。解构赋值的语法使用花括号()表示对象,方括号[]表示数组。通过解构赋值,函数更方便进行参数接受等 !
数组解构赋值
- 可以通过数组解构将数组中的值赋值给变量,语法为:
let[a,b,c]=[1,2,3];//新增变量名任意合法即可,本质是按照顺序进行初始化变量的值
console.log(a)://1
console.log(b);//2
console.log(c);//3
- 该语句将数组[1,2,3]中的第一个值赋值给a变量,第二个值赋值给b变量,第三个值赋值给c变量。可以使用默认值为变量提供备选值,在数组中缺失对应位置的值时使用该默认值。例如:
let [a,b,c,d=4]=[1,2,3];
console.log(d);//4
对象解构赋值
- 可以通过对象解构将对象中的值赋值给变量,语法为:
let {a,b}={a:1 ,b:2};
//新增变量名必须和属性名相同,本质是初始化变量的值为对象中同名属性的值
//等价于 let a=对象.a let b=对象.b
console.log(a);//1
console.log(b);//2
- 该语句将对话{a:1 b:2} 中的a属性值赋值给a变量 , b属性值赋值给b变量。可以为标识符分配不同的变量名称,使用:操作符指定新的变量名。例如:
let{a:x,b:y}={a:1,b: 2};
console.log(x);//1
console.log(y);//2
函数参数解构赋值
// 结构表达式应用在方法的参数列表
function showArr([a,b,c= 10]){
console.log(a,b,c)
}
showArr(arr)
整体综合代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
let arr = [11,22]
// 使用解构表达式取出数据中的元素
let[a,b,c,d,e=10]= arr
console.log(a,b,c,d,e)
let person={
name:"zhangsan",
age :10
}
//使用解构表达式获取对象的属性值
let{age,name}= person
console.log(age,name)
// 结构表达式应用在方法的参数列表
function showArr([a,b,c= 10]){
console.log(a,b,c)
}
showArr(arr)
</script>
</head>
<body>
</body>
</html>
2.4 es6的箭头函数
ES6 允许使用“箭头”义函数。语法类似Java中的Lambda表达式
2.4.1声明和特点
<script>
//ES6 允许使用“箭头"(=>)定义函数。
//1.函数声明
let fn1 = function(){}
let fn2 =()=>{} //箭头函数,此处不需要书写function关键字
let fn3 = x =>{}//单参数可以省略(),多参数无参数不可以!
let fn4 =x=>console.log(x)//只有一行方法体可以省略{};
let fun5 = x =>x +1 //当函数体只有一句返回值时,可以省略花括号和 return 语句
//2.使用特点 箭头函数this关键字
//在 JavaScript 中,this 关键字通常用来引用函数所在的对象,
//或者在函数本身作为构造函数时,来引用新对象的实例。
//但是在箭头函数中,this 的含义与常规函数定义中的含义不同,
//并且是由箭头函数定义时的上下文来决定的,而不是由函数调用时的上下文来决定的。
//箭头函数没有自己的this,this指向的是外层上下文环境的this
console.log(this)
let person ={
name:'张三'
showName:function(){
console.log(this.name)
viewName:()
console.log(this)
person.showName()
person.viewName()
</script>
箭头函数中this应用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#xdd{
display: inline-block;
width: 200px;
height: 200px;
background-color:red;
}
</style>
</head>
<body>
<div id="xdd"></div>
<script>
var xdd = document.getElementById("xdd")
xdd.onclick=function(){
window.setTimeout(()=>{
this.style.backgroundColor="yellow"
},2000)
}
</script>
</body>
</html>
2.5 es6的对象创建和拷贝
2.5.1对象创建的语法糖
ES6中新增了对象创建的语法糖,支持了class extends constructor等关键字,让ES6的语法和面向对象的语法更加接近
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
//2.5 es6的对象创建和拷贝
//2.5.1对象创建的语法糖
//ES6中新增了对象创建的语法糖,支持了class extends constructor等关键字,让ES6的语法和面向对象的语法更加接近
class Person{
// 属性
#n; //加# 号 相当于私有,需通过方法使用,且名字带#
age;
get name(){
return this.#n;}
set name(n){
this.#n =n;}
// 实例方法
eat(food){
console.log(this.age+"岁的"+this.#n+"用筷子吃"+food)}
//静态方法
static sum(a,b){
return a+b;}
// 构造器
constructor(name,age) {
this.#n=name ;
this.age = age;
}
}
let person =new Person("张三",10);
class Student extends Person{
socre
study(){
console.log(`${this.age}岁的${this.name}正在努力学习`)}
constructor(name,age,score) {
super(name,age);
this.score = score;
}
}
</script>
</head>
<body>
</body>
</html>
2.5.2 对象的深拷贝和浅拷贝
对象的拷贝,快速获得一个和已有对象相同的对象的市式
- 浅拷贝 指向相同的内存
- 深拷贝 形成一个新的对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
//2.5.2 对象的深拷贝和浅拷贝
//对象的拷贝,快速获得一个和已有对象相同的对象的市式
//浅拷贝 指向相同的内存
let arr =['java','c','python']
let person ={
name :'张三',
language:arr
}
//浅拷贝,person2和person指向相同的内存
let person2 = person;
person2.name="小黑"
console.log(person.name)
</script>
<script>
//深拷贝,形成一个新的对象
let arr2 =['java','c','python']
let person2 ={
name:'张三',
language :arr
}
//深拷贝,通过JSON和字符串的转换形成一个新的对象
let person3=JSON.parse(JSON.stringify(person2))
person3.name="小黑"
console.log(person2.name)
console.log(person3 .name)
//深拷贝,通过解构表达式方式实现
let person2 ={...person}
person.name="小照"
console.log(person2)
</script>
</head>
<body>
</body>
</html>
2.6 es6的模块化处理
2.6.1模块化介绍
横块化是十种组织和管理前端代码的方式,将代码拆分成小的模块单元,使得代码更易于维护、扩展和复用。它包括了定义、导出、导入以及管理模块的方法和规范。前端模块化的主要优势如下:
- 提高代码可维护性:通过将代码拆分为小的模块单元,使得代码结构更为清晰,可读性更高,便于开发者阅读和维护。
- 提高代码可复用性:通过将重复使用的代码变成可复用的块,减少代码重复率,降低开发成本。
- 提高代码可扩展性:通过模块化来实现代码的松合,便于更改和替换模块,从而方便地扩展功能。
目前,前端模块化有多种规范和实现,包括 CommonJS、AMD和 ES6 模块化。ES6模块化是Javascript 语言的模块标准,使用 import 和export 关键字来实现模块的导入和导出。现在,大部分浏览器都已经原生支持 ES6 模块化,因此它成为了最为广泛使用的前端模块化标准.
- ES6模块化的几种暴露和导入方式
- 分别导出
- 统一导出
- 默认导出
- ES6中无论以何种方式导出,导出的都是一个对象,导出的内容都可以理解为是向这个对象中添加属性或者方法
- 分别导出
需要在被导出js文件中对每个属性都注明权限,默认为隐私,export 表示可被导出
//变量和方法以及类前加 export 才可以被引用,否则属于私密状态不可以被除本文件外其他文件引用
// 变量
exort const PI1 = 3.14
const PI2 = 3.14
// 方法
function sum(a,b){
return a+b}
//类
class Person{
constructor(name,age){
this.name =name;
this.age =age;
}
sayHello(){
console.log(`hello ,my name is ${this.name} ,I'am ${this.age} years old`)
}
}
在其他js文件中引用另一个js文件属性时使用 import * as ml from "./module.js"
// //导入module.js文件
// *代表module.js的所有成员
// 无论何种方式导入,导入的内容都会被当成一个对象处理
// 使用一个对象来接收所有的成员
import * as ml from "./module.js"
console.log(m1.PI)
console.log(m1.PI2)
在html文件中引用js文件,使用如下定义
<script src="./app.js" type="module">
- 统一导出
需要在大括号内填上需要导出的属性名字export{PI1,PI2,sum,Person}
//变量和方法以及类前加 export 才可以被引用,否则属于私密状态不可以被除本文件外其他文件引用
// 变量
const PI1 = 3.14
const PI2 = 3.14
// 方法
function sum(a,b){
return a+b}
//类
class Person{
constructor(name,age){
this.name =name;
this.age =age;
}
sayHello(){
console.log(`hello ,my name is ${this.name} ,I'am ${this.age} years old`)
}
}
//统一导出,需要在大括号内填上需要导出的属性名字
export{PI1,PI2,sum,Person}
在其他js文件中引用另一个js文件属性时使用 import * as ml from "./module.js"。或者使用import { PI1,PI1 as pi,PI2,sum,Person } from "./module.js" //引用时可以利用as取别名
// //导入module.js文件
// *代表module.js的所有成员
// 无论何种方式导入,导入的内容都会被当成一个对象处理
// 使用一个对象来接收所有的成员
import * as ml from "./module.js"
//第二种统一引用方法
import { PI1,PI1 as pi,PI2,sum,Person } from "./module.js" //引用时可以利用as取别名
console.log(m1.PI)
console.log(m1.PI2)
let person = new m1.Person("张三",10)
person.sayHello()
- 默认导出
默认导出在一个js中只能有一个,使用 export default sum 进行定义
//变量和方法以及类前加 export 才可以被引用,否则属于私密状态不可以被除本文件外其他文件引用
// 变量
const PI1 = 3.14
const PI2 = 3.14
// 方法
function sum(a,b){
return a+b}
//类
class Person{
constructor(name,age){
this.name =name;
this.age =age;
}
sayHello(){
console.log(`hello ,my name is ${this.name} ,I'am ${this.age} years old`)
}
}
//统一导出,需要在大括号内填上需要导出的属性名字
export{PI1,PI2,sum,Person}
//默认导出在一个js中只能有一个,其在文件被import时直接被导入
export default sum
在其他js文件中引用另一个js文件的默认属性时使用,import * as ml from "./module.js"
或者import {default as add} from './module.js' 以及import add from './module.js' (为第二种的省略写法)
// //导入module.js文件
// *代表module.js的所有成员
// 无论何种方式导入,导入的内容都会被当成一个对象处理
// 使用一个对象来接收所有的成员
//import * as ml from "./module.js" //此方法即可导入默认方法
//
//import {default as add} from './module.js' //第二种方法导入默认方法
import add from './module.js' //第三种方法导入默认方法,为第二种的省略写法
console.log(m1.PI)
console.log(m1.PI2)
三 前端工程环境搭建
3.1nodejs的简介和安装
3.1.1什么是Nodejs
Node.js是一个基于 chrome V8引|擎的 Javascript运行时环境,可以使 JavaScript 运行在服务器端。使用 Node.js,可以方便地开发服务器端应用程序,如 Web 应用、AP1、后端服务,还可以通过 Node.js构建命令行工具等。相比于传统的服务器端语言(如 PHP、Java、python等),Node.js 具有以下特点:
- 单线程,但是采用了事件驱动、异步 I/0 模型,可以处理高并发请求,
- 轻量级,使用 C++ 编写的 V8 引擎让 Node.js 的运行速度很快。
- 模块化,Node.js 内置了大量模块,同时也可以通过第三方模块扩展功能。
- 跨平台,可以在 Windows、Linux、Mac 等多种平台下运行。
Node.js的核心是其管理事件和异步 I0 的能力。Node.js 的异步 I/0 使其能够处理大量并发请求,并且能够避免在等待10 资源时造成的阻塞。此外,Node.s还拥有高性能网络库和文件系统库,可用于搭建 Websocket 服务器、上传文件等。在 Node.js 中,我们可以使用JavaScript 来编写服务器端程序,这也使得前端开发人员可以利用自己已经熟悉的技能来开发服务器端程序,同时也让 Javascriot成为一种全找语言。Node.js受到了广泛的应用,包括了大型企业级应用、云计算、物联网、游戏开发等领域。常用的 Node.js 框架包括 Express、Koa、Egg.js 等,它们能够显著提高开发效率和代码质量。
3.1.2如何安装nodejs
- 打开官网https://nodejs.org/en下载对应操作系统的 LTS 版本。
- 双击安装包进行安装,安装过程中遵循默认选项即可(或者参照htps:/www.runoob.com/nodeis/nodejs-instal-setup.html)。安装完成后可以在命令行终端输入 node-v 和 npm-v 査看 Node.js 和 npm 的版本号。
- 定义一个app.js文件,cmd到该文件所在目录,然后在dos上通过 node app.js 命令即可运行
function sum(a,b){
return a+b;}
function main(){
console.log(sum(10,20))}
main()
3.2 npm 配置和使用
3.2.1 npm介绍
NPM全称Node package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于后端的Maven。
3.2.2 npm 安装和配置
1.安装
安装node,自动安装npm包管理工具!
2.配置依赖下载使用阿里镜像
- npm 安装依赖包时默认使用的是官方源,由于国内网络环境的原因,有时会出现下载速度过慢的情况。为了解决这个问题,可以配置使用阿里镜像来加速 npm 的下载速度,具体操作如下:
- 打开命令行终端,执行以下命令,配置使用阿里镜像:
npm config set registry https://registry.npm.taobao.org/
- 确认配置已生效,可以使用以下命令查看当前registny的配置:如果输出结果为https://registry.npm.taobao.org/,说明配置已成功生效。
npm config get registry
3.配置全局依赖下载后存储位置
- 在 Windows 系统上,npm 的全局依赖默认安装在<用户目录>\AppData\Roaming\npm 目录下。
- 如果需要修改全局依赖的安装路径,可以按照以下步骤操作:
1 . 创建一个新的全局依赖存储目录,例如 D:\GlobalNodeModules
2 . 打开命令行终端,执行以下命令来配置新的全局依赖存储路径:
npm config set prefix "D:\GlobalNodeModules"
3 .确认配置已生效,可以使用以下命令查看当前的全局依赖存储路径
npm config get prefix
4.升级npm版本
- cmd 输入npm -v查看版本
- 如果node中自带的npm版本过低!则需要升级至9.6.6!
npm install -g npm@9.6.6
3.2.3 npm 常用命令
1.项目初始化
- npm init
- 进入一个vscode创建好的项目中,执行 npm init 命令后,npm 会引导您在命令行界面上回答一些问题,例如项目名称、版本号、作者、许可证等信息,并最终生成一个package.json 文件。package.json信息会包含项目基本信息!类似maven的pom.xml
- npm init -y
- 执行,-y yes的意思,所有信息使用当前文件夹的默认值!不用挨个写!
2.安装依赖(查看所有依赖地址 https://www.npmjs.com)
- npm install 包名 或者 npm install 包名@版本号
- 安装包或者指定版本的依赖包(安装到当前项目中)
安装成功后下载文件,并在package.json中记录下载的依赖名及版本号
- npm install -g 包名
- 安装全局依赖包(安装到d:/GlobalNodeModules)则可以在任何项目中使用它,而无需在每个项目中独立安装该包。
- npm install
- 安装package.ison中的所有记录的依赖
3.升级依赖
- npm update 包名
- 将依赖升级到最新版本
4.卸载依赖
- npm uninstall 包名
5.查看依赖
- npm ls
- 查看项目依赖
- npm list -g
- 查看全局依赖
6.运行命令
- npm run 命令是在执行 npm 脚本时使用的命令。npm 脚本是一组在 package.json 文件中定义的可执行命令。npm 脚本可用于启动应用程运行测试,生成文档等,还可以自定义命令以及配置需要运行的脚本。
- 在 package.json 文件中,scripts 字段是一个对象,其中包含一组键值对,键是要运行的脚本的名称,值是要执行的命令。例如,以下是一个简单的package.json 文件
vscode中使用npm命令如下所示
四 Vue介绍和快速体验
4.1 Vue3介绍
渐进式JavaScript 框架
vue(发音为 /iu:/,类似 view)是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSs 和 Javascript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面, 都可以胜任。官网为:https:/cn.vuejs.org/
Vue的两个核心功能:
- 声明式渲染:ve基于标准 HIML拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML!和.Javascript状态之间的关系
- 响应性:Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM
4.2 vue3快速体验(非工程化方式)
首先导入Vue依赖
- 链接导入:<script src="https://unpkg.com/vue@3.4.29/dist/vue.global.js"></script>
- 这里也可以用浏览器打开连接,然后将获得的文本单独保存进入一个vue.is的文件,导入vue.is文件即可。如: <script src = './js/vue.js'></script>
再定义VueApp,并设置好数据以及方法并返回(未返回的属性不可被外界使用)。并使用 app.mount("#app") 将app对象挂载在指定的元素上,被挂载的元素内部就可以通过vue框架实现数据的渲染了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=<device-width>, initial-scale=1.0">
<title>Document</title>
<!--导入Vue依赖-->
<!-- <script src="https://unpkg.com/vue@3.4.29/dist/vue.global.js"></script> -->
<!--这里也可以用浏览器打开连接,然后将获得的文本单独保存进入一个vue.is的文件,导入vue.is文件即可-->
<script src = './js/vue.js'></script>
</head>
<body>
<div id="app">
<h1 v-text="message" v-bind:style="colorStyle">hello vue</h1>
<h1>{{message}}</h1>
<button @click="fun1()">change</button>
</div>
<script>
const app = Vue.createApp({
setup() {
//定义数据 以变量/对象形式
let message = "hello"
let headline ='vue'
let colorStyle ={"background-color":"yellow"}
function fun1(){
alert("hello")
}
// 在return中返回的变量/方法
return {
message,
headline,
colorStyle,
fun1
}
}
})
//将app对象挂载在指定的元素上,被挂载的元素内部就可以通过vue框架实现数据的渲染了
app.mount("#app")
</script>
</body>
</html>
五、vue3通过vite实现工程化
5.1 Vite的介绍
在浏览器支持 ES 模块之前,Javascript 并没有提供原生机制让开发者以模块化的方式进行开发。这也正是我们对“打包”这个涉念熟悉的原因:使用工具抓取、处理并将我们的源码模块串联成可以在浏览器中运行的文件。时过境迁,我们见证了诸如 webpack、Rolup 和 Parceu等工具的变迁,它们极大地改善了前端开发者的开发体验。
- 当我们开始构建越来越大型的应用时,需要处理的JavaScript 代码量也呈指数级增长,。
- 包含数千个模块的大型项目相当普遍。基于JavaScript 开发的工具就会开始遇到性能瓶颈:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。
vite 旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,且越来越多 Javascript 工具使用编译型语言编写。htps://cn.viteis.dev/guide/why.html前端工程化的作用包括但不限于以下几个方面:
- 快速创建项目:使用脚手架可以快速搭建项目基本框架,避免从零开始搭建项目的重复劳动和繁琐操作,从而节省时间和精力。
- 统一的工程化规范:前端脚手架可以预设项目目录结构、代码规范、gt提交规范等统一的工程化规范,让不同开发者在同一个项目上编写出风格一致的代码,提高协作效率和质量。
- 代码模板和组件库:前端脚手架可以包含一些常用的代码模板和组件库,使开发者在实现常见功能时不再重复造轮子,避免因为轮子质量不高带来的麻烦,能够更加专注于项目的业务逻辑。
- 自动化构建和部署:前端脚手架可以自动进行代码打包、压缩、合并、编译等常见的构建工作,可以通过集成自动化部署脚本,自动将代码部署到测试、生产环境等。
5.2 vite创建Vue3工程化项目
5.2.1 Vite+vue3项目的创建、启动、停止
1 使用命令行创建工程
- 在磁盘的合适位置上,创建一个空目录用于存储多个前端项目
- 用vscode打开该目录
- 在vocode中打开命令行运行如下命令
pm create vite@latest
- 第一次使用vite时会提示下载vite,输入y回车即可,下次使用vite就不会出现了
- 注意:选择vue+JavaScript选项即可
2 安装项目所需依赖
- cd进入刚刚创建的项目目录
- npm install命令安装基础依赖
cd ./vue3-demo1
npm install (或npm i ,将按照package.json安装依赖到node_modules下)
- npm run dev 命令运行项目 。在浏览器输入Local网址即可进入,或者按住ctrl+点击网址
- 按住ctrl+c停止项目
5.2.2 Vite+vue3项目的目录结构
1.下面是 Vite 项目结构和入口的详细说明:
- public/目录:用于存放一些公共资源,如 HTML 文件、图像、字体等,这些资源会被直接复制到构建出的目标目录中。
- src/目录:存放项目的源代码,包括JavaScript、css、vue 组件、图像和字体等资源。在开发过程中,这些文件会被 vite 实时编译和处理并在浏览器中进行实时预览和调试。以下是src内部划分建议:
- assets/目录:用于存放一些项目中用到的静态资源,如图片、字体、样式文件等。
- components/目录:用于存放组件相关的文件。组件是代码复用的一种方式,用于抽象出一个可复用的 U部件,方便在不同的场景中进行重复使用。
- layouts/目录:用于存放布局组件的文件。布局组件通常负责整个应用程序的整体布局,如头部、底部、导航菜单等
- pages/目录:用于存放页面级别的组件文件,通常是路由对应的组件文件。在这个目录下,可以创建对应的文件夹,用于存储不同的页面组件。
- plugins/目录:用于存放 Vite 插件相关的文件,可以按需加载不同的插件来实现不同的功能,如自动化测试、代码压缩等。
- router/目录:用于存放 vue.js 的路由配置文件,负责管理视图和 URL 之间的映射关系,方便实现页面之间的跳转和数据传递,
- store/ 目录:用于存放 vuex 状态管理相关的文件,负责管理应用程序中的教据和状态,方便统一管理和共享数据,提高开发效率
- utils/日录: 用于存放一些通用的工具函数,如日期处理函数、字符串操作函数等。
- vite,configis文件:vite 的配置文件,可以通过该文件配置项目的参数、插件、打包优化等。该文件可以使用 commonJs 或 ES6 模块的语法进行配置。
- package.json 文件:标准的 Node.js项目配置文件,包含了项目的基本信息和依赖关系。其中可以通过 scripts 字段定义几个命令,如 dev、build、serve 等,用于启动开发、构建和启动本地服务器等操作。
- vite 项目的入口为 src/main.js文件,这是 vue.js 应用程序的启动文件,也是整个前端应用程序的入口文件。在该文件中,通常会引入 vue.js及其相关插件和组件,同时会创建Vue 实例,挂载到 HTML 页面上指定的 DOM 元素中。
2.vite的运行界面
在安装了 vite 的项目中,可以在 npm scripts中使用 vite 可执行文件,或者直接使用 npx vite 运行它。下面是通过脚手架创建的 vite 项目中默认的 npm scripts:(package.json)
"scripts":{
"dev":"vite",// 启动开发服务器,别名:'vite dev','vite serve
"build":"vite build",// 为生产环境构建产物
preview":"vite preview"//本地预览生产构建产物
}
运行设置端口号:(vite.config.js)
//修改vite项目配置文件 vite.config.js
export default defineConfig({
plugins:[vue()],
server:{
port:3000 }})
5.2.3 Vite+Vue3项日组件(SFC入门)
什么是VUE的组件?
- 个页面作为整体,是由多个部分组成的,每个部分在这里就可以理解为一个组件
- 每个.vue文件就可以理解为一个组件,多个.vue文件可以构成一个整体页面
- 组件化给我们带来的另一个好处就是组件的复用和维护非常的方便
什么是.vue文件?
- 传统的页面有.html文件,css文件和.js文件三个文件组成(多文件组件)
- vue将这文件合并成一个.ue文件(Single-File component,简称 SFC,单文件组件)
- vue文件对is/css/html统一封装,这是VUE中的概念 该文件由三个部分组成<script><template><style>
- template标签 代表组件的html部分代码 代替传统的.html文件
- script标签 代表组件的js代码 代替传统的.js文件
- style标签 代表组件的css样式代码 代替传统的.css文件
工程化vue项目如何组织这些组件?
- index,html是项目的入口,其中 <div id ='app'></div>是用于挂载所有组建的元素
- index,html中的script标签引入了一个main,js文件,具体的挂载过程在main.js中执行
- main.js是vue工程中非常重要的文件,他决定这项目使用哪些依赖,导入的第一个组件
- App.vue是vue中的核心组件,所有的其他组件都要通过该组件进行导入,该组件通过路由可以控制页面的切换
5.2.4 Vite+Vue3响应式入门和setup函数
1 使用vite创建一个 vue+JavaScript项日
npm create vite
npm install
npm run dev
- App.vue
<script>
//存储vue页面逻辑js代码
</script>
<template>
<!--页面的样式的是htm1代码-->
</template>
<style scoped>
/**存储的是css代码!<style scoped>是 Vue.is 单文件组件中用于设置组件样式的一种方式。它的含义是将样式局限在当前组件中,不对全局样式造成影响。*/
</style>
2 vue3响应式数据入门
- 响应式数据: 在数据变化时,vue框架会将变量最新的值更新到dom树中,页面数据就是实时最新的
- 非响应式数据: 在数据变化时,vue框架不会将变量最新的值更新到dom树中,页面数据就不是实时最新的
- vue2中,数据不做特殊处理,默认就是响应式的
- vue3中,数据要经过 ref /reactive 函数的处理才是响应式的
- ref reactive函数是vue框中给我们提供的方法,导入进来即可使用。 import {ref} from 'vue'
- ref处理的响应式数据,在操作时需要注意
- 在script标签中,操作ref的响应式数据需要通过.value的形式操作
- 在template标签中,操作ref的响应式数据,无需使用.value
<script >
import {ref} from 'vue'
export default{
setup(){
//定义一些要展示到html上的一些数据 变量/对象
//响应式数据:在数据变化时,vue框架会将变量最新的值更新到dom树中,页面数据就是实时最新的
//非响应式数据:在数据变化时,vue框架不会将变量最新的值更新到dom树中,页面数据就不是实时最新的
// vue2中,数据不做特殊处理,默认就是响应式的
// vue3中,数据要经过ref /reactive函数的处理才是响应式的
// ref reactive函数时vue框中给我们提供的方法,导入进来即可使用
// ref处理的响应式数据,在操作时需要注意
// 在script标签中,操作ref的响应式数据需要通过.value的形式操作
// 在template标签中,操作ref的响应式数据,无需使用.value
let counter = ref(1)
//让counter 自增的方法
function counterIncr(){
counter.value++}
// 让counter自减的方法
function counterDecr(){
counter.value--}
return{
counter,
counterIncr,
counterDecr
}
}
}
</script>
<template>
<div>
<button @click="counterIncr"> + </button>
<span v-text="counter"> </span>
<button @click="counterDecr"> - </button>
</div>
</template>
<style scoped>
</style>
3 setup语法糖
上述代码中的冗余部分,如export default{setup{......return....}} ,可以删除,在<script>标签中用 setup 代替。如下所示
<script setup>
import {ref} from 'vue'
//定义一些要展示到html上的一些数据 变量/对象
//响应式数据:在数据变化时,vue框架会将变量最新的值更新到dom树中,页面数据就是实时最新的
//非响应式数据:在数据变化时,vue框架不会将变量最新的值更新到dom树中,页面数据就不是实时最新的
// vue2中,数据不做特殊处理,默认就是响应式的
// vue3中,数据要经过ref /reactive函数的处理才是响应式的
// ref reactive函数时vue框中给我们提供的方法,导入进来即可使用
// ref处理的响应式数据,在操作时需要注意
// 在script标签中,操作ref的响应式数据需要通过.value的形式操作
// 在template标签中,操作ref的响应式数据,无需使用.value
let counter = ref(1)
//让counter 自增的方法
function counterIncr(){
counter.value++}
// 让counter自减的方法
function counterDecr(){
counter.value--}
</script>
5.2.5 Vite+Vue3关于样式的导入方式
1.全局引入main.js
import './style/reset.css' //书写引入的资源的相对路径即可!
2.vue文件script代码引入
import './style/reset.css'
3.Vue文件style代码引入
@import './style/reset.css'
5.2.6 关于JS和TS选择的问题
TS是JS的一个超集,使用TS之后,JS的语法更加的像JAVA,实际开发中用的确实更多,那么这里为什么我们没有立即使用TS进行开发,原因如下
- 为了降低难度,提高前端工程化的效率
- 对于学JAVA的我们来说,学习TS非常容易,但是还是需要一些时间
- TS不是非学不可,不用TS仍然可以正常开发工程化的前端项目
- 尚硅谷已经发布了TS的专项课程,请大家在B站上自行搜索"尚硅谷 TS"
- 建议大家先学完完整的前端工程化内容,然后再根据需求单独学习TS即可