ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从零到一:我用“快递驿站”彻底搞懂了Java Web三层架构

2026/8/18 11:44:13 拓冰建站 浏览量
从零到一:我用“快递驿站”彻底搞懂了Java Web三层架构 前言我是一名刚学Java Web的学生。老师带着我们写了一个员工管理系统代码能跑起来但我心里始终有个疙瘩为什么要分层为什么要继承HttpServlet为什么service方法要写那么多看不懂的东西直到有一天我给自己讲了一个“快递驿站”的故事所有代码突然就通了。这篇文章不讲高深理论从一个初学者的视角用最笨的语言把整个MVC架构拆开揉碎讲给你听。如果你也在迷茫希望能给你一点光亮。一、先看整体这个系统长什么样我们有一个员工管理系统功能很简单用户输入员工编号和姓名 → 登录登录成功后看到所有员工列表列表里每行有个“删除”按钮技术栈JSP Servlet JDBC Tomcat MySQL目录结构核心部分二、为什么需要分层我的三层架构理解1. 数据层DAO—— 仓库管理员职责只负责写SQL语句执行增删改查。// EmpDaoImpl.java Override public ListEmp selectAll() { String sql SELECT * FROM emp; return db.query(Emp.class, sql); // 只写SQL别的不管 }为什么单独拆出来如果没有DAO层每个功能都要重复写Class.forName、Connection、PreparedStatement、ResultSet、try-catch-finally——20个功能就要抄20遍。拆出来之后只写一次SQL所有地方复用。2. 业务层Service—— 调度中心职责处理业务逻辑比如登录时判断账号是否被冻结调用DAO获取数据。// EmpServiceImpl.java Override public Emp login(Emp emp) { // 以后可以在这里加if (emp被封禁) return null; return empDao.selectByEnameAndEmpno(emp); // 目前只是简单调用 }为什么现在看着像“空壳”因为老师为了先教会Web流程暂时没往里加复杂逻辑。但架子必须搭好以后加“转账事务”、“权限校验”等复杂业务时只能写在Service层不能写在DAO层。3. 控制层Controller—— 快递驿站职责接收浏览器请求调用Service根据结果跳转页面。它是连接网络请求和业务逻辑的桥梁。三、核心比喻用“快递驿站”理解整个流程Tomcat 物流公司负责监听端口、接收包裹、派送web.xml 派工单规定哪个驿站负责哪个地址Controller 快递驿站门口有分拣员负责接待和派活service方法 前台分拣员看一眼地址喊对应负责人来干活login/del等方法 不同负责人拆包裹、干活、装箱回信request 送来的包裹里面装着用户输入的所有数据response 回信空箱子往里面装要返回的内容转发(forward) 驿站内部递包裹包裹不出去内部转交重定向(sendRedirect) 退货重发告诉客户重新填单JSP 印刷车间把数据画成HTML网页四、从头走一遍完整流程第一步客户填单index.jsp用户在浏览器打开index.jsp看到登录表单form actionemp/login methodpost 员工编号input typetext nameempno/br 员工姓名input typetext nameename/br input typesubmit value登陆/ /form关键点actionemp/login告诉浏览器点按钮后把数据发到服务器地址的emp/login这个位置。第二步物流公司Tomcat查派工单web.xmlTomcat收到请求地址emp/login打开web.xml查看servlet-mapping servlet-nameempController/servlet-name url-pattern/emp/*/url-pattern /servlet-mapping/emp/*的意思是所有以/emp/开头的请求全部交给EmpController处理。如果用户乱填actionabc/loginTomcat查不到匹配规则直接返回404。所以不是Controller主动拒接而是Tomcat根本就不会把活派给它。第三步驿站分拣员service方法出场Tomcat根据派工单找到了EmpController调用它的service方法。Override protected void service(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { // 1. 拆出完整地址 String requestURI request.getRequestURI(); // 结果/gh/emp/login // 2. 切出最后一个斜杠后面的词 String str requestURI.substring(requestURI.lastIndexOf(/) 1); // 结果login // 3. 翻花名册找有没有叫login的负责人 ClassEmpController cla EmpController.class; Method method cla.getDeclaredMethod(str, HttpServletRequest.class, HttpServletResponse.class); // 4. 找到了让负责人上岗干活 method.invoke(this, request, resp); }这里有两个核心问题Q1为什么参数里要写HttpServletRequest request, HttpServletResponse resp因为Tomcat调用service方法时已经提前把浏览器发来的数据打包成了一个request对象里面装着用户输入的empno和ename又创建了一个空的response对象让负责人往里面填回信内容然后强行把这两个塞进方法的参数里。不写这两个参数就拿不到用户输入的数据也没法给浏览器回话。Q2为什么重写service而不是写doGet/doPost父类HttpServlet默认的service方法是按“GET/POST”分流的。如果我们在子类里写doGet和doPost用户发GET请求走doGet发POST请求走doPost同一个截取地址的逻辑要在两个方法里各抄一遍。所以直接重写service在最前面的总入口里只写一次截取逻辑不管GET还是POST统统先在这里截取后缀再派发给具体方法。第四步负责人拆包裹login方法分拣员喊了login方法它开始干活protected void login(HttpServletRequest request, HttpServletResponse resp) { // 1. 从包裹里拿数据贴纸上的名字就是input框的name属性 String empno request.getParameter(empno); // 用户输入的编号 String ename request.getParameter(ename); // 用户输入的姓名 // 2. 装进标准文件袋 Emp emp new Emp(); emp.setEmpno(Integer.parseInt(empno)); emp.setEname(ename); // 3. 喊业务部Service去查数据库 Emp e empService.login(emp); // 4. 根据结果回信 if (e null) { // 查无此人 → 回信告诉浏览器重新填单 resp.sendRedirect(/gh/index.jsp); } else { // 查到了 → 把包裹转交给findAll同事 findAll(request, resp); } }第五步成功后的数据展示findAll 和 show.jspfindAll方法被login调用protected void findAll(HttpServletRequest request, HttpServletResponse resp) { // 1. 从数据库查出所有员工 ListEmp list empService.findAll(); // 2. 在包裹内侧贴新标签 list request.setAttribute(list, list); // 3. 把包裹递给印刷车间show.jsp画网页 request.getRequestDispatcher(/show.jsp).forward(request, resp); }关键知识点forward转发 vssendRedirect重定向特性转发forward重定向sendRedirect本质服务器内部移交包裹告诉浏览器去另一个地址请求次数1次2次浏览器重新发起request数据保留数据继续传递丢失新请求没有旧数据应用场景需要携带数据跳转如查完列表要展示不需要携带数据如登录失败跳回首页所以login失败用sendRedirect数据没用了成功和findAll用forward必须带着list数据给页面。第六步印刷车间印网页show.jspshow.jsp收到forward递来的包裹% // 撕开包裹取出贴着list标签的数据 ListEmp list (ListEmp) request.getAttribute(list); for (int i 0; i list.size(); i) { % tr td%list.get(i).getEmpno()%/td td%list.get(i).getEname()%/td td%list.get(i).getJob()%/td td%list.get(i).getHiredate()%/td td%list.get(i).getDeptno()%/td td a hrefemp/del?empno%list.get(i).getEmpno()%删除/a /td /tr % } %注意这里用的是request.getAttribute(list)和findAll里的setAttribute(list, list)是完美对称的。第七步删除功能回显消息del 和 show.jsp 的 div用户点“删除”链接地址是emp/del?empno7369。service方法切出del反射调用del方法protected void del(HttpServletRequest request, HttpServletResponse resp) { String empno request.getParameter(empno); Integer result empService.deleteByid(Integer.parseInt(empno)); // 关键在包裹里贴一张消息纸条 request.setAttribute(mess, result 0 ? 删除成功 : 删除失败); // 把包裹转给 findAll它会再贴list数据一起送给show.jsp findAll(request, resp); }show.jsp里用三目运算符显示消息div stylecolor: red %request.getAttribute(mess) null ? : request.getAttribute(mess)% /div为什么需要这个null判断如果用户第一次访问页面没执行过删除mess不存在。不判断的话页面上会显示一个难看的红色null。这个写法保证只有真有消息时才显示没消息时干干净净。五、核心知识点总结1. 为什么必须继承HttpServletTomcat物流公司只认识实现了Servlet接口的类。extends HttpServlet就是给这个类打上“合格证”告诉Tomcat“放心我是符合规范的可以接收网络请求。”不继承Tomcat扫描到它也不会理用户请求过来直接404。2. 为什么要手动new Service对象private EmpService empService new EmpServiceImpl();Tomcat只负责帮你new Controller。Controller内部的Service、DAO对象必须由程序员自己创建并建立依赖关系。这不叫跨层这是标准的依赖注入在没有Spring框架的情况下。3. Controller 可以直调 DAO 吗绝对不行。调用链必须是Controller → Service → DAO。跳过Service直接调DAO就是跨层违反了分层架构原则后期维护会出大问题。4. request 和 response 的生命周期request一次请求创建一个请求结束销毁。转发forward时同一个request对象所以数据可以传递。重定向sendRedirect时浏览器发起新请求旧request销毁数据丢失。response一次请求对应一个response往里面写内容最终由Tomcat封装成HTTP响应发给浏览器。5. 方法名、反射、后缀之间的铁三角web.xml配置了/emp/*归Controller管 → 用户发emp/login→service切出login→ 反射调用login方法。你想加新功能比如update只需要三步在页面写actionemp/update或hrefemp/update?...。在EmpController里写protected void update(request, resp)方法。无需修改web.xml无需修改service方法反射自动帮你找到新方法这就是反射统一入口设计的威力开闭原则——对扩展开放对修改关闭。六、完整数据流转图七、最后的叮嘱如果你现在依然觉得有些代码是“硬背”的没关系这很正常。当初我把service方法里的反射代码抄了十几遍才真正理解它在干什么。但请你记住这句话所有的分层、继承、反射、转发归根结底就是为了两件事——把用户传来的数据安全地送到数据库再把数据库的结果漂亮地还给用户。剩下的都是为了让代码更整洁、更可维护、更可扩展而做的设计。