当前位置: 首页 > news >正文

JavaWeb开发

浏览器工作原理

详细内容可见前端基础,本文仅作简要总结和回顾。

服务器和浏览器通过应用层协议http进行通信,浏览器请求,服务器响应,返回的可视化结果多通过html超文本标记语言实现。

tomcat服务器

tomcat安装

服务器是安装在服务端的软件,可以实现对HTTP协议的封装,让开发者无需关注底层通信方法,只要构造html页面和后端逻辑就能让客户端通过浏览器访问本机资源。

tomcat是Apache体系下的JavaWeb服务器,该服务器专注于动态内容构建,对于静态资源的处理效率较低,通常与高性能静态资源服务器如Nginx搭配使用。并且因为其与Java生态的深度绑定性,该款服务器仍然具有不可替代性。

可到tomcat官网下载服务器软件
tomcat官网下载
三个版本对jdk的要求也越来越高,企业生产环境中通常新特性用的不多,最主要的需求是可用性和稳定性,所以我也下载的Tomcat 9版本。
下载tomcat 9 64位
解压后可得如下文件夹:
tomcat文件夹
随后确认系统变量环境:
系统变量
执行bin\startup.bat启动服务后,在浏览器中访问localhost:8080得到如下页面说明安装成功:tomcat欢迎界面

IDEA集成tomcat

已有项目中首先点击文件,再选择项目属性,
项目结构
选择新建web
新建web
应用并确定后在项目结构中可见添加了web文件夹。
添加web文件夹
随后在右上角运行文件选项框内选择编辑配置,选择增加本地Tomcat
![增加本地tomcat服务器](https://i-blog.csdnimg.cn/direct/32974a91c02141b39c22c728225bc53f.png#pic_center 600x600)
可以修改的几个位置,并点击最后提示的警告,配置工件
tomcat配置
点击修复后增加展开型应用程序,即war包
增加展开型应用程序
增加后可见如下页面,其中应用程序上下文决定了访问路径:
添加后工件
再回到服务器设置页面可见url更改,浏览器可通过该url为根目录访问资源:
添加url

Servlet服务端程序

Servlet是用Java编写的服务器端程序,其主要功能在于交互式地浏览和修改数据,生成动态Web内容,Java通过实现该接口完成对客户端请求的解析与响应,本质是上浏览器与服务器数据库和应用程序的中间层

Servlet导入与示例

Servlet依赖的jar包在tomcat目录下的lib文件夹内:
Servlet包位置
将其复制到项目的web目录下的WEB-INF内,新建lib并添加为库,因为WEB-INF是用户无法直接访问的目录,相对安全,添加后可得下图结果:
添加servlet为库
补充介绍jar包和war包:
如果说python通过pip安装第三方库实现功能扩展,Java就是通过jar包安装新功能。jar包是Java基于zip的​​标准归档格式​​,实现将一组核心的Java类和依赖打包为一个jar包,便于分发复用,相比python的pip,如今Java也有自动的包管理工具MavenGradle
WAR包则是Java Web应用的​​标准化打包格式,用于将一个完整的Java Web应用(包括前端资源、后端代码、配置文件等)打包成一个文件,方便部署到Servlet容器(如Tomcat、Jetty)或Java EE应用服务器(如WildFly、WebLogic)中运行。

项目通过implements Servlet实现接口后需要重写其中的所有方法,重写的方法为Servlet的完整生命周期,初始化方法init、执行业务逻辑方法service、关闭Tomcat容器时的销毁方法destroy和两个获取配置信息方法。

简单示例如下:

import javax.servlet.*;
import java.io.IOException;public class test implements Servlet {@Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println("servlet init");}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("servlet service");}@Overridepublic String getServletInfo() {return "";}@Overridepublic void destroy() {System.out.println("servlet destroy");}
}

web.xml配置文件

有了Servlet处理请求,如何指定url访问特定的后端文件呢?
有两种解决方案,首先是注解修饰,添加注解@WebServlet(资源路径),此时在url中输入指定路径即可触发相关代码。

另一种方案是在添加的web结构下的WEB-INF目录下的web.xml中更新配置信息。
web.xml更新配置信息
该文件类似html,使用标签标识内容,在其中添加<servlet>配置资源信息,并通过设置别名与<servlet-mapping>资源映射信息匹配,实现url路径与访问资源的匹配,也更推荐使用这种方法,因为配置文件中的设置除了能绑定资源与路径的,还能实现局部变量和整体信息的设置,示例如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><servlet>
<!--        设置别名--><servlet-name>test01</servlet-name>
<!--        访问资源的全类名--><servlet-class>test</servlet-class>
<!--        设置初始化信息--><init-param><param-name>name</param-name><param-value>jack</param-value></init-param></servlet><servlet-mapping>
<!--        别名对应--><servlet-name>test01</servlet-name>
<!--        访问的url--><url-pattern>/test01</url-pattern></servlet-mapping>
<!--    设置全局变量--><context-param><param-name>bigname</param-name><param-value>JACK</param-value></context-param>
</web-app>

此时通过url访问资源即可在控制台查看到响应输出,下面介绍Servlet常用的几个实例。

常用对象介绍

ServletConfig配置信息
ServletConfig实例由Tomcat创建,用于读取项目的初始化信息,该实例作为参数传给Servlet的初始化方法,该实例局部有效,只针对一个Servlet可见,常用方法可见如下代码:

public void init(ServletConfig servletConfig) throws ServletException {System.out.println("servlet init");// 获取初始化参数String name=servletConfig.getInitParameter("name");System.out.println(name);// 获取全局域对象ServletContext servletContext=servletConfig.getServletContext();//获取当前资源别名System.out.println(servletConfig.getServletName());// 获取所有初始化参数的nameEnumeration<String> names = servletConfig.getInitParameterNames();// 循环读取对应的valuewhile (names.hasMoreElements()) {String name1=names.nextElement();// 读取key为name1的valueString value=servletConfig.getInitParameter(name1);System.out.println(name1+"="+value);}
}
// 对应输出为
servlet init
jack
test01
name=jack

ServletContext全局域对象
该对象是整个Web项目用于共享和存储资源的实例,该域对象是单例对象,可被项目中的多个Servlet共享,常用方法如下:

public void init(ServletConfig servletConfig) throws ServletException {System.out.println("servlet init");// 创建servletContextServletContext servletContext = servletConfig.getServletContext();// 存储全局信息servletContext.setAttribute("age", "18");// 获取全局信息System.out.println(servletContext.getAttribute("age"));// 获取全局变量System.out.println(servletContext.getInitParameter("bigname"));
}

ServletRequest请求对象
ServletRequest表示客户端的请求,请求头等相关信息都封装在该对象内,通常使用的是继承该类的HttpServletRequest,该类针对HTTP协议进一步封装,提供了更多方法,常用方法实例如下:

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("servlet service");// 接收客户端参数值,get/post方法都可System.out.println(servletRequest.getParameter("name"));// 接收多个参数值String[] values = servletRequest.getParameterValues("name");for (String value : values) {System.out.println(value);}// 设置字符集servletRequest.setCharacterEncoding("UTF-8");// 转换为http请求格式HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;// 获取请求头System.out.println(httpServletRequest.getHeader("User-Agent"));// 获取客户端请求方式System.out.println(httpServletRequest.getMethod());// 设置键值对信息httpServletRequest.setAttribute("age", "18");// 获取键值信息,一次请求内有效System.out.println(httpServletRequest.getAttribute("age"));// 内部资源跳转,地址栏不变httpServletRequest.getRequestDispatcher("index.jsp").forward(servletRequest, servletResponse);
}
// 通过url=http://localhost:8081/web/test01?name=jack&name=kevin&name=bob访问
// 输出为
servlet service
jack
jack
kevin
bob
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0
GET
18

ServletResponse响应对象
该对象负责对请求进行响应,可通过字节或字符流的形式将回应信息发送给浏览器端,甚至可直接将html以字符串的形式发送给浏览器,相关用法可见如下代码:

@WebServlet("/main")
public class Main extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 设置编码格式resp.setCharacterEncoding("UTF-8");// 告知浏览器解析编码方式,先文本类型再字符集,避免中文乱码resp.setContentType("text/html;charset=UTF-8");// 获取字符流对象Writer writer=resp.getWriter();// 写入信息,可用htmlwriter.write("<h1>Hello World</h1>");writer.write("你好世界");// 获取字节流对象,一次响应只能获取一次对象OutputStream outputStream=resp.getOutputStream();outputStream.write(1234);// 响应重定向,可引入外部资源,且地址栏变化resp.sendRedirect("https://www.baidu.com");}
}

