标签归档:Java

XML创建/解析/验证


最近用到XML结合JMS TextMessage传输数据的技术。采用TextMessage+XML格式传输对象是考虑到这个平台不仅仅使用Java语言开发,同时还需要能够支持其他语言接入:比如C, Ruby, Perl, Python, PHP, ActionScript/Flash, Smalltalk,借助于ActiveMQ提供的Stomp,这些非Java语言能够很方便的就接入平台。

总结下来整个传输过程如下(Java to Java):

DTO对象->XML对象->XML文本->JMS TextMessage->XML文本->XML对象->DTO

对象

涉及技术点:

  1. JavaDTO对象如何转变为XML对象
  2. 如何从XML对象中获得XML文本
  3. 如何通过XML文本构造XML对象
  4. 遍历XML对象创建JavaDTO对象

先说第一点,JavaDTO对象如何转变为XML对象

// 创建DocumentBuilderFactory,创建和解析XML都需要
DocumentBuilderFactory domfac = DocumentBuilderFactory.newInstance();
// 创建DocumentBuilder
DocumentBuilder builder = domfac.newDocumentBuilder();
// 新建文档
Document doc = builder.newDocument();
doc.setXmlVersion("1.0");
// 创建文档根节点
Element root = doc.createElement("root");
// 循环创建叶子节点
Element leaf = doc.createElement("leaf");
// 添加叶子节点属性
leaf.setAttribute("name", "从Java对象中获取");
// 增加叶子节点至根节点
root.appendChild(leaf);

至此,XML对象已经创建完成,现在需要从XML对象中获取XML文本。

继续阅读

Log4j中添加自定义的日志级别


Log4j自带了trace,debug,info.warn,error,fatal几个日志级别。

由于项目需要拆分业务和系统日志,而我又想偷懒,所以决定在log4j日志框架上增加自定义的日志级别专门用于业务日志输出。需要增加的日志级别定为audit,在log4j所有自带日志级别之上。

Log4j官方推荐的增加自定义的日志级别的方法是“继承org.apache.log4j.Level类”,具体的可以参考log4j中的示例XLevel。由于系统开发时不直接使用Log4j,而是通过slf4j调用log4j,官方推荐的方法行不通,所以决定修改log4j和slf4j相关源代码。

Log4j源代码修改点如下:

org.apache.log4j.Logger添加audit方法

public void audit(Object message) {
	if (repository.isDisabled(Level.AUDIT_INT)) {
		return;
	}

	if (Level.AUDIT.isGreaterOrEqual(this.getEffectiveLevel())) {
		forcedLog(FQCN, Level.AUDIT, message, null);
	}
}

org.apache.log4j.Priority类添加AUDIT_INT属性

public final static int AUDIT_INT = 60000; //日志级别大于log4j包含的所有日志级别,便于后期通过Threshold属性过滤日志

org.apache.log4j.Level类添加AUDIT相关属性和方法

public static final Level AUDIT = new Level(AUDIT_INT, "AUDIT", 0);public static Level toLevel(int val, Level defaultLevel) {
	switch(val) {
		case ALL_INT: return ALL;
		case AUDIT_INT: return Level.AUDIT;
		case DEBUG_INT: return Level.DEBUG;
		case INFO_INT: return Level.INFO;
		case WARN_INT: return Level.WARN;
		case ERROR_INT: return Level.ERROR;
		case FATAL_INT: return Level.FATAL;
		case OFF_INT: return OFF;
		case TRACE_INT: return Level.TRACE;
		default: return defaultLevel;
	}
}

至此Log4j修改完成,下面修改slf4j相关源代码。slf4j需要修改slf4j-api和slf4j-log4j12两个包 继续阅读

FWK005 parse may not be called while parsing.


最近在使用javax.xml.parsers.DocumentBuilder解析xml文件的时候偶尔会出错:

org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
        at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:263)
        at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:284)
        at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:208)
...

跟踪了一下代码,发现这个异常是在com.sun.org.apache.xerces.internal.parsers.DTDConfiguration.parse(DTDConfiguration.java:546)抛出来的。该段代码结构如下:

if(fParseInProgress) {
    throw new XNIException("FWK005 parse may not be called while parsing.");
}

fParseInProgress = true;

// 解析xml文件

finally {
    fParseInProgress = false;
}

从程序逻辑来看,如果当前DocumentBuilder对象正在转换文档,此时再次请求转换文档,那么直接抛出XNIException(“FWK005 parse may not be called while parsing.”);异常。

这个问题也比较好解决,一种是对转换xml文档的方法,增加synchronized关键字,这样子不会有两个线程同时访问方法。

还有一种方法是创建一个DocumentBuilder类型的ThreadLocal变量,这样子每个线程都拥有自己的DocumentBuilder对象,能够同时转换多个xml文件。代码如下:

private static ThreadLocal docBuildeIns = new ThreadLocal() {
    protected DocumentBuilder initialValue() {
        try {
            return DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            String msg = "DocumentBuilder 对象初始化失败!";
            log.error(msg, e);
            throw new IllegalStateException(msg, e);
        }
    }
};

