Java Web 傻瓜式形象入门

前面讲了基本的jsp编程,这部分还是很基础的,整体把Java代码嵌入hmtl的行为和PHP比较类似,接下来就要介绍JAVA比较独有的部分了——JavaBean、spring框架、MVC模式、servlet等东西,听起来可能不太好理解,我刚学的时候也是一脸懵,下面我就将按照我理解时候的思路,以一个形象的例子,暂时忽视一些太过具体的技术细节,大致地以初学者的角度来说一下这都是个什么东西。

例子1:从杂货铺到大商场——jsp的解耦进程

解耦这个概念贯穿着接下来的整个JavaWeb编程过程,对概念不清晰的同学可以百度一下,这里就不赘述了

想象一下,你刚刚从卷生卷死的互联网大厂辞职,回到家乡继承了你老爹的一家祖传杂货铺,虽然麻雀虽小五脏俱全,还赚得到几个钱,但顾客进来看到的店面是这样的

  • 大米堆在收银台旁边(HTML结构)
  • 酱油瓶上贴着价格标签(CSS样式)
  • 老板一边算账(Java逻辑),一边整理货架

对应的jsp代码belike(当刚学了jsp后一般写出的也是这样的代码):

<%-- 混合Java逻辑、HTML、CSS的JSP --%>
<html>
<head>
    <style>
        .highlight { color: red; } /* CSS内嵌 */
    </style>
</head>
<body>
    <h1>商品列表</h1>
    <% 
        // Java代码直接嵌入(像老板亲自搬货)
        List<Product> products = (List<Product>) request.getAttribute("products");
        for (Product p : products) {
            String style = p.getStock() < 10 ? "highlight" : "";
    %>
            <div class="<%= style %>"> <!-- 动态应用CSS -->
                <%= p.getName() %> - 库存:<%= p.getStock() %>
            </div>
    <% } %>
</body>
</html>

虽然你爸几十年都是这么过来的,但是敏锐的你发现这种形式存在几个问题:

  • 老板身兼数职:既要处理数据(库存计算),又要摆放商品(HTML结构),还要设计标签样式(CSS),虽然省了人工但是又累又容易出错
  • 改动困难:若想调整商品布局,可能踩到地上的大米(误改Java代码)

于是经过你大刀阔斧的改造后,小杂货铺引入JavaBean摇身一变成为了现代化的超市,现在进入店里,所有环节都分工明确多了:

  1. 仓库管理员(JavaBean)专门管理商品数据
  2. 陈列设计师(HTML/CSS)专注货架布局和美观
  3. 店长(Servlet)负责协调各方,不直接碰商品

Servlet具体是啥等下会说

对应的代码也变成了这样:

1. JavaBean作为“标准化商品包装”

public class ProductBean implements Serializable {
    private String name;
    private int stock;
    
    // 标准getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    // 其他属性...
}

2. Servlet作为“店长”协调流程

protected void doGet(HttpServletRequest request, HttpServletResponse response) {
    List<ProductBean> products = productService.loadProducts(); // 从服务层获取数据
    request.setAttribute("products", products); // 将商品送到陈列区
    request.getRequestDispatcher("/product-list.jsp").forward(request, response); // 通知设计师开工
}

3. JSP变身“陈列设计师”专注展示

<%-- 纯净的HTML/CSS,只关注如何展示 --%>
<html>
<head>
    <link rel="stylesheet" href="product-style.css"> <!-- 外联CSS -->
</head>
<body>
    <h1>商品列表</h1>
    <c:forEach items="${products}" var="p">
        <div class="${p.stock < 10 ? 'highlight' : ''}">
            ${p.name} - 库存:${p.stock}
        </div>
    </c:forEach>
</body>
</html>

JavaBean在这里就像商品的标准分装包装,航运里的集装箱,起到了一个标准化的包装,以前客户买大米你可能要亲自去铺子的角落称量好交给他,现在他只需要到商品区条一包包装好的五公斤稻香米交给售货员结账就可以了

PS:JavaBean VS 普通类

特性 JavaBean 普通类
构造方法 必须有无参构造方法 可有可无
属性访问 严格通过getter/setter 可直接操作public字段
设计目的 通用性,可被框架、工具操作 可以实现具体业务逻辑
序列化 一般都会有 不一定支持

好啦,我们现在来看看这样做的好处是什么