验证机制——cookie与session

有关概念辨析可见一文彻底搞清cookie、session和token的区别。

cookie是除get/post请求以外,对用户透明的浏览器服务端信息交换过程,一般用于存储用户身份验证信息,记住用户偏好等无需显示提交的场景,cookie伴随请求一齐发送,适用于页面跳转后保持上一个页面信息的场景,相比请求其核心优势是自动传递、无感知、轻量级,最大4KB。

Servlet中通过响应对象通知浏览器的方式保存cookie,使用Cookie cookie = new Cookie("cookie", "test");创建后,resp.addCookie(cookie);响应对象告知浏览器将cookie保存,页面右击检查,可见页面cookie如图:
浏览器保存cookie
其他常用方法通过代码展示:

// 获取请求方所有cookie
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {// 获取key与valueSystem.out.println(cookie.getName() + " : " + cookie.getValue());
}
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {System.out.println(cookie.getName() + " : " + cookie.getValue());// 获取指定cookieif (cookie.getName().equals("cookie")) {// 设置存活时间,单位为秒,0马上删除,默认-1随浏览器关闭而关闭cookie.setMaxAge(60*60*24*30);// 修改cookie值并添加,cookie不允许key重复,新的会覆盖旧的cookie.setValue("newtest");resp.addCookie(cookie);}
}

修改后可见时间如下图:
修改cookie时间
该方法可用于用户的免密码登陆,可用EL语句${cookie.name.value}获取,但要注意的是一个cookie只能存储一个键值对,浏览器发送时会将多个cookie合并为一个请求头。

完整的用户信息可能很长,每次发送都要携带用户名、密码等加密字符对网络开销较大,基于简化该部分的想法,产生了session
session给用户分配一个随机且唯一的字符串,用于标识用户身份,以后cookie只要发sessionid即可代表用户身份,且因为代表的信息都存在服务端,相比cookie也更安全。

常用方法如下:

// 获取session对象
HttpSession session=req.getSession();
resp.getWriter().print("session:");
// 获取sessionid
resp.getWriter().print(session.getId());
// session域对象写入信息
session.setAttribute("name",req.getParameter("name"));
session.getAttribute("name");
// 设置存活时间,默认三十分钟
session.setMaxInactiveInterval(30*60);
// 获取存活时间
session.getMaxInactiveInterval();
// 设置马上超时
session.invalidate();

补充知识——DOM4j解析xml配置文件

上面提到session的默认生存时间其实也是写在配置文件里的,在tomcat工程的conf目录下的web.xml文件中可见下图配置:
配置文件设置session过期时间
xml和html类似,也是一种标记型语言,首先通过声明<?xml version="1.0" encoding="UTF-8"?>开始,后续是一系列双标签、元素和属性,如:

<student><name id="1">kevin</name><age>24</age>
</student>

该结构声明student标签内有两个子标签nameage,其中name有属性id值为1。
web.xml无法自定义标签是因为其设置了约束xmln,只能使用定义好的标签,

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0">

dom4j是Java解析xml文件的工具包,可到官网下载,在项目中引入后即可使用,常规方法如下:

try {// 创建流对象SAXReader reader = new SAXReader();// 读取xml文件Document document=reader.read("src/test.xml");// 获取根节点Element root=document.getRootElement();// 输出节点下指定标签的值System.out.println(root.elementTextTrim("name"));// 获取所有节点List<Element> elements=root.elements();for (Element element : elements) {System.out.println(element);}} catch (DocumentException e) {throw new RuntimeException(e);
}

JSP Java服务器页面

全称JavaServer Pages,使用标签<% 代码段 %>将Java代码嵌入html文件,<%! %>声明变量,<%= %>获取表达式。
本质还是Servlet的进一步封装,底层通过流对象writer.print(html结构)将整个html文件发送到浏览器,实现前端页面的变化。

简单使用实例如下:
jsp使用实例
PS:补充对比,JS、JSP、PHP
三者都是用于实现网页的动态变化,也都通过标签嵌入html文件,但各自侧重不同
js侧重网页与用户的交互,如弹窗,按钮点击事件等,与后端的交互性不足;
​​JSP/PHP在服务端执行,处理业务逻辑后动态生成html内容,再将html发送给客户端。
不过jsp是专用于Java生态的动态页面方法,只能用于tomcat服务器,而php则可用于apache。