解析xml文件时的调用方法:

docBuildIns.get().parse(File);

get()方法返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。

跟踪 C3p0 连接池连接泄漏之参数优化与日志分析


上一篇:跟踪 C3p0 连接池连接泄漏 我们有谈到c3p0的debugUnreturnedConnectionStackTraces、unreturnedConnectionTimeout参数的说明,此篇日志进一步介绍应用这两个参数后的一些经验。

首先,为了跟踪问题,debugUnreturnedConnectionStackTraces参数肯定是设置为true的。下面主要说明unreturnedConnectionTimeout参数的设置,unreturnedConnectionTimeout参数是在连接被应用程序checkout后指定时间内未checkin则由连接缓冲池执行kill操作,同时打印堆栈跟踪信息。在我的应用里,maxIdleTime的设置是120秒,所以,我把unreturnedConnectionTimeout设置成150秒,如果达到最大存活时间后,连接还是不能被连接缓冲池正常关闭,那么肯定出现了连接泄漏,此时,再过30秒后,由连接缓冲池主动执行kill。

通过以上设置后,确实收获了一些成果,通过分析日志,找到了连接泄漏问题代码。异常信息如下:

2011-04-06 15:49:42,599 INFO : com.mchange.v2.resourcepool.BasicResourcePool.removeResource(BasicResourcePool.java:1395) – Logging the stack trace by which the overdue resource was checked-out.
java.lang.Exception: DEBUG ONLY: Overdue resource check-out stack trace.
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:506)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:525)
at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:128)
at org.springframework.orm.hibernate3.LocalDataSourceConnectionProvider.getConnection(LocalDataSourceConnectionProvider.java:82)
at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:423)
at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:144)
at org.hibernate.jdbc.AbstractBatcher.prepareQueryStatement(AbstractBatcher.java:139)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1547)
at org.hibernate.loader.Loader.doQuery(Loader.java:673)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
at org.hibernate.loader.Loader.doList(Loader.java:2220)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2104)
at org.hibernate.loader.Loader.list(Loader.java:2099)
at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:289)
at org.hibernate.impl.SessionImpl.listCustomQuery(SessionImpl.java:1695)
at org.hibernate.impl.AbstractSessionImpl.list(AbstractSessionImpl.java:142)
at org.hibernate.impl.SQLQueryImpl.list(SQLQueryImpl.java:152)
at com.leo.dao.XXXDAO.queryXXX(XXXDAO.java:20)
at jsp_servlet._keyareas._country.__taskcdb_add._jspService(__taskcdb_add.java:195)
at weblogic.servlet.jsp.JspBase.service(JspBase.java:34)
at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:300)
at weblogic.servlet.internal.ServletStubImpl.onAddToMapException(ServletStubImpl.java:416)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:326)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:183)
at weblogic.servlet.internal.RequestDispatcherImpl.invokeServlet(RequestDispatcherImpl.java:526)
at weblogic.servlet.internal.RequestDispatcherImpl.forward(RequestDispatcherImpl.java:253)

通过堆栈信息,很容易就确认了问题点。这个问题点是由于DAO使用不规范导致的,XXXDAO继承了HibernateDaoSupport,在方法中使用了getHibernateTemplate().getSessionFactory().openSession()获得session,然后通过session.createSQLQuery(),随后却并没有执行session.close()。

关于日志大小:在目前maxIdleTime=120,maxPoolSize=18,minPoolSize=5,idleConnectionTestPeriod=30的情况下,日志PatternLayout %d %-5p: %l – %m%n,一天的日志记录情况大约是12M,在硬盘空间允许的情况下建议按天存储日志。

使用JRockit JVM运行eclipse


来源:Running Eclipse on JRockit JVM

Oracle收购Sun之后,曾有媒体爆出Oracle考虑整合Sun JVM和JRockit JVM,要知道JRockit JVM也不是Oracle自己开发的,来自于之前的BEA公司。其实作为开发人员,真心希望两个JVM整合,以提供更好的性能。最近就在折腾把eclipse以及tomcat的开发环境从Sun JVM切换到JRockit JVM。

虽然JRockit JVM是着眼与服务器端的高性能JVM,但是也能运行客户端程序,像eclipse就能在JRockit JVM上很好的运行。

一、最简单的配置方法

-showsplash
org.eclipse.platform
-vm
/opt/Oracle/jrockit-jdk1.6.0_22-R28.1.1-4.0.1/bin/java

二、指定堆内存大小

-showsplash
org.eclipse.platform
-vm
/opt/Oracle/jrockit-jdk1.6.0_22-R28.1.1-4.0.1/bin/java
-vmargs -Xms384m -Xmx384m

三、配置deterministic GC,提高响应速度

