Intecepter分析
请求处理与渲染的完整生命周期
当一个 HTTP 请求进入系统后,
LoginTicketInterceptor和LoginRequiredInterceptor的各个方法会按照特定顺序执行,最终完成页面渲染。以下是详细的执行流程:一、拦截器方法执行顺序
1. 请求进入时(按注册顺序执行 preHandle)
java
// WebMvcConfig.java 中注册拦截器的顺序
registry.addInterceptor(loginTicketInterceptor) // 第一个拦截器
.excludePathPatterns("/**/*.css", "/**/*.js");
registry.addInterceptor(loginRequiredInterceptor) // 第二个拦截器
.excludePathPatterns("/**/*.css", "/**/*.js");
执行顺序:
-
LoginTicketInterceptor.preHandle()- 从 Cookie 中获取 ticket
- 验证 ticket 有效性
- 将用户信息存入
HostHolder(ThreadLocal)
-
LoginRequiredInterceptor.preHandle()- 检查请求的方法是否有
@LoginRequired注解 - 如果有注解且用户未登录,则重定向到登录页
- 检查请求的方法是否有
2. 控制器处理请求
- 如果两个拦截器的
preHandle都返回true,请求到达目标控制器方法 - 控制器处理业务逻辑,返回
ModelAndView或视图名称
3. 视图渲染前(按注册顺序的逆序执行 postHandle)
-
LoginRequiredInterceptor.postHandle()- (此拦截器未实现
postHandle,无操作)
- (此拦截器未实现
-
LoginTicketInterceptor.postHandle()- 从
HostHolder获取用户信息 - 将用户信息添加到
ModelAndView中(model.addAttribute("loginUser", user))
- 从
4. 请求完成后(按注册顺序的逆序执行 afterCompletion)
-
LoginRequiredInterceptor.afterCompletion()- (此拦截器未实现
afterCompletion,无操作)
- (此拦截器未实现
-
LoginTicketInterceptor.afterCompletion()- 清理
HostHolder中的用户信息(防止内存泄漏)
- 清理
二、数据流转与渲染流程
1. 数据流转路径
plaintext
Cookie(ticket) → LoginTicketInterceptor.preHandle() →
HostHolder(ThreadLocal) → LoginRequiredInterceptor.preHandle() →
Controller → LoginTicketInterceptor.postHandle() →
Model(loginUser) → Thymeleaf模板 → 浏览器渲染
2. 关键渲染步骤
-
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>
- 模板中的
-
动态生成 HTML
- 根据
loginUser是否存在,决定显示哪些元素 - 例如:已登录用户看到头像和下拉菜单,未登录用户看到登录 / 注册按钮
- 根据
-
最终响应
- 渲染完成的 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. 未登录用户访问需要登录的页面
LoginRequiredInterceptor.preHandle()检测到@LoginRequired注解- 从
HostHolder获取用户为null - 执行
response.sendRedirect("/login") - 返回
false终止请求流程 - 直接跳转到登录页,后续拦截器和控制器不再执行
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>
总结
这两个拦截器通过分工协作,实现了:
- 前置验证:在请求到达控制器前,验证登录状态
- 数据注入:在视图渲染前,将用户信息注入 Model
- 资源清理:请求处理完毕后,清理线程局部变量
- 动态渲染: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 中:javamodelAndView.addObject("loginUser", user);
2. 表达式解析步骤
- Thymeleaf 引擎读取模板文件中的
${loginUser}表达式。 - 从 Model 中查找名为
loginUser的属性。 - 根据属性值(
null或具体 User 对象)决定如何渲染相关 HTML 元素。
3. 典型应用场景
- 条件渲染:
th:if="${loginUser!=null}" - 属性动态设置:
th:src="${loginUser.headerUrl}" - 文本内容替换:
th:text="${loginUser.username}"
三、HTML “生成” 的本质:动态组装
1. 标签的显示与隐藏
- 模板中的
th:if、th:unless等条件标签会根据数据决定是否保留对应的 HTML 元素。 - 示例:
html
<!-- 模板中的条件标签 --> <div th:if="${success}"> 操作成功! </div> <!-- 若success=true,最终HTML包含该div --> <div>操作成功!</div> <!-- 若success=false,最终HTML中该div被移除 -->
2. 属性与内容的动态替换
- 模板中的
th:attr、th: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>
四、模板渲染的完整流程
- 请求到达控制器:控制器处理业务逻辑,将数据存入 Model。
- Thymeleaf 加载模板:根据视图名称(如
index)找到templates/index.html。 - 表达式解析:
- 从 Model 中获取数据(如
loginUser)。 - 计算表达式(如
${loginUser!=null})。
- 从 Model 中获取数据(如
- 动态组装 HTML:
- 显示或隐藏条件元素。
- 替换属性和文本内容。
- 循环生成列表元素。
- 返回最终 HTML:将组装好的 HTML 响应给浏览器。
五、对比:静态 HTML 与 Thymeleaf 动态 HTML
1. 静态 HTML(无模板引擎)
- 文件内容固定,无法根据用户状态变化。
- 示例:无论谁访问,
login.html始终显示登录表单。
2. Thymeleaf 动态 HTML
- 同一个模板文件(如
index.html)可根据用户状态生成不同内容:- 未登录用户看到 “登录 / 注册” 按钮。
- 已登录用户看到头像和 “退出登录” 按钮。
六、核心总结
Thymeleaf 的 “生成 HTML” 本质是:
- 模板作为骨架:定义 HTML 结构和动态位置。
- 数据作为填充:从 Model 获取数据,填充到模板的表达式中。
- 条件作为筛选:根据数据决定哪些 HTML 元素显示、哪些隐藏。
这种 “动态组装” 机制使同一模板能根据不同数据生成不同的 HTML 内容,实现了 “一次开发,多场景适用” 的效果,这也是模板引擎的核心价值所在。
modelAndView里添加了user的attrribute, themyleaf就能使用了吗
在 Spring MVC 框架中,当使用
ModelAndView 向视图中添加用户属性(Attribute)后,Thymeleaf 能否直接使用该属性取决于以下几个关键条件。以下是详细解析:一、ModelAndView 与 Thymeleaf 的数据传递机制
-
ModelAndView 的作用
ModelAndView是 Spring MVC 中用于封装模型数据(Model)和视图信息(View)的对象。通过addAttribute(String name, Object value)方法添加的属性,会被存入Model中,最终传递给视图层。 -
Thymeleaf 的数据访问方式
Thymeleaf 作为视图模板引擎,会从 Spring MVC 传递的Model中获取数据。它通过表达式语法(如th:text、th: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. 类型转换异常
- 若属性是复杂对象(如
List、Map),需确保 Thymeleaf 语法符合对象结构:html<!-- 正确遍历 List --> <li th:each="item : ${user.items}" th:text="${item}"> </li>
四、Thymeleaf 高级用法:对象绑定与选择表达式
-
对象绑定(
th:object)
可通过th:object绑定模型属性,简化访问:html<div th:object="${user}"> <p th:text="*{name}">用户名</p> <!-- 等价于 ${user.name} --> <p th:text="*{age}">年龄</p> </div> -
安全导航操作符(
?)
防止空指针异常:html<p th:text="${user?.address?.city}">城市</p> <!-- 若 user.address 为 null,表达式返回 null 而非报错 -->
总结:Yes,但需满足条件
- 结论:当
ModelAndView正确添加属性(如addAttribute("user", user)),且 Thymeleaf 模板通过正确的属性名(如${user.name})访问时,可以直接使用该属性。 - 核心要点:
- 属性名一致;
- 模板语法正确;
- 视图解析器配置无误。
- 延伸:若需更复杂的逻辑(如条件判断、循环),可结合 Thymeleaf 的
th:if、th:switch等指令实现。
浙公网安备 33010602011771号