如何直接从Thymeleaf模板访问Spring单例Bean?
我正在学习Spring,遇到了一个困惑。我定义了一个单例Bean:
@Component public class PuzzleUtil { @Bean @Scope("singleton") public PuzzleUtil puzzleUtil() { return new PuzzleUtil(); } public ArrayList<String> getCategories() { // ... } }
在Web应用类中,加载index页面时会将categories设置为Model属性:
@Controller @SpringBootApplication public class MathPuzzlesWebApplication extends SpringBootServletInitializer { @Autowired private PuzzleUtil puzzleUtil; @GetMapping("/index.html") public String index(Model model) { model.addAttribute("categories", puzzleUtil.getCategories()); return "index"; } // ... }
现在可以在index.html模板中访问categories数组:
<table> <th:block th:each="category: ${categories}"> <tr> <td th:text="${category}"></td> </tr> </th:block> </table>
一切运行正常,但我觉得在Controller中将属性添加到Model是多余的步骤,因为该Bean是单例,应该可以直接从模板访问它。尝试了以下写法:
<th:block th:each="category: ${puzzleUtil.categories}">
但出现错误:
EL1007E: Property or field 'categories' cannot be found on null
请问我是否遗漏了什么,或者我的需求本身不可行?
1. 先修正Bean定义的冗余问题
你当前的PuzzleUtil类同时使用@Component和内部的@Bean方法,会导致Spring生成两个独立的PuzzleUtil实例,这是冗余且易出错的,必须二选一:
- 保留
@Component,删除内部@Bean方法:
@Component public class PuzzleUtil { public ArrayList<String> getCategories() { // ... } }
- 移除
@Component,将@Bean方法放到专门的配置类中:
@Configuration public class AppConfig { @Bean @Scope("singleton") // singleton是默认作用域,可省略 public PuzzleUtil puzzleUtil() { return new PuzzleUtil(); } } public class PuzzleUtil { public ArrayList<String> getCategories() { // ... } }
2. 实现模板直接访问Spring Bean的两种方式
Thymeleaf不会自动把所有Spring Bean暴露到模板上下文,需通过以下方式实现:
方式一:全局注入到模板上下文
创建@ControllerAdvice类,将PuzzleUtil添加到所有模板的全局模型中:
@ControllerAdvice public class GlobalControllerAdvice { private final PuzzleUtil puzzleUtil; public GlobalControllerAdvice(PuzzleUtil puzzleUtil) { this.puzzleUtil = puzzleUtil; } @ModelAttribute("puzzleUtil") public PuzzleUtil getPuzzleUtil() { return puzzleUtil; } }
之后所有模板都能直接通过${puzzleUtil}访问Bean,调用方法:
<th:block th:each="category: ${puzzleUtil.categories}">
注:Thymeleaf会自动根据属性名categories匹配对应的getCategories() getter方法,写法合法。
方式二:使用Thymeleaf Spring Bean表达式
Thymeleaf提供专属语法直接访问Spring容器中的Bean,无需额外配置,写法为@beanName:
<th:block th:each="category: ${@puzzleUtil.getCategories()}">
这里的puzzleUtil是Bean的默认名称(类名首字母小写),直接调用方法即可。
3. 错误原因解析
你之前遇到的EL1007E错误,本质是${puzzleUtil}在模板上下文中为null——Spring并没有把该Bean暴露为模板变量,自然找不到对应的属性。
内容的提问来源于stack exchange,提问作者Neil

