• XXX系统发展综述(SSH+Jquery EasyUI)


    一个、该项目总体介绍

    前一段时间的工作。我大概花了两三个月开发Web管理信息系统。用于框架集Struts2.3.1+Spring3.0+Hibernate3+Jquery EasyUI1.3.5。业务逻辑并不复杂。数据收集完毕(问卷的形式)、计算处理和形成报告公布。EasyUI是一个非常优秀的JS UI框架,使用简单方便,效果也还能够,是UI效果和带宽速度之间的一个折中之选。

    系统中还有新闻公布模块,用到了富文本编辑器,在比較了非常多插件之后,选择了kindeditor,原因非常easy。由于它有网上现成的和EasyUI结合的样例。另外生产图表时用到了JFreechart插件。系统开发执行环境为windows7下Myeclipse8.5+JDK1.6+Tomcat6.0+SqlServer2008R2。

    二、关于EasyUI

    EasyUI非常方便使用。一般仅仅须要在一个页面中导入一些文件之后,其它的页面都是嵌在当前页买中。

    EasyUI提供了非常多组件,如layout、panel、tabs、datagrid等。仅仅须要初始化这些组件。并提供对应的数据就可以。每一个组件都有两种方式声明或者创建。即使用标记元素的形式和使用js的形式。

    详细引用參见项目源代码或者EasyUI官网提供的API。

    EasyUI官网:http://www.jeasyui.com/index.php

    EasyUI官方API:http://www.jeasyui.com/documentation/index.php

    这里想补充说明一下两点。一是easyUI的tree的使用。二是这里的使用的fastjson插件。

    首先说下tree。easyUI提供了两种载入方式的tree,即同步Tree和异步Tree。同步树就是一次性载入全然部的节点。异步树是指。当点击一个节点后,才展开其子节点,不是一次性载入完。以下解说一下异步树和同步树的实例。

    首先看下数据库设计。基本字段有:菜单ID、菜单名称、菜单URL、父菜单ID,其它。


    1、异步树实现

    效果图:


    异步树实现,前端jsp比較简洁,代码例如以下

    <ul id="layout_west_tree" class="easyui-tree"
    				data-options="
    					url : '${pageContext.request.contextPath}/menuAction!getTreeNode.action',
    					fit:true,
    					lines : true,
    					onClick : function(node){
    						 loadTab(node);
    					}
    					">
    </ul>
    loadTab是自定义的一个函数。这里不用管它。前台就这么多内容。以下看后台

    action:

    /**
    	 * 异步树获取方法
    	 */
    	public void getTreeNode() {
    		List<Menu> list = null;
    		try {
    			list = menuService.getTreeNode(id);
    		} catch (Exception e) {
    			logger.error("获取树节点出错:"+e.getMessage(),e);
    		}
    		writeJSON(list);
    	}

    easyUI使用json数据格式。所以这里调用了writeJSON将获取到的树节点数据(一个list)写到前台去。writeJSON是自定义的一个方法,将list转为json并输出到前台。

    这里发现

    menuService.getTreeNode(id)
    有个參数id,这个id非常关键。easyUI使用异步树的时候,每次点击节点时,easyUI会向后台传递一个參数id,就是当前点击的菜单节点的id。这样后台能够依据这个id。载入该节点的下一级子节点。

    我们看下getTreeNode(id)的实现

    public List<Menu> getTreeNode(Integer id) throws Exception {
    		/*
    		 * 这是异步载入方式的树。该方式须要从前台获得參数id,用来做查询參数。
    		 * 
    		 * 此处假设直接返回TbCdxx 的list。在将数据写到JSon中时。json插件会不断的去查找给TnMenu的子节点,搜索全部的节点
    		 * 终于将全部节点都查出来,影响性能。所以这里用Menu取代TbCdxx,使其脱离Hibernate机制。不自己主动载入全部节点
    		 */
    		List<TbCdxx> list = null;
    		List<Menu> mlist = new ArrayList<Menu>();
    		String hql = null;
    		if (id == 0) {
    			// 第一次查询时,前台传来的id为null,这时查询全部根节点
    			hql = "from TbCdxx t where t.tbCdxx is null";
    		} else {
    			// 第二次,依据父节点的id,查询父节点下的子节点
    			hql = "from TbCdxx t where t.tbCdxx.cdId = " + id;
    		}
    		list = menuDao.get(hql);
    		if (list != null && list.size() > 0) {
    			for (TbCdxx ts : list) {
    				Menu m = new Menu();
    				m.setId(ts.getCdId());
    				m.setText(ts.getCdName());
    				Map<String,Object> attributes = new HashMap<String, Object>();
    				attributes.put("url", ts.getCdUrl());
    				m.setAttributes(attributes);
    				Set<TbCdxx> set = ts.getTbCdxxes();
    				// 假设该节点有子节点。

    设置图表状态为折叠 if (set != null && !set.isEmpty()) { m.setState("closed"); } // 假设没有子节点。图标状态为打开 else { m.setState("open"); } mlist.add(m); } } return mlist; }

    详细的实现逻辑非常清晰,就是依据当前传进来的id获取其下一级子节点的信息,得到的是一个list。然后将这个list的相关信息初始化好,返回到action中即可。这里list和mlist实际是一个model,仅仅只是一个是action(page)层的一个是dao层的,实际应用中也能够不用这样转换,直接用一个model即可。

    到这里异步树的实现就结束了。以下看同步树

    2、同步树效果图就不展示了,就是将上面异步树的所有节点所有展开(包含所拥有的子节点)

    同步树有两种实现方式。一种是依照官方api的方法。在后台遍历全部树节点。并组织好父子关系,也就是说,最后获得的list转换成json之后。就是一个格式良好的数据结构,比方一个菜单有一个子菜单。子菜单下又有子菜单。其json数据已经可以直观完整的反映了这样的层级关系。这样后台可能须要递归,假设数据非常多的话。这里笔者并没有去实现(要实现的话,逻辑非常easy。仅仅是后台拼数据可能比較复杂),而是採用了夏悸(夏悸微博)的一个扩展插件,源码见下方。使用了这个插件之后,同步树的后台数据获取就比較方便,仅仅须要将数据表中的记录作为list写到前台就可以。

    扩展js:

    /**
     * 扩展:同步树生成
     */
    $.fn.tree.defaults.loadFilter = function(data, parent) {
    	var opt = $(this).data().tree.options;
    	var idFiled, textFiled, parentField;
    	if (opt.parentField) {
    		idFiled = opt.idFiled || 'id';
    		textFiled = opt.textFiled || 'text';
    		parentField = opt.parentField;
    		var i, l, treeData = [], tmpMap = [];
    		for (i = 0, l = data.length; i < l; i++) {
    			tmpMap[data[i][idFiled]] = data[i];
    		}
    		for (i = 0, l = data.length; i < l; i++) {
    			if (tmpMap[data[i][parentField]] && data[i][idFiled] != data[i][parentField]) {
    				if (!tmpMap[data[i][parentField]]['children'])
    					tmpMap[data[i][parentField]]['children'] = [];
    				data[i]['text'] = data[i][textFiled];
    				tmpMap[data[i][parentField]]['children'].push(data[i]);
    			} else {
    				data[i]['text'] = data[i][textFiled];
    				treeData.push(data[i]);
    			}
    		}
    		return treeData;
    	}
    	return data;
    };

    同步树的前台jsp代码:

    <ul id="layout_west_tree" class="easyui-tree"
    				data-options="
    					url : '${pageContext.request.contextPath}/menuAction!getAllTreeNode.action',
    					parentField : 'pid',
    					fit:true,
    					lines : true,
    					onClick : function(node){
    						 loadTab(node);
    					}
    					">
    </ul>
    action:

    /**
    	 * 查询树节点。同步树获取方法
    	 * 
    	 */
    	public void getAllTreeNode() {
    		
    		try {		
    		    writeJSON(menuService.getAllTreeNode());	
    		} catch (Exception e) {
    			logger.error("获取树节点出错:"+e.getMessage(),e);
    		}
    	}
    service中实现:

    public List<Menu> getAllTreeNode() throws Exception {
    		List<TbCdxx> list = null;
    		List<Menu> mlist = new ArrayList<Menu>();
    		String hql = "from TbCdxx t ";
    		list = menuDao.get(hql);
    		if (list != null && list.size() > 0) {
    			for (TbCdxx ts : list) {
    				Menu m = new Menu();
    				m.setId(ts.getCdId());
    				m.setText(ts.getCdName());
    				Map<String, Object> attributes = new HashMap<String, Object>();
    				attributes.put("url", ts.getCdUrl());
    				m.setAttributes(attributes);
    				if (ts.getTbCdxx() != null) {
    					m.setPid(ts.getTbCdxx().getCdId());
    				}
    				mlist.add(m);
    			}
    		}
    		return mlist;
    	}
    这里比較简单,就是将数据表中的数据。拼成list就可以。然后写到前台去。只是这里注意前台jsp须要一个额外的字段
    parentField : 'pid',

    用的时候不要忘了。到这里tree就讲完了。接下来说下json插件。

    因为easyUI与后台交互的数据都是json格式,这就须要一个比較好的json插件。这里採用的是阿里巴巴开发的fastjson插件。据说其速度非常快。使用也非常easy,下载插件的jar包增加到project路径,然后就能够使用了。

    只是这里须要注意的是,fastjson默认将时间格式的数据都转换成了long,所以可能导致前后台转换时遇到意想不到的效果。这里做了个简单的设置,其核心代码例如以下:

    /**
    	 * 往前台写json数据
    	 * 
    	 * @param object
    	 */
    	protected void writeJSON(Object object) {
    		PrintWriter out = null;
    		try {
    			String json = JSON.toJSONStringWithDateFormat(object,
    					"yyyy-MM-dd HH:mm:ss");
    			ServletActionContext.getResponse().setContentType(
    					"text/html;charset=utf-8");
    			ServletActionContext.getResponse().setCharacterEncoding("UTF-8");
    			out = ServletActionContext.getResponse().getWriter();
    			out.write(json);
    			out.flush();
    			out.close();
    		} catch (IOException e) {
    			logger.error("输出JSON出错:" + e.getMessage(), e);
    		}
    	}
    注意这里是基于struts2的。

    注意:补充说明下,eeasyUI1.3.5版本号在使用form时,需指定method为post。否则可能会出现乱码问题。

    三、关于系统架构

    系统应用了常规的“三层架构”,即视图展示层、业务逻辑层和数据处理层。

    当中EasyUI+Struts2的Action负责页面展示、数据抓取和推送、视图流程控制,Service层负责业务逻辑的实现,Dao层与Hibernate结合,负责数据的处理,与数据库打交道。而Spring则扮演一个管家的角色。其功能有IOC容器、依赖注入、集成其它插件等,负责将整个系统融合起来,合理调度。

    以下对Action、Service、Dao的设计简单小结一下。其主要的流程是Action调用Service。Service调用Dao,且各层都使用Spring的依赖注入来实例化各层属性。

    1、  Action层

    Action负责接收前台提交的请求,获取请求数据,然后调用Service完毕业务处理,然后将请求转发到下一流程(可能返回到某个页面或者还有一个Action),并将对应的数据随之中的一个并转发。

    本项目中首先写了一个BaseAction,继承了Struts2的ActionSupport。由于EasyUI接收的数据格式都是json格式。所以这里用到了一个json插件fastjson而且提供了一个writeJSON(Object o)方法,用来在须要的时候,将数据写到前台页面,供EasyUI的组件读取。

    其它的Action仅仅需继承此Action就可以。项目中获取请求參数时採用了模型驱动的方式。即Action实现ModelDriven接口,该接口接受一个泛型T,这里T通常是一个数据模型类,这样前台就能够直接写某个类的属性。后台便能抓取到它们并自己主动封装到一个对象中。

    项目中异常处理在Action层中完毕,Service和Dao仅仅负责捕捉。并往上一层抛,在Action才处理。这也并非绝对的,看详细的要求。

    2、  Service

    Service层完毕详细的业务逻辑。如依据不同的条件调用不同的Dao等。

    3、Dao

    Dao结合Hibernate与详细的数据库打交道,即完毕数据的持久化,或者载入持久化数据对象。这里Dao的设计採用了泛型和接口的结合,首先写了一个带泛型的公用接口BaseDaoI<T>,接口中定义了一些经常使用的,通用的方法,这些方法接受的或者返回的类型都是泛型。然后实现了该接口,其泛型信息依旧保留。这样一来,详细的和某一表相关联的Dao,仅仅需在编写接口时继承该通用接口,并将泛型替换为实体类型。然后编写实现时去实现刚才的接口,并继承通用接口的实现,就可以。

    这种优点是。通用的功能,都在公用接口中定义了,并且利用泛型,完毕了类型的良好过度和检查,而不必用Object来强制转换。

    举个样例,以用户实体相关操作为例。

    (1)、公用接口

    import java.io.Serializable;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author Administrator
     * 
     * @param <T>
     */
    public interface BaseDaoI<T> {
    	/**
    	 * 保存实体
    	 * 
    	 * @param o
    	 * @throws Exception
    	 */
    	void save(T o) throws Exception;
    
    	/**
    	 * 删除实体
    	 * 
    	 * @param o
    	 * @throws Exception
    	 */
    	void delete(T o) throws Exception;
    	/**
    	 * 以sql形式删除数据
    	 * @param sql 删除sql
    	 * @throws Exception
    	 */
    	void delete(String sql) throws Exception;
    
    	/**
    	 * 更新实体
    	 * 
    	 * @param o
    	 * @throws Exception
    	 */
    	void update(T o) throws Exception;
    
    	/**
    	 * 保存或更新
    	 * 
    	 * @param o
    	 * @throws Exception
    	 */
    	void saveOrUpdate(T o) throws Exception;
    
    	/**
    	 * @param c
    	 * @param id
    	 * @return
    	 * @throws Exception
    	 */
    	T get(Class<T> c,Serializable id)throws Exception;
    	/**
    	 * 不带參数的hql查询
    	 * 
    	 * @param hql
    	 *            完整的hql语句
    	 * @return 结果集合
    	 * @throws Exception
    	 */
    	List<T> get(String hql) throws Exception;
    
    	/**
    	 * 带參数的hql查询。该方式使用问号占位符,并按顺序传递參数(不建议使用)
    	 * 
    	 * @param hql
    	 *            带參数的hql语句
    	 * @param params
    	 *            顺序良好的參数列表
    	 * @return 结果集合
    	 * @throws Exception
    	 */
    	List<T> get(String hql, Object[] params) throws Exception;
    
    	/**
    	 * 带參数的hql查询,该方式使用【:參数名】占位符,并使用Map传递參数(建议使用)
    	 * 
    	 * @param hql
    	 *            带參数的hql语句
    	 * @param params
    	 *            Map參数列表
    	 * @return 结果集合
    	 * @throws Exception
    	 */
    	List<T> get(String hql, Map<String, Object> params) throws Exception;
    
    	/**
    	 * 带參数的分页查询语句,可查询指定条数的记录
    	 * 
    	 * @param hql
    	 *            查询hql
    	 * @param params
    	 *            參数列表
    	 * @param starRow
    	 *            查询起始行(Hibernate行数从0開始)
    	 * @param size
    	 *            查询数量
    	 * @return 结果集合
    	 * @throws Exception
    	 */
    	List<T> get(String hql, Map<String, Object> params, int starRow, int size)
    			throws Exception;
    
    	/**
    	 * 不带參数的分页查询语句,可查询指定条数的记录
    	 * 
    	 * @param hql
    	 *            查询hql
    	 * @param starRow
    	 *            查询起始行(Hibernate行数从0開始)
    	 * @param size
    	 *            查询数量
    	 * @return 结果集合
    	 * @throws Exception
    	 */
    	List<T> get(String hql, int starRow, int size) throws Exception;
    
    	/**
    	 * 不带參数查询记录总数
    	 * @param hql 查询hql(比如"select count(*) from XXXX") 
    	 * @return 记录总数
    	 * @throws Exception
    	 */
    	Long getTotalAmount(String hql) throws Exception;
    
    	/**
    	 * 带參数查询记录总数
    	 * @param hql 查询hql (比如"select count(*) from XXXX where ...")
    	 * @param params 參数列表
    	 * @return 记录总数
    	 * @throws Exception
    	 */
    	Long getTotalAmount(String hql, Map<String, Object> params) throws Exception;
    	/**
    	 * 运行特定的hql语句
    	 * @param hql
    	 * @return
    	 * @throws Exception
    	 */
    	int executeHql(String hql)throws Exception;
    }
    

    (2)、公用接口的实现

    import java.io.Serializable;
    import java.util.List;
    import java.util.Map;
    
    import org.hibernate.Query;
    import org.hibernate.SQLQuery;
    import org.hibernate.Session;
    import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
    
    import com.xxx.dao.BaseDaoI;
    
    public class BaseDaoImpl<T> extends HibernateDaoSupport implements BaseDaoI<T> {
    
    	public void save(T o) throws Exception {
    		getHibernateTemplate().save(o);
    	}
    
    	public List<T> get(String hql) throws Exception {
    		List<T> list = getHibernateTemplate().find(hql);
    		return list;
    	}
    
    	public List<T> get(String hql, Object[] params) throws Exception {
    		Session session = getHibernateTemplate().getSessionFactory()
    				.getCurrentSession();
    		Query q = session.createQuery(hql);
    		if (params != null && params.length > 0) {
    			for (int i = 0; i < params.length; i++) {
    				q.setParameter(i, params[i]);
    			}
    		}
    		List<T> list = q.list();
    		return list;
    	}
    
    	public List<T> get(String hql, Map<String, Object> params) throws Exception {
    		Session session = getHibernateTemplate().getSessionFactory()
    				.getCurrentSession();
    		Query q = session.createQuery(hql);
    		for (String key : params.keySet()) {
    			q.setParameter(key, params.get(key));
    		}
    		List<T> list = q.list();
    		return list;
    	}
    
    	public void delete(T o) throws Exception {
    		getHibernateTemplate().delete(o);
    	}
    
    	public void saveOrUpdate(T o) throws Exception {
    		getHibernateTemplate().saveOrUpdate(o);
    	}
    
    	public void update(T o) throws Exception {
    		getHibernateTemplate().update(o);
    	}
    
    	public List<T> get(String hql, Map<String, Object> params, int starRow,
    			int size) throws Exception {
    		Session session = getHibernateTemplate().getSessionFactory()
    				.getCurrentSession();
    		Query q = session.createQuery(hql);
    		for (String key : params.keySet()) {
    			q.setParameter(key, params.get(key));
    		}
    		q.setFirstResult(starRow);
    		q.setMaxResults(size);
    		List<T> list = q.list();
    		return list;
    	}
    
    	public List<T> get(String hql, int starRow, int size) throws Exception {
    		Session session = getHibernateTemplate().getSessionFactory()
    				.getCurrentSession();
    		Query q = session.createQuery(hql);
    		q.setFirstResult(starRow);
    		q.setMaxResults(size);
    		List<T> list = q.list();
    		return list;
    	}
    
    	public Long getTotalAmount(String hql) throws Exception {
    		Session session = getHibernateTemplate().getSessionFactory()
    				.getCurrentSession();
    		Query q = session.createQuery(hql);
    		Long count = (Long) q.uniqueResult();
    		return count;
    	}
    
    	public Long getTotalAmount(String hql, Map<String, Object> params)
    			throws Exception {
    		Session session = getHibernateTemplate().getSessionFactory()
    				.getCurrentSession();
    		Query q = session.createQuery(hql);
    		for (String key : params.keySet()) {
    			q.setParameter(key, params.get(key));
    		}
    		Long count = (Long) q.uniqueResult();
    		return count;
    	}
    
    	public void delete(String sql) throws Exception {
    		Session session = getHibernateTemplate().getSessionFactory()
    				.getCurrentSession();
    		SQLQuery q = session.createSQLQuery(sql);
    		q.executeUpdate();
    	}
    
    	public T get(Class<T> c, Serializable id) throws Exception {
    		return getHibernateTemplate().get(c, id);
    	}
    
    	public int executeHql(String hql) throws Exception {
    		Session session = getHibernateTemplate().getSessionFactory()
    			.getCurrentSession();
    		Query q = session.createQuery(hql);
    		int i = q.executeUpdate();
    		return i;
    	}
    
    }
    

    (3)、User实体接口

    import com.xxx.model.database.TbYhxx;
    
    public interface UserDaoI extends BaseDaoI<TbYhxx> {
    
    }
    

    (4)、User实体接口实现

    </pre><pre name="code" class="java">import com.xxx.dao.UserDaoI;
    import com.xxx.model.database.TbYhxx;
    
    public class UserDaoImpl extends BaseDaoImpl<TbYhxx> implements UserDaoI {
    	……(独有的方法)
    }
    

    4、权限控制

    设计思路:项目採用基于角色的权限控制,即用户的权限是以角色来区分的。用户权限包含菜单资源权限、增删改查操作权限。

    用户登录时,系统依据用户的角色,载入用户所具有的菜单资源和操作权限。匹配的操作才干被完毕。

    菜单资源权限的实现:这个逻辑比較简单。为角色分配的菜单存放在数据表中,用户登录时,依据角色载入菜单项就可以。

    操作权限的实现:操作权限是指详细的增删改查操作。比方查看新闻、加入新闻、删除用户等操作。这里採用Struts2的拦截器实现操作权限控制,针对Action层过滤。

    拦截到请求的action和方法之后,首先推断需不须要进行权限检查,由于不是全部的操作都须要权限控制。比方查看新闻。

    默认全部的aciton请求都须要检查,假设有例外。通过struts.xml中通过ignoreActions配置。

    检查流程为,先检查是否登录,假设未登录。阻止请求。并给出提示,若已登录,则检查当前请求的操作权限与用户所具有的是否匹配。匹配则放行,否则阻止请求,给出提示。

    5、实体模型

    一般在SSH应用中,一个数据表相应一个实体模型,这个Model既完毕持久化的相关工作,也是往视图层传递的数据载体,这样比較简单,但同一时候也带来一定的弊端。比方前台页面中。除了须要该Model相应的数据表中的字段之外。还须要一些额外的參数(分页參数、自己定义其它參数)作为属性,又比方在使用Hibernate映射时,一般外键会映射成持有外键表相应Model的对象,而在前台页面,可能并不须要这个对象的全部信息而仅仅是须要一些特定的属性值,这时假设都写在一个Model时。在完毕持久化时比較不清晰,对于开发维护人员来说可能会造成逻辑混乱,这里採用的是双Model机制,即一张表相应两个Model实体,一个是全然的数据表字段作为属性,还有一个则是与之相应的专门与视图层关联的Model复制体,而这个复制体可能有很多其它的视图层须要的信息,同一时候在Service层完毕这两个Model的数据复制,利用Spring的BeanUtils类提供的静态copyProperties()方法。以用户信息表为例。用户信息表有例如以下字段

    Model1:Hibernate反向映射自己主动生成的

    public class TbYhxx implements java.io.Serializable {
    
    	// Fields
    
    	private Integer yhId;
    	private TbDyxx tbDyxx;
    	private TbYbxx tbYbxx;
    	private TbJsxx tbJsxx;
    	private String yhName;
    	private String yhPwd;
    	private String yhXm;
    	private String phone;
    	private String email;
    	private Integer yhState;
    	private Integer extend1;
    	private String extend2;
    	private String extend3;
    	private Set tbKzwjtbs = new HashSet(0);
    	private Set tbWjtbs = new HashSet(0);
    	private Set tbGgxxes = new HashSet(0);
    	private Set tbFxbgs = new HashSet(0);
    	private Set tbYbkzs = new HashSet(0);
    	private Set tbTsxxes = new HashSet(0);
    	private Set tbTjxxes = new HashSet(0);
    	private Set tbZhzses = new HashSet(0);
    	private Set tbXwggs = new HashSet(0);
    	private Set tbTjjgs = new HashSet(0);
    
    	......setters and getters
    }
    

    Model2:与视图层关联的,前台页面相关联的

    public class User extends BaseModel {
    	// Fields
    	// datagrid须要的字段名
    	private Integer yhId;
    	private Integer dyId;
    	private String dyName;
    	private Integer qyId;
    	private String qyName;
    	private Integer jsId;
    	private String jsName;
    	private String yhName;
    	private String yhPwd;
    	//用户改动password信息时使用
    	private String newPwd;
    	private String yhXm;
    	private String phone;
    	private String email;
    	private Integer yhState;
    	private Integer extend1;
    	private String extend2;
    	private String extend3;
    
    	........setters and getters
    }
    

    从样例中能够看出,这两个Model还是有一定差别的。且是有实际意义的。

    6、日志记录

    日志信息能够检測系统的日常运营信息。如用户操作和异常或者错误场景,一个设计良好的管理信息系统应该具有日志记录功能。本系统採用的是经常使用的log4j插件作为日志工具。log4j使用相对简单,导入jar包后。添加一个配置文件就可以。本项目的配置见下文。

    log4j.properties:

    log4j.rootCategory=INFO, stdout , R
    
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=[<span style="font-family: Arial, Helvetica, sans-serif;">ProjectName</span><span style="font-family: Arial, Helvetica, sans-serif;">] %p [%d] %C.%M(%L) | %m%n</span>
    
    log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.R.File=D:/log/daily.log
    log4j.appender.R.layout=org.apache.log4j.PatternLayout
    log4j.appender.R.layout.ConversionPattern=[ProjectName] %p : %m - %c | %t | %d %n
    



    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    react native 之页面跳转
    react native 之异步请求
    react native 之 redux
    react native 之页面布局
    ES6的相关新属性
    css中的选择器
    js中return的作用
    校园商铺-7商品类别模块-2商品类别列表从后到前
    校园商铺-6店铺编辑列表和列表功能-9店铺管理页面的前端开发
    校园商铺-6店铺编辑列表和列表功能-8店铺列表展示前端开发
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/4719206.html
Copyright © 2020-2023  润新知