-showsplash
org.eclipse.platform
-vm
/opt/Oracle/jrockit-jdk1.6.0_22-R28.1.1-4.0.1/bin/java
-vmargs
-Xms384m
-Xmx384m
-XgcPrio:deterministic
-XpauseTarget:20
-XXcompactratio:1

安装完成后,从主观感受上来看,eclipse响应速度确实快了不少。客观上,使用384内存出来的效果和Sun JVM服务器模式运行512内存出来的效果一样,也就是说相比调整后Sun JVM(Eclipse JVM 性能调优),内存占用有所降低。

此外,JRockit JVM还提供了Mission Control for eclipse插件,可以在eclipse通过Mission Control perspective视图分析JVM工作情况。这个插件的安装方法也很简单。找到安装目录下的missioncontrol目录,将里面的featrues和plugins文件夹复制到eclipse中即可。

对于tomcat,我们在Server Runtime Environment中选择jrockit jvm即可实现使用jrockit jvm运行tomcat,启动速度那是飕飕的!!!有木有!!!

资源:
Oracle JRockit Online Documentation

解决JasperReport 导出 Html 时图片显示问题


JasperReport导出为Html时,图片显示不正常的问题,从项目一开始就困扰着我,今天终于解决了。太爽了!o(∩∩)o…

首先自己犯了一个错误,Html报表的图片输出没有使用net.sf.jasperreports.j2ee.servlets.ImageServlet,导致新打开的报表总是使用的之前报表的图片,且第一次输出报表时,图片不加载。

JasperReport导出Html报表的正确配置应该是

  1. 在web.xml配置net.sf.jaasperreports.j2ee.servlets.ImageServlet
  2. 设置JRHtmlExporterParameter.IMAGES_DIR_NAME,用于存放图片
  3. 导出报表时设置JRHtmlExporterParameter.IMAGES_URI变量,内容和web.xml中配置的ImageServlet的url-pattern一致
  4. 在Session中设置print对象
  5. 然后就可以执行导出了

下面是代码

String jasperFilePath = “WEB-INF/reports/test.jasper”;
// 获得报表模板文件对象
File reportFile = getApplicationContext().getResource(jasperFilePath).getFile();

JasperPrint print = JasperFillManager.fillReport(reportFile.getAbsolutePath(), map, dataSource.getConnection());

JRHtmlExporter exporter = new JRHtmlExporter();
// 组装图片请求URI,增加time参数,防止同一个报表的图片缓存。
String imageURI = request.getContextPath() + “/service/reportImages?time=” + System.currentTimeMillis() + “&image=” ;

exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, imageURI);
request.getSession().setAttribute(ImageServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE, print);
exporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN, Boolean.TRUE);
exporter.setParameter(JRExporterParameter. JASPER_PRINT, print);

exporter.setParameter(JRExporterParameter. OUTPUT_WRITER, response.getWriter());
exporter.setParameter(JRHtmlExporterParameter.FLUSH_OUTPUT, Boolean.TRUE);

exporter.exportReport();

参考:Chart caching issues with JasperReports web output? A solution!

Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.


今天碰到了一个很头疼的问题,自定义的一个XML文档,在eclipse中做测试的时候能够正常解析,但是一旦部署到服务器上就会出现下面的错误:

[Fatal Error] :1:1476: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.
net.sf.json.JSONException: nu.xom.ParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”. at line 1, column 1476
at net.sf.json.xml.XMLSerializer.read(XMLSerializer.java:331)
at net.sf.json.xml.XMLSerializer.FromStream(XMLSerializer.java:391)
at net.sf.json.xml.XMLSerializer.FromFile(XMLSerializer.java:355)
……
Caused by: nu.xom.ParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”. at line 1, column 1476
at nu.xom.Builder.build(Unknown Source)
at nu.xom.Builder.build(Unknown Source)
at net.sf.json.xml.XMLSerializer.read(XMLSerializer.java:309)
… 62 more
Caused by: org.xml.sax.SAXParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.

从错误提示来看,是有标签没有结束,清空文件内容,一点点的添加,逐步排查,发现所有的标签都正常结束了阿。按照异常提示,一层层阅读源代码,从json-lib.jar到xom.jar,最后恍然大悟,应该直接拿

org.xml.sax.SAXParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.

作为搜索条件,果不其然,使用这个关键字搜索的结果比net.sf.json.JSONException和nu.xom.ParsingException的结果多多了。

最后在关于dom4j解析编码的问题,org.xml.sax.SAXParseException: Invalid byte 1 of 1-byte UTF-8 sequence找到了关联答案,在二楼有人说

UTF-8编码中中文解析有问题
将编码格式改成“GB2312”后就可以正常解析了。<?xml version=”1.0″ encoding=”GB2312″?>

马上联想,这么奇怪的问题是不是也是编码问题导致的呢?最后把encoding从UTF-8修改为GB18030,问题解决。

疑问:为什么在eclipse中测试的时候没有出现问题,部署到tomcat或者weblogic都会出现这样的问题呢?

问题的根本原因找到了: 继续阅读