实际开发中,jsp文件放在WEB-INF目录下,因为这个目录下的内容不能直接通过request请求得到,更加安全。
演示:直接请求webapp下.jsp文件
另外我们也喜欢把所有的.jsp文件放在统一的views文件夹下。
这样,我们的ModelAndView()中的路径就显得更加累赘了……可以在springmvc-servlet.xml进行配置:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean>
InternalResourceViewResolver是一个类,SpringMVC默认使用这个类在解析(resolve).jsp视图(view)。
我们这里为添加了两个自定义规则,修正ModelAndView()中的参数:
所以我们所有的handler method可以这样返回:
大家觉得JSP的语法怎么样?事实上,更多的时候我们更愿意使用一些更强大更优雅的“模板”来代替.jsp,比如freemarker。
freemarker自己的包:
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> <!-- 最新版 2.3.31 --> </dependency>
以及,嵌入SpringMVC需要的:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.0.2.RELEASE</version> </dependency>
也可以使用其他后缀名,但
最后,告诉SpringMVC使用FreeMarkerViewResolver解析
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="suffix" value=".ftl" /> </bean>
演示:FreeMarkerViewResolver实现
#体会:多态的作用,可插拔式设计(包括JDBC、Hibernate……)
FreeMarkerViewResolver不要加前缀,而是要在FreeMarkerConfigurer通过配置templateLoaderPath指定ftl模板所在的位置:
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/views" /> </bean>上述配置都显式指定了编码格式,使用utf-8解决中文乱码的问题。
相对来说,FreeMarker比JSP更为复杂,因为它几乎创建了一个专门的语法:FTL (FreeMarker Template Language)
Tips:
<#assign name="飞哥">
也可以一次声明多个:
<#assign name="fg" age=23>
对同一变量的再次赋值会覆盖/修改之前的值。(演示:${name})
注意这里已经没有类型声明了,但FTL还是有类型的(类似于弱类型语言仍然有类型),包括:
<#assign weight = 76.5> <#assign age = 23> <#assign credit = -100>
<#assign isMale = true>
<#assign enroll = .now>
<#assign students=["atai", "bo", "lang"]>
<#assign numbers=[1, 2, 3, 4, 5]> <#--可简写成:--> <#assign numbers=1..5>
<#assign students={"atai":"阿泰", "bo":"波仔", "lang":"浪神"}>
字符串、数字和日期都可以直接表达:
姓名:${name} <br /> 体重:${weight} 年龄:${age} 积分: ${credit} <br /> 报到时间:${enroll}时间可以格式化:
报到日期:${enroll?string("yyyy年MM月dd日")} <br /> 报到时间:${enroll?string("HH点mm分ss秒")} <br />
但bool值不行(一般也不会直接输出)
性别:${isMale?string("男","女")} <br />确实要输出true和false,用:
性别:${isMale?string} 或者: <br /> 性别:${isMale?c}
${numbers[0]}循环取值,使用关键字 list 和 as:
<#list numbers as n> ${n}- </#list>list中如果要取元素下标,直接在元素后加后缀_index,
当前下标:${n_index}还可以用下标(从0开始)进行截取
<#list numbers[2..4] as n>
这个语法用于截取字符串非常方便:(由此可见,FTL把字符串当成了集合)
<#assign greet="一起帮·源栈欢迎你"> ${greet[0..5]}
按键(key)取单个元素的值:
${students["atai"]}
遍历要首先经过转换
<#list students?keys[1..2] as n> 或者 <#list students?values[1..2] as n>
3 + 2 = ${3 + 2} <br /> 3 + 2.0 = ${3 + 2.0} <br /> 3 / 2 = ${3 / 2} <br /> 3 % 2 = ${3 % 2} <br /> 3 % 2.0 = ${3 % 2.0} <br /> 3 % 1.5 = ${3 % 1.5} <br /> 3 % 1.1 = ${3 % 1.1} <br />区别:没有整数除整数结果仍为整数的要求,包含小数之后取余有点墨幻
因为<和>容易和HTML标签混淆,所以TPL引入了以下别名:
可以直接比较时间,好评!
${(.now>.now)?c} ${(.now==.now)?c} ${(.now<.now)?c} ${(.now!=.now)?c}不同类型直接的数据不能比较(比JavaScript好)
和Java完全一致:或(||)且(&&)取反(!)
<#-- <#assign name="飞哥"> --> <#-- ${name} --> <#-- <input type="text"> -->居然没有快捷键,o(╥﹏╥)o
<#if .now gt .now> <p>.now gt .now</p> <#else> <p>.now NOT gt .now</p> </#if>一样可以嵌套:
<#if .now gt .now> <#if false> <p>.now gt .now</p> </#if> <#else>但是else if中间没有空格要连起来写:
<#elseif 3 lt 2>
FTL里面没有关键字null,所以这样的写法是会报错的:
<#if name==null>
要判断一个变量是不是为null(unassigned/undefined),要使用双问号(??)
<#if name??> <h2>${name}</h2> <#else> <h2>匿名</h2> </#if>
还可以直接给null值变量
${name!"匿名用户"} ${name!0} ${name!""}
FTL中使用函数有两个特点(和Java的不同点):
准备一个字符串:
<#assign greet = "源栈欢迎你,atai!">统计:
${greet?length} <br />
检索:
${greet?contains("atai")?c} <br />
${greet?starts_with("源栈")?c} <br />
${greet?ends_with()("!")?c} <br />
${greet?index_of("你")} <br />如果没有找到子串,则返回-1。
${greet?index_of("你", 5)} <br />
更改:
${greet?substring(0,3)} <br />注意和[..]的区别:
${greet[0..2]} <br />
${greet?upper_case} <br />
${greet?lower_case} <br />
${greet?html} <br />
${greet?replace("aTai", "阿泰")} <br />
${greet?split(",")[0]} <br /> ${greet?split(",")[1]} <br />
${greet?trim} <br />
类型转换
<#assign n = "23"> ${n?number+1} <br />
<#assign now="2021-11-26 10:29:00"> ${now?time("HH-mm")} <br /> ${now?date} <br /> ${now?datetime} <br />
取绝对值abs
<#assign score=-98.5> ${score?abs}
取整
<#assign score=98.5> 向下:${score?ceiling} <br /> 向上:${score?floor} <br /> 四舍五入:${score?round} <br />
取小数位数
<#assign score=98.576>
${score?string(".00")}
${score?string(".##")}
时间除了前文已述的string(),还可以截取日期(date)和时间(time):
<#assign now=.now> ${now?date} <br /> ${now?time}布尔值string(ifTrue,ifFalse)前文已述
序列(sequence)集合可以:
<#assign sequence=[8,2,3,10,5]> ${sequence?first} <br /> ${sequence?last} <br /> ${sequence?size} <br /> <hr /> ${sequence?reverse?first} <br /> ${sequence?sort?first} ${sequence?sort?last}
哈希(hash)表有所不同:
<#list students as k, v> ${k}: ${v} <br /> </#list>
或者获取keys或values后再进行迭代
<#list students?keys as k> ${k}<br /> </#list>
<#list students?values as v> ${v}<br /> </#list>
声明:关键字function,方法名Add,参数a和b,
<#function Add a b> <#assign sum = a+b> <#return sum /> </#function>
参数可以使用空格分隔,也可以使用圆括号和逗号:
<#function Add(a,b)>
函数体中
调用:使用圆括号而不是$
${Add(3,5)}
尽量把逻辑封装在Controller中,而不是View里面
和函数相对应,宏(macro)是能输出但不能返回的代码块:
<#macro greet name shown=true> <h3>源栈欢迎你 ${name}</h3> <#if shown> <#nested> </#if> </#macro>
以上是宏的声明,包括
已声明的宏,可以按以下方式调用:
<@greet name="新同学" shown=false> <p>大神小班,拎包入住</p> </@greet>
说明:
#常见面试题:宏和函数的区别?#
两者都可以引入其他.ftl模板页:
<#include "shared/_header.ftl"> <#import "/shared/_footer.ftl" as footer>说明:
但是,两者的区别是很明显的。
演示当被导入模板中只有HTML内容时:
将被导入模板内容更换为宏:
<#macro content>
<#include "shared/_header.ftl"> <@content /> <#import "/shared/_footer.ftl" as footer> <@footer.content />
注意在import时,就需要使用footer做前缀,这在freemarker中被称之为名称空间,作用是避免被引用的模板中同名变量的冲突。
演示:同时include _header.ftl 和 _footer.ftl……
定义一个_layout.flt作为布局页,其中一个宏,宏里面有一个放置header、footer和nested
<#macro body> <#include "_header.ftl"> <hr /> <#nested> <hr /> <#include "_footer.ftl"> </#macro>
在内容页首先引入_layout,然后使用宏,嵌入正文内容
<#include "/shared/_layout.ftl"> <@body> <h1>注册</h1> <form method="post"> <input type="text"> <button type="submit">提交</button> </form> </@>
PS:嵌入(nested)的内容可能遇到的编码问题。
原因:嵌入的.ftl文件用的编码格式和HTML指示不一致
解决办法:windows - preferences - General - Text file encoding:改为UTF-8
实际开发中,比如_header中的导航栏,它是可以(根据用户是否登录、有无权限等)变化的,而这些信息都是内容页(通过controller/model等)才有的,所以还要在内容页和布局页之间共享数据。
<#assign hasLogin=false>
<#if hasLogin> <span>源栈欢迎你</span> <#else> <a>登录</a> | <a>注册</a> </#if>
注意声明的位置,一定要在include之前。@想一想@:为什么?
如果不是include而是import的话,就需要通过宏的参数进行传递。
<#import "_header.ftl" as header> <@header.content hasLogin=logined />
<#macro content hasLogin> <p>我是header</p> <#if hasLogin>@想一想@:这样做好不好?
<#attempt> <@sidebar /> <#recover> </#attempt>即:如果有sidebar这个宏,我们就调用;否则就算了,不会报错!
实际开发中,布局还可以是嵌套的。比如还可以在_header、_footer以外的正文中加一个_sidebar,怎么办?
首先要引入一个_layoutWithSidebar.ftl,
<#macro layoutWithSidebar > <#include "/shared/_layout.ftl"> <@body> <div style="width:60%; float:left;"> <#nested> </div> <div style="width:40%; float:right;"> <#include sidebarPath> </div> </@> </#macro>注意这个_layoutWithSidebar.ftl:
<#include "/shared/_layoutWithSidebar.ftl"> <#assign logined=false, sidebarPath="/sidebar.ftl"> <@layoutWithSidebar > <h1>注册</h1>
仍然建议path总是使用绝对路径,否则这里你会懵……
多快好省!前端后端,线上线下,名师精讲
更多了解 加: