JavaWeb开发
浏览器工作原理
详细内容可见前端基础,本文仅作简要总结和回顾。
服务器和浏览器通过应用层协议http
进行通信,浏览器请求,服务器响应,返回的可视化结果多通过html
超文本标记语言实现。
tomcat服务器
tomcat安装
服务器是安装在服务端的软件,可以实现对HTTP协议的封装,让开发者无需关注底层通信方法,只要构造html
页面和后端逻辑就能让客户端通过浏览器访问本机资源。
tomcat是Apache体系下的JavaWeb服务器,该服务器专注于动态内容构建,对于静态资源的处理效率较低,通常与高性能静态资源服务器如Nginx
搭配使用。并且因为其与Java生态的深度绑定性,该款服务器仍然具有不可替代性。
可到tomcat官网下载服务器软件
三个版本对jdk的要求也越来越高,企业生产环境中通常新特性用的不多,最主要的需求是可用性和稳定性,所以我也下载的Tomcat 9
版本。
解压后可得如下文件夹:
随后确认系统变量环境:
执行bin\startup.bat
启动服务后,在浏览器中访问localhost:8080
得到如下页面说明安装成功:
IDEA集成tomcat
已有项目中首先点击文件,再选择项目属性,
选择新建web
应用并确定后在项目结构中可见添加了web文件夹。
随后在右上角运行文件选项框内选择编辑配置,选择增加本地Tomcat

可以修改的几个位置,并点击最后提示的警告,配置工件
点击修复后增加展开型应用程序,即war包
增加后可见如下页面,其中应用程序上下文决定了访问路径:
再回到服务器设置页面可见url更改,浏览器可通过该url为根目录访问资源:
Servlet服务端程序
Servlet是用Java编写的服务器端程序,其主要功能在于交互式地浏览和修改数据,生成动态Web内容,Java通过实现该接口完成对客户端请求的解析与响应,本质是上浏览器与服务器数据库和应用程序的中间层。
Servlet导入与示例
Servlet依赖的jar包在tomcat目录下的lib文件夹内:
将其复制到项目的web目录下的WEB-INF内,新建lib
并添加为库,因为WEB-INF是用户无法直接访问的目录,相对安全,添加后可得下图结果:
补充介绍jar包和war包:
如果说python通过pip安装第三方库实现功能扩展,Java就是通过jar包安装新功能。jar包是Java基于zip的标准归档格式,实现将一组核心的Java类和依赖打包为一个jar包,便于分发复用,相比python的pip
,如今Java也有自动的包管理工具Maven
与Gradle
。
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中更新配置信息。
该文件类似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[] 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);}
}
修改后可见时间如下图:
该方法可用于用户的免密码登陆,可用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
文件中可见下图配置:
xml和html类似,也是一种标记型语言,首先通过声明<?xml version="1.0" encoding="UTF-8"?>
开始,后续是一系列双标签、元素和属性,如:
<student><name id="1">kevin</name><age>24</age>
</student>
该结构声明student
标签内有两个子标签name
和age
,其中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
文件发送到浏览器,实现前端页面的变化。
简单使用实例如下:
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实例执行时的文件目录,复制该目录可见如下结构:
分别是配置文件,日志和实际工作目录,若用户未访问.jsp
资源,工作目录中只有空文件夹,这再次证明了解析执行的过程,访问资源后,可在当前文件夹下的\work\Catalina\localhost\web\org\apache\jsp\jsp
路径找到编译的Servlet
类文件与对应的字节码文件,说明解析执行完毕。
html文件存在哪?
我们前面提到,jsp本质是用servlet生成html文件结构并发送给服务端的,所以并不显式存储html文件,而是在内存生成,下次访问请求如果一致,则直接调用字节码class
文件交由虚拟机执行,再次发送即可。
打开java
文件有如下源码:
可见的确是将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
,则pageContext
与request
全显示为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
类型的input
的name
和value
用于区分,相同表单的多个按钮可用可分别设置name
和value
,后端统一接收解析,根据不同的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可能更简单高效,比如登陆后跳转界面,搜索等大规模重新加载,以及一些低版本浏览器的兼容场景。