本文共 16403 字,大约阅读时间需要 54 分钟。
在上面把日志文件打印到了D:\log下,考虑到Liunx服务器环境下,让最终用户修改可能不可接受,接下来完成三件事情:(1)应用程序指定输出路径(2)完善异常类的处理(3)完成页面跳转的封装处理
一、指定输出路径
由于Logback的<FILE>指定相对路径与Log4J存在差异,所以在修改日志输出路径之前,得让Eclipse能调试代码。
1、在下载Tomcat插件,下载时确认自已的JDK、Eclipse、Tomcat与该插件是否配套。我曾见同事使用的插件不配套,导致Eclipse不能启动Tomcat
2、假如Eclipse的路径为%Eclipse%,在%Eclipse%下新建links\tomcatplugin\features和links\tomcatplugin\plugins文件夹,把下载的插件解压到links\tomcatplugin\plugins下,其目录结构如图:
3、在%eclipse%\links\下创建tomcat.ini文件,填写如下内容:
path=D:\\eclipse\\links\\tomcatplugin
其中D:\\eclipse即为%eclipse%,读者视自己环境更改。
4、若Eclipse打开则请关闭,然后删除%eclipse%\configuration\org.eclipse.update目录,重启Eclipse后应该在工具栏上看到小猫咪的图标,表明加载成功:
5、在Eclipse配置Tomcat。依次选择“Window > Preferences > Tomcat”,其中“Tomcat Version”选择“Version 7.x”,“Tomcat home”选择Tomcat放置路径。在前面说过我把Tomcat放置到了D盘下,如图:
6、确认后点击小猫咪图标,应该能看到Tomcat在Eclisps的启动信息了
接下来我们要修改logconfig.xml中的日志输出路径了。由于要修改XML,所以我们创建一个FrameXmlUtil.java类,该类用于解析XML内容、读取结点的属性集合、保存XML内容等。
1、创建FrameXmlUtil,由于涉及代码较多这里只粘贴出代码构造,具体代码见附件。读者也可以自已写此工具类:
public class FrameXmlUtil{ /** * XML文档 */ private Document xmlDocument = null; /** * XML的根元素 */ private Element rootElement = null; /** * 读取XML文件 */ public void readXmlFile(String filePath) throws FrameException { // 这里省略,具体见附件 } /** * 获取结点属性集合 */ public MapgetAttrs(Node node) { // 这里省略,具体见附件 } /** * 保存XML */ public void saveXML() throws FrameException { // 这里省略,具体见附件 } /** * 获取XML根元素 */ public Element getRootElement() { return rootElement; }}
【备注】:关注解析XML在JAVA开发中有多种不同的解析方法,感兴趣的话可以在谷歌上搜索一下,有很多人写过这方面的贴子
2、 对FrameConfigUtil增加静态工厂方法modifyLogOutPath(),对D:\medical\war\etc\logconfig.xml中的<property name="LOG_HOME" value="D:\logs" />进行修改:
/** * 修改D:/medical/war/etc/logconfig.xml文件中的日志输出路径 */public static void modifyLogOutPath(ServletContext context) throws FrameException{ String webPath = context.getRealPath("/"); StringBuilder filePath = new StringBuilder(webPath); filePath.append(File.separator).append("etc"); filePath.append(File.separator).append("logconfig.xml"); // 载入D:\medical\war\etc\logconfig.xml文件 FrameXmlUtil xmlUtil = new FrameXmlUtil(); xmlUtil.readXmlFile(filePath.toString()); // 初步判断文件的合法性 Element rootElement = xmlUtil.getRootElement(); if (rootElement == null) { return; } // 由logconfig.xml知日志输出路径宏定义就在根结点下,所以此处遍历根结点的孩子 NodeList childNodeList = rootElement.getChildNodes(); for (int index = 0; index < childNodeList.getLength(); index++) { Node childNode = childNodeList.item(index); if (Node.ELEMENT_NODE != childNode.getNodeType()) { continue; } // 判断孩子结点是否为Element childElement = (Element) childNode; String elementName = childElement.getNodeName(); String logHome = childElement.getAttribute("name"); if (FrameConstant.LOG_PROPERTY.equals(elementName) && FrameConstant.LOG_HOME.equals(logHome)) { StringBuilder path = new StringBuilder(webPath); path.append(File.separator).append("var").append(File.separator).append("logs"); childElement.setAttribute(FrameConstant.LOG_VALUE, path.toString()); break; } } // 保存修改后的XML文件 xmlUtil.saveXML();}
3、若使用logback则在加载logconfig.xml之后调用FrameConfigUtil.modifyLogOutPath()方法,所以修改FrameLauncher的init()方法,修改后如下:
@Overridepublic void init() throws ServletException{ ServletContext context = getServletContext(); try { FrameConfigUtil.modifyLogOutPath(context); FrameConfigUtil.initLogConfig(context); } catch (FrameException e) { throw new ServletException("[FrameLauncher] init error.", e); }}
好了,在Eclipse中点击小猫咪图标启动Tomcat服务,在浏览器中输入http://localhost:8080/medical,理论上应该在D:\medical\war\var\logs下生成日志文件,但理想与现实之间往往存在差距。这是什么原因呢?细心观察在Tomcat目录下多出一个medicalwarvarlogs文件夹,里面的日志文件正是我们想要的!!
这说明什么?
说明我们修改logconfig.xml之后的<property name="LOG_HOME" value="D:\medical\war\var\logs"/>存在问题。
那是什么问题呢?想想什么是转义字符?\n是什么?\b是什么?明白了吧:value="D:\medical\war\var\logs"是错误的,应该对\再进行转义。修改FrameConfigUtil.modifyLogOutPath()方法中输出路径代码处增加如下处理:
String realPath = path.toString().replaceAll("\\\\", "/"); childElement.setAttribute(FrameConstant.LOG_VALUE, realPath);
再重启Tomcat服务,在浏览器中输入http://localhost:8080/medical,点击下图所示的超链接,此时会发现日志文件能按您的预期输出了。
二、完善异常类
在上面日志文件输出搞定之后,让我们回到第二天的异常类封装问题上,当时说异常的描述应该从中英文资源中读取,随着我们的想法自然地前行吧。
1、在D:\medical\war\etc\下新建local文件夹,然后在local下分别创建en、zh文件夹,再在en、zh下分别创建resource.properties文件,其目录结构如下:
2、向resource.properties中填写信息,由于我们这个斗医系统是给中国人看的,外国人不懂中医是什么,所以只填写zh下的resource.properties内容即可。这里暂时把前面所涉及的错误码填写上:
#UTF-8
1=创建XML生成器时异常
5=SAX解析XML文件时异常
10=解析XML时出现IO异常
15=解析XML时参数设置异常
20=生成XML翻译器失败
25=翻译XML失败
100=HTTP请求动作跳转异常
3、考虑到异常信息只要从一个地方读取,这里新建一个FrameCache.java文件,专门用于缓存全局数据
public class FrameCache{ private static FrameCache instance = new FrameCache(); /** * 全局资源文件缓存 */ private Properties resourceProp = new Properties(); private FrameCache() { } public static FrameCache getInstance() { return instance; } public void setResourceProp(Properties resourceProp) { this.resourceProp = resourceProp; } public String getResourceValue(String resourceKey) { return resourceProp.getProperty(resourceKey, resourceKey); }}
4、在Servlet加载时需要把这些资源信息读入内存,这样才能根据错误码读取到异常描述信息,所以需要
(1)在FrameConfigUtil中定义loadResource(),用于加载Properties文件
public static void loadResource(ServletContext context) throws FrameException{ // 获取中文资源文件路径 StringBuilder resourcePath = new StringBuilder(context.getRealPath("/")); resourcePath.append(File.separator).append("etc").append(File.separator).append("local"); resourcePath.append(File.separator).append("zh").append(File.separator).append("resource.properties"); // 具体加载动作 Properties resourceProp = new Properties(); try { InputStream in = new BufferedInputStream(new FileInputStream(resourcePath.toString())); resourceProp.load(in); } catch (IOException e) { throw new FrameException(FrameErrorCode.Prop_ERROR_LOAD, e); } // 设置到全局缓存中 FrameCache.getInstance().setResourceProp(resourceProp);}
(2)在FrameLauncher的init()方法中调用loadResource()方法
ServletContext context = getServletContext();try{ // 加载logback配置 FrameConfigUtil.modifyLogOutPath(context); FrameConfigUtil.initLogConfig(context); // 加载中文资源配置 FrameConfigUtil.loadResource(context);}catch (FrameException e){ throw new ServletException("[FrameLauncher] init error.", e);}
5、为了测试,我们在FrameLauncher.doGet()方法中打印一句话
System.out.println(FrameCache.getInstance().getResourceValue("1"));
6、在Eclipse中启动Tomcat服务,在浏览器中输入http://localhost:8080/medical,点击“Test Logback”超链接,可以看到有类似如下输出:
【备注】:这里的乱码是由于字符的编码问题,在此处我们只要能加载即可,具体的可读性遗留到后面界面展示时处理。
7、完善FrameException读取异常描述的遗留,把原来标注“// errorDesc应该从中英文资源文件中根据errorCode读取”的地方,使用errorDesc = FrameCache.getInstance().getResourceValue(String.valueOf(errorCode));代替。
三、页面跳转封装
接下来我们做一件系统较为重要的事情:页面跳转封装。比如在系统导航菜单上点击了“话题”菜单项,那么希望系统能进入“话题”页面,站在纯HTML角度来看只需要<a>标签即可,但有时候还需要处理一些逻辑,所以此封装还是较有意义的。封装之后的XML类似如下:
从这个XML上很容易看出,这个业务是进入topic页面,在进入之前用户可以不登录,同时进入页面之前的逻辑部分由FrameTopic处理,无论处理结果如何都最终进入topic.html。下面封装FrameBusiness类来对应这个业务实体:
1、创建com.medical.frame.config.FrameForward类,它包括successPath(成功时跳转路径)、failurePath(失败时跳转路径),然后再对外提供get和set方法
public class FrameForward{ /** * 成功跳转路径 */ private String successPath = null; /** * 失败跳转路径 */ private String failurePath = null; // 省略相关的get&set方法 }
2、创建com.medical.frame.config.FrameBusiness类,里面有name、mustLogin、businessClass和FrameForward对象,然后再对外提供get和set方法
public class FrameBusiness{ /** * 业务名称 */ private String name = null; /** * 是否必须登录 */ private boolean mustLogin = false; /** * 业务逻辑处理类 */ private String businessClass = null; /** * 业务跳转路径 */ private FrameForward forward = null; // 省略相关的get&set方法}
3、假如系统有多个业务,我们不希望所有业务的配置都放到一个XML,同时不同业务的XML又希望按业务文件夹放置,如下:
(1)在运行环境D:\medical\war\WEB-INF下创建config文件夹
(2)在config下创建sm和test两个文件夹
(3)在sm和test下分别创建system-action.xml和test-action.xml
(4)在system-action.xml中填充如下内容:
【备注】:后面若无特殊说明,文件均以UTF-8编码
(5)的test-action.xml中填充如下内容:
4、前面说过action这个Servlet,现在我们希望该Servlet在启动时把上面的业务配置文件(xxx-action.xml)里的业务配置加载到内存
(1)在FrameLauncher.init()方法中调用FrameConfigUtil.loadBusiness(),加载业务配置
public void init() throws ServletException{ ServletContext context = getServletContext(); try { // 加载logback配置 FrameConfigUtil.modifyLogOutPath(context); FrameConfigUtil.initLogConfig(context); // 加载中文资源配置 FrameConfigUtil.loadResource(context); // 加载业务配置文件 FrameConfigUtil.loadBusiness(context); } catch (FrameException e) { throw new ServletException("[FrameLauncher] init error.", e); }}
(2)在FrameConfigUtil中定义一个公共静态方法loadBusiness(),用于加载D:\medical\war\WEB-INF\config下的所有业务配置
public static void loadBusiness(ServletContext context) throws FrameException{ findActFile(context, "/WEB-INF/config"); parseBusiness(context);}
(3)上面的loadBusiness()方法中的findActFile()作用是查找出"/WEB-INF/config"下的所有以-action.xml结尾的文件,并把文件名保存到全局缓存中
I、在FrameCache中定义文件集合,并提供get()/add()方法
/** * 系统业务配置文件路径集合 */private SetbusActFileSet = new HashSet ();public Set getBusinessActFile(){ return busActFileSet;} public void addBusinessActFile(String businessActFile){ this.busActFileSet.add(businessActFile);}
II、在FrameConfigUtil.findActFile()中查找-action.xml文件,并保存在全局缓存中
private static void findActFile(ServletContext context, String path){ SetbusinessSet = context.getResourcePaths(path); if (businessSet == null) { return; } for (String item : businessSet) { if (item == null) { continue; } if (item.endsWith(FrameConstant.BUS_CONF_POSTFIX)) { FrameCache.getInstance().addBusinessActFile(item); } else { findActFile(context, item); } }}
(4)FrameConfigUtil.parseBusiness()是真正的解析过程,也是本文的重点,所以请读者重点关注。下面把其逻辑分别列举出:
I、从全局缓存中读取XXX-action.xml文件集合,判断是否为空,若为空则返回
SetactionFileSet = FrameCache.getInstance().getBusinessActFile();if (actionFileSet == null || actionFileSet.isEmpty()){ return;}
II、若集合不为空,则把XXX-action.xml文件用FrameXMlUtil工具解析
// 遍历XXX-action.xml文件for (String fileName : actionFileSet){ InputSource in = null; try { URL url = context.getResource(fileName); in = new InputSource(url.toExternalForm()); } catch (MalformedURLException e) { throw new FrameException(FrameErrorCode.XML_ERROR_GET_PATH); } try { FrameXmlUtil xmlUtil = new FrameXmlUtil(); xmlUtil.readXmlFile(in); //....... } catch (FrameException e) { throw e; }}
III、真正解析结点形成FrameBusiness对象,并缓存到全局变量中
FrameCache.java部分代码:/** * 系统配置业务 */private MapbusinessMap = new HashMap ();public FrameBusiness getBusiness(String businessName){ return businessMap.get(businessName);} public void addBusiness(FrameBusiness business){ businessMap.put(business.getName(), business);}
FrameConfigUtil.parseBusiness(ServletContext)部分代码:FrameXmlUtil xmlUtil = new FrameXmlUtil();xmlUtil.readXmlFile(in);Element rootElement = xmlUtil.getRootElement();NodeList busNodeList = rootElement.getElementsByTagName(FrameConstant.BUS_KEY);if (busNodeList == null || busNodeList.getLength() == 0){ continue;}// 开始解析for (int i = 0; i < busNodeList.getLength(); i++){ Node busNode = busNodeList.item(i); MapbusNodeAttributs = xmlUtil.getAttrs(busNode); FrameBusiness business = new FrameBusiness(); business.setName(busNodeAttributs.get(FrameConstant.BUS_NAME)); business.setBusinessClass(busNodeAttributs.get(FrameConstant.BUS_CLASS)); String mustLogin = busNodeAttributs.get(FrameConstant.BUS_MUST_LOGIN); mustLogin = mustLogin == null ? "false" : mustLogin; business.setMustLogin("TRUE".equalsIgnoreCase(mustLogin)); // 解析 属性 NodeList forwardNodeList = ((Element) busNode).getElementsByTagName(FrameConstant.BUS_FORWARD); if (forwardNodeList == null || forwardNodeList.getLength() != 1) { continue; } Element forwardElement = (Element) forwardNodeList.item(0); NodeList pathNodeList = forwardElement.getElementsByTagName(FrameConstant.BUS_PATH); if (pathNodeList == null || pathNodeList.getLength() == 0) { continue; } FrameForward forward = new FrameForward(); for (int j = 0; j < pathNodeList.getLength(); j++) { Node fowardNode = pathNodeList.item(j); Map forwardAttrMap = xmlUtil.getAttrs(fowardNode); String forwardName = forwardAttrMap.get(FrameConstant.BUS_FORWARD_NAME); if (FrameConstant.BUS_FORWARD_SUCCESS.equals(forwardName)) { forward.setSuccessPath(forwardAttrMap.get(FrameConstant.BUS_PATH)); } else if (FrameConstant.BUS_FORWARD_FAILURE.equals(forwardName)) { forward.setFailurePath(forwardAttrMap.get(FrameConstant.BUS_PATH)); } } business.setForward(forward); // 添加到全局缓存中 FrameCache.getInstance().addBusiness(business);}
【备注】:上面的代码是该封装的关键代码,限于篇幅问题,这里只把较为关键的代码粘贴出,其中涉及FrameXmlUtil.readXmlFile(InputSource in)、FrameConstant接口常量值定义等都没有列出,具体可参见附件。
若读者有兴趣运行的话,强烈建议读者亲身写一下。
转载地址:http://qqado.baihongyu.com/