企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 【第十一章】 SSH集成开发积分商城 之 11.2 实现通用层 ——跟我学spring3 ## 11.2  实现通用层 ### 11.2.1  功能概述 通过抽象通用的功能,从而复用,减少重复工作: * 对于一些通用的常量使用一个专门的常量类进行定义; * 对于视图分页,也应该抽象出来,如JSP做出JSP标签; * 通用的数据层代码,如通用的CRUD,减少重复劳动,节约时间; * 通用的业务逻辑层代码,如通用的CRUD,减少重复劳动,节约时间; * 通用的表现层代码,同样用于减少重复,并提供更好的代码结构规范。 ### 11.2.2 通用的常量定义 目标:在一个常量类中定义通用的常量的好处是如果需要修改这些常量值只需在一个地方修改即可,变的地方只有一处而不是多处。 如默认分页大小如果在多处硬编码定义为10,突然发生变故需要将默认分页大小10为5,怎么办?如果当初我们提取出来放在一个通用的常量类中是不是只有一处变动。 ``` package cn.javass.commons; public class Constants { public static final int DEFAULT_PAGE_SIZE = 5; //默认分页大小 public static final String DEFAULT_PAGE_NAME = "page"; public static final String CONTEXT_PATH = "ctx"; } ``` 如上代码定义了通用的常量,如默认分页大小。 ### 11.2.2通用分页功能 分页功能是项目中必不可少的功能,因此通用分页功能十分有必要,有了通用的分页功能,即有了规范,从而保证视图页面的干净并节约了开发时间。 **1、 分页对象定义,用于存放是否有上一页或下一页、当前页记录、当前页码、分页上下文,该对象是分页中必不可少对象,一般在业务逻辑层组装Page对象,然后传送到表现层展示,然后通用的分页标签使用该对象来决定如何显示分页:** ``` package cn.javass.commons.pagination; import java.util.Collections; import java.util.List; public class Page<E> {/** 表示分页中的一页。*/ private boolean hasPre; //是否有上一页 private boolean hasNext; //是否有下一页 private List<E> items; //当前页包含的记录列表 private int index; //当前页页码(起始为1) //省略setter public int getIndex() { return this.index; } public boolean isHasPre() { return this.hasPre; } public boolean isHasNext() { return this.hasNext; } public List<E> getItems() { return this.items == null ? Collections.<E>emptyList() : this.items; } } ``` 2、  **分页标签实现**,将使用Page对象数据决定如何展示分页,如图11-9和11-10所示: ![](https://box.kancloud.cn/2016-05-13_573547235e5f1.JPG)   图11-9 11-10 通用分页标签实现 图11-9和11-10展示了两种分页展示策略,由于分页标签和集成SSH没多大关系且不是必须的并由于篇幅问题不再列出分页标签源代码,有兴趣的朋友请参考cn.javass.commons.pagination.NavigationTag类文件。[代码下载地址](http://sishuok.com/forum/blogPost/list/2561.html) ### 11.2.3 通用数据访问层 目标:通过抽象实现最基本的CURD操作,提高代码复用,可变部分按需实现。 **1、通用数据访问层接口定义** ``` package cn.javass.commons.dao; import java.io.Serializable; import java.util.List; public interface IBaseDao<M extends Serializable, PK extends Serializable> { public void save(M model);// 保存模型对象 public void saveOrUpdate(M model);// 保存或更新模型对象 public void update(M model);// 更新模型对象 public void merge(M model);// 合并模型对象状态到底层会话 public void delete(PK id);// 根据主键删除模型对象 public M get(PK id);// 根据主键获取模型对象 public int countAll();//统计模型对象对应数据库表中的记录数 public List<M> listAll();//查询所有模型对象 public List<M> listAll(int pn, int pageSize);// 分页获取所有模型对象 } ``` 通用DAO接口定义了如CRUD通用操作,而可变的(如查询所有已发布的接口,即有条件查询等)需要在相应DAO接口中定义,并通过泛型“M”指定数据模型类型和“PK”指定数据模型主键类型。 **2、通用数据访问层DAO实现** 此处使用Hibernate实现,即实现是可变的,对业务逻辑层只提供面向接口编程,从而隐藏数据访问层实现细节。 实现时首先通过反射获取数据模型类型信息,并根据这些信息获取Hibernate对应的数据模型的实体名,再根据实体名组装出通用的查询和统计记录的HQL,从而达到同样目的。 注意我们为什么把实现生成HQL时放到init方法中而不是构造器中呢?因为SessionFactory是通过setter注入,setter注入晚于构造器注入,因此在构造器中使用SessionFactory会是null,因此放到init方法中,并在Spring配置文件中指定初始化方法为init来完成生成HQL。 ``` package cn.javass.commons.dao.hibernate; //为节省篇幅省略import public abstract class BaseHibernateDao<M extends Serializable, PK extends Serializable> extends HibernateDaoSupport implements IBaseDao<M, PK> { private Class<M> entityClass; private String HQL_LIST_ALL; private String HQL_COUNT_ALL; @SuppressWarnings("unchecked") public void init() {//通过初始化方法在依赖注入完毕时生成HQL //1、通过反射获取注解“M”(即模型对象)的类类型 this.entityClass = (Class<M>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; //2、得到模型对象的实体名 String entityName = getSessionFactory().getClassMetadata(this.entityClass).getEntityName(); //3、根据实体名生成HQL HQL_LIST_ALL = "from " + entityName; HQL_COUNT_ALL = " select count(*) from " + entityName; } protected String getListAllHql() {//获取查询所有记录的HQL return HQL_LIST_ALL; } protected String getCountAllHql() {//获取统计所有记录的HQL return HQL_COUNT_ALL; } public void save(M model) { getHibernateTemplate().save(model); } public void saveOrUpdate(M model) { getHibernateTemplate().saveOrUpdate(model); } public void update(M model) { getHibernateTemplate().update(model); } public void merge(M model) { getHibernateTemplate().merge(model); } public void delete(PK id) { getHibernateTemplate().delete(this.get(id)); } public M get(PK id) { return getHibernateTemplate().get(this.entityClass, id); } public int countAll() { Number total = unique(getCountAllHql()); return total.intValue(); } public List<M> listAll() { return list(getListAllHql()); } public List<M> listAll(int pn, int pageSize) { return list(getListAllHql(), pn, pageSize); } protected <T> List<T> list(final String hql, final Object... paramlist) { return list(hql, -1, -1, paramlist);//查询所有记录 } /** 通用列表查询,当pn<=-1 且 pageSize<=-1表示查询所有记录 * @param <T> 模型类型 * @param hql Hibernate查询语句 * @param pn 页码 从1开始, * @param pageSize 每页记录数 * @param paramlist 可变参数列表 * @return 模型对象列表 */ @SuppressWarnings("unchecked") protected <T> List<T> list(final String hql, final int pn, final int pageSize, final Object... paramlist) { return getHibernateTemplate(). executeFind(new HibernateCallback<List<T>>() { public List<T> doInHibernate(Session session) throws HibernateException, SQLException { Query query = session.createQuery(hql); if (paramlist != null) { for (int i = 0; i < paramlist.length; i++) { query.setParameter(i, paramlist[i]);//设置占位符参数 } } if (pn > -1 && pageSize > -1) {//分页处理 query.setMaxResults(pageSize);//设置将获取的最大记录数 int start = PageUtil.getPageStart(pn, pageSize); if(start != 0) { query.setFirstResult(start);//设置记录开始位置 } } return query.list(); } }); } /** 根据查询条件返回唯一一条记录 * @param <T> 返回类型 * @param hql Hibernate查询语句 * @param paramlist 参数列表 * @return 返回唯一记录 */ @SuppressWarnings("unchecked") protected <T> T unique(final String hql, final Object... paramlist) { return getHibernateTemplate().execute(new HibernateCallback<T>() { public T doInHibernate(Session session) throws HibernateException, SQLException { Query query = session.createQuery(hql); if (paramlist != null) { for (int i = 0; i < paramlist.length; i++) { query.setParameter(i, paramlist[i]); } } return (T) query.setMaxResults(1).uniqueResult(); } }); } //省略部分可选的便利方法,想了解更多请参考源代码 } ``` 通用DAO实现代码相当长,但麻烦一次,以后有了这套通用代码将会让工作很轻松,该通用DAO还有其他便利方法因为本示例不需要且由于篇幅原因没加上,请参考源代码。 ### 11.2.4 通用业务逻辑层 目标:实现通用的业务逻辑操作,将常用操作封装提高复用,可变部分同样按需实现。 **1、通用业务逻辑层接口定义** ``` package cn.javass.commons.service; //由于篇幅问题省略import public interface IBaseService<M extends Serializable, PK extends Serializable> { public M save(M model); //保存模型对象 public void saveOrUpdate(M model);// 保存或更新模型对象 public void update(M model);// 更新模型对象 public void merge(M model);// 合并模型对象状态 public void delete(PK id);// 删除模型对象 public M get(PK id);// 根据主键获取模型对象 public int countAll();//统计模型对象对应数据库表中的记录数 public List<M> listAll();//获取所有模型对象 public Page<M> listAll(int pn);// 分页获取默认分页大小的所有模型对象 public Page<M> listAll(int pn, int pageSize);// 分页获取所有模型对象 } ``` **3、 通用业务逻辑层接口实现** 通用业务逻辑层通过将通用的持久化操作委托给DAO层来实现通用的数据模型CRUD等操作。 通过通用的setDao方法注入通用DAO实现,在各Service实现时可以通过强制转型获取各转型后的DAO。 ``` package cn.javass.commons.service.impl; //由于篇幅问题省略import public abstract class BaseServiceImpl<M extends Serializable, PK extends Serializable> implements IBaseService<M, PK> { protected IBaseDao<M, PK> dao; public void setDao(IBaseDao<M, PK> dao) {//需要依赖注入 this.dao = dao; } public IBaseDao<M, PK> getDao() { return this.dao; } public M save(M model) { getDao().save(model); return model; } public void merge(M model) { getDao().merge(model); } public void saveOrUpdate(M model) { getDao().saveOrUpdate(model); } public void update(M model) { getDao().update(model); } public void delete(PK id) { getDao().delete(id); } public void deleteObject(M model) { getDao().deleteObject(model); } public M get(PK id) { return getDao().get(id); } public int countAll() { return getDao().countAll(); } public List<M> listAll() { return getDao().listAll(); } public Page<M> listAll(int pn) { return this.listAll(pn, Constants.DEFAULT_PAGE_SIZE); } public Page<M> listAll(int pn, int pageSize) { Integer count = countAll(); List<M> items = getDao().listAll(pn, pageSize); return PageUtil.getPage(count, pn, items, pageSize); } } ``` ### 11.2.6通用表现层 目标:规约化常见请求和响应操作,将常见的CURD规约化,采用规约编程提供开发效率,减少重复劳动。 Struts2常见规约编程: * **通用字段驱动注入:**如分页字段一般使用“pn”或“page”来指定当前分页页码参数名,通过Struts2的字段驱动注入实现分页页码获取的通用化; * **通用Result:**对于CURD操作完全可以提取公共的Result名字,然后在Strust2配置文件中进行规约配置; * **数据模型属性名:**在页面展示中,如新增和修改需要向值栈或请求中设置数据模型,在此我们定义统一的数据模型名如“model”,这样在项目组中形成约定,大家只要按照约定来能提高开发效率; * **分页对象属性名:**与数据模型属性名同理,在此我们指定为“page”; * **便利方法:**如获取值栈、请求等可以提供公司内部需要的便利方法。 **1、通用表现层Action实现:** ``` package cn.javass.commons.web.action; import cn.javass.commons.Constants; //省略import public class BaseAction extends ActionSupport { /** 通用Result */ public static final String LIST = "list"; public static final String REDIRECT = "redirect"; public static final String ADD = "add"; /** 模型对象属性名*/ public static final String MODEL = "model"; /** 列表模型对象属性名*/ public static final String PAGE = Constants.DEFAULT_PAGE_NAME; public static final int DEFAULT_PAGE_SIZE = Constants.DEFAULT_PAGE_SIZE; private int pn = 1; /** 页码,默认为1 */ //省略pn的getter和setter,自己补上 public ActionContext getActionContext() { return ActionContext.getContext(); } public ValueStack getValueStack() {//获取值栈的便利方法 return getActionContext().getValueStack(); } } ``` **2、通用表现层JSP视图实现:** 将视图展示的通用部分抽象出来减少页面设计的工作量。 **2.1、通用JSP页头文件(WEB-INF/jsp/common/inc/header.jsp):** 此处实现比较简单,实际中可能包含如菜单等信息,对于可变部分使用请求参数来获取,从而保证了可变与不可变分离,如标题使用“${param.title}”来获取。 ``` <%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>${param.title}</title> </head> <body> ``` **2.2、通用JSP页尾文件(WEB-INF/jsp/common/inc/footer.jsp):** 此处比较简单,实际中可能包含公司版权等信息。 ``` </body> </html> ``` **2.3、通用JSP标签定义文件(WEB-INF/jsp/common/inc/tld.jsp):** 在一处定义所有标签,避免标签定义使代码变得凌乱,且如果有多个页面需要新增或删除标签即费事又费力。 ``` <%@taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@taglib prefix="s" uri="/struts-tags" % ``` **2.4、通用错误JSP文件(WEB-INF/jsp/common/error.jsp):** 当系统遇到错误或异常时应该跳到该页面来显示统一的错误信息并可能在该页保存异常信息。 ``` <%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> <jsp:include page="inc/header.jsp"/> 失败或遇到异常! <jsp:include page="inc/footer.jsp"/> ``` **2.5、通用正确JSP文件(WEB-INF/jsp/common/success.jsp):** 对于执行成功的操作可以使用通用的页面表示,可变部分同样可以使用可变的请求参数传入。 ``` <%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> <jsp:include page="inc/header.jsp"/> 成功! <jsp:include page="inc/footer.jsp"/> ``` **3、通用设置web环境上下文路径拦截器:** 用于设置当前web项目的上下文路径,即可以在JSP页面使用“${ctx}”获取当前上下文路径。 ``` package cn.javass.commons.web.filter; //省略import /** 用户设置当前web环境上下文,用于方便如JSP页面使用 */ public class ContextPathFilter implements Filter { @Override public void init(FilterConfig config) throws ServletException { } @Override public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String contextPath = ((HttpServletRequest) request).getContextPath(); request.setAttribute(Constants.CONTEXT_PATH, contextPath); chain.doFilter(request, response); } @Override public void destroy() { } } ``` ### 11.2.7通用配置文件 目标:通用化某些常用且不可变的配置文件,同样目标是提高复用,减少工作量。 **1、Spring资源配置文件(resources/applicationContext-resources.xml):** 定义如配置元数据替换Bean、数据源Bean等通用的Bean。 ``` <bean class= "org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:resources.properties</value> </list> </property> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy"> <property name="targetDataSource"> <bean class="org.logicalcobwebs.proxool.ProxoolDataSource"> <property name="driver" value="${db.driver.class}" /> <property name="driverUrl" value="${db.url}" /> <property name="user" value="${db.username}" /> <property name="password" value="${db.password}" /> <property name="maximumConnectionCount" value="${proxool.maxConnCount}" /> <property name="minimumConnectionCount" value="${proxool.minConnCount}" /> <property name="statistics" value="${proxool.statistics}" /> <property name="simultaneousBuildThrottle" value="${proxool.simultaneousBuildThrottle}" /> <property name="trace" value="${proxool.trace}" /> </bean> </property> </bean> </beans> ``` 通过通用化如数据源来提高复用,对可变的如数据库驱动、URL、用户名等采用替换配置元数据形式进行配置,具体配置含义请参考【7.5集成Spring JDBC及最佳实践】。 **2、替换配置元数据的资源文件(resources/resources.properties):** 定义替换配置元数据键值对用于替换Spring配置文件中可变的配置元数据。 ``` #数据库连接池属性 proxool.maxConnCount=10 proxool.minConnCount=5 proxool.statistics=1m,15m,1h,1d proxool.simultaneousBuildThrottle=30 proxool.trace=false db.driver.class=com.mysql.jdbc.Driver db.url=jdbc:mysql://localhost:3306/point_shop?useUnicode=true&characterEncoding=utf8 db.username=root db.password= ``` **3、通用Struts2配置文件(WEB-INF/struts.xml):** 由于是要集成Spring,因此需要使用StrutsSpringObjectFactory,我们需要在action名字中出现“/”因此定义struts.enable.SlashesInActionNames=true。 在此还定义了“custom-default”包继承struts-default包,且是抽象的,在包里定义了如全局结果集全局异常映射。 ``` <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <constant name="struts.objectFactory" value="org.apache.struts2.spring.StrutsSpringObjectFactory"/> <!-- 允许action的名字中出现"/" --> <constant name="struts.enable.SlashesInActionNames" value="true"/> <package name="custom-default" extends="struts-default" abstract="true"> <global-results> <result name="success">/WEB-INF/jsp/common/success.jsp</result> <result name="error">/WEB-INF/jsp/common/error.jsp</result> <result name="exception">/WEB-INF/jsp/common/error.jsp</result> </global-results> <global-exception-mappings> <exception-mapping result="exception" exception="java.lang.Exception"/> </global-exception-mappings> </package> </struts> ``` **4、通用log4j日志记录配置文件(resources/log4j.xml):** 可以配置基本的log4j配置文件然后在其他地方通过拷贝来定制需要的日志记录配置。 ``` <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- Appenders --> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <param name="Target" value="System.out" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p: %c - %m%n" /> </layout> </appender> <!-- Root Logger --> <root> <priority value="DEBUG" /> <appender-ref ref="console" /> </root> </log4j:configuration> ``` **4、通用web.xml配置文件定义(WEB-INF/web.xml):** **定义如通用的集成配置、设置web环境上下文过滤器、字符过滤器(防止乱码)、通用的Web框架拦截器(如Struts2的)等等,从而可以通过拷贝复用。** ``` <?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!-- 通用配置开始 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext-resources.xml </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- 通用配置结束 --> <!-- 设置web环境上下文(方便JSP页面获取)开始 --> <filter> <filter-name>Set Context Path</filter-name> <filter-class>cn.javass.commons.web.filter.ContextPathFilter</filter-class> </filter> <filter-mapping> <filter-name>Set Context Path</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 设置web环境上下文(方便JSP页面获取)结束 --> <!-- 字符编码过滤器(防止乱码)开始 --> <filter> <filter-name>Set Character Encoding</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>Set Character Encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 字符编码过滤器(防止乱码)结束 --> <!-- Struts2.x前端控制器配置开始 --> <filter> <filter-name>struts2Filter</filter-name> <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class> </filter> <filter-mapping> <filter-name>struts2Filter</filter-name> <url-pattern>*.action</url-pattern> </filter-mapping> <!-- Struts2.x前端控制器配置结束 --> </web-app> ``` 原创内容,转载请注明私塾在线【[http://sishuok.com/forum/blogPost/list/2515.html](http://sishuok.com/forum/blogPost/list/2515.html#7240)】