SSH2框架对实体层和表现层进行了很好的封装,并通过Spring注入的方式解决了各层之间的耦合,本文章对该框架部分实现进行代码整合,提高复用率。
首先我们了解一下SSH2框架的基本流程:
常见的代码整合部分如下图:
我们按照开发从前台到后台的顺序进行分析:
【1.jsp页面整合】
最常见的应用是公共引用和组件部分进行抽取,jsp提供了一种.jspf格式的公共抽取文件,在jsp页面引用该文件时添加:
<%@ includefile="/WEB-INF/jsp/public/commons.jspf"%>
例如分页组件的提取:pageView.jspf
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<div id=PageSelectorBar>
<div id=PageSelectorMemo>
页次:${currentPage}/${pageCount}页
每页显示:${pageSize}条
总记录数:${recordCount}条
</div>
<div id=PageSelectorSelectorArea>
<a href="javascript:gotoPage{1}" title="首页" style="cursor: hand;">
<img src="${pageContext.request.contextPath}/style/blue/images/pageSelector/firstPage.png"/>
</a>
<s:iterator begin="%{beginPageIndex}" end="%{endPageIndex}" var="num">
<s:if test="#num==currentPage">
<!-- 当前页 -->
<span class="PageSelectorNum PageSelectorSelected">${num}</span>
</s:if>
<s:else>
<!-- 非当前页 -->
<span class="PageSelectorNum" style="cursor: hand;" onClick="gotoPage(${num});">${num }</span>
</s:else>
</s:iterator>
<a href="javascript:gotoPage(${pageCount})" title="尾页" style="cursor: hand;">
<img src="${pageContext.request.contextPath}/style/blue/images/pageSelector/lastPage.png"/>
</a>
转到:
<select οnchange="gotoPage(this.value)" id="_pn">
<s:iterator begin="1" end="%{pageCount}" value="num">
<option value="${num}">${num}</option>
</s:iterator>
</select>
<script type="text/javascript">
$("#_pn").val("${currentPage}");
</script>
</div>
</div>
<script type="text/javascript">
/* function gotoPage(pageNum){
window.location.href="forum_show.action?id=${id}&pageNum="+pageNum;
} */
function gotoPage(pageNum){
$(document).forms[0].apend("<input type='hidden' name='pageNum' value='"+pageNum+"'>");
document.forms[0].submit();
}
</script>
【2. action整合】
【2.1】统一实现ModelDriven接口(统一把实体类当成页面数据的收集对象,这也是为什么在struts2的jsp页面中可以直接写实体属性的原因),但是存在问题:如何获得泛型集合的Class属性?
解决方法:利用反射
//定义class:
private Class<T> clazz=null; //TODO:无法获得T的class类型
//类设置为abastract类型
//在构造函数利用反射技术获得类型
public BaseDaoImpl(){
//使用反射技术获得泛型T的类型
ParameterizedType pt=(ParameterizedType) this.getClass().getGenericSuperclass(); //获取属性类型
this.clazz=(Class<T>)pt.getActualTypeArguments()[0];
System.out.println("clazz---->>"+clazz);
}
2.2 统一注入service,声明公共变量,如:pageNum,pageSize
【3. Utils--工具方法】
【3.1】 监听器:listener
我们经常会创建一些监听器对对象进行监听,如在页面初始化时,监听初始化对象,加载用户的所有权限地址,用以判断action的请求能否进入系统。
public class InitListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
// 获取容器与相关的Service对象
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
PrivilegeService privilegeService = (PrivilegeService) ac.getBean("privilegeServiceImpl");
// 准备数据:topPrivilegeList
List<Privilege> topPrivilegeList = privilegeService.findTopList();
sce.getServletContext().setAttribute("topPrivilegeList", topPrivilegeList);
System.out.println("------------> 已准备数据 <------------");
//准备数据:allPrivilegeUrls
Collection<String> allPrivilegeUrls = privilegeService.findAllPrivilegeUrls();
sce.getServletContext().setAttribute("allPrivilegeUrls", allPrivilegeUrls);
System.out.println("------------> 已准备数据 <------------");
}
在web.xml配置文件中进行配置:
<!-- 配置Spring的用于初始化容器对象的监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
【 3.2】 拦截器:interceptor
在权限控制系统中,往往需要验证所有的用户action请求,这时需要我们建立自定义拦截器拦截所有的用户请求,判断用户具有该请求权限方可进入,否则无法完成系统操作:
public class CheckPrivilegeInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
//准备user数据
User user=(User) ActionContext.getContext().getSession().get("user");
//准备要拦截的url地址
String namespace=invocation.getProxy().getNamespace();
String actionName=invocation.getProxy().getActionName();
String privUrl=namespace+actionName;
//判断是否已经登录,判断是否是去登录,是则放行,否则返回登录
if(user==null){
if(privUrl.startsWith("/user_login")){
return invocation.invoke();
}else{
return "loginUI";
}
}else{
//如果登录,则判断权限
if(user.hasPrivilegeByUrl(privUrl)){
return invocation.invoke();
}else{
return "noPrivilegeError";
}
}
}
}
在struts.xml配置文件中声明拦截器,且放在默认拦截器的最开始:
<interceptors>
<!-- 声明拦截器 -->
<interceptor name="checkPrivilege" class="cn.itcast.oa.util.CheckPrivilegeInterceptor"></interceptor>
<!-- 重新定义默认的拦截器栈 -->
<interceptor-stack name="defaultStack">
<interceptor-ref name="checkPrivilege"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
【3.3】 加载树状列表
树状显示结构在系统的目录显示中非常常用,我们可以采用递归方式进行实现,例如下面是部门显示的树状结构:
public class departmentUtils {
public static List<Department> getAllDepartments(List<Department> topList) {
List<Department> list=new ArrayList<Department>();
walkGetAllDepartment("┝",topList,list);
return list;
}
public static void walkGetAllDepartment(String prox,Collection<Department> topList,List<Department> list){
for(Department top:topList){
Department department=new Department();
department.setId(top.getId());
department.setName(prox+top.getName());
list.add(department);
walkGetAllDepartment(" "+prox,top.getChildren(),list);
}
}
}
但存在两个问题:
1)在action输入多个空格,在jsp页面总是显示一个,这样使用全角空格即可解决。
2)新添加节点后树状结构会发生乱序,这是因为在定义struts的一对多映射结构时,指定children为set属性,而set往往是无序的,解决方式:在映射文件中添加order-by属性设定,例如:order-by="id ASC"
【3.4 】queryHelper(分页+过滤查询条件)
分页显示在系统是经常使用的,但是如果添加搜索条件,并按照多个搜索条件进行升序或者降序排列,往往在拼接Hql语句时涉及多个if判断,这样,我们将拼接Hql语句的部分进行代码抽取,简化程序开发人员在Hql拼接方面的难度。
public class QueryHelper {
private String fromClause; //From子句
private String whereClause=""; //where子句
private String orderByClause=""; //orederby子句
private List<Object> parameters=new ArrayList<Object>();
/**
* 生成from子句
*
* @param clazz
* @param alias
*/
public QueryHelper(Class clazz,String alias){
fromClause="From "+clazz.getSimpleName()+" " +alias;
}
/**
* 添加where子句
*
* @param condition
* @param params
* @return
*/
public QueryHelper addCondition(String condition,Object... params){
if(whereClause.length()==0){
whereClause=" where "+condition;
}else{
whereClause+=" and "+condition;
}
if(params!=null){
for(Object p:params){
parameters.add(p);
}
}
return this;
}
/**
* 如果第一个参数为true,则拼接where子句
*
* @param append
* @param condition
* @param params
* @return
*/
public QueryHelper addCondition(boolean append,String condition,Object... params){
if(append){
addCondition(condition,params);
}
return this;
}
/**
* 添加orderBy属性
*
* @param propertyName
* @param asc
* @return
*/
public QueryHelper addOrderProperty(String propertyName,boolean asc){
if(orderByClause.length()==0){
orderByClause=" order by "+propertyName+" "+(asc ?"ASC":"DESC");
}else{
orderByClause+=","+propertyName+" "+(asc? "ASC":"DESC");
}
return this;
}
/**
* 判断是否要添加orderby属性
*
* @param append
* @param propertyName
* @param asc
* @return
*/
public QueryHelper addOrderProperty(boolean append,String propertyName,boolean asc){
if(append){
addOrderProperty(propertyName,asc);
}
return this;
}
/**
* 获取用于生成查询数据列表的HQL语句
* @return
*/
public String getListQueryHql(){
return fromClause+whereClause+orderByClause;
}
/**
* 获取用于生成查询总记录数的HQL语句
* @return
*/
public String getCountQueryHql(){
return "select count(*) "+fromClause+whereClause;
}
/**
* 获取参数列表
*
* @return
*/
public List<Object> getParameters(){
return parameters;
}
/**
* 通过prearePageBean将数据放入值栈中
* @param service
* @param pageNum
* @param pageSize
*/
public void preparePageBean(DaoSupport<?> service,int pageNum,int pageSize){
PageBean pageBean =service.getPageBean(pageNum, pageSize, this);
ActionContext.getContext().getValueStack().push(pageBean);
}
}
这样,在action中用户拼接所有可能发生的情况时,就得到了简化:
new QueryHelper(Topic.class,"t")
//过滤条件
.addCondition("t.forum=?", forum) //查询的论坛实体
.addCondition((viewtype==1),"t.type=?", Topic.TYPE_BEST) //是否为精帖
//排序条件
.addOrderProperty((orderBy==1), "t.lastUpdateTime",asc) //是否按照最后更新时间排序
.addOrderProperty((orderBy==2), "t.postTime", asc) //是否按照发布时间排序
.addOrderProperty((orderBy==3),"t.replyCount" ,asc) //是否按照回复数量排序
.addOrderProperty((orderBy==0),"(CASE t.type WHEN 2 THEN 2 ELSE 0 END)", false)
.addOrderProperty((orderBy==0),"t.lastUpdateTime",false)
.preparePageBean(topicService, pageNum, pageSize);
【总结】
在系统设计过程中,抽象是面向对象设计的第一步,将公共代码进行抽象封装,以组件化的形式存在,不但提高代码复用率,降低耦合,在业务变更时,也可以快速整理。