jsp的执行本质——解析执行

有一个点需要额外理解,为什么代码写在前端,却在后端执行
这是因为服务器解析执行请求。
后端程序在得到访问.jsp文件的请求时,会将该请求交给Tomcat的内置引擎处理,用户首次访问时JSP引擎会将该jsp文件编译成Servlet类的java文件以及字节码文件,执行其中语句后将动态生成的html文件发送给客户端。
所以这部分代码只是存储在前端,后端执行完后给前端纯粹的html文件,而js则是对用户直接可见的。

部署时我们可见控制台的相关路径:
控制台路径
CATALINA_BASE表示Tomcat实例执行时的文件目录,复制该目录可见如下结构:
CATALINA_BASE目录结构
分别是配置文件,日志和实际工作目录,若用户未访问.jsp资源,工作目录中只有空文件夹,这再次证明了解析执行的过程,访问资源后,可在当前文件夹下的\work\Catalina\localhost\web\org\apache\jsp\jsp路径找到编译的Servlet类文件与对应的字节码文件,说明解析执行完毕。

html文件存在哪?
我们前面提到,jsp本质是用servlet生成html文件结构并发送给服务端的,所以并不显式存储html文件,而是在内存生成,下次访问请求如果一致,则直接调用字节码class文件交由虚拟机执行,再次发送即可。

打开java文件有如下源码:
生成的Servlet源码
可见的确是将jsp文件代码编译成Servlet类并使用流对象发送html文件的。

jsp域对象

首先还是要从tomcat的lib目录导入jsp_api.jar包,并添加为库,才能使用下面的一些方法。

jsp中常用的有四大域对象,按作用域从小到大分别是:
pageContext,仅在当前jsp文件内起作用
request,在一次请求内有效
session,浏览器开启状态内有效
application,整个web工程内有效

使用如下代码作为测试:

<%@ page import="jdk.nashorn.internal.ir.RuntimeNode" %><%--Created by IntelliJ IDEA.User: hpDate: 2025/7/21Time: 13:32To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%pageContext.setAttribute("pageContext","pageContext");request.setAttribute("request","request");session.setAttribute("session","session");application.setAttribute("application","application");
%>
<html>
<head><title>Title</title>
</head>
<body>pageContext:<%= pageContext.getAttribute("pageContext")%><br>request:<%= request.getAttribute("request")%><br>session:<%= session.getAttribute("session")%><br>application:<%= application.getAttribute("application")%><br>
</body>
</html>
// 对应页面输出为
pageContext:pageContext
request:request
session:session
application:application

此时若访问相同body的文件如test02.jsp,则pageContextrequest全显示为null

脚本简化方法——EL表达式与jstl标签库

上面获取域对象内容的方法还是复杂了,下面介绍一种更简单的方法——EL表达式
相比表达式内加getAttribute方法,EL表达式通过${作用域.key}的方式获取属性方法。
示例为:

<%@ page import="util.User" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%User user1=new User("bob",22);application.setAttribute("user",user1);
%>
<html>
<head><title>Title</title>
</head>
<body>
username:${applicationScope.user.name}<br>
age:${applicationScope.user.age}<br>
</body>
</html>

后端资源修改要重新部署jsp中的Java代码也算后端资源

同时要注意,jsp引用的文件不能直接来自src目录,必须在一个包内。

对象.属性的方法调用属性本质是调用类的get方法,同时EL表达式还支持三目运算符${empty 对象?表达式1:表达式2}

jstl标签库是用于代替脚本,使用标签完成业务逻辑的库,有关该库的详细说明可见jstl标签库,本文仅介绍安装及两种常用方法。

apache官网可下载该库的jar包,最新1.1.2版本的也是2004年的,真是老当益壮。
解压后将lib目录下的两个jar包复制到项目并添加为库即可,使用时要在jsp文件前加<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>引用核心库,后续使用实例直接上代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%User user1=new User("bob",22);User user2=new User("alice",18);User user3=new User("kevin",25);ArrayList<User> users=new ArrayList<>();users.add(user1);users.add(user2);users.add(user3);request.setAttribute("users",users);
%>
<html>
<head><title>Title</title>
</head>
<body>
<c:if test="${not empty users}">成功获取对象<br>
</c:if>
<c:forEach var="user" items="${users}">${user}<br>
</c:forEach>
</body>
</html>

