ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JSP与Servlet实战分工:从环境配置到用户管理系统开发

JSP与Servlet实战分工:从环境配置到用户管理系统开发 1. JSP到底是个什么角色为什么学了Servlet还要学它先说一个不少初学者心里的嘀咕Servlet不是能输出HTML吗折腾JSP不是多此一举这个问题我当年也纠结过但真正在项目里跑一遍就明白了Servlet适合干“逻辑活”JSP适合干“展示活”两者搭配才是JavaWeb项目的正常形态。JSP全称是Java Server Pages本质上是Servlet的简化写法。你把一个.jsp文件扔进Tomcat容器会把它转译成一个Java类这个类说到底就是一个Servlet只是在编写方式上换了个思路——Servlet里你得用out.println(h1hello/h1)把页面代码硬生生拼在Java字符串里而JSP允许你在HTML标签中间直接穿插Java代码片段。它解决的核心痛点就是视图层代码的可读性和维护性一个页面长什么样直接看JSP源码就八九不离十不用再在那一大坨字符串拼接里数引号。可以用一个生活化的类比来理解Servlet像个厨师负责在后厨把菜配好、炒熟JSP像个摆盘师傅负责把菜装进好看的盘子里端给客人。厨师可以顺便摆个简单盘但要是每道菜都让厨师精细雕花后厨就乱套了。项目里的分工就是Controller和Service用Servlet或者框架处理业务JSP只负责把结果数据渲染成页面。配合上一篇文章JavaWeb-08 Servlet的内容理解会更顺Servlet里你处理请求、调业务方法、把结果塞进request域然后request.getRequestDispatcher(/xxx.jsp).forward()转发到JSP页面JSP再从request域里把数据捞出来渲染。这个“Servlet管逻辑、JSP管展示”的分工模式是理解后面SSM、SpringBoot模板引擎这些技术的基础。JSP的学习门槛并不高核心就三块指令、脚本元素、内置对象。把这三块搞明白了再补几个实际开发天天用的标签库JSTL和EL表达式你就能写出一个像样的JSP页面了。这篇笔记我按自己当时的学习路径来写重点放在“能直接上手用”的部分一些偏门冷知识点到为止。2. IDEA里跑JavaWeb项目的那些破事先搞定环境再谈语法这一节其实是实打实的血泪经验。很多人跟着教程写代码兴致勃勃结果卡在环境配置上页面死活出不来又不知道去哪看日志。先把IDEA里跑JavaWeb项目的整套流程捋顺下面写代码才不慌。2.1 Tomcat版本和IDEA版本的匹配问题先说版本坑。IDEA Ultimate旗舰版才有原生的JavaWeb项目创建向导社区版没有直接创建Web项目的选项需要手动配置不少同学下载的是社区版一上来就懵了。解决方式有两种要么换用旗舰版要么用Maven骨架创建一个maven-archetype-webapp项目后者即使是社区版也能操作。更隐蔽的是IDEA内置的Tomcat集成和Tomcat本身的版本兼容问题。比如JDK 17配Tomcat 11或者老项目用JDK 8却硬要跑Tomcat 10就会出现各种ClassNotFound、Servlet注解扫不到的情况。推荐一套稳妥组合JDK 8 Tomcat 8.5/9.0 IDEA 2023或更新版本。Tomcat 8.5和9.0在javax.servlet包路径上一脉相承教程里常见代码不用改包名就能跑起来。Tomcat 10开始把javax.servlet换成jakarta.servlet很多老代码直接迁移会报错没必要一上来就给自己上难度。2.2 IDEA里配置Tomcat的完整步骤这里把操作路径写细一点照着点就行下载Tomcat压缩包解压到一个没有中文和空格的路径比如D:\dev\apache-tomcat-9.0.85。放在中文路径下偶尔会有资源加载乱码的问题不是必然但没必要赌。IDEA中打开Run菜单 - Edit Configurations - 左上角加号 - Tomcat Server - Local注意不要选Tomcat Server - Remote那个是远程调试用的。在Application server右侧点Configure选到Tomcat解压目录JRE选择你当前项目的JDKHTTP port默认8080如果被占用可以改成8081。Deployment部署标签页是关键点加号 - Artifact把xxx:war exploded添加进去Application context填/或者你的项目名比如/javaweb。war exploded是解压部署模式开发时热部署更快不需要打成war包再扔进去。回到Server标签页把On Update action和On frame deactivation都选成Update classes and resources这样改JSP和静态资源后IDEA会自动同步不用频繁重启Tomcat。配置完之后点右上角的绿色三角启动浏览器访问http://localhost:8080/你的上下文路径/能跳出Tomcat首页或者你的测试页面环境就算通了。2.3 端口被占用和控制台乱码的快速处理端口被占用是高频问题。Tomcat启动后控制台直接报Port 8080 was already in use处理方式很直接打开命令行执行netstat -ano | findstr 8080找到占用端口的PID然后到任务管理器里看清楚是什么进程。如果是残留的Java进程直接结束它如果不知道是什么东西也可以直接把Tomcat端口换成8081改一下IDEA的HTTP port就行。控制台中文乱码的问题则源于IDEA默认编码和Tomcat日志编码不一致。Tomcat 9的日志输出用UTF-8的话在Help - Edit Custom VM Options里加上-Dfile.encodingUTF-8同时把conf\logging.properties里那几个java.util.logging.ConsoleHandler.encoding从UTF-8改成GBKWindows环境一般就能解决。这两个动作做完还乱的话检查一下IDEA右下角文件编码是不是UTF-8JSP文件里的pageEncoding也要和IDE编码保持一致。3. JSP核心语法与个人信息展示页实战环境通了下面进入正文。JSP页面看起来像HTML但多了三种东西指令Directive、脚本元素Scripting、动作Action。实际开发中动作标签用得比较少重点掌握前两个再熟练运用JSTL和EL表达式基本就能应付绝大多数开发场景。3.1 三个指令的用途别再都往页面顶部塞了JSP指令用于“吩咐”容器在转译时做一些额外处理格式是% 指令名 属性值 %。常见的有page、include、taglib三个指令实际开发中90%的页面只需要page和taglib。page指令让页面支持Java代码设置编码导入需要用到的Java类。比如% page contentTypetext/html;charsetUTF-8 languagejava %是保证页面显示中文不乱码的基础如果你要在页面里写Java代码操作List等其他类就得用% page importjava.util.List %。include指令把另一个文件的内容静态包含进来。比如一个后台系统的多个页面都有相同的顶部导航栏就可以把导航栏抽成一个header.jsp用% include fileheader.jsp %引入。注意它是静态包含发生在转译期相当于把被包含文件的源码原样嵌进来。taglib指令引入标签库最典型的就是JSTL% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %。prefix是标签的前缀用的时候就是c:forEach这样。注意page指令中import属性如果用到多个类用逗号分隔写成一行就行不要拆成多条page指令风格上大家默认这样写。有一个容易混淆的点是%-- JSP注释 --%和!-- HTML注释 --的区别。JSP注释在转译阶段就被剔除了用户查看网页源代码完全看不到HTML注释会随页面输出到浏览器用户右键查看源码就能看到。所以涉及后端逻辑说明的注释用JSP注释前端调试用的注释才用HTML注释。3.2 JSP脚本元素三种写法分别用在哪JSP脚本元素就是页面里跟Java相关的代码片段一共三种形态第一种JSP表达式% 表达式 %等价于out.print(表达式)用于直接输出一个值。比如% request.getAttribute(username) %。它的特点是不能加分号结尾只能放一个表达式别把多条语句塞进去。第二种JSP小脚本% Java语句 %这里面写的就是普通的Java代码块可以写多行可以写循环、判断等逻辑。比如% User user (User) request.getAttribute(user); if (user ! null) { out.println(h2 user.getName() /h2); } %第三种JSP声明%! 方法或变量 %声明的是成员变量和方法。这个在实际开发中尽量少用因为JSP本质是Servlet成员变量的生命周期远长于一次请求极易引发并发数据混乱。记住一句话在JSP页面里声明成员变量属于给自己埋雷的操作能用request域和局部变量解决的事就不要声明成员变量。这里要补充一个很多初学者容易误解的经典问题% %和% %的区别到底在哪简而言之小脚本里你写什么Java代码都行哪怕是一条赋值语句表达式里就只能是一个可以被out.print()接收的值。比如% int a 1; %是对的但% int a 1; %一定编译不过。3.3 JSP九大内置对象先记住这几个就够用了内置对象意味着你不用声明直接在页面里拿来就用。这九个是request、response、session、application、out、page、pageContext、config、exception。实际写页面最常用的就前五个逐个说清楚request一个请求来了所有参数和信息都在这个对象里。取参数用request.getParameter(name)取Servlet转发进来的数据用request.getAttribute(xxx)。注意getParameter拿到的是客户端提交的原始参数getAttribute拿到的是服务端set进去的对象两者别搞混。response给客户端回响应。页面里直接用的场景不多主要是重定向response.sendRedirect(index.jsp)和设置响应头response.setContentType(text/html)。session会话对象从浏览器打开到关闭这一段期间的共享数据。典型场景是登录后把用户对象塞进session其他页面就能直接判断session.getAttribute(loginUser) ! null来识别登录状态。application全局对象所有用户共享同一个application。适合存全局配置信息比如网站名、版本号但别往里面放跟具体用户相关的数据。out向页面输出内容的对象表达式% %底层的输出方式就是它。什么时候用request域、什么时候用session域是实际开发里非常常见的判断题。一个原则是只在一次请求转发链路上需要的数据优先用request域需要在多个请求之间维持的数据才考虑session域。比如个人信息展示Servlet查完数据转发给JSP那数据放request域就够了别画蛇添足往session里塞session里对象多了内存压力大还容易造成数据残留问题。3.4 实操做一个JSP个人信息展示页面下面我按照实际项目里最常遇到的场景写一个完整的个人信息展示页面。假设已经登录用户对象存在session里页面要展示昵称、头像、简介还要根据用户类型显示不同的操作按钮。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title个人主页/title /head body c:if test${sessionScope.loginUser ! null} div classprofile-card h2${sessionScope.loginUser.nickname}/h2 p简介${sessionScope.loginUser.bio}/p p注册时间fmt:formatDate value${sessionScope.loginUser.createTime} patternyyyy-MM-dd //p c:choose c:when test${sessionScope.loginUser.vipLevel 1} spanVIP用户/span /c:when c:otherwise span普通用户/span /c:otherwise /c:choose /div /c:if c:if test${sessionScope.loginUser null} p你还没有登录a hreflogin.jsp去登录/a/p /c:if /body /html这段代码里有几个值得留意的细节。EL表达式写法${sessionScope.loginUser.nickname}是标准做法它会自动从session域取loginUser对象并调用其getter方法比写%((User)session.getAttribute(loginUser)).getNickname()%简直优雅太多了。页面里凡是涉及从域对象取数据输出的地方优先用EL而不是Java小脚本。另一个细节是${sessionScope.loginUser ! null}这个判断。EL里访问不存在的属性不会抛异常会乖乖返回null所以这里可以直接判空。如果用传统的Java小脚本你得先User u (User) session.getAttribute(loginUser);再判空一旦忘了强转或者引用了不存在的key页面直接给你一大片异常堆栈。3.5 JSTL和EL表达式把页面里的Java代码赶出去很多学过JSP的人后来转向模板引擎如Thymeleaf但JSTL配合EL这套组合在JavaWeb原生生态里依然是统治级的存在。为什么ELExpression Language解决的是数据怎么取的问题。${user.name}、${sessionScope.loginUser.vipLevel}、${param.id}这些写法取代了request.getParameter、session.getAttribute之类的冗长调用。它还能直接访问集合元素${list[0].title}、${map[key]}。JSTLJSP Standard Tag Library解决的是页面逻辑怎么写的问题。比较常见的是core核心库里的几个标签c:if test${条件}条件判断没有else需要配合c:choose实现分支。c:choose / c:when / c:otherwise相当于Java里的switch-case。c:forEach items${list} varitem varStatusst遍历集合varStatus还能拿当前循环的下标st.index和是否是第一条st.first。我见过很多同学在JSP页面里写这样的代码% for (int i 0; i list.size(); i) { %然后HTML和% } %混在一起页面逻辑一复杂引号、括号配对的错误找半天。用c:forEach之后遍历一个列表就三行标签的事页面干净太多。JSTL本身不是Tomcat内置的需要额外引入两个jar包jstl.jar和standard.jar。用Maven项目的话在pom.xml里加依赖即可。如果是传统方式把它们扔到WEB-INF/lib目录下。漏掉这一步的典型报错是页面出现Unable to find taglib [http://java.sun.com/jsp/jstl/core]比较容易排查。4. JSP页面里几个高频实战问题自动刷新、图片定位和部署路径这一节专门聊我常见到的新人提问也是热搜词里高密度出现的问题。这些问题不解决页面效果就总是差一口气。4.1 页面加载完后自动刷新一次这个需求怎么实现场景很典型某些数据页面在用户访问时可能还没准备好或者页面需要在加载完成后从后端拉一遍最新数据比如一个排队进度页面。热搜词里就有“jsp页面让加载完后刷新一次”说明这是真实高频需求。第一种方法前端实现在JSP里加一个meta标签页面加载后几秒自动重刷meta http-equivrefresh content5; urlcurrentPage.jsp意思是5秒后跳转到currentPage.jsp。如果url不写就是原地刷新。这种方式最常见但有个坑——如果当前页面本来就是由Servlet转发过来的刷新时请求参数已经丢了那需要考虑改成重新请求Servlet的地址。第二种方法让Servlet承担刷新逻辑更符合JavaWeb分层思路。在Servlet的doGet里做一次跳转response.setHeader(Refresh, 5; URL request.getContextPath() /user/list);这个响应头告诉浏览器5秒后自动访问/user/list这个地址。好处是URL干净刷新后的请求又重新走了一遍Servlet业务逻辑不会丢。第三种方法后端控制是否刷新。某些场景只有满足特定条件才需要自动刷新比如数据还没准备好时刷新已准备好就直接展示。那就在Servlet里判断后决定要不要加这个响应头。灵活度最高。注意不管哪种方式刷新之后如果页面内容依赖POST提交的数据建议把表单提交改成Post/Redirect/Get模式否则浏览器刷新时会弹窗提示“确认重新提交表单”体验很差。4.2 JSP里的图片坐标定位别用错了CSS单位这个问题也经常有人踩坑。网上有不少旧教程教你在JSP页面里用Java代码算坐标实际上所有坐标定位的问题都应该交给CSS和HTML解决JSP只需要输出数据即可。需求一般是这样的页面上要显示一张地图或者一张产品示意图需要在特定位置叠加标记点。比如一个门店分布图要根据门店的经纬度换算成相对于图片的像素坐标来放标记。先看基础方案静态图片定位的正确打开方式div styleposition: relative; display: inline-block; img srcmap.png alt地图 stylewidth: 600px; height: 400px; div styleposition: absolute; left: 180px; top: 120px; span classmarker门店A/span /div /div核心逻辑就是父容器position: relative图片放里面当背景标记点用position: absolute相对父容器定位。距离单位用px还是百分比取决于业务场景——图片是固定尺寸用px最直观图片要自适应缩放用百分比更稳。如果是动态数据比如从数据库查出每个门店的坐标那JSP配合JSTL循环输出即可。假设后台算好了每个门店相对图片的x和y坐标单位是px存进了shop对象的locX和locY字段c:forEach items${shopList} varshop div styleposition: absolute; left: ${shop.locX}px; top: ${shop.locY}px; span classmarker${shop.name}/span /div /c:forEach这里要注意EL表达式输出的是纯数字拼接px单位的时候必须显式写出来写成left: ${shop.locX}px不要只写left: ${shop.locX}。后者生成的CSS是无效的标记会统统堆在左上角。坐标怎么换算如果地图图片的展示尺寸和原始尺寸一致那坐标直接用图片原始尺寸上的点即可。如果图片有缩放比如CSS里把600px宽的图显示成了300px那坐标也得按比例缩放计算公式为显示坐标 原始坐标 × 显示尺寸 / 原始尺寸。这个换算可以放在后台完成也可以页面里用max-width:100%配合百分比坐标来处理。4.3 request.getContextPath()为什么每个链接都要带上初学者经常写这样的链接a href/user/detail?id1详情/a然后页面点击进去发现404。原因是你的项目部署context path不一定是/可能是/javaweb之类的路径。写成绝对路径/user/detail浏览器会去访问http://localhost:8080/user/detail而正确地址是http://localhost:8080/javaweb/user/detail。解决方式在JSP页面里动态获取项目根路径% String basePath request.getScheme() :// request.getServerName() : request.getServerPort() request.getContextPath() /; % base href%basePath%把这段放在head里之后页面里所有相对路径都会自动基于这个base地址解析。一个比较省事的写法是直接在HTML里用ELbase href${pageContext.request.contextPath}/${pageContext.request.contextPath}取到的就是context path相当于/javaweb。有了base之后写a hrefuser/detail?id1就不会再丢项目前缀了。如果是Servlet向JSP转发JSP里还要注意request.getContextPath()返回的是部署路径与文件物理路径不是一回事。用${pageContext.request.contextPath}这个写法是最稳妥的兼容各种部署环境。5. 从展示页到完整项目JSPMySQL的用户管理系统怎么搭单看一个JSP页面体现不出它的工程价值我拿一个常见的“基于JSP的XX管理系统”案例来拆解这也是热搜词里“javaweb项目完整案例mysql”、“基于jsp的毕业论文管理过程系统”背后的共性需求。不管系统名头多大骨架都是一个套路登录认证 CRUD 列表分页。学会了这套骨架换任何业务字段都能套用。5.1 项目分层与目录结构安排这里插一句最重要的建议JSP页面必须放在WEB-INF目录之外还是之内一直有争论但原生JavaWeb开发中真正该被保护的页面如登录后的主页建议放在WEB-INF下通过Servlet转发访问。为什么要这样WEB-INF目录下的资源不能被浏览器直接URL访问所有请求必须走Servlet转发进去这样相当于加了一层访问控制。没有被拦截的页面直接暴露在web目录下别人输入路径就能打开安全性差很多。一个典型的管理系统分层如下javaweb-demo/ ├── pom.xml ├── src/main/java │ ├── com.demo.entity // 实体类 │ ├── com.demo.dao // 数据访问层JDBC操作 │ ├── com.demo.service // 业务逻辑层 │ ├── com.demo.servlet // 控制层继承HttpServlet │ └── com.demo.util // 工具类如DBUtil、MD5工具 └── src/main/webapp ├── static/css ├── static/js ├── WEB-INF/jsp // JSP页面放这里 └── index.jsp // 入口很多人学JavaWeb时有一个误区JDBC代码直接写在Servlet里一个Servlet又查数据库又做判断还转发页面。代码量少的时候还行业务一多就完全失控。从开始学就养成Servlet只做“接收参数、调Service、跳转页面”的习惯后面学框架会顺很多。5.2 用户列表页JSTL循环展示MySQL数据先看列表页这个页面几乎是所有后台管理系统的原型。Servlet从数据库查出用户列表放request域后转发到JSPJSP用c:forEach循环渲染成表格。Servlet关键代码WebServlet(/admin/user/list) public class UserListServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { UserService service new UserService(); ListUser userList service.listAllUsers(); req.setAttribute(userList, userList); req.getRequestDispatcher(/WEB-INF/jsp/userList.jsp).forward(req, resp); } }JSP里的表格展示table classtable thead tr thID/th th用户名/th th昵称/th th注册时间/th th操作/th /tr /thead tbody c:forEach items${userList} varu varStatusst tr td${st.count}/td td${u.username}/td td${u.nickname}/td tdfmt:formatDate value${u.createTime} patternyyyy-MM-dd HH:mm:ss //td td a hrefadmin/user/edit?id${u.id}编辑/a a hrefadmin/user/delete?id${u.id} onclickreturn confirm(确认删除?)删除/a /td /tr /c:forEach /tbody /table两个亮点值得注意。fn标签处理时间格式用fmt:formatDate它是JSTL的格式化标签库需要额外引入urihttp://java.sun.com/jsp/jstl/fmt避免在页面里手动拼字符串格式化日期。另一个是varStatusstst.count从1开始计数比${u.id}这种数据库自增主键更适合做序号列删除后ID有空洞也不会显示得奇怪。5.3 分页查询的实现思路别一查就全表初学者最容易犯的毛病就是SELECT * FROM user一把梭数据一多页面直接卡死。分页是管理系统必备技能这里给一个简易版本的分页实现。分页要搞定的参数有三个当前页码pageNum、每页条数pageSize、总记录数total。对应的SQL是-- 查询总条数 SELECT COUNT(*) FROM user; -- 查询某一页数据 SELECT * FROM user LIMIT #{offset}, #{pageSize};其中offset (pageNum - 1) * pageSize。写JDBC代码时参数用PreparedStatement的?占位符不要拼接字符串否则有SQL注入风险。JSP页面的分页导航条写成这样div classpagination c:if test${pageNum 1} a hrefadmin/user/list?pageNum${pageNum-1}pageSize${pageSize}上一页/a /c:if span第 ${pageNum} / ${totalPages} 页/span c:if test${pageNum totalPages} a hrefadmin/user/list?pageNum${pageNum1}pageSize${pageSize}下一页/a /c:if /div分页里最容易踩的坑是第一页点删除某条数据后跳回列表结果页码越界。比如总共只有3页当前在第3页删除这条后总页数可能变成2页如果还在第3页查数据查出来就是空列表。处理方式有两种删完后跳回pageNum-1页或者Servlet里判断如果当前页没有数据但总页数大于0就重定向到最后一页。5.4 登录拦截的两种基础实现管理系统里的JSP页面一般不能裸奔未登录用户不能直接访问。原生JavaWeb里最直观的方式就是用一个Filter做登录拦截这也是JavaWeb三大组件Servlet、Filter、Listener中Filter的经典应用场景。写一个LoginFilterWebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; // 放行登录相关的路径和静态资源 String uri request.getRequestURI(); if (uri.contains(/login) || uri.endsWith(.css) || uri.endsWith(.js) || uri.endsWith(.png)) { chain.doFilter(req, resp); return; } // 判断session里有没有登录用户 HttpSession session request.getSession(); if (session.getAttribute(loginUser) ! null) { chain.doFilter(req, resp); } else { // 没登录就跳转到登录页 response.sendRedirect(request.getContextPath() /login.jsp); } } }这段代码里有一处新手比较容易忽略的uri.contains(/login)不能换成uri.endsWith(/login)因为登录请求可能是/login.jsp也可能是登录接口/loginServlet用endsWith(/login)就漏放了。另外静态资源不放行会导致一个很隐蔽的体验问题未登录时跳转到登录页但登录页的CSS和JS全被拦截了页面光秃秃的没有样式。这个问题我在练习项目里遇到过好几次排查思路就是先确认Filter有没有把静态资源访问路径放出去。5.5 基于JSP的项目还能怎么扩展原生JSP项目虽然现在主流开发已经不太直接用了但它作为JavaWeb基础课的地位不会动摇。把这个用户管理系统跑熟练之后后面往两个方向扩展都顺理成章一个方向是引入MVC框架把Servlet换成SpringMVC类似思想JSP换成模板渲染。你会发现当初在Servlet里写的那些“取参数-调服务-转发页面”的动作框架里一个注解就搞定了JSP里的JSTLEL那套写法在模板引擎里也能找到对应的语法糖。另一个方向是前后端分离。后端只提供JSON接口前端用Vue、React这类框架渲染数据。这时候原来JSP里渲染表格的工作变成了前端调用接口、拿到JSON后在前端循环生成DOM。Java后端的技术栈重心从“怎么渲染页面”变成“怎么提供接口”。但我依然建议所有学JavaWeb的人认真把JSP这块啃下来。它让你理解一个最基本的事实Web应用的本质是请求和响应页面就是响应的一种呈现形式。理解了这个后面不管技术栈怎么换整个请求处理的链路都是相通的。最后说说我的经验感受。JSP被很多新项目抛弃是事实但作为技术学习路径的一环它的价值不是让你以后靠它吃饭而是帮你建立“视图层和服务端如何配合”的完整认知。我见过不少直接上手SpringBoot的同学遇到请求转发、域对象、会话跟踪这些问题时一知半解反过来补JavaWeb基础。先把这个地基打牢后面框架学起来是事半功倍的。动起手来把IDEA里的Tomcat跑通照着这篇笔记写一个用户列表演示出来比只看不练强一百倍。
返回列表