Intecepter分析

请求处理与渲染的完整生命周期

当一个 HTTP 请求进入系统后,LoginTicketInterceptorLoginRequiredInterceptor的各个方法会按照特定顺序执行,最终完成页面渲染。以下是详细的执行流程:

一、拦截器方法执行顺序

1. 请求进入时(按注册顺序执行 preHandle)

java
 
 
// WebMvcConfig.java 中注册拦截器的顺序
registry.addInterceptor(loginTicketInterceptor)  // 第一个拦截器
    .excludePathPatterns("/**/*.css", "/**/*.js");
    
registry.addInterceptor(loginRequiredInterceptor)  // 第二个拦截器
    .excludePathPatterns("/**/*.css", "/**/*.js");
 

执行顺序:

  1. LoginTicketInterceptor.preHandle()
    • 从 Cookie 中获取 ticket
    • 验证 ticket 有效性
    • 将用户信息存入HostHolder(ThreadLocal)
  2. LoginRequiredInterceptor.preHandle()
    • 检查请求的方法是否有@LoginRequired注解
    • 如果有注解且用户未登录,则重定向到登录页

2. 控制器处理请求

  • 如果两个拦截器的preHandle都返回true,请求到达目标控制器方法
  • 控制器处理业务逻辑,返回ModelAndView或视图名称