用到最多就是上面的if判断方法和forEach循环遍历方法,其中items表示循环的数据集,var表示取出的单个对象。

JSP使用场景——视图展示

jsp可以实现将Java代码写在前端页面,那技术上来说登陆验证,数据库增删改查操作都可以写在jsp文件中,且不会被用户感知,但是这样做显然不合理,jsp和servlet分别在不同场景有其作用,而非替代关系。

Servlet本质是Java类,负责处理http请求的完整生命周期,主要执行逻辑操作;
jsp本质是html模板,虽然允许在html中嵌入Java代码,但核心职责是动态渲染用户页面。
执行也有很大区别,servlet返回用户请求数据,jsp则是根据代码指令,动态生成html返回给用户,如果将逻辑操作也写在jsp中,首先会造成代码可读性维护性下降;其次会降低程序复用性,后端servlet可以通用处理请求;还会造成安全问题,面对注入等前端攻击jsp可能难以应对;最后是违反了MVC原则,模型视图控制三层合一为jsp页面,造成分工不明确,代码混乱。

案例——用户详细信息增删改查

案例目的

本文主要侧重Java构建的服务端,结合以前学过的前端知识,将学习的知识通过实践串联起来还是很有必要的。

主要目的是实现前端回传数据,后端进行逻辑处理,对数据库进行增删改查等操作,其实一个登陆界面就够了,但是为了一定提高对前端的认识和熟练度,还是做的稍微复杂些。

首先用户信息应该较为详细,除了姓名电话以外,还应该包含性别、兴趣爱好等,并分别使用不同的组件实现,此外还需要对用户信息提供管理功能,并及时回传到后端,最好在一些场景使用Ajax的异步传输功能。

案例分析

首先是用户信息:
包括姓名、电话、性别、爱好、自我描述,其中姓名和电话可使用字符串类型接收,性别应该使用单选框实现,爱好可使用复选框,并增加全选全不选功能,自我描述也用字符串类型接收,但可以尝试添加类似搜索提示的功能,使用Ajax将栏中信息回传给后端进行模糊查询,查到的显示在栏内,点击添加。

数据库创建的表及类型如图:
数据库
结构具体设计上,后端servlet负责执行数据的增删改查,登陆页面返回字符串permitted,用户不存在则返回false,数据展示返回封装好的userinfo对象,交互使用jsp的域对象实现,同时借助域对象信息对html组件进行动态渲染。
大致功能图如下:
设计功能图

前端页面构建

本次登陆就不设置密码验证了,输入姓名直接登陆到查看自己的信息界面,所以前端应该有两个页面:
1、登陆页面
2、登陆后的显示本人信息页面

第一个页面很简单,只是跳板,输入用户名后将用户名发送给后端,后端查询存在则跳转到信息展示页面,否则跳到原始页面,这里布置一个小功能,如果登陆失败后跳转回来,原有的登陆提示要变成红色的登陆失败,使用jstl的if标签实现,代码展示如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--引入jstl标签库--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>login</title>
</head>
<body>
<%--表达式内是一个整体,所有逻辑语句都写在其中--%>
<c:if test="${'false'.equals(requestScope.response)}"><h1 align="center" id="prompt" style="color: red">登陆失败</h1>
</c:if><c:if test="${empty requestScope.response}"><h1 align="center" id="prompt">请登陆</h1>
</c:if><form id="loginform" action="/web/back" method="post"><p align="center">请输入用户名: <input name="username" type="text"></p><button type="submit" style="display: block;margin: auto">登陆</button>
</form></body>
</html>

用户名不存在跳回来时的提示变化:
登陆失败提示

第二个页面要实现增删改的操作,首先要将查询到的信息显示到文本框内,后端设置一个用户信息对象,对应输入框的value值使用el表达式填充。

整体设置留下三个按钮即可,保存、取消和删除,不同按钮设置统一name,后端根据该值接收参数,再通过value执行不同操作。
保存操作将修改内容写入数据库,取消则再次从数据库中查询数据并回显到数据框内,取消则是删除本人记录,跳回登陆界面。

