Grails 3中如何基于SiteMesh实现多终端动态布局选择
为什么你之前写的动态meta标签不生效
Grails2时代的SiteMesh实现,会等待完整GSP内容渲染完成后,扫描页面头部的<meta name="layout">标签值来匹配对应装饰器,这套逻辑在Grails3开始已经被替换为SiteMesh3的实现了:装饰器的匹配决策会前置到Spring MVC拦截器链的执行阶段,在GSP正式渲染前就完成选定,根本不会等待页面内容生成后再回头扫描你写的meta标签。
你在页面里写的<meta name="layout" content="${defineLayout()}"/>存在两个绕不开的时序问题:
- GSP里的EL表达式是在页面渲染阶段才会被替换为实际值,等meta标签拼出正确的layout名称时,装饰器早在拦截器阶段就已经定死了,完全赶不上匹配时机
- 新版SiteMesh默认没有开启渲染后扫meta的逻辑,就算meta能正常拼出来,也没有组件会去读取这个标签的内容
可直接落地的实现方案
方案1:Controller层渲染时直接指定布局(改动最小,最稳妥)
Grails3的render方法原生支持传入layout参数,这个参数会在GSP渲染前置阶段直接绑定对应装饰器,完全不依赖页面meta扫描的流程。你只需要在每个需要适配的action里,提前判断请求来源的设备类型,直接把布局名传给render方法即可:
def showPage() { // 基于UA简单判断设备类型,可替换为更成熟的设备识别工具 def ua = request.getHeader("User-Agent") ?: "" def isMobile = ua =~ /Mobile|Android|iPhone|iPad/i render view: "target-view", model: [pageData: xxx], layout: isMobile ? "mobile-main" : "pc-main" }
对应的布局文件只需要按照Grails的默认路径放在grails-app/views/layouts/下即可,比如mobile-main.gsp、pc-main.gsp,插件会自动识别路径。
方案2:自定义全局装饰器匹配器(无需逐页面改Controller)
如果不想每个Controller action都重复写设备判断的逻辑,可以直接覆盖SiteMesh默认的装饰器选择器,把设备判断的逻辑内置到全局拦截流程里:
- 在
grails-app/conf/spring/resources.groovy中注册自定义的DecoratorSelector实现,替代默认的基于静态路径匹配的选择逻辑 - 自定义选择器的核心逻辑就是:拿到当前请求后先判断设备类型,根据判断结果返回对应布局的装饰器路径
- 这套方式是SiteMesh原生支持的扩展点,没有额外的性能损耗,一次配置所有页面的布局匹配逻辑都能统一生效
方案3:重新开启页面meta标签解析逻辑(不推荐)
如果你已经在业务页面里写了大量动态判断布局的GSP方法,不想改动现有代码,也可以通过调整SiteMesh的过滤器执行顺序,把meta解析的过滤器链路放到GSP渲染之后、装饰器应用之前。但这种方式需要把整个渲染完成的页面内容读入内存做正则匹配找meta标签,相比前两个方案性能损耗较高,仅适合低流量的内部系统使用。
内容的提问来源于stack exchange,提问作者demon101