环节 杂货铺(未解耦) 现代超市(JavaBean解耦)​
数据管理 商品随意堆放在角落(request属性) 标准化包装存储在仓库(JavaBean)
职责划分 老板既管账又理货(代码混杂) 店长、仓库员、设计师各司其职(MVC)
修改成本 调整货架可能打翻酱油瓶(牵一发而动全身) 可单独更换货架布局(仅改JSP/CSS)
协作效率 老板忙得团团转(代码臃肿) 多人并行工作(前后端分离开发)

可以看到相比传统店长把所有事情都干完的小作坊模式,解耦后各司其职的模式让效率提高了,那么这里提到的MVC和Servlet的概念又是什么呢,我们再细细说来

例子2:连锁餐厅的进化史——MVC 和 Servlet概念解析

1. 什么是MVC?——成功餐厅的管理模式

开商场挣的钱还是不够多,于是你决定进军餐饮界,既然要赚大钱,当然不能继续搞路边小店那一套,根据你在互联网大厂的耳濡目染和优化自己小铺子的经验,整出来了一套管理方法来让员工们各司其职,这套方法就是我们接下来要说的MVC:

  • ​厨师(Model)​​:负责准备食材和烹饪(处理数据)
  • ​服务员(Controller)​​:接收顾客订单并协调后厨(处理请求)
  • ​摆盘师(View)​​:将菜品美观地呈现给顾客(展示页面)

对应到Web开发:

​餐厅角色​ ​技术组件​ ​职责​
厨师 Model 处理业务逻辑和数据(如JavaBean)
服务员 Servlet 接收请求并协调Model和View
摆盘师 JSP 生成最终用户看到的HTML页面

2. Servlet是什么?——餐厅里的“智能服务员”​

  • ​基本定义​​:Servlet是一个Java类,专门处理HTTP请求(如GET/POST)并返回响应。
  • ​核心职责​​:
    • 接收顾客(浏览器)的订单(请求)
    • 通知厨师(Model)需要做什么菜(调用业务逻辑)
    • 将做好的菜(数据)交给摆盘师(JSP)装饰
    • 最终把装饰好的菜品(HTML页面)送回顾客

​代码示例​​:

// 一个处理用户登录的Servlet(服务员)
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        // 1. 接收顾客订单(获取请求参数)
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // 2. 通知厨师做菜(调用业务逻辑)
        UserService service = new UserService();
        boolean isValid = service.checkLogin(username, password);

        // 3. 将结果交给摆盘师(传递数据到JSP)
        request.setAttribute("isValid", isValid);

        // 4. 指定摆盘师处理(转发到JSP页面)
        request.getRequestDispatcher("/login-result.jsp").forward(request, response);
    }
}

3. MVC模式详解——餐厅协作流程​​

​好的,那么现在餐厅开业了,你作为老板决定视察一下自己的这个模式运行得如何,你走进餐厅,看到一个顾客点了一盘番茄炒蛋,你拿出小本本记录下整个餐厅提供这一盘番茄炒蛋的流程。