另外想设置一个较难的功能,输入个人描述时使用Ajax异步将数据传回服务端进行模糊查询,将查询到类似的结果返回给前端页面展示,用户点击选项后将信息写到数据框内。
本来想的是做成根百度一样的效果,一个下拉框弹出相近的搜索结果,但是首先组件太复杂了,后来想做成类似代码补全的效果,虚字体tab自动补全,又是涉及很复杂的前端操作。前端还是不算主业,这些复杂东西整不明白属实折磨自己了,就整个简化版,下面再放一个文本框,描述的onchange事件内Ajax异步通信,回传数据,将查询到的结果显示在联想框内,用户点击该框后把其中的值赋给描述框。

整个展示页面的代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--导入jquery--%>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<html>
<head><style>h1{text-align: center}label{text-align: left;margin-left: 20px;margin-top: 20px;}input{text-align:left;margin-right:20px;margin-top:20px}.form{border: black;border-style: double;width: 400px;margin: 0 auto;text-align: center;}.button{text-align: center;margin-top: 30px;margin-bottom: 30px;width:70px;height: 35px;}</style><title>display</title><script>var send=function (){var descr=$("[name='describe']").val()$.ajax({url:"http://localhost:8081/web/back",type:"post",data:{"describe":descr},success:function (obj){$("#imagedesc").prop("value",obj);},dataType:"Text"})}var change=function (){$("[name='describe']").prop("value",$("#imagedesc").val())}</script>
</head>
<body><form action="/web/back" method="post"><div class="form"><h1 >用户信息</h1><%--用户id不可更改,设置为文本展示--%><label>用户id:${requestScope.userid}</label><br><label>用户名:</label><input type="text" name="username" value="${requestScope.username}"><br><label>性别: </label><input type="radio" name="sex" id="male" ${requestScope.sex=='male'?'checked':""}><label></label><input type="radio" name="sex" id="falme"${requestScope.sex=='female'?'checked':""}><label></label><br><label>电话:</label><input type="text" name="phone" value="${requestScope.number}"><br><label>爱好:</label><input type="text" name="hobby" value="${requestScope.hobby}"><br><label>个人描述:</label><input type="text" name="describe" value="${requestScope.describe}" onchange="send()"><br><label>联想描述:</label><input type="text" id="imagedesc" value="联想描述" onclick="change()"><br><input class="button" type="submit" name="action" value="保存"><input class="button" type="submit" name="action" value="删除"><input class="button" type="submit" name="action" value="取消"></div>
</form>
</body>
</html>

保存等按钮的事件操作还是由后端完成,带着新的对象和域对象信息重新访问该页面,实现重新加载的功能,形成页面如图:
信息展示页面

后端逻辑

