尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JavaWeb+HTML入门:从环境配置到前后端交互实战

JavaWeb+HTML入门:从环境配置到前后端交互实战 先说个真实感受你搜“JavaWebHTML”多半是因为刚接触Java后端开发却发现怎么都绕不开HTML这个看起来“不属于后端”的东西。Servlet写好了、MySQL表建好了结果一启动项目浏览器里一片空白或者只有一个孤零零的index.jsp默认页面。这时候才意识到JavaWeb项目的最终展示层还是得靠HTML来撑。这篇内容我就把“JavaWeb HTML”这条完整链路拆开揉碎讲清楚环境怎么配、IDEA里怎么跑起第一个Web项目、HTML页面怎么和后端Java代码对接、一个带MySQL的完整案例长什么样、以及新手最容易踩的坑怎么排查。适合刚学完Java基础、准备进入Web开发阶段的人也适合学了一段时间但始终没把前后端串起来的同学参考。1. JavaWeb和HTML到底是什么关系先理清几个概念1.1 一个请求从浏览器到服务器的完整旅程理解JavaWeb和HTML的关系最直接的办法是看一次完整的HTTP请求过程。假设你在地址栏输入http://localhost:8080/javaweb/login.html浏览器做了这些事情浏览器先解析这个URL找到主机名localhost和端口8080然后向这个地址发起一个HTTP请求。TomcatJavaWeb最常用的Servlet容器监听着8080端口收到请求后根据路径/javaweb/login.html去项目的webapp目录下找名为login.html的文件。找到后Tomcat把文件内容原封不动地作为响应体返回给浏览器浏览器拿到HTML代码后再解析渲染成你看到的页面。这里有个关键点HTML文件本身是不经过Java代码处理的它只是被Tomcat当作静态资源读出来。真正需要Java处理的是当你提交表单、请求动态数据的时候。比如表单的action指向某个Servlet路径Tomcat接收到请求后会调用对应的Java类Java代码里查数据库、拼数据最后再把结果以HTML形式送回浏览器。生活化类比一下HTML是餐厅的菜单浏览器是顾客Tomcat是服务员Servlet是后厨。菜单HTML摆在桌上直接就能看但你要点菜提交请求服务员Tomcat得把单子传到后厨Servlet后厨做好菜生成动态HTML再让服务员端出来。1.2 静态页面和动态页面的区别新手最容易糊涂的就是“什么是静态页面什么是动态页面”。静态页面就是login.html这类文件内容写死所有用户看到的都一样浏览器直接渲染中间不需要Java参与。动态页面则是指展示内容会变化——比如一个用户列表页张三登录看到张三的信息李四登录看到李四的信息或者数据库里新增了一条记录页面就会多一行数据。这种“每次都现做”的页面就得靠Java代码在服务器端生成。JavaWeb中生成动态页面的主流方式有几种最早期是Servlet直接out.println()输出HTML字符串写起来特别痛苦一长串标签拼接得头晕后来出现JSP允许在HTML里嵌Java代码方便很多再后来发展到模板引擎前后端职责越来越清晰。但不管你用哪种方式最终浏览器拿到的、浏览器能识别的依然是一段HTML。这就是为什么说“JavaWeb离不开HTML”——后端代码再复杂你呈现在用户面前的始终是HTML。1.3 为什么要一起学我看到很多学习路线把JavaWeb和HTML分成两门独立的课导致不少同学学完了Servlet却不会写页面学完了HTML却不知道往哪里提交表单。其实这两者在真实开发中是密不可分的静态资源层图片、样式表、JS文件、普通页面都是HTML家族的事表现层JSP、模板引擎渲染出来的动态页面本质还是HTML数据交互层表单提交、AJAX请求返回值可能是JSON或HTML片段学的时候就应该把两者当一条线走。只学Java不碰HTML你连项目跑起来什么样都看不到只学HTML不碰JavaWeb你写的页面永远是“死”的数据全靠手敲。2. 环境全攻略JDK、IDEA、Maven、Tomcat、MySQL一次配齐2.1 版本选型稳定压倒一切配置环境前先把版本选定不然后面全是坑。我给的这套组合是经过大量新手实践验证的不容易出幺蛾子组件推荐版本说明JDK1.8 或 11JDK 8生态最成熟教程最多11也行但注意部分老库兼容性IDEA2022.1 专业版专业版自带Tomcat集成社区版得靠插件Maven3.6.3 或 3.8.x3.9也能用配置方式一样Tomcat9.0.x对应Servlet 4.0规范包名还是javax开头MySQL5.7 或 8.0推荐8.0JDBC驱动用8.x的mysql-connector-java8.0.33驱动类名是com.mysql.cj.jdbc.Driver为什么我特意不推荐Tomcat 10因为Tomcat 10把包名从javax.*换成了jakarta.*网上大量老教程里的import javax.servlet.*全部编译不过对新手来说纯粹是增加排错难度。等基本功扎实了再去接触Tomcat 10不迟。2.2 用IDEA创建第一个Maven Web项目我习惯用Maven骨架创建Web项目这样目录结构是标准的。打开IDEA按下面的步骤操作点击New Project左侧选Maven勾选Create from archetype在列表里找到org.apache.maven.archetypes:maven-archetype-webapp选中它填好GroupId一般是公司域名反写和ArtifactId项目名选择本地JDK点击Create第一次创建会下载骨架耐心等一两分钟。完成后项目结构里会有一个src/main/webapp目录里面只有index.jsp和WEB-INF/web.xml。但这还不是完整的Maven Web项目缺少src/main/java目录。手动新建这两个目录右键src/main→New→Directory起名叫java再用同样的方式建resources。IDEA可能不会自动识别java目录为源码根目录需要右键java目录 →Mark Directory as→Sources Root。接着改pom.xml加上Servlet依赖和MySQL驱动依赖。注意Servlet依赖的scope是provided表示编译时需要、打包时不需要Tomcat自带Servlet APIdependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies2.3 配置TomcatIDEA运行JavaWeb项目的关键步骤这是整个配置流程里新手最容易卡住的地方。先把Tomcat解压到本地比如D:\tools\apache-tomcat-9.0.83然后在IDEA里配置点击顶部工具栏的运行配置下拉框选Edit Configurations点左上角号往下拉找到Tomcat Server选Local在Server标签页里Application server右侧点Configure选择Tomcat安装目录切到Deployment标签页点号选Artifact再选项目名:war exploded下面Application context填/javaweb也可以填/表示根路径点Apply和OK保存这里有几个容易搞混的概念。war和war exploded的区别是前者是打包好的压缩包Tomcat每次部署都要解压慢后者是解压后的目录结构IDEA直接把webapp目录映射到Tomcat改完代码刷新就生效调试快得多。新手就用war exploded没毛病。启动时如果提示端口被占用看第6章的处理方法。启动成功后IDEA会打开一个Run窗口看到“Server startup in [xxx] milliseconds”就说明成功了浏览器访问http://localhost:8080/javaweb/就能看到Maven骨架自带的index.jsp页面。2.4 MySQL安装和JDBC驱动选择MySQL装好后在命令行或者Navicat里创建一个测试库后面案例要用CREATE DATABASE javaweb_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER javaweblocalhost IDENTIFIED BY javaweb123; GRANT ALL PRIVILEGES ON javaweb_db.* TO javaweblocalhost; FLUSH PRIVILEGES;不用root直接连的原因很简单项目里用独立账号万一库被误删不至于连累整个MySQL实例老板看到也会觉得你专业。JDBC驱动版本必须和MySQL版本对应。MySQL 5.7用com.mysql.jdbc.Driver、URL里不用加serverTimezoneMySQL 8.0则要用com.mysql.cj.jdbc.DriverURL里必须带时区参数否则启动就报错。如果驱动版本不匹配常见的报错是ClassNotFoundException或者Unable to load authentication plugin caching_sha2_password。Maven仓库下载慢的话在settings.xml里加阿里云镜像这是国内最稳的方案mirror idaliyunmaven/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror3. 从index.html到Servlet前后端第一次打交道3.1 HTML页面的标准结构很多新手看别人写的HTML代码第一行是!DOCTYPE html就跟着抄但不知道这行的作用。这是HTML5的文档类型声明告诉浏览器“接下来按HTML5标准解析”。没有这行浏览器会进入“怪异模式”可能导致样式错乱所以写页面必须带。!DOCTYPE html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title第一个页面/title /head body h1Hello JavaWeb/h1 /body /htmlhtml langzh-cn里的lang属性告诉浏览器和搜索引擎页面内容主要是中文简体。这个属性看起来不起眼但对浏览器翻译功能、屏幕阅读器、SEO都有影响规范的项目里都会写。meta charsetutf-8必须放在head里尽可能靠前的位置最好就是第一行。因为浏览器解析HTML文档是从上往下扫描的如果先扫到其它内容再遇到charset声明前面的字符可能已经按默认编码解析出乱码了。中文项目里这个位置就是生命线千万别挪到title后面。meta nameviewport contentwidthdevice-width, initial-scale1是移动端适配的关键。没有这行iPhone打开你的页面会默认缩放字小得看不清。虽然JavaWeb案例大部分在电脑上跑但我已经习惯每页都写上。3.2 表单提交的秘密HTML表单是前后端交互最基础的桥梁。看一个典型例子form actionlogin methodpost input typetext nameusername placeholder用户名 input typepassword namepassword placeholder密码 button typesubmit登录/button /formaction指向的是后端Servlet的访问路径可以是全路径如/javaweb/login也可以是相对路径如login。method是请求方式常用的就两个get和post。get方式会把参数拼在URL后面适合查询场景post方式把参数放在请求体里适合提交数据、登录这种不能暴露参数信息的场景。这里有个90%新手都会踩的坑input控件没有name属性点击提交后参数根本不会传给后端。我在帮人调试时经常看到这种情况——前端明明输入了内容后端request.getParameter(username)拿到的却是null。因为name是参数的键名没有它浏览器不知道以什么名字提交这个值。还有一个细节button typesubmit里的type默认值是submit但很多人写button登录/button也能提交这是因为默认行为就是提交。但如果你在一个表单里放了多个按钮想让其中一个做别的操作必须明确指定typebutton否则一敲回车就意外触发提交。3.3 Servlet接收请求并响应前端表单写好了后端得有东西接。一个最简单的Servlet是这样WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); String username request.getParameter(username); String password request.getParameter(password); PrintWriter out response.getWriter(); out.println(!DOCTYPE html); out.println(html lang\zh-cn\); out.println(headmeta charset\utf-8\title登录结果/title/head); out.println(body); out.println(h1欢迎 username /h1); out.println(/body/html); } }这里要做两件编码相关的事一是request.setCharacterEncoding(UTF-8)处理POST请求携带的中文参数不设的话username拿到手就是乱码二是response.setContentType(text/html;charsetUTF-8)告诉浏览器响应内容按UTF-8解析。这两行写错一个前后端中文必乱。注意WebServlet(/login)这个注解括号里的路径就是外界访问该Servlet的URL。前端表单actionlogin能匹配上是因为页面本身在/javaweb下浏览器会自动拼成/javaweb/login。如果页面的URL路径变了这个相对路径就可能失效所以稍微复杂一点的项目里我会直接用绝对路径比如action${pageContext.request.contextPath}/login。通过这个简单交互就能明白HTML负责收集数据Servlet负责处理数据处理完再输出一段HTML作为响应。这就是JavaWeb项目最核心的循环。4. HTML/CSS/JS基础语法核心够用就行的前端三板斧4.1 标签与文档结构HTML标签看起来多实际JavaWeb开发里高频使用的不超过20个。我把这些按用途分组列一下文本标签h1到h6标题、p段落、span行内文本、strong加粗结构标签div块级容器、header、footer、nav、main列表标签ulli无序列表、olli有序列表、tabletrtd表格表单标签form、input、selectoption、textarea、button媒体标签img图片、a超链接div是块级元素占满整行宽度span是行内元素只占内容本身的宽度。新手常犯的错误是拿div强行塞进文字里导致布局崩坏。最简单的选择逻辑只包文字用span包一整块区域用div。table在JavaWeb后台管理页面里用得很多因为列表数据天然适合表格展示。注意table结构里thead放表头行tbody放数据行这样语义更清晰也方便后期做样式。4.2 CSS布局内联、内部、外部怎么选CSS有三种引入方式对应不同使用场景内联样式div stylecolor:red写在标签里。缺点是无法复用只适合临时调试内部样式style标签写在head里适合单页面项目外部样式link relstylesheet hrefstyle.css公司项目都用这个方便统一维护和缓存实际项目里推荐外部样式但入门阶段为了少开文件用内部样式把样式和结构放一起看也挺方便。关键是别三种混用后期维护会疯。新手写CSS最容易栽在盒模型上。默认情况下width是内容区的宽度加上padding和border后实际占位更宽。如果担心算不清可以加一行* { box-sizing: border-box; }这行代码让width包含padding和border就跟日常理解的“总宽度”一致了。实测能解决大量布局对不齐的问题。4.3 JS与后端数据交互从表单提交到AJAX最基本的JS用法是事件监听。HTML元素上可以用onclick属性直接绑定点击事件button onclickloadData()加载数据/button script function loadData() { alert(按钮被点击了); } /script但更常见的是把JS写到外部文件里HTML页面底部引入script srcjs/app.js/script。注意script标签放/body之前而不是head里。原因是页面是从上往下解析的如果JS放在头部浏览器得先下载执行JS再继续渲染后续HTML会明显拖慢首屏速度。放底部能确保DOM结构先加载完成JS事件绑定才有对象可绑。JS和后端交互目前最主流的方案是fetch JSON不再推荐老掉牙的Ajax写法了fetch(/javaweb/student/list, { method: GET, headers: { Accept: application/json } }) .then(response response.json()) .then(data { // data是后端返回的JSON数组渲染到页面上 renderTable(data); });后端配合返回JSON的话就需要引入JSON序列化工具比如阿里巴巴的Fastjson或者Jackson。这块内容适合学完基础交互之后再深入。5. JavaWeb项目完整案例学生信息管理MySQL版理论说了这么多不如一个完整例子来得实在。这里用一个最经典的学生信息管理系统串联全部环节数据库 → Servlet → HTML页面。5.1 数据库设计学生表不长但字段类型和字符集要选对USE javaweb_db; CREATE TABLE student ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL COMMENT 姓名, age INT COMMENT 年龄, gender VARCHAR(10) COMMENT 性别, class_name VARCHAR(50) COMMENT 班级 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生表; INSERT INTO student (name, age, gender, class_name) VALUES (张三, 20, 男, 计算机1班), (李四, 21, 女, 计算机1班), (王五, 22, 男, 计算机2班);表名用student而不是students字段名用class_name而不是class——class是SQL保留字用了就得加反引号纯属给自己找麻烦。5.2 后端JDBC工具类和Servlet先写一个最简单的JDBC工具类用静态代码块加载驱动、提供连接方法。密码直接写在源码里对于练手项目可以接受生产环境必须放配置文件加密public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/javaweb_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf-8; private static final String USER javaweb; private static final String PASSWORD javaweb123; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }URL里的serverTimezoneAsia/Shanghai是MySQL 8.0必需的不写就报错characterEncodingutf-8保证中文数据读写不出乱码useSSLfalse是为了省去SSL握手的时间本地开发无所谓。学生列表Servlet这样写WebServlet(/student/list) public class StudentListServlet extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ListStudent students new ArrayList(); String sql SELECT id, name, age, gender, class_name FROM student; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setName(rs.getString(name)); s.setAge(rs.getInt(age)); s.setGender(rs.getString(gender)); s.setClassName(rs.getString(class_name)); students.add(s); } } catch (SQLException e) { e.printStackTrace(); } request.setAttribute(students, students); request.getRequestDispatcher(/student/list.jsp).forward(request, response); } }特意用了PreparedStatement而不是Statement因为预编译语句既防SQL注入性能也更好。等你做带查询条件的列表时拼SQL的复杂度和出错概率会直线上升用PreparedStatement能省掉一半烦恼。5.3 前端页面JSP里的HTML新增学生的页面用纯HTML写文件名addStudent.html放在webapp/student目录下!DOCTYPE html html langzh-cn head meta charsetutf-8 title新增学生/title style .form-item { margin-bottom: 12px; } label { display: inline-block; width: 60px; } /style /head body h1新增学生/h1 form action/javaweb/student/add methodpost div classform-item label姓名/label input typetext namename required /div div classform-item label年龄/label input typenumber nameage required /div div classform-item label性别/label select namegender option value男男/option option value女女/option /select /div div classform-item label班级/label input typetext nameclassName /div button typesubmit保存/button a href/javaweb/student/list返回列表/a /form /body /htmlrequired属性是HTML5自带的非空校验能省掉一部分JS代码。注意表单里每个控件都写上了name属性题目的参数才能正确传到后端。列表页我用了JSP而不是纯HTML原因前面说过——纯HTML没法动态渲染数据库数据。JSP页面长这样% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html langzh-cn head meta charsetutf-8 title学生列表/title style table { border-collapse: collapse; width: 100%; } table, th, td { border: 1px solid #ddd; } th, td { padding: 8px 12px; text-align: left; } /style /head body h1学生列表/h1 pa hrefaddStudent.html新增学生/a/p table thead tr thID/th th姓名/th th年龄/th th性别/th th班级/th /tr /thead tbody c:forEach items${students} varstu tr td${stu.id}/td td${stu.name}/td td${stu.age}/td td${stu.gender}/td td${stu.className}/td /tr /c:forEach /tbody /table /body /htmlJSP第一行的page指令里contentTypetext/html;charsetUTF-8决定了页面输出编码。languagejava是历史遗留写上无妨。JSTL标签库需要额外引入依赖如果不想引依赖用% %嵌Java代码也能跑但页面会很乱不推荐。5.4 完整流程验证启动Tomcat后依次访问下面三个地址整个链路是否通的判断标准很清楚http://localhost:8080/javaweb/student/addStudent.html→ 显示新增表单填写数据提交 → 观察IDEA控制台有没有报错浏览器是否跳转到列表页http://localhost:8080/javaweb/student/list→ 表格显示所有人包含刚新增的如果第2步报404查一下表单action里的路径是不是和Servlet的WebServlet(/student/add)完全匹配。注意路径大小写敏感/Student/add和/student/add是两个完全不同的地址。这是新手最容易犯的错。新增的Servlet代码这里不完整贴出了核心逻辑就是doPost里拿参数、写SQL、执行插入、然后response.sendRedirect(/javaweb/student/list)重定向到列表页。为什么用重定向而不是转发因为如果转发用户刷新浏览器会重复提交表单造成数据重复插入。重定向让浏览器重新请求列表页刷新时不会再走提交逻辑这是Web开发的一个基本常识。6. 常见问题排查实录与避坑指南6.1 端口占用和Tomcat启动失败报错Port 8080 was already in use说得很直白了8080被占了。Windows上用命令找到占用进程netstat -ano | findstr 8080 taskkill /pid 进程号 /f但也可能是你自己上次启动的Tomcat还在后台跑着IDEA里看Run窗口右侧有没有红色方块有就先点掉。我踩过最深的一次坑是Windows服务里有个旧版Tomcat设为开机自启怎么启动都报端口冲突折腾了半天才想起来去看系统服务。6.2 404和500错误到底是谁的锅404表示“找不到资源”出现时先判断是页面前缀还是Servlet路径问题。用浏览器F12开发者工具看Network请求确认实际请求的URL是不是和预期一致。常见原因有三个Application context设置成了带前缀比如/javaweb但你URL里漏写了Servlet注解路径写错webapp目录下文件名拼写有误。500表示“服务器内部错误”问题在后端代码。IDEA控制台会打印堆栈异常永远先看第一行异常信息新手最容易犯的错误是盯着红字最后几行看其实最顶上的才是根源。常见异常有NullPointerException该判空的没判、ClassNotFoundExceptionJDBC驱动没引入、SQLExceptionSQL写错了或者连不上库。6.3 中文乱码的完整解决方案乱码是JavaWeb新手第一杀手而且乱的地方还不一样。我按场景整理了一张表乱码场景解决方式HTML页面显示乱码meta charsetutf-8放在head最前面POST提交的中文乱码Servlet里第一行request.setCharacterEncoding(UTF-8)GET提交的URL参数中文乱码Tomcat 8起默认UTF-8旧版本需改server.xml增加URIEncodingUTF-8响应中文乱码response.setContentType(text/html;charsetUTF-8)数据库存的乱码JDBC URL里加characterEncodingutf-8建表时指定utf8mb4这里补充一个排查顺序先看数据库里存的对不对。如果数据库里都对那是显示层问题如果数据库里就是乱码那是JDBC或建表字符集问题。从源头往下一层层排查比瞎调快得多。6.4 几个提升效率的习惯以我个人的体会JavaWeb项目有个特点配置类问题占了一半以上纯代码逻辑问题反而不多。所以环境装好之后建议把下面几个习惯练成肌肉记忆。第一IDEA里改了HTML或JSP后如果浏览器刷新没变化先点一下Build菜单的Rebuild Project不用每次都重启Tomcat。IDEA的热部署对静态资源一般能自动生效但JSP改动偶尔不触发手动重新构建是最保险的。第二运行配置里的Application context统一用/javaweb这种带项目名的路径别用/。因为如果你有两个项目同时部署路径都是/就会冲突。带前缀还有一个好处是HTML里的超链接、表单action、JS里的fetch地址能保持一致不用分别计算相对路径。第三养成看IDEA下方Build窗口的习惯。项目运行失败时很多错误其实在编译期就已经报了只是Run窗口里没显示。编译报错和运行报错是两类问题先解决编译报错运行时报错自然少一半。第四SQL语句有疑问时先拿到Navicat或命令行里跑一遍确认语法没问题再放进Java代码。JDBC报错的英文提示比较难懂但SQL本身的错误信息给了足够的定位线索比如哪个字段不存在、语法哪里不对。第五碰到没见过的问题先别急着百度把报错信息完整读一遍。大多数报错信息已经把解决方案写在了里面比如缺类、缺依赖、端口占用、连接超时这些都是直接指向问题的提示。复制粘贴报错信息去搜的时候一定要带英文部分别只搜中文翻译否则大概率搜不到有价值的答案。这套“JavaWeb HTML”的组合是后端开发的基本盘。搞懂这层关系后面再学Spring Boot、MyBatis、前端框架都只是在同样的“浏览器请求 → 后端处理 → 返回HTML/JSON”这条主线上不断升级而已。骨架烂熟于心加肉加菜只是时间问题。
返回列表