在上面把日志文件打印到了D:\log下,考虑到Liunx服务器环境下,让最终用户修改可能不可接受,接下来完成三件事情:(1)应用程序指定输出路径(2)完善异常类的处理(3)完成页面跳转的封装处理
一、指定输出路径
由于Logback的<FILE>指定相对路径与Log4J存在差异,所以在修改日志输出路径之前,得让Eclipse能调试代码。
1、在http://www.eclipsetotale.com/tomcatPlugin.html#A3下载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,由于涉及代码较多这里只粘贴出代码构造,具体代码见附件。读者也可以自已写此工具类:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public
class
FrameXmlUtil
{
/**
* XML文档
*/
private
Document xmlDocument =
null
;
/**
* XML的根元素
*/
private
Element rootElement =
null
;
/**
* 读取XML文件
*/
public
void
readXmlFile(String filePath)
throws
FrameException
{
// 这里省略,具体见附件
}
/**
* 获取结点属性集合
*/
public
Map<String, String> getAttrs(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" />进行修改:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
/**
* 修改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
;
}
// 判断孩子结点是否为<property name="LOG_HOME" value="D:\logs" />
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()方法,修改后如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Override
public
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()方法中输出路径代码处增加如下处理:
|
1
|
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文件,专门用于缓存全局数据
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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文件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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()方法
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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()方法中打印一句话
|
1
|
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类似如下:
|
1
2
3
4
5
6
|
<
business
name
=
"topic"
mustlogin
=
"false"
business-class
=
"com.medical.FrameTopic"
>
<
forward
>
<
path
name
=
"success"
path
=
"/module/topic.html"
/>
<
path
name
=
"failure"
path
=
"/module/topic.html"
/>
</
forward
>
</
business
>
|
从这个XML上很容易看出,这个业务是进入topic页面,在进入之前用户可以不登录,同时进入页面之前的逻辑部分由FrameTopic处理,无论处理结果如何都最终进入topic.html。下面封装FrameBusiness类来对应这个业务实体:
1、创建com.medical.frame.config.FrameForward类,它包括successPath(成功时跳转路径)、failurePath(失败时跳转路径),然后再对外提供get和set方法
|
1
2
3
4
5
6
7
8
9
10
11
12
|
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方法
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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中填充如下内容:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
business-config
>
<
business
name
=
"index"
mustlogin
=
"false"
>
<
forward
>
<
path
name
=
"success"
path
=
"/main.html"
/>
<
path
name
=
"failure"
path
=
"/main.html"
/>
</
forward
>
</
business
>
<
business
name
=
"login"
mustlogin
=
""
>
<
forward
>
<
path
name
=
"success"
path
=
"/login.html"
/>
<
path
name
=
"failure"
path
=
"/login.html"
/>
</
forward
>
</
business
>
</
business-config
>
|
【备注】:后面若无特殊说明,文件均以UTF-8编码
(5)的test-action.xml中填充如下内容:
|
1
2
3
4
5
6
7
8
9
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
business-config
>
<
business
name
=
"test"
mustlogin
=
"true"
business-class
=
"com.medical.frame.Demo"
>
<
forward
>
<
path
name
=
"success"
path
=
"/main.html"
/>
<
path
name
=
"failure"
path
=
"/main.html"
/>
</
forward
>
</
business
>
</
business-config
>
|
4、前面说过action这个Servlet,现在我们希望该Servlet在启动时把上面的业务配置文件(xxx-action.xml)里的业务配置加载到内存
(1)在FrameLauncher.init()方法中调用FrameConfigUtil.loadBusiness(),加载业务配置
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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下的所有业务配置
|
1
2
3
4
5
|
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()方法
|
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* 系统业务配置文件路径集合
*/
private
Set<String> busActFileSet =
new
HashSet<String>();
public
Set<String> getBusinessActFile()
{
return
busActFileSet;
}
public
void
addBusinessActFile(String businessActFile)
{
this
.busActFileSet.add(businessActFile);
}
|
II、在FrameConfigUtil.findActFile()中查找-action.xml文件,并保存在全局缓存中
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private
static
void
findActFile(ServletContext context, String path)
{
Set<String> businessSet = 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文件集合,判断是否为空,若为空则返回
|
1
2
3
4
5
|
Set<String> actionFileSet = FrameCache.getInstance().getBusinessActFile();
if
(actionFileSet ==
null
|| actionFileSet.isEmpty())
{
return
;
}
|
II、若集合不为空,则把XXX-action.xml文件用FrameXMlUtil工具解析
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 遍历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对象,并缓存到全局变量中
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
FrameCache.java部分代码:
/**
* 系统配置业务
*/
private
Map<String, FrameBusiness> businessMap =
new
HashMap<String, FrameBusiness>();
public
FrameBusiness getBusiness(String businessName)
{
return
businessMap.get(businessName);
}
public
void
addBusiness(FrameBusiness business)
{
businessMap.put(business.getName(), business);
}
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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);
Map<String, String> busNodeAttributs = 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));
// 解析<forward>属性
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<String, String> 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://down.51cto.com/data/2363664
本文转自qingkechina 51CTO博客,原文链接:http://blog.51cto.com/qingkechina/1315778,如需转载请自行联系原作者