连接数据库
首先添加连接文件为库并导入包,后台程序仅需要一个数据库操作对象,并且一定需要,所以将数据库连接操作写在static静态代码块内,随着类的加载而加载,后续操作只需要对静态变量statement进行操作,该部分形成代码如下:

    private static Statement statement;private static Connection connection;static {try {Class.forName("com.mysql.cj.jdbc.Driver");connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","123456");statement=connection.createStatement();if(statement==null){System.out.println("数据库连接失败");}}catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (SQLException e) {throw new RuntimeException(e);}finally {try {if (statement != null) {statement.close();}if (connection != null) {connection.close();}} catch (SQLException e) {throw new RuntimeException(e);}}}

不同请求类型处理
请求大类上有两种,一个是用户名登陆请求,另外是数据展示页面的保存、取消、删除操作,主要是如何区分不同表单与不同事件的提交请求。
不同表单的区别可设置hidden类型的inputnamevalue用于区分,相同表单的多个按钮可用可分别设置namevalue,后端统一接收解析,根据不同的value执行不同操作,这里我将所有按钮的name都设置为action,仅用value作区分,后端接收后识别执行。

登陆验证
用户名登陆,接收action按钮的值,如果是登陆标识request_login,查询数据库找到用户信息后将所有数据添加到requset域对象中,相关代码如下:

// 指定请求字符集,用于识别中文req.setCharacterEncoding("UTF-8");PreparedStatement preparedStatement=null;ResultSet resultSet=null;// 获取数据库操作对象Statement stmt=BackStage.statement;String requesttype = req.getParameter("action");if(requesttype==null){} else if (requesttype.equals("request_login")) {// 登陆逻辑String sql="select * from userinfo where name=?";try {preparedStatement=connection.prepareStatement(sql);preparedStatement.setString(1,req.getParameter("username"));resultSet=preparedStatement.executeQuery();if(resultSet.next()){// 找到用户数据,添加到域对象System.out.println(resultSet.getString("name"));req.setAttribute("userid",resultSet.getString("id"));req.setAttribute("username",resultSet.getString("name"));// 数据库存储的是中文数据,不知道为啥jsp不能识别String sex=resultSet.getString("sex");if (sex.equals("男")){req.setAttribute("sex","male");}else{req.setAttribute("sex","female");}req.setAttribute("number",resultSet.getString("phone"));req.setAttribute("hobby",resultSet.getString("hobby"));req.setAttribute("describe",resultSet.getString("describe"));req.getRequestDispatcher("/jsp/display.jsp").forward(req,resp);System.out.println("login success");}else{System.out.println("login failed");req.setAttribute("response","false");req.getRequestDispatcher("/jsp/login.jsp").forward(req,resp);}} catch (SQLException e) {throw new RuntimeException(e);}

保存操作
读取到按钮的value为保存时,读取输入框内所有数据,保存到数据库中,实现代码如下:

else if(requesttype.equals("保存")){String id= req.getParameter("id");String username=req.getParameter("username");String sex=req.getParameter("sex");String number=req.getParameter("phone");String hobby=req.getParameter("hobby");String describe=req.getParameter("describe");String sql="update userinfo set name=?,sex=?,phone=?,hobby=?,description=? where id=?";try {preparedStatement=connection.prepareStatement(sql);preparedStatement.setString(1,username);if(sex.equals("male")){preparedStatement.setString(2,"男");}else{preparedStatement.setString(2,"女");}preparedStatement.setString(3,number);preparedStatement.setString(4,hobby);preparedStatement.setString(5,describe);preparedStatement.setInt(6,Integer.parseInt(id));preparedStatement.executeUpdate();} catch (SQLException e) {throw new RuntimeException(e);}}

这代码看着简单,我调了半天,首先sql内部有关键字describe,导致插入一直失败;还有原本id是用label直接展示的,因为label没有name字段怎么也获取不到,最后改成了<input type="text" readonly name="id" value=${requestScope.userid}>只读模式的输入框,才能获取,经过验证目前已经能够实现保存操作。

但后续呢?保存成功以后是应该留在当前页面还是跳走呢,肯定是不用跳回登陆框的,如果再次跳回展示页面,request对象域内的数据都被全部清空了,应该重新从数据库加载展示,这部分应该封装成一个函数便于复用,但是体验嘛,凑合凑合得了。

接上代码:

req.setAttribute("response","true");String display_sql="select * from userinfo where name=?";
try {preparedStatement=connection.prepareStatement(display_sql);preparedStatement.setString(1,username);resultSet=preparedStatement.executeQuery();if(resultSet.next()) {req.setAttribute("userid", resultSet.getString("id"));req.setAttribute("username", resultSet.getString("name"));sex = resultSet.getString("sex");if (sex.equals("男")) {req.setAttribute("sex", "male");} else {req.setAttribute("sex", "female");}req.setAttribute("number", resultSet.getString("phone"));req.setAttribute("hobby", resultSet.getString("hobby"));req.setAttribute("describe", resultSet.getString("description"));req.getRequestDispatcher("/jsp/display.jsp").forward(req, resp);}
}catch (SQLException e) {throw new RuntimeException(e);
}

这一个整烦了,后面两个取消和删除都大同小异,不搞了。
取消操作
删除操作

模糊查询实时展示
在自我描述栏的onchange事件设置Ajax异步传输,发生变化时将当前内容传给后端,后端在数据库进行模糊查询,将能匹配到的第一条数据赋给联想描述栏,联想描述的点击事件为将联想写入描述栏。

实现代码如下:

String description=req.getParameter("description");
System.out.println("get description:"+description);
if(description!=null){System.out.println("select in database");String sql="select * from userinfo where description like ?";try {preparedStatement=connection.prepareStatement(sql);preparedStatement.setString(1,"%"+description+"%");resultSet=preparedStatement.executeQuery();if (resultSet.next()) {System.out.println("get description from database");System.out.println(resultSet.getString("description"));resp.getWriter().print(resultSet.getString("description"));}} catch (SQLException e) {throw new RuntimeException(e);}
}

案例小结

本次案例边做边记录,修修改改,可能出现前后代码不一致的情况,以后面的为准。

做完还是感觉朦朦胧胧,只能说勉强打通了w/s的交互逻辑,很多前端页面的处理和展示还是不算熟练,一些常用的方法如下:
前后端信息交互的手段
域对象
form表单
Ajax
流对象
页面动态变化
jsp域对象设置html元素
el表达式判断
JavaScript控制组件

总结——前端组件和功能的完成?

技术栈学了不少,前端的JavaScript,服务端程序Servlet,动态页面jsp,事件form表单提交和异步交互ajax,但是直接面对案例就蒙住了,该用什么技术来实现呢?
jsp中能写Java代码就也能写后端逻辑,能不能取代servlet?form表单提交这么繁琐,能不能全用Ajax?掌握技术同时也要辨析技术的使用场景,知其然也知其所以然,对技术特性的掌握也很关键。

首先总结应用场景,jsp负责动态页面的构建,但侧重初始化阶段;Servlet负责接受请求并响应,主要执行后端逻辑;JavaScript是前端唯一的编程语言,负责页面的事件;异步提交Ajax允许网页在不跳转和整体刷新的情况下与后台交换数据并展示。

回答前面的问题:
jsp能不能写后端逻辑?——能,但不建议。
jsp代码执行在后端,效果其实和servlet差不多,技术上在jsp中写后端程序是可行的,但会带来很多问题。
首先会造成代码维护性差,html和后端代码写在一起,结构混乱,并且方法没法复用,同时调试也不方便;
另外违反了MVC模型-视图-控制架构,破坏分层设计;
最后会导致性能与安全问题,jsp中的Java代码每次请求都会被重新编译执行,逻辑计算太多增加性能负担,并且jsp中执行拼接sql语句等后端操作有极大的安全风险。

Ajax能不能全面取代form表单提交?——大多数可以。
表单提交后将所有内容发送并跳转到后端,后端随后返回完整页面,效率较低,Ajax异步非阻塞,提交数据不影响页面状态,支持局部内容动态变化,体验更流畅,所以通常可以用Ajax取代form表单提交。
但一些常规场景下,form可能更简单高效,比如登陆后跳转界面,搜索等大规模重新加载,以及一些低版本浏览器的兼容场景。

http://www.dtcms.com/a/313088.html

相关文章:

  • XSS-DOM 2
  • [硬件电路-150]:数字电路 - 数字电路与模拟电路的异同
  • 洛谷 B3841:[GESP202306 二级] 自幂数判断
  • 当Windows远程桌面出现“身份验证错误。要求的函数不受支持”的问题
  • 方差 协方差矩阵是什么
  • java的隐式类型转换和强制转换类型
  • 科威特塔观测指南:412米高空俯瞰石油城变迁
  • 在AI技术快速迭代的背景下,如何通过RAG技术提升模型的实时性和准确性?从Naive RAG到Modular RAG:AI技术进化的关键路径
  • 生成式人工智能展望报告-欧盟-04-社会影响与挑战
  • 86、信息系统建设原则
  • Java 中的多态性及其实现方式
  • AI + 云原生:正在引爆下一代应用的技术革命
  • 中国计算机学会杭州分部副主席朱霖潮:多模态大模型的研究进展与未来
  • k8s+isulad 国产化技术栈云原生技术栈搭建4-添加worker节点
  • Java函数式编程之【Stream终止操作】【上】【简单约简】
  • ethtool,lspci,iperf工具常用命令总结
  • 前端面试手撕题目全解析
  • CXGrId中按回车控制
  • 微店所有店铺内的商品数据API接口
  • 宝马集团与SAP联合打造生产物流数字化新标杆
  • 达梦数据库备份与还原终极指南:从基础到增量策略实战
  • [leetcode] 位运算
  • 【网络与爬虫 39】Crawlee现代爬虫革命:TypeScript驱动的智能数据采集框架
  • 井盖识别数据集-2,700张图片 道路巡检 智能城市
  • C的运算符与表达式
  • iNavFlight飞控固件学习-4《LED初始化》
  • MVCC的实现原理
  • git配置公钥/密钥
  • Android XR SDK深度解析:构建下一代沉浸式体验的开发指南
  • 《从原理到实践:MySQL索引优化与SQL性能调优全解析》