java-springmvc4+freemarker-实现layout布局页
使用 jsp 开发业务系统时,一般一个页面的js脚本针对当前页面的逻辑比较复杂无法公用,js 又常常更新。
客户端由于缓存问题,浏览器会缓存js到本地,不去下载最新的 js 文件(除非引用js时加版本号,如:order.js?v=1.0)
为了解决以上开发问题,可以将 js 写到 jsp 页面的下方,这样就可以每次加载页面时下载最新的 js 了。
但是,在开发时就不方便了。开发时经常需要看“html 结构”还要看“js 逻辑”,这时需要上下不停拖动jsp编辑器的滚动条。
为了解决上述问题:可以考虑在开发时,使 jsp 和 js 页面分离,再响应时将js文件合并到 jsp 中进行输出。
这样既保证了开发的时效和开发体验,也兼顾了客户端得不到最新js导致的各种问题。
下面,利用 maven + springmvc + freemarker 实现布局页,开发时将一个页面分割成三部分,使用时组合成一个页面输出:
1.layout 部分:只定义布局
2.js 部分:只定义当前页面使用的 js 脚本
3.body 部分:只定义页面中 body 标签内容的内容
约定:三部分必须有相同的前缀,如 order 页面,分为三部分时个文件的命名为:
1.layout 部分:orderLayout.ftl
2.js 部分:orderJs.js
3.body 部分:orderViewer.ftl
目标:实现两种 jsp 响应方式;
1.jsp(模板+数据)在请求时同时输出
2.只输出页面和js,数据使用 ajax 请求
前置:已经搭建好 springmvc + freemarker 项目,项目命名为 mavens-web
1.目录结构
1.1 后台代码目录
-demo
.+-mvc
..+-base
...|-PageController.java
..+-controllers
...|-HelloController.java
1.2 前端代码目录
-webapp
.+-views
..+-hello //对应 controller 名
...+-js
....|-indexJs.js
....|-masterJs.js
...+-layout
....|-indexLayout.ftl
....|-masterLayout.ftl
...+-pub
....|-indexViewer.ftl
....|-masterViewer.ftl
2.controller 代码
2.1 控制器基类 PageController.java
2.1 hello 控制器 HelloController.java
3. js 文件
3.1 webapp/views/hello/js/indexJs.js
3.2 webapp/views/hello/js/masterJs.js
4. layout 布局文件
4.1 webapp/views/hello/layout/indexLayout.ftl
4.2 webapp/views/hello/layout/masterLayout.ftl
5. 页面 body 部分
5.1 webapp/views/hello/pub/indexViewer.ftl
5.2 views/hello/pub/masterViewer.ftl
6.部署到 tomcat 测试页面
6.1 jsp(模板+数据)在请求时同时输出
http://localhost:8080/mavens-web/hello/master
6.2 只输出页面和js,数据使用 ajax 请求(这种方式可以手动在tomcat添加页面,然后修改与之对应的前缀名即可访问到页面,这里index 即是前缀名)
使用 jsp 开发业务系统时,一般一个页面的js脚本针对当前页面的逻辑比较复杂无法公用,js 又常常更新。
客户端由于缓存问题,浏览器会缓存js到本地,不去下载最新的 js 文件(除非引用js时加版本号,如:order.js?v=1.0)
为了解决以上开发问题,可以将 js 写到 jsp 页面的下方,这样就可以每次加载页面时下载最新的 js 了。
但是,在开发时就不方便了。开发时经常需要看“html 结构”还要看“js 逻辑”,这时需要上下不停拖动jsp编辑器的滚动条。
为了解决上述问题:可以考虑在开发时,使 jsp 和 js 页面分离,再响应时将js文件合并到 jsp 中进行输出。
这样既保证了开发的时效和开发体验,也兼顾了客户端得不到最新js导致的各种问题。
下面,利用 maven + springmvc + freemarker 实现布局页,开发时将一个页面分割成三部分,使用时组合成一个页面输出:
1.layout 部分:只定义布局
2.js 部分:只定义当前页面使用的 js 脚本
3.body 部分:只定义页面中 body 标签内容的内容
约定:三部分必须有相同的前缀,如 order 页面,分为三部分时个文件的命名为:
1.layout 部分:orderLayout.ftl
2.js 部分:orderJs.js
3.body 部分:orderViewer.ftl
目标:实现两种 jsp 响应方式;
1.jsp(模板+数据)在请求时同时输出
2.只输出页面和js,数据使用 ajax 请求
前置:已经搭建好 springmvc + freemarker 项目,项目命名为 mavens-web
1.目录结构
1.1 后台代码目录
-demo
.+-mvc
..+-base
...|-PageController.java
..+-controllers
...|-HelloController.java
1.2 前端代码目录
-webapp
.+-views
..+-hello //对应 controller 名
...+-js
....|-indexJs.js
....|-masterJs.js
...+-layout
....|-indexLayout.ftl
....|-masterLayout.ftl
...+-pub
....|-indexViewer.ftl
....|-masterViewer.ftl
2.controller 代码
2.1 控制器基类 PageController.java
package demo.mvc.base;
import java.io.File;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import freemarker.cache.*;
/**
*
*/
public abstract class PageController {
/**
* 控制器名称,不包含“Controller”后缀
* @param controllerName
*/
public PageController(String controllerName){
ctrlName= controllerName.toLowerCase();
}
String ctrlName="";
@Autowired
org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer _fmc;
//static freemarker.cache.FileTemplateLoader _ftl = null;
static String _templateLoaderPath=null;
/**
* 初始化 freemarker 模板地址
*
*/
boolean initTemplateLoaderPath() {
if(_templateLoaderPath!=null)
return true;
gsPrintln("_fmc = {0}", _fmc==null?"null":_fmc.toString());
//布局模板是否存在
if(_fmc!=null){
MultiTemplateLoader utll = (MultiTemplateLoader)_fmc
.getConfiguration()
.getTemplateLoader();
int n=utll.getTemplateLoaderCount();
gsPrintln("TemplateLoaderCount="+n);
TemplateLoader tl = null;
File file = null;
for(int i=0;i<n;i++) {
tl= utll.getTemplateLoader(i);
gsPrintln(tl.toString());
//获取模板加载目录地址
if(tl instanceof freemarker.cache.FileTemplateLoader){
_templateLoaderPath = ((freemarker.cache.FileTemplateLoader)tl)
.getBaseDirectory()
.getAbsolutePath();
}
}
}
gsPrintln("freemark 模板地址:{0}", _templateLoaderPath);
return _templateLoaderPath!=null;
}
/**
* 判断模板是否存在
* @param filePath
* @return
*/
boolean IsExistsTemplate(String filePath){
initTemplateLoaderPath();
File file = new File(gsFormat("{0}/{1}", _templateLoaderPath,filePath));
boolean exist = file.exists();
gsPrintln("viewer[{1}]:{0}",file,exist);
return exist;
}
/**
* 公共视图解析器:controllerName/viewer/{file}/{prefix}
* @param prefix
* @param pars
* @return
*/
@RequestMapping("viewer/{prefix}")
public String viewer(@PathVariable String prefix
,Map<String, Object> pars){
return freeMarkerViewResult(prefix, pars);//布局模板
}
/**
* freemarker 布局视图解析
* @param prefix
* @param pars
* @return
*/
public String freeMarkerViewResult(String prefix
,Map<String, Object> pars){
//布局模板
String layout = gsFormat("{0}/layout/{1}Layout.ftl", ctrlName,prefix);
//js内容
String js = gsFormat("{0}/js/{1}Js.js", ctrlName,prefix);
if(!IsExistsTemplate(js)){
js="";//不存在时,地址赋值为空
}
//页模板
String body = gsFormat("{0}/pub/{1}Viewer.ftl", ctrlName,prefix);
//页面参数
pars.put("body_file_path", body);
pars.put("js_file_path", js);
//优先使用布局页
if(IsExistsTemplate(layout)){
return layout.replaceAll("\\.ftl$", ""); //模板不需要后缀
}
return body.replaceAll("\\.ftl$", "");//模板不需要后缀
}
public static void gsPrintln(String msg, Object... args){
System.out.println(gsFormat(msg,args));
}
/**
* 字符串内容格式化输出,内部使用{0}\{1}\{2}...为参数占位符
* @param msg 格式化模板
* @param args 不固定参数
* @return
*/
public static String gsFormat(String msg, Object... args)
{
return java.text.MessageFormat.format(msg, args);
}
}
2.1 hello 控制器 HelloController.java
package demo.mvc.controllers;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.mail.MailParseException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import demo.models.hello.UserInfoModel;
import demo.mvc.base.PageController;
/**
*
*/
@Controller
@RequestMapping("/hello")
public class HelloController extends PageController {
public HelloController(){
super("Hello"); //将当前 controller 名传入,与 viewer 中的 hello 文件夹名对应
}
/**
* 请求地址:项目名/hello/master
* @return
*/
@RequestMapping("master")
public String freemarkerLayout(Map<String, Object> map){
map.put("name", "张三");
map.put("date", new Date());
return this.freeMarkerViewResult("master", map);
}
}
3. js 文件
3.1 webapp/views/hello/js/indexJs.js
<script language="JavaScript">
function init(){
document.write("<div>这个是 indexJs.ftl 内容</div>");
}
init();
</script>
3.2 webapp/views/hello/js/masterJs.js
<script language="JavaScript">
function init(){
document.write("<div>这个是 masterJs.ftl 内容</div>");
}
init();
</script>
4. layout 布局文件
4.1 webapp/views/hello/layout/indexLayout.ftl
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="Author" content="">
<meta name="Keywords" content="">
<meta name="Description" content="">
<title>index 布局页</title>
</head>
<body>
<#--body内容-->
<#include "${body_file_path}">
</body>
</html>
<#--js内容,不进行 ftl 解析;include 地址为空时,不抛出异常-->
<#include "${js_file_path}" parse=false ignore_missing=true>
4.2 webapp/views/hello/layout/masterLayout.ftl
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="Author" content="">
<meta name="Keywords" content="">
<meta name="Description" content="">
<title>index 布局页</title>
</head>
<body>
<#--body内容-->
<#include "${body_file_path}">
</body>
</html>
<#--js内容,不进行 ftl 解析;include 地址为空时,不抛出异常-->
<#include "${js_file_path}" parse=false ignore_missing=true>
5. 页面 body 部分
5.1 webapp/views/hello/pub/indexViewer.ftl
<div>
这个是 indexViewer.ftl 页面内容
</div>
<#--模板+数据-->
<div>
</div>
5.2 views/hello/pub/masterViewer.ftl
<div>
这个是 indexViewer.ftl 页面内容
</div>
<#--模板+数据-->
<div>
</div>
6.部署到 tomcat 测试页面
6.1 jsp(模板+数据)在请求时同时输出
http://localhost:8080/mavens-web/hello/master
6.2 只输出页面和js,数据使用 ajax 请求(这种方式可以手动在tomcat添加页面,然后修改与之对应的前缀名即可访问到页面,这里index 即是前缀名)
http://localhost:8080/mavens-web/hello/viewer/index