3. 视图渲染前(按注册顺序的逆序执行 postHandle)

  1. LoginRequiredInterceptor.postHandle()
    • (此拦截器未实现postHandle,无操作)
  2. LoginTicketInterceptor.postHandle()
    • HostHolder获取用户信息
    • 将用户信息添加到ModelAndView中(model.addAttribute("loginUser", user)

4. 请求完成后(按注册顺序的逆序执行 afterCompletion)

  1. LoginRequiredInterceptor.afterCompletion()
    • (此拦截器未实现afterCompletion,无操作)
  2. LoginTicketInterceptor.afterCompletion()
    • 清理HostHolder中的用户信息(防止内存泄漏)

二、数据流转与渲染流程

1. 数据流转路径

plaintext
 
 
Cookie(ticket) → LoginTicketInterceptor.preHandle() → 
HostHolder(ThreadLocal) → LoginRequiredInterceptor.preHandle() → 
Controller → LoginTicketInterceptor.postHandle() → 
Model(loginUser) → Thymeleaf模板 → 浏览器渲染
 

2. 关键渲染步骤

  1. Thymeleaf 解析表达式
    • 模板中的${loginUser}从 Model 中获取用户信息
    • 示例:
      html
       
      预览
       
       
       
       
      <li th:if="${loginUser==null}">  <!-- 未登录显示 -->
          <a th:href="@{/login}">登录</a>
      </li>
      <li th:if="${loginUser!=null}">  <!-- 已登录显示 -->
          <img th:src="${loginUser.headerUrl}" />
      </li>
      
       
  2. 动态生成 HTML
    • 根据loginUser是否存在,决定显示哪些元素
    • 例如:已登录用户看到头像和下拉菜单,未登录用户看到登录 / 注册按钮
  3. 最终响应
    • 渲染完成的 HTML 页面发送到浏览器

三、完整时序图

plaintext
 
 
请求进入
  ↓
LoginTicketInterceptor.preHandle()
  ├─ 从Cookie获取ticket
  ├─ 验证ticket有效性
  └─ 将用户存入HostHolder
  ↓
LoginRequiredInterceptor.preHandle()
  ├─ 检查方法是否有@LoginRequired注解
  └─ 如果有注解且未登录 → 重定向到登录页
  ↓
控制器处理请求
  ↓
LoginRequiredInterceptor.postHandle() (无操作)
  ↓
LoginTicketInterceptor.postHandle()
  ├─ 从HostHolder获取用户
  └─ 将用户添加到Model(loginUser)
  ↓
Thymeleaf模板渲染
  ├─ 解析${loginUser}表达式
  └─ 根据登录状态生成HTML
  ↓
LoginRequiredInterceptor.afterCompletion() (无操作)
  ↓
LoginTicketInterceptor.afterCompletion()
  └─ 清理HostHolder中的用户信息
  ↓
响应返回浏览器
 

四、特殊场景处理

1. 未登录用户访问需要登录的页面

  1. LoginRequiredInterceptor.preHandle()检测到@LoginRequired注解
  2. HostHolder获取用户为null
  3. 执行response.sendRedirect("/login")
  4. 返回false终止请求流程
  5. 直接跳转到登录页,后续拦截器和控制器不再执行

2. 静态资源请求(CSS/JS/ 图片)

  • 由于配置了excludePathPatterns,静态资源请求不会触发这两个拦截器
  • 直接由 Servlet 容器处理,提高性能

五、性能优化建议

1. 减少不必要的数据库查询

  • LoginTicketInterceptor中添加 Redis 缓存,缓存用户信息
  • 示例:
    java
     
     
    User user = (User) redisTemplate.opsForValue().get("user:" + userId);
    if (user == null) {
        user = userService.findUserById(userId);
        redisTemplate.opsForValue().set("user:" + userId, user, 3600, TimeUnit.SECONDS);
    }
    
     

2. 异步处理非关键操作

  • 将日志记录等非关键操作放到异步线程执行
  • 示例:
    java
     
     
    @Async
    public void recordLoginLog(String userId, String ip) {
        // 记录登录日志
    }
    
     

3. 缓存视图片段

  • 对于不经常变化的页面部分(如导航栏),使用 Thymeleaf 的缓存机制
  • 示例:
    html
     
    预览
     
     
     
     
    <th:block th:cache="true" th:cacheKey="'nav' + ${loginUser!=null}">
        <!-- 导航栏内容 -->
    </th:block>
    
     

总结

这两个拦截器通过分工协作,实现了:

  1. 前置验证:在请求到达控制器前,验证登录状态
  2. 数据注入:在视图渲染前,将用户信息注入 Model
  3. 资源清理:请求处理完毕后,清理线程局部变量
  4. 动态渲染:Thymeleaf 根据登录状态生成不同的 HTML 内容

这种设计模式使登录状态管理与业务逻辑解耦,提高了代码的可维护性和安全性。
 
Thymeleaf模板渲染 ├─ 解析${loginUser}表达式 └─ 根据登录状态生成HTML , 这里如何生成html,是什么意思,html不是本来就存在吗
 
已完成思考
 
 

Thymeleaf 动态渲染机制解析

您提到的 “生成 HTML” 并非指创建全新的 HTML 文件,而是指 Thymeleaf 将静态模板文件与动态数据结合,生成最终响应给浏览器的 HTML 内容。这一过程类似于 “填空” 或 “条件筛选”,下面详细解释:

一、Thymeleaf 的核心工作模式

1. 模板文件与最终 HTML 的区别

  • 模板文件(.html):存在于src/main/resources/templates/目录下,包含 Thymeleaf 特有的标签和表达式(如th:if${loginUser})。
  • 最终 HTML:服务器将模板文件中的表达式替换为实际数据后,返回给浏览器的标准 HTML。

2. 动态渲染示例

模板文件(register.html)中的片段:

html
 
 
<li class="nav-item" th:if="${loginUser==null}">
    <a class="nav-link" th:href="@{/login}">登录</a>
</li>
 

渲染后的最终 HTML(用户未登录时):

html
 
 
<li class="nav-item">
    <a class="nav-link" href="/login">登录</a>
</li>
 

渲染后的最终 HTML(用户已登录时):

html
 
 
<!-- th:if条件不满足,该li元素被移除 -->
 

二、${loginUser} 表达式的解析过程

1. 数据来源

  • loginUser变量由LoginTicketInterceptor.postHandle()方法添加到 Model 中:
    java
     
     
    modelAndView.addObject("loginUser", user);
    
     

2. 表达式解析步骤

  1. Thymeleaf 引擎读取模板文件中的${loginUser}表达式。
  2. 从 Model 中查找名为loginUser的属性。
  3. 根据属性值(null或具体 User 对象)决定如何渲染相关 HTML 元素。

3. 典型应用场景

  • 条件渲染:th:if="${loginUser!=null}"
  • 属性动态设置:th:src="${loginUser.headerUrl}"
  • 文本内容替换:th:text="${loginUser.username}"

三、HTML “生成” 的本质:动态组装

1. 标签的显示与隐藏

  • 模板中的th:ifth:unless等条件标签会根据数据决定是否保留对应的 HTML 元素。
  • 示例:
    html
     
     
    <!-- 模板中的条件标签 -->
    <div th:if="${success}">
        操作成功!
    </div>
    
    <!-- 若success=true,最终HTML包含该div -->
    <div>操作成功!</div>
    
    <!-- 若success=false,最终HTML中该div被移除 -->
    
     

2. 属性与内容的动态替换

  • 模板中的th:attrth:text等属性会将表达式替换为实际值。
  • 示例:
    html
     
     
    <!-- 模板中的表达式 -->
    <img th:src="${user.headerUrl}" alt="用户头像">
    
    <!-- 若user.headerUrl="http://xxx.png",最终HTML -->
    <img src="http://xxx.png" alt="用户头像">
    
     

3. 列表循环生成

  • 使用th:each遍历数据列表,动态生成多个 HTML 元素。
  • 示例:
    html
     
     
    <!-- 模板中的循环 -->
    <ul>
        <li th:each="post:${posts}" th:text="${post.title}"></li>
    </ul>
    
    <!-- 若posts有3条数据,最终HTML -->
    <ul>
        <li>帖子1标题</li>
        <li>帖子2标题</li>
        <li>帖子3标题</li>
    </ul>
    
     

四、模板渲染的完整流程

  1. 请求到达控制器:控制器处理业务逻辑,将数据存入 Model。
  2. Thymeleaf 加载模板:根据视图名称(如index)找到templates/index.html
  3. 表达式解析:
    • 从 Model 中获取数据(如loginUser)。
    • 计算表达式(如${loginUser!=null})。
  4. 动态组装 HTML:
    • 显示或隐藏条件元素。
    • 替换属性和文本内容。
    • 循环生成列表元素。
  5. 返回最终 HTML:将组装好的 HTML 响应给浏览器。

五、对比:静态 HTML 与 Thymeleaf 动态 HTML

1. 静态 HTML(无模板引擎)

  • 文件内容固定,无法根据用户状态变化。
  • 示例:无论谁访问,login.html始终显示登录表单。

2. Thymeleaf 动态 HTML

  • 同一个模板文件(如index.html)可根据用户状态生成不同内容:
    • 未登录用户看到 “登录 / 注册” 按钮。
    • 已登录用户看到头像和 “退出登录” 按钮。

六、核心总结

Thymeleaf 的 “生成 HTML” 本质是:

  1. 模板作为骨架:定义 HTML 结构和动态位置。
  2. 数据作为填充:从 Model 获取数据,填充到模板的表达式中。
  3. 条件作为筛选:根据数据决定哪些 HTML 元素显示、哪些隐藏。

这种 “动态组装” 机制使同一模板能根据不同数据生成不同的 HTML 内容,实现了 “一次开发,多场景适用” 的效果,这也是模板引擎的核心价值所在。
 
 
modelAndView里添加了user的attrribute, themyleaf就能使用了吗
 
在 Spring MVC 框架中,当使用 ModelAndView 向视图中添加用户属性(Attribute)后,Thymeleaf 能否直接使用该属性取决于以下几个关键条件。以下是详细解析:

一、ModelAndView 与 Thymeleaf 的数据传递机制

  1. ModelAndView 的作用
    ModelAndView 是 Spring MVC 中用于封装模型数据(Model)和视图信息(View)的对象。通过 addAttribute(String name, Object value) 方法添加的属性,会被存入 Model 中,最终传递给视图层。
  2. Thymeleaf 的数据访问方式
    Thymeleaf 作为视图模板引擎,会从 Spring MVC 传递的 Model 中获取数据。它通过表达式语法(如 th:textth:each)访问模型中的属性。

二、Thymeleaf 能直接使用属性的前提条件

1. 属性已正确添加到 ModelAndView 中

  • 示例代码:
    java
     
     
    ModelAndView mav = new ModelAndView("userView");
    User user = new User("张三", 25);
    mav.addAttribute("user", user);  // 属性名为 "user"
    return mav;
    
     
  • 关键:确保属性名(如 "user")与 Thymeleaf 中使用的名称一致。

2. Thymeleaf 模板正确引用属性

  • 在 Thymeleaf 中,通过 ${属性名} 或 *{} 语法访问属性:
    html
     
     
    <!-- 访问简单属性 -->
    <p th:text="${user.name}">用户名</p>
    
    <!-- 访问对象属性 -->
    <p th:text="${user.age}">年龄</p>
    
    <!-- 集合属性遍历 -->
    <ul>
      <li th:each="order : ${user.orders}" th:text="${order.product}">订单商品</li>
    </ul>
    
     

3. 视图解析器配置正确

  • Spring MVC 需配置 Thymeleaf 视图解析器,确保请求能正确映射到 Thymeleaf 模板:
    java
     
     
    // 配置示例(Java Config)
    @Bean
    public ViewResolver thymeleafViewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(thymeleafTemplateEngine());
        resolver.setCharacterEncoding("UTF-8");
        return resolver;
    }
    
     

三、常见问题与解决方案

1. 属性名拼写错误

  • 错误示例:
    java
     
     
    mav.addAttribute("userInfo", user);  // 属性名是 "userInfo"
    
     

    html
     
     
    <p th:text="${user.name}"> </p>  <!-- 错误引用 "user" -->
    
     
  • 解决方案:确保 addAttribute 的属性名与 Thymeleaf 中 ${属性名} 一致。

2. 作用域问题

  • Spring MVC 的 Model 仅在当前请求作用域有效。若使用 Session 或 Application 作用域,需手动存入:
    java
     
     
    // 存入 Session
    request.getSession().setAttribute("user", user);
    
     

    Thymeleaf 中访问:
    html
     
     
    <p th:text="${session.user.name}"> </p>
    
     

3. 类型转换异常

  • 若属性是复杂对象(如 ListMap),需确保 Thymeleaf 语法符合对象结构:
    html
     
     
    <!-- 正确遍历 List -->
    <li th:each="item : ${user.items}" th:text="${item}"> </li>
    
     

四、Thymeleaf 高级用法:对象绑定与选择表达式

  1. 对象绑定(th:object
    可通过 th:object 绑定模型属性,简化访问:
    html
     
     
    <div th:object="${user}">
        <p th:text="*{name}">用户名</p>  <!-- 等价于 ${user.name} -->
        <p th:text="*{age}">年龄</p>
    </div>
    
     
     
     
     
     
     
     
     
     
     
     
     
     
  2. 安全导航操作符(?
    防止空指针异常:
    html
     
     
    <p th:text="${user?.address?.city}">城市</p>  <!-- 若 user.address 为 null,表达式返回 null 而非报错 -->
    
     
     
     
     
     
     
     
     
     
     

总结:Yes,但需满足条件

  • 结论:当 ModelAndView 正确添加属性(如 addAttribute("user", user)),且 Thymeleaf 模板通过正确的属性名(如 ${user.name})访问时,可以直接使用该属性。
  • 核心要点:
    1. 属性名一致;
    2. 模板语法正确;
    3. 视图解析器配置无误。
  • 延伸:若需更复杂的逻辑(如条件判断、循环),可结合 Thymeleaf 的 th:ifth:switch 等指令实现。
posted @ 2025-07-06 11:31  我爱麻辣香锅  阅读(13)  评论(0)    收藏  举报