​步骤​ ​餐厅流程​ ​MVC对应操作​
1 顾客下单 浏览器发送HTTP请求到Servlet(如/login
2 服务员记录订单并通知后厨 Servlet获取请求参数,调用Model(如UserService.checkLogin()
3 厨师烹饪并装盘 Model返回处理结果(如boolean isValid
4 服务员将菜品交给摆盘师装饰 Servlet将数据存入request作用域(request.setAttribute()
5 摆盘师用精美餐具呈现菜品 JSP读取数据并生成HTML(${isValid}
6 顾客看到最终菜品 浏览器渲染JSP生成的HTML页面

​4. 为什么需要MVC?——解决“厨房灾难”​​

​虽然前面运营你的祖传小卖部的时候已经说过一次了,但让我们再来看看如果没有MVC的混乱场景,加深一下印象​​(直接混用JSP和Java代码):

  • 厨师(数据处理)和摆盘师(页面展示)挤在同一个厨房
  • 服务员(Servlet)既要炒菜又要摆盘,手忙脚乱
  • 修改菜品样式可能打翻调料(改动页面影响业务逻辑)

​MVC的优势​​:

  • ​分工明确​​:
    • 服务员(Servlet)专注协调,不碰具体烹饪或摆盘
    • 厨师(Model)专注数据处理,不关心如何展示
    • 摆盘师(JSP)专注页面,不涉及业务逻辑
  • ​易于维护​​:
    • 更换摆盘风格(修改JSP)无需重做菜品(不动Model和Servlet)
    • 调整菜品配方(修改Model)不影响页面展示

​5. Servlet核心机制——服务员的工作手册​

​一些比较关键的知识点​​:

  • ​生命周期​​:
    • init():服务员入职培训(Servlet初始化)
    • service():处理顾客请求(自动区分GET/POST)
    • destroy():服务员离职(Servlet销毁)
  • ​请求处理流程​​:
    1. 浏览器访问URL → Web服务器(如Tomcat)找到对应的Servlet
    2. Servlet根据请求类型(GET/POST)调用doGet()doPost()
    3. 处理完成后,通过forward()sendRedirect()跳转页面
  • ​数据传递方式​​:
    • request.setAttribute("key", value) → 转发到JSP(同一次请求)
    • session.setAttribute("key", value) → 跨请求保留数据(如用户登录状态)

​6. 完整流程示例:用户登录功能​

唠嗑了这么多,也应该理解了,我们再来看看在web方面的实操代码,以最简单的用户登录为例:​

​步骤1:用户提交表单(顾客下单)​

<!-- login.html -->
<form action="/login" method="post">
    <input type="text" name="username">
    <input type="password" name="password">
    <button>登录</button>
</form>

​步骤2:Servlet处理请求(服务员协调)​

// LoginServlet.java
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
    String username = request.getParameter("username");
    String password = request.getParameter("password");

    // 调用Model处理业务
    UserService service = new UserService();
    boolean isValid = service.checkLogin(username, password);

    // 传递结果到JSP
    request.setAttribute("isValid", isValid);
    request.getRequestDispatcher("/login-result.jsp").forward(request, response);
}

​步骤3:JSP显示结果(摆盘师装饰)​

<!-- login-result.jsp -->
<html>
<body>
    <c:choose>
        <c:when test="${isValid}">
            <h1 style="color:green">登录成功!</h1>
        </c:when>
        <c:otherwise>
            <h1 style="color:red">用户名或密码错误</h1>
        </c:otherwise>
    </c:choose>
</body>
</html>

例子3:Spring框架——新时代的智能化餐厅

​需求推动进步,为什么需要用Spring框架呢?当然是现有的技术还存在缺陷,满足不了需求啦。

你的餐厅因为优秀的管理和餐品质量,成功地在和小铺子的竞争中胜出,越做越大,但在和萨莉亚这些连锁店的battle中,你发现了不少存在的问题,和可以进一步降本增效的方法,于是你决定用新科技进一步赋能你的餐厅~

存在的问题和spring的解决方案

1. 依赖管理混乱:手动拼装“零件工厂”​

​传统 Java 难题​​:

  • ​紧耦合​​:每个类需手动 new 依赖对象,如厨师要自己找刀。
  • ​难以复用​​:依赖关系硬编码在代码中,修改一处可能影响全局。
// 传统方式:每个厨师自己造刀
public class Chef {
    private Knife knife = new Knife(); // 直接依赖具体实现
}

​Spring 的解决​​:

  • ​依赖注入(DI)​​:通过 @Autowired 声明需求,由容器自动装配。
  • ​面向接口编程​​:依赖抽象(接口)而非具体实现,提升灵活性。
@Component
public class Chef {
    @Autowired  // 容器自动配送刀具(无论刀是铁制还是激光刀)
    private Knife knife; 
}

​比喻​​:
传统方式像每个厨师自己打铁造刀,效率低下;Spring 像中央刀具库,按需配送,厨师专注烹饪。


​2. 配置地狱:维护成吨的 XML 文件​

​传统 Java 难题​​:

  • ​XML 配置繁琐​​:Bean 定义、数据源、事务管理等配置分散在多个 XML 文件中。
  • ​易出错​​:手动配置导致项目启动失败是家常便饭。
<!-- 传统 Spring XML 配置 -->
<bean id="dataSource" class="com.mysql.jdbc.Datasource">
    <property name="url" value="jdbc:mysql://localhost:3306/db" />
</bean>

​Spring 的解决​​:

  • ​Java Config​​:用 @Configuration 和 @Bean 替代 XML。
  • ​Spring Boot 自动配置​​:约定优于配置,内嵌默认行为。
@SpringBootApplication // 一键启动,自动配置数据源、Web 服务器等
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

​比喻​​:
传统方式像手写每份菜单,容易写错;Spring Boot 像智能点餐系统,自动生成标准化菜单。


​3. 事务管理分散:代码中到处是重复的模板代码​

​传统 Java 难题​​:

  • ​模板代码重复​​:每次数据库操作都要手动处理事务(begincommitrollback)。
// 传统 JDBC 事务管理
Connection conn = dataSource.getConnection();
try {
    conn.setAutoCommit(false);
    // 执行SQL...
    conn.commit();
} catch (SQLException e) {
    conn.rollback();
} finally {
    conn.close();
}

​Spring 的解决​​:

  • ​声明式事务​​:通过 @Transactional 注解集中管理。
  • ​AOP 自动代理​​:非侵入式地为方法添加事务边界。
@Service
public class UserService {
    @Transactional // 一句注解搞定事务
    public void updateUser(User user) {
        userRepository.save(user);
    }
}

​比喻​​:
传统方式像每次烹饪都要手动开关燃气灶;Spring 像智能燃气系统,自动根据菜品控制火候和时间。


​4. 模块化不足:牵一发动全身​

​传统 Java 难题​​:

  • ​高耦合​​:模块间直接调用,如订单模块直接 new 库存模块对象。
  • ​测试困难​​:无法轻松替换依赖(如用 Mock 对象测试)。

​Spring 的解决​​:

  • ​松耦合设计​​:通过接口和 DI 实现模块隔离。
  • ​测试支持​​:Spring Test 提供 @MockBean 等工具。
// 订单模块只依赖库存接口
@Service
public class OrderService {
    @Autowired
    private InventoryService inventoryService; // 接口而非具体类
}

// 测试时替换为 Mock
@SpringBootTest
class OrderServiceTest {
    @MockBean
    private InventoryService mockInventory;
}

​比喻​​:
传统方式像厨房各部门共用一把刀,一旦损坏全瘫痪;Spring 像为每个部门配独立工具包,互不影响。


spring中注解的作用

如果你仔细看了上面的代码,可能会和一开始的我一样有一个疑问,这里面的@是什么??注释吗?其实它的名字叫做注解,可以理解成厨师点击屏幕,对餐厅的智能系统(框架)发出的指令,以前厨师可能要亲自去干很多事情,但在你的餐厅升级了智能化系统后,厨师只需要下指令就让这个系统去可以完成很多事情,下面来举个例子理解一下:

先拿准备刀具这个简单的例子:

// 传统方式:厨师自己找食材(手动创建依赖)
Chef chef = new Chef();
chef.setKnife(new Knife()); // 自己准备刀具

// Spring方式:系统自动配送
@Component
public class Chef {
    @Autowired // 系统自动注入刀具
    private Knife knife; 
}

1. 传统方式(手动管理依赖)

// 厨师自己找刀具(强耦合)
Chef chef = new Chef();
Knife knife = new Knife();  // 手动创建依赖
chef.setKnife(knife);       // 手动注入依赖
  • ​问题​​:
    • ​耦合度高​​:Chef 必须知道如何创建 Knife
    • ​难以测试​​:无法轻松替换 Knife 为测试用的 MockKnife
    • ​资源浪费​​:每次创建 Chef 都要重复初始化 Knife

2. Spring方式(自动依赖注入)

@Component        // 告诉Spring:这是一个需要管理的Bean
public class Chef {
    @Autowired    // 告诉Spring:自动给我配一把刀
    private Knife knife; // 无需手动new,由容器注入
}
  • ​关键点​​:
    • ​依赖解耦​​:Chef 不关心 Knife 如何创建,只声明需要它。
    • ​生命周期管理​​:Knife 的创建和销毁由 Spring 容器控制。
    • ​灵活替换​​:可通过配置文件或 @Qualifier 切换 Knife 实现类。

@Autowired 就像餐厅的 ​​智能物流系统​​:

  • 传统方式:厨师(Chef)需要亲自去仓库找刀(new Knife()),效率低下。
  • Spring方式:厨师只需声明“我需要刀”,物流系统(Spring容器)自动配送,厨师专注烹饪(业务逻辑)。
    通过这种方式,Spring 实现了​​依赖的自动化管理​​,让代码更简洁、更易维护。

再来加深下理解:

注解 = 厨师的智能操作指令​

1. ​​基础指令​​(定义角色)

@Component         // 指令:我是厨师,加入餐厅员工列表
public class Chef {
    @Autowired     // 指令:我需要一把刀,请系统配送
    private Knife knife;
}
  • ​操作流程​​:
    • ​点击屏幕​​(添加 @Component 注解)→ 厨师在系统中注册身份。
    • ​勾选需求​​(添加 @Autowired 注解)→ 系统自动配送刀具到工位。

2. ​​高级指令​​(精确控制)

@Qualifier("sushiKnife")  // 指令:不要普通刀,要寿司专用刀
@Scope("prototype")       // 指令:每次做菜都用新刀(默认是单例刀)
  • ​场景​​:
    • 寿司厨师在屏幕上选择“专用刀具”,系统根据指令精准配送。

3. ​​协作指令​​(跨部门协作)

@RestController          // 指令:我是接待员,负责对接顾客订单
@RequestMapping("/sushi") // 指令:我的工位在寿司订单处理区
public class SushiController {
    @Autowired          // 指令:我需要寿司厨师团队支持
    private SushiChef sushiChef;
}
  • ​流程​​:
    • 顾客在手机 App 下单寿司 → 系统自动路由到寿司工位(@RequestMapping)。
    • 接待员(Controller)呼叫寿司厨师(Service)处理订单。

​指令系统的运行逻辑​

1. ​​指令接收阶段(启动时)​

flowchart TD A[主厨打开智能系统] --> B[扫描所有员工工牌] B --> C{是否标记@Component?} C -->|是| D[录入员工名单] C -->|否| E[忽略] D --> F[检查需求指令] F --> G{是否有@Autowired?} G -->|是| H[自动配送工具] G -->|否| I[完成注册]
  • ​对应代码​​:Spring 容器启动时扫描所有 @Component 注解的类,创建 Bean 并解析依赖。

2. ​​指令执行阶段(运行时)​

// 顾客下单寿司
顾客 -- 订单 --> 接待员(@RestController)
接待员 -- @Autowired --> 寿司厨师(SushiChef)
寿司厨师 -- @Autowired --> 刀具库(KnifeRepository)
刀具库 -- @Value("${knife.type}") --> 配置中心
  • ​动态协作​​:系统根据注解指令自动连接各个模块,无需人工协调。

贴一个​常见指令对照表​

厨师操作 对应注解 系统行为
登记入职 @Component 将对象加入容器管理
申请专用刀具 @Qualifier("knifeA") 按名称注入指定工具
设置工位牌(寿司区A座) @RequestMapping 将 HTTP 请求路由到指定方法
要求每次使用新厨具 @Scope("prototype") 每次提供新实例
查看操作手册配置 @Value("${config}") 从配置文件读取参数

例子4:补充聊聊AOP,和容器、servlet的定义问题

1. 什么是AOP,为什么要AOP?

你想想,既然有了智能化系统,那你肯定希望在一位客人给出差评说你们餐厅的菜不够好吃的时候,去调查是厨房火候不对还是仓储阶段出了问题导致菜不够新鲜吧,AOP就相当于厨房的监控和餐厅各处的传感器,让使用和维护者可以随时监控到整个项目。

AOP餐厅监控系统图

flowchart TB 顾客 --> 服务员[服务员接单] 服务员 -->|触发| 监控系统{AOP切面} 监控系统 --> 摄像头[" 记录订单时间(@Before)"] 监控系统 --> 电子秤["⚖️ 校验食材分量(@Around)"] 监控系统 --> 警报器[" 异常处理(@AfterThrowing)"] 监控系统 --> 服务员 服务员 --> 厨师[厨师烹饪]

代码实现:

@Aspect
@Component
public class KitchenMonitor {
    
    // 监控所有烹饪操作
    @Pointcut("execution(* com.restaurant.kitchen.*.cook*(..))")
    public void cookingOperations() {}

    // 烹饪前校验食材
    @Around("cookingOperations()")
    public Object checkIngredients(ProceedingJoinPoint pjp) throws Throwable {
        if(!ingredientService.checkStock()){
            throw new InsufficientIngredientsException();
        }
        return pjp.proceed();
    }

    // 记录异常事件
    @AfterThrowing(pointcut="cookingOperations()", throwing="ex")
    public void logKitchenError(Exception ex) {
        alertSystem.sendAlert("厨房异常:" + ex.getMessage());
    }
}

2. Servlet是个什么东西?

在Java Web开发中,​​Servlet本身是一个广义的技术组件​​,它可以承担不同的角色。具体到MVC模式中的Controller和JSP编译生成的Servlet,它们的​​定义、作用和使用场景有本质区别​​。以下通过对比和示例详细说明:


​​1. Servlet的广义定义​

Servlet是Java EE规范中定义的​​服务器端组件​​,用于处理HTTP请求并生成动态响应。它本质是一个实现了javax.servlet.Servlet接口的Java类,核心方法是service()doGet()/doPost()
​所有Servlet的共同点​​:

  • 由容器(如Tomcat)管理生命周期。
  • 接收请求(HttpServletRequest)并返回响应(HttpServletResponse)。

​2. 两种Servlet的不同角色​​

​​(1) JSP编译生成的Servlet​​

  • ​定义​​:
    由JSP文件转换而来的Servlet,例如hello.jsp会被编译为hello_jsp.java(继承自HttpJspBase)。
  • ​作用​​:
    • 将JSP中的HTML模板和动态代码(<% ... %>)转换为out.write()输出。
    • ​本质是视图(View)层​​,负责生成最终的HTML内容。
  • ​示例​​:
    用户访问hello.jsp时,容器调用hello_jsp_jspService()方法,输出HTML。

​​(2) MVC模式中的Controller Servlet​​

  • ​定义​​:
    开发者手动编写的Servlet,例如UserController.java(继承自HttpServlet)。
  • ​作用​​:
    • 接收用户请求(如/user/login),解析参数。
    • 调用业务逻辑层(Service)处理数据。
    • 将处理结果传递给视图(如转发到JSP或返回JSON)。
    • ​本质是控制器(Controller)层​​,负责协调Model和View。
  • ​示例​​:
    public class UserController extends HttpServlet {
        protected void doPost(HttpServletRequest request, HttpServletResponse response) {
            // 1. 获取参数(如用户名、密码)
            String username = request.getParameter("username");
            // 2. 调用Service层验证用户
            UserService userService = new UserService();
            boolean isValid = userService.validateUser(username);
            // 3. 将结果存储到请求作用域
            request.setAttribute("isValid", isValid);
            // 4. 转发到视图(JSP)
            request.getRequestDispatcher("/result.jsp").forward(request, response);
        }
    }
    

​3. 关键区别​​

​对比维度​ ​JSP编译的Servlet​ ​MVC中的Controller Servlet​
​角色​ 视图层(View) 控制层(Controller)
​代码来源​ 由容器自动生成 开发者手动编写
​核心职责​ 渲染HTML 处理请求逻辑、协调模型和视图
​是否直接输出响应​ 是(通过out.write()生成HTML) 否(通常转发请求或重定向到视图)
​典型代码​ 包含大量out.write()语句 包含业务逻辑调用和请求参数处理

​​4. 协作流程(MVC模式示例)​​

  1. ​用户请求​​:访问/login,由UserController处理。
  2. ​Controller处理​​:
    • 调用UserService验证用户。
    • 将结果(如isValid=true)存入request作用域。
    • 转发到result.jsp
  3. ​JSP编译后的Servlet​​:
    • 读取request中的isValid值。
    • 动态生成HTML(如显示“登录成功”或“登录失败”)。
  4. ​最终响应​​:用户看到渲染后的页面。

5. 为什么JSP(JavaServer Pages)最终会被编译成Servlet?

​JSP本质上是一种简化Servlet开发的模板技术​​,其底层实现依赖于Servlet容器(如Tomcat)的编译机制。下面进行概念解析和示例展示:

​核心概念解析​

  1. ​JSP的本质​​:
    • JSP 是一种动态网页技术,允许在HTML中嵌入Java代码(通过<% ... %>等标签)。
    • 但Servlet容器无法直接执行JSP文件,必须将其转换为标准的Java类(即Servlet)。
  2. ​编译过程​​:
    • ​第一次访问JSP时​​,容器(如Tomcat)会将其编译成.java文件(即Servlet源码)。
    • 接着将.java文件编译成.class文件(字节码),最终由JVM执行。
    • 后续请求直接调用已编译的Servlet,除非JSP文件被修改。

​示例:JSP → Servlet的转换​​

​​1. 原始JSP代码​​

假设有一个简单的JSP文件 hello.jsp

<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>JSP示例</title>
</head>
<body>
    <% 
        String name = "World";
        out.println("Hello, " + name + "!");
    %>
</body>
</html>
​2. 编译后的Servlet代码​​

Tomcat会生成类似以下的Servlet(简化版):

public class hello_jsp extends HttpServlet {
    public void _jspService(HttpServletRequest request, HttpServletResponse response) 
        throws IOException, ServletException {

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        out.write("<html>");
        out.write("<head>");
        out.write("<title>JSP示例</title>");
        out.write("</head>");
        out.write("<body>");

        String name = "World";
        out.println("Hello, " + name + "!");

        out.write("</body>");
        out.write("</html>");
    }
}
​关键转换规则​
  • ​JSP标签​​:
    • <% ... %> → 直接嵌入到_jspService()方法中。
    • <%= ... %> → 转换为out.print(...)
    • <%@ page %> → 转换为Servlet的导入包或设置响应头(如contentType)。
  • ​HTML内容​​ → 通过out.write()逐行输出。

​总结:为什么需要编译成Servlet​
  1. ​统一执行模型​​:
    • Servlet容器(如Tomcat)的设计基于Servlet规范,所有动态内容必须通过Servlet处理。
    • 编译后的Servlet可以利用Servlet的生命周期(init()service()destroy())。
  2. ​性能优化​​:
    • 首次编译后,后续请求直接调用已编译的类,避免重复解析JSP。
  3. ​分离关注点​​:
    • JSP专注于视图层(HTML + 简单逻辑),Servlet处理业务逻辑,符合MVC模式。

​实际应用中的验证​​
  1. ​查看生成的.java文件​​:
    • 有自己写过或者接触JavaWeb的项目的话可以去翻翻,在Tomcat的work/Catalina/localhost/项目名/org/apache/jsp目录下,可以找到编译后的.java.class文件。
  2. ​调试JSP​​:
    • 由于JSP最终是Servlet,你可以像调试普通Java类一样调试生成的代码。

3. 上面提到的Tomcat容器跟我们常说的docker容器区别在哪?

**_先来扔出严格的定义和表格对比:

1. Tomcat 容器(Servlet 容器)​​

​定义​

  • ​Tomcat​​ 是一个 ​​Servlet 容器​​(也称为 Web 容器),它实现了 Java EE 规范中的 Servlet 和 JSP 标准。
  • ​核心功能​​:
    • 管理 Servlet 的生命周期(初始化、请求处理、销毁)。
    • 处理 HTTP 请求,将请求路由到对应的 Servlet。
    • 编译 JSP 文件为 Servlet,并执行生成的 Servlet。
    • 提供线程池、连接池等资源管理功能。

**​工作原理​​

  1. ​部署 Web 应用​​:将 WAR 文件(Web Application Archive)部署到 Tomcat 的 webapps 目录。
  2. ​启动容器​​:Tomcat 启动时,会加载 WAR 文件中的 Servlet 和 JSP。
  3. ​处理请求​​:
    • 用户访问 http://localhost:8080/hello.jsp
    • Tomcat 接收请求,检查 hello.jsp 是否已编译为 Servlet。
    • 若未编译,则将其转换为 hello_jsp.java 并编译为 .class 文件。
    • 调用 hello_jsp 的 _jspService() 方法生成 HTML 响应。

**​​关键特点​​

  • ​轻量级​​:专注于 Servlet/JSP 的执行,不包含完整的 Java EE 功能(如 EJB)。
  • ​单进程​​:运行在单个 JVM 进程中,所有 Web 应用共享同一进程资源。

​2. Docker 容器​​

​定义​

  • ​Docker 容器​​ 是一种 ​​操作系统级虚拟化技术​​,用于将应用程序及其依赖环境(如库、配置文件)打包成一个独立的、可移植的单元。
  • ​核心功能​​:
    • 隔离进程和资源(CPU、内存、文件系统)。
    • 提供一致的运行环境(开发、测试、生产环境一致)。
    • 快速部署和扩展应用。

​工作原理​

  1. ​构建镜像​​:通过 Dockerfile 定义应用环境(如基础操作系统、JDK、Tomcat)。

    FROM tomcat:9.0
    COPY my-webapp.war /usr/local/tomcat/webapps/
    
  2. ​运行容器​​:基于镜像启动一个容器实例。

    docker run -d -p 8080:8080 my-tomcat-app
    
  3. ​隔离环境​​:容器内的 Tomcat 和 Web 应用与宿主机及其他容器隔离。

​关键特点​

  • ​资源隔离​​:每个容器拥有独立的文件系统、网络和进程空间。
  • ​轻量级​​:共享宿主机的内核,无需启动完整的虚拟机。
  • ​可移植性​​:镜像可在任何支持 Docker 的环境中运行。

​3. 两者的核心区别​​

​对比维度​ ​Tomcat(Servlet 容器)​ ​Docker 容器​
​抽象层级​ 应用服务器,管理 Servlet/JSP 生命周期 操作系统级虚拟化,隔离应用运行环境
​核心目标​ 执行 Java Web 应用 打包和分发任意类型的应用
​资源隔离​ 无(所有 Web 应用共享同一 JVM 进程) 是(每个容器独立)
​依赖管理​ 仅管理 Java 相关依赖(如 Servlet API) 管理所有依赖(OS、库、配置文件)
​典型使用场景​ 运行 Java Web 应用 部署微服务、数据库、前端应用等

​4. 为什么都叫“容器”?​​

  • ​Tomcat 容器​​:名称来源于“Servlet 容器”,强调它对 Servlet 生命周期的管理(类似“容器”装载 Servlet)。
  • ​Docker 容器​​:名称来源于“运输集装箱”,强调它对应用环境的标准化封装和隔离。

5.形象例子解释

_如果还是不能理解的话,还是回到前面你开的餐厅的例子,身为大老板的你已经不亲自去管理餐厅的运作了,于是你请了一个经理来帮你打理这家餐厅,这就是Tomcat容器:

1. Tomcat容器 → 餐厅经理​​

  • ​经理的职责​​:

    • ​员工调度​​:管理厨师(Controller Servlet)、服务员(JSP编译后的Servlet)的工作时间(生命周期),如上班时初始化(init())、下班时清理资源(destroy())。
    • ​订单分发​​:顾客(用户)进入餐厅后,经理(Tomcat)将订单(HTTP请求)分配给对应的厨师(Controller Servlet)。
    • ​设备维护​​:确保厨房的炉灶(线程池)、餐具(连接池)正常运作,避免厨师因资源不足而停工。
    • ​标准化流程​​:如果顾客点了一份“动态套餐”(JSP页面),经理会检查是否需要重新准备食材(编译JSP为Servlet)。
  • ​比喻对应​​:

    • ​员工管理​​ → Servlet生命周期管理。
    • ​订单分发​​ → HTTP请求路由到指定Servlet。
    • ​资源池​​ → Tomcat的线程池和数据库连接池。
    • ​JSP编译​​ → 经理确保菜谱(JSP)已转换为可操作的指令(Servlet)。

​​2. MVC中的Controller Servlet → 主厨​​

  • ​厨师的职责​​:

    • ​核心烹饪​​:根据订单要求(请求参数),烹饪菜品(执行业务逻辑),例如:
      • 调用助手(Service层)处理食材(数据)。
      • 决定最终摆盘方式(选择视图,如JSP或JSON)。
    • ​专注专业领域​​:厨师不关心餐厅租金(服务器资源)或食材采购(依赖管理),只负责烹饪(处理业务逻辑)。
  • ​比喻对应​​:

    • doGet()/doPost() → 接收订单并烹饪。
    • 调用Service层 → 让助手处理食材切配、调味。
    • 转发到JSP → 将菜品交给服务员摆盘。

那么大老板你去干嘛了呢?当然是去开连锁店做大做强,为了保证让开出去的加盟店、连锁店都保持你第一家餐厅的味道,保持口碑,你决定设立一个连锁店标准和标准化执行团队,连锁店的所有食材、工具都要从你的供应链采购,装修风格和宣传物料都必须用的是同一套,这样既帮分店老板省心省力又保证了菜品和服务质量,这就是Docker容器

3. Docker容器 → 连锁总部标准化团队​​

  • ​标准化团队的职责​​:

    • ​分店复制​​:将主厨的菜谱(代码)、厨房设备(Tomcat)、食材(依赖库)打包成“分店模板”(Docker镜像)。
    • ​环境一致性​​:无论分店开在商场还是街边(开发、测试、生产环境),打开模板即可获得完全相同的厨房。
    • ​隔离与安全​​:每家分店独立运营(容器隔离),一家分店失火(应用崩溃)不会影响其他分店。
  • ​比喻对应​​:

    • Docker镜像 → 标准化的分店模板。
    • docker run → 根据模板开一家新分店。
    • 资源隔离 → 每家分店有独立的水电系统。

大概就讲到这里了,因为作者本身也是刚学JavaWeb的小白,一边上课悄悄偷玩手机啃书查资料一边生搬硬套憋出来的这篇笔记,只能说半懂不懂,如果有什么错漏请各位佬指出。

END

posted @ 2025-04-06 16:38  饺子龙  阅读(79)  评论(0)    收藏  举报