本文的完成感谢葛严大神授权使用LogUtil类,其次感谢Tavor大神的EBS OAF开发日志(见: EBS OAF开发中日志(Logging) )。
日志的使用是一门极大的学问,若读者有兴趣,可以自行选择阅读以下参考:
首先,尽量不要在代码中使用System.out.println()方法直接打印日志,虽然这在开发过程中极其便捷,但是也带来了以下影响
1.大量的 sop 将产生大量的IO操作 同时在生产环境中 无法合理的控制是否需要输出
2.专门的日志框架可以合理的控制日志 实现 文件 DB 控制分片容量 Email预警等。
OAF 框架本身也提供了日志级别,同时提供了日志输出方法fnd_log.STRING和writeDiagnostics方法,但是我觉得不太好用,日志级别倒是极好用的。
参考:Logging in OAF Pages – A Technical Note!
OAF: How to add logging / debug messages in Oracle Application Framework(需翻|墙)
writeDiagnostics() method of OAF(需翻|墙)
所以,只需要开启了FND 诊断(配置文件:FND 诊断),在地址栏输入 &AFLOG_LEVEL=ERROR(或其他日志级别)
如:http://devapp.xxxxxx.com:8080/OA_HTML/OAHOMEPAGE&AFLOG_LEVEL=ERROR即可在网页下方看到输出的日志了,在网页中显示日志使用的writeDiagnostics()方法。
关于JDeveloper集成log4j可参考我的另一篇博客:(OAF)jdeveloper集成log4j并将日志输出到指定文件并写入数据库
集成Logback同理,两种方式需要的配置文件也自行百度。
LogUtil类如下
package cux.oracle.apps.cux.common.util; import java.sql.CallableStatement; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.Date; import oracle.apps.fnd.framework.OAException; import oracle.apps.fnd.framework.OAFwkConstants; import oracle.apps.fnd.framework.server.OADBTransaction; import oracle.apps.fnd.framework.server.OAEntityImpl; import oracle.apps.fnd.framework.server.OAViewObjectImpl; import oracle.apps.fnd.framework.server.OAViewRowImpl; import oracle.apps.fnd.framework.webui.OAPageContext; import oracle.jbo.ApplicationModule; import oracle.jbo.Row; import oracle.jbo.ViewObject; /** * LogUtil 实现Log输出的核心逻辑,它可以在CO,AM,EO,VO中,以及 * 任何可以获得OADBTransaction的地方使用。 * * 静态工程方法of负责初始化log的内容,内容分为类名称和详细内容,详细内容 * 可以是一个String,也可以是一个二维数组。内容经过格式化后,通过print输出。 * print方法在输出的时候,必须依靠LogContext参数,来确定输出方式和输出层次上。 * * 对于LogContext参数,传入的是一个实现了LogContext接口的一个calss,通常, * 对于特定的功能开发最好新建一个这样的calss,这样此功能开发的log输出就可以 * 很好地和其他log输出分离,且可以独立地控制输出方式和输出层次。 * * Log内容包含三种信息:类名称,必须具有。方法名称和变量的值,非必须。 * * GEYAN 2010-01-06 */ /** * 2010-06-05 改进建议: * 是否命令行输出,可通过 isDeveloperMode;诊断输出也没有必要依靠一个开关,因为它本身就具有开关。 * 因而完全可以将 LogUtil 和 LogContext 构成的手柄模式合并为单类,或者至少可以进一步简化。 */ /** * 2010-06-08 * 新的版本不再依赖 LogContext ,代之以 logLevel 默认值,以及可以改变此默认值的 Builder 模式实现。 * 而输出开关则依赖Oracle本身的机制,isDeveloperMode 和 isLoggingEnabled , * 这样更加合理,且不会影响性能。 * */ public class LogUtil { private String[][] output = new String[1][2]; private String className; //private final static org.slf4j.Logger logger = LoggerFactory.getLogger(LogUtil.class.getName()); /** * 输出层次定义, logLevel, * 是 oracle.apps.fnd.framework.OAFwkConstants 的六个常量, 值依次从1到6. * OAFwkConstants.STATEMENT * OAFwkConstants.PROCEDURE * OAFwkConstants.EVENT * OAFwkConstants.EXCEPTION * OAFwkConstants.ERROR * OAFwkConstants.UNEXPECTED * * 系统中默认的是 OAFwkConstants.UNEXPECTED,故默认层次不能选择UNEXPECTED。 * * 这里默认为 OAFwkConstants.ERROR,通过logLevel方法来更改其所需的值。 * * &aflog_level=ERROR */ private int logLevel = OAFwkConstants.ERROR; private LogUtil(String str, Object object) { output = new String[1][2]; output[0][0] = str; this.className = getClassName(object); } private LogUtil(String[][] stra, Object object) { output = new String[stra.length][2]; System.arraycopy(stra, 0, output, 0, output.length); this.className = getClassName(object); } public LogUtil logLevel(int logLevel) { this.logLevel = logLevel; return this; } /** * object的作用是得到类的名称,它可以是this指针,也可以是String形式的类名称。 * * 通常object的参数的值是this,通过this.getClass().getName()来获取类名称。 * 但是在static方法里,this指针不可用,只有以String形式传入类名称。 */ private static String getClassName(Object object) { if (java.lang.String.class.equals(object.getClass())) { return object.toString(); } return object.getClass().getName(); } /** * 两个静态工厂方法of,分别构造单个字符串和二维字符串数组类型的 LogUtil 实例。 * * object参数应该传入当前调用所在的类的this指针,通过this得到类名称。 * 在static方法中,则以String形式传入类名称。 */ public static LogUtil of(String str, Object object) { return new LogUtil(str, object); } public static LogUtil of(String[][] stra, Object object) { return new LogUtil(stra, object); } private String getCurrentTime() { Date currentTime = new Date(System.currentTimeMillis()); return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(currentTime); } private String getOutput() { // String result = // " -------------------------------------------------> log : [" + getCurrentTime() + "] "; // // result = result + " & " + "class" + " is " + "[" + className + "] "; // // if (output.length == 1 && (output[0][1] == null || "".equals(output[0][1]))) // { // return result + " " + output[0][0] + " "; // } // // for (int i = 0; i < output.length; i++) // { // if (output[i] != null) // { // result = result + " & " + output[i][0] + " is " + output[i][1] + " "; // } // } // // return result; StringBuffer result = new StringBuffer(); result.append(" -------------------------------------------------> log : [" + getCurrentTime() + "] "); result.append(" & " + "class" + " is " + "[" + className + "] "); //等价 // StackTraceElement[] trace = new Throwable().getStackTrace(); StackTraceElement[] trace = Thread.currentThread().getStackTrace(); for (int len=0; len<trace.length; len++){ // trace的长度为从函数入口到print方法执行过的函数层数 // 下标为0的元素是上一行语句的信息, 下标为1的才是调用print的地方的信息 StackTraceElement tmp = trace[len]; if(tmp.getClassName().length()>=6 && "oracle".equals(tmp.getClassName().substring(0,6))) //函数的入口为java.lang.Thread.run //中间的层次结构太长 //故循环到标准方法即停止,仅显示客户化调用 break; result.append(" & "+ tmp.getClassName() + "." + tmp.getMethodName() + "(" + tmp.getFileName() + ":" + tmp.getLineNumber() + ") " ); } if (output.length == 1 && (output[0][1] == null || "".equals(output[0][1]))) { return result + " " + output[0][0] + " "; } for (int i = 0; i < output.length; i++) { if (output[i] != null) { result.append(" & " + output[i][0] + " is " + output[i][1] + " "); } } return result.toString(); } // /** // * 2010-06-08 // * 不再推荐以下的委托LogContext对象的几个版本,推荐使用不带LogContext的版本。 // * // * 提供在CO,AM,VO,EO,中常用的版本,和一个通用版本OADBTransaction tsn。 // * // * */ // public void print(OAPageContext pageContext, LogContext logContext) // { // String output = getOutput(); // // if(logContext.isCommandLineLog()) // { // System.out.print(output); // } // // if(logContext.isDiagnosticLog()) // { // if(pageContext.isLoggingEnabled(logContext.getLogLevel())) // { // pageContext.writeDiagnostics(this.className, output, logContext.getLogLevel()); // } // } // // } // // public void print(OADBTransaction tsn, LogContext logContext) // { // String output = getOutput(); // // if (logContext.isCommandLineLog()) // { // System.out.print(output); // } // // if(logContext.isDiagnosticLog()) // { // if(tsn.isLoggingEnabled(logContext.getLogLevel())) // { // tsn.writeDiagnostics(this.className, output, logContext.getLogLevel()); // } // } // // } // // public void print(OAApplicationModule am, LogContext logContext) // { // OADBTransaction tsn = am.getOADBTransaction(); // print(tsn, logContext); // } // // public void print(OAViewObjectImpl vo, LogContext logContext) // { // OADBTransaction tsn = (OADBTransaction)vo.getDBTransaction(); // print(tsn, logContext); // } // // public void print(OAViewRowImpl voRow, LogContext logContext) // { // OAViewObjectImpl vo = (OAViewObjectImpl)voRow.getViewObject(); // OADBTransaction tsn = (OADBTransaction)vo.getDBTransaction(); // print(tsn, logContext); // } // // public void print(OAEntityImpl eo, LogContext logContext) // { // OADBTransaction tsn = (OADBTransaction)eo.getDBTransaction(); // print(tsn, logContext); // } /** * 2010-06-08 推荐使用以下几个不带LogContext的版本。 * * 提供在 CO,AM,VO,EO,中常用的版本,和一个通用版本 OADBTransaction tsn。 */ public void print(OAPageContext pageContext) { OADBTransaction tsn = pageContext.getRootApplicationModule().getOADBTransaction(); print(tsn); } public void print(OADBTransaction tsn) { String output = getOutput(); //开发者模式 if (tsn.isDeveloperMode()) { System.out.print(output); //Update By hongbo //不再使用System.out.print方法,代之以LogBack // logger.info(className); //System.out.println("className is "+className); // logger.info(output); // org.slf4j.Logger log = LoggerFactory.getLogger(className); // log.info(output); //End } if (tsn.isLoggingEnabled(this.logLevel)) { tsn.writeDiagnostics(this.className, output, this.logLevel); cux_log_messages(tsn, output); } //Add By hongbo,20160525 //此处使用集成slf4j实现日志输出,具体实现可自行百度 //将Logback或者log4j相关jar包引入工程目录 (服务器放入$JAVA_TOP目录) //并配置相关配置文件logback.xml或log4j.propertites 放入工程根目录,即myprojects目录 // org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(className); // log.info(output); //此处直接使用log4j实现日志输出 // org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(className); // logger.info(output); //End // /* // * 通过配置文件 CUX_LOG_MESSAGES_FILE,取得log文件的全路径。 // * 例如 /usr/tmp/user_log_temp.log,再将日志写入此文件。 // */ // String logFile = tsn.getProfile("CUX_LOG_MESSAGES_FILE"); // if (logFile != null && !"".equals(logFile)) // { // FileUtil.write(logFile, getOutput()); // } } private void cux_log_messages(OADBTransaction tsn, String output) { try { //cux_common_util_pkg各位自己去实现,此处就不罗列了 String sql = "begin cux_common_util_pkg.cux_log_messages(p_log_sequence => :1, " + " p_module => :2, " + " p_log_level => :3, " + " p_message_text => :4); end;"; CallableStatement cs = tsn.createCallableStatement(sql, 1); cs.setLong(1, System.currentTimeMillis()); cs.setString(2, this.className); cs.setInt(3, this.logLevel); cs.setString(4, output); cs.execute(); cs.close(); } catch (SQLException e) { throw OAException.wrapperException(e); } } public void print(ApplicationModule am) { OADBTransaction tsn = (OADBTransaction) am.getTransaction(); print(tsn); } public void print(ViewObject vo) { OADBTransaction tsn = (OADBTransaction) ((OAViewObjectImpl) vo).getDBTransaction(); print(tsn); } public void print(Row row) { OAViewObjectImpl vo = (OAViewObjectImpl) ((OAViewRowImpl) row).getViewObject(); OADBTransaction tsn = (OADBTransaction) vo.getDBTransaction(); print(tsn); } public void print(OAEntityImpl eo) { OADBTransaction tsn = (OADBTransaction) eo.getDBTransaction(); print(tsn); } public void printScreen(OAPageContext pageContext) { OADBTransaction tsn = pageContext.getRootApplicationModule().getOADBTransaction(); printScreen(tsn); } public void printScreen(ApplicationModule am) { OADBTransaction tsn = (OADBTransaction)am.getTransaction(); printScreen(tsn); } public void printScreen(OADBTransaction tsn) { String output = getOutput(); if (tsn.isDeveloperMode()) { System.out.print(output); } if (tsn.isLoggingEnabled(this.logLevel)) { tsn.writeDiagnostics(this.className, output, this.logLevel); } } }
调用方法如下:
CO中LogUtil.of(stringContent or String[][] , this ).print(pageContext);
其他如EO,AM,VO等直接LogUtil.of(String or String[][], this).print(this);
网页显示日志在开启诊断之后在地址栏后加入 &aflog_level=error(日志级别自定义)即可。
VO对象常用类:ModelUtil
package cuxA.oracle.apps.cux.common.util; import java.sql.SQLException; import oracle.apps.fnd.common.MessageToken; import oracle.apps.fnd.framework.OAApplicationModule; import oracle.apps.fnd.framework.OAException; import oracle.apps.fnd.framework.OANLSServices; import oracle.apps.fnd.framework.server.OADBTransaction; import oracle.apps.fnd.framework.server.OAViewObjectImpl; import oracle.apps.fnd.framework.webui.OAPageContext; import oracle.jbo.ApplicationModule; import oracle.jbo.Row; import oracle.jbo.ViewLink; import oracle.jbo.ViewObject; /** * 提供mvc之Model端的常用工具。 * * geyan 2010-01-06 */ public class ModelUtil { private ModelUtil() { } public static final String CLASS_NAME = "com.ncgc.oracle.apps.cux.por.util.ModelUtil"; /** * 以一种简短的方式获得 OANLSServices,进而获得OANLSServices提供的一系列服务。 */ public static OANLSServices getNls(OAApplicationModule am) { return am.getOADBTransaction().getOANLSServices(); } public static OANLSServices getNls(OADBTransaction tsn) { return tsn.getOANLSServices(); } public static oracle.jbo.domain.Number stringToNumber(String str) { try { return new oracle.jbo.domain.Number(str); } catch (SQLException e) { throw OAException.wrapperException(e); } } public static void clearVoCache(ViewObject vo) { vo.clearCache(); vo.setMaxFetchSize(-1); vo.setWhereClauseParams(null); vo.setWhereClause("1=2"); vo.executeQuery(); } public static void clearVoCache(ApplicationModule am, String voName) { ViewObject vo = findVo(am, voName); clearVoCache(vo); } public static ApplicationModule getChildAm(ApplicationModule parentAm, String childAmName) { String[] amNames = parentAm.getApplicationModuleNames(); for (int i = 0; i < amNames.length; i++) { if (amNames[i].endsWith(childAmName)) { return parentAm.findApplicationModule(amNames[i]); } else { ApplicationModule childAm = parentAm.findApplicationModule(amNames[i]); ApplicationModule am = getChildAm(childAm, childAmName); if (am != null) { return am; } } } return null; } public static ApplicationModule getChildAm(OAPageContext pageContext, String childAmName) { ApplicationModule parentAm = pageContext.getRootApplicationModule(); return ModelUtil.getChildAm(parentAm, childAmName); } public static ViewObject findVo(ApplicationModule am, String voName) { ViewObject vo = am.findViewObject(voName); if (vo == null) { MessageToken[] errTokens = { new MessageToken("OBJECT_NAME", voName), }; throw new OAException("AK", "FWK_TBX_OBJECT_NOT_FOUND", errTokens); } return vo; } public static ApplicationModule findAm(ApplicationModule parentAm, String amName) { ApplicationModule am = parentAm.findApplicationModule(amName); if (am == null) { MessageToken[] errTokens = { new MessageToken("OBJECT_NAME", amName), }; throw new OAException("AK", "FWK_TBX_OBJECT_NOT_FOUND", errTokens); } return am; } /** * 此方法返回 vo 当前 fetch 的所有行。 */ public static Row[] getVoFetchedRows(ApplicationModule am, String voName) { ViewObject vo = findVo(am, voName); return getVoFetchedRows(vo); } public static Row[] getVoFetchedRows(ViewObject vo) { String[][] stra = { { "method", "getVoFetchedRows" }, { "vo.getName()", vo.getName() }, { "vo.isExecuted()", vo.isExecuted() + "" }, { "vo.getFetchedRowCount()", vo.getFetchedRowCount() + "" }, }; LogUtil.of(stra, CLASS_NAME).print(vo); if (!vo.isExecuted()) { return new Row[0]; } vo.setRangeStart(0); vo.setRangeSize(-1); return vo.getAllRowsInRange() != null ? vo.getAllRowsInRange() : new Row[0]; } /** * 通过 attributeValue 删除Vo当前Fetch到的行,一行或多行。 */ public static void removeVoFetchedRow(ViewObject vo, String attributeName, String attributeValue) { Row[] rows = getVoFetchedRows(vo); String[][] stra = { { "method", "removeVoFetchedRow" }, { "attributeName", attributeName }, { "attributeValue", attributeValue }, { "rows.length", rows.length + "" }, }; LogUtil.of(stra, CLASS_NAME).print(vo); for (int i = 0; i < rows.length; i++) { String value$row = rows[i].getAttribute(attributeName).toString(); if (value$row.equals(attributeValue)) { rows[i].remove(); } } } public static void removeVoFetchedRow(ApplicationModule am, String voName, String pkAttributeName, String pkValue) { ViewObject vo = findVo(am, voName); removeVoFetchedRow(vo, pkAttributeName, pkValue); } /** * 返回 "多选框"选择的行。 * 此方法简化了"未选择行","返回行为空"的处理。 */ public static Row[] getVoFilteredRows(ApplicationModule am, String voName, String attribute) { ViewObject vo = findVo(am, voName); return getVoFilteredRows(vo, attribute); } /** * 返回 "多选框"选择的行。 * 此方法简化了"未选择行","返回行为空"的处理。 */ public static Row[] getFilteredRows(OAApplicationModule am, String voName, String attribute) { ViewObject vo = findVo(am, voName); // 若vo尚无任何行,则返回 0长度数组 vo.reset(); if (!vo.hasNext()) { return new Row[0]; } Row[] rows = vo.getFilteredRows(attribute, "Y"); return rows != null && rows.length > 0 ? rows : new Row[0]; } public static Row[] getVoFilteredRows(ViewObject vo, String attribute) { // 若vo尚无任何行,则返回 0长度数组 vo.reset(); if (!vo.hasNext()) { return new Row[0]; } Row[] rows = vo.getFilteredRows(attribute, "Y"); return rows != null && rows.length > 0 ? rows : new Row[0]; } public static Row getVoFirstFilteredRow(ApplicationModule am, String voName, String attribute) { OAViewObjectImpl vo = (OAViewObjectImpl)findVo(am, voName); return getVoFirstFilteredRow(vo, attribute); } public static Row getVoFirstFilteredRow(OAViewObjectImpl vo, String attribute) { return vo.getFirstFilteredRow(attribute, "Y"); } /** * remove当前am中的一个vo。 */ public static void removeVo(ApplicationModule am, String voName) { ViewObject vo = findVo(am, voName); vo.remove(); } /** * 返回"下一个"序号,给定当前VO和其序号所在的列,返回现有列最大值加1的数字。 */ public static Integer getNextSeqNum(ViewObject vo, String attributeName) { Row[] rows = getVoFetchedRows(vo); if (rows == null || rows.length == 0) { return 1; } // 否则,依次比较并返回 "最大值 + 1" int result = 0; for (int i = 0; i < rows.length; i++) { Object seqNum$row = rows[i].getAttribute(attributeName); if (seqNum$row == null) { continue; } int seqNumRowInt = Integer.valueOf(seqNum$row.toString()); result = seqNumRowInt > result ? seqNumRowInt : result; } return result + 1; } public static Integer getNextSeqNum(ApplicationModule am, String voName, String attributeName) { ViewObject vo = ModelUtil.findVo(am, voName); return getNextSeqNum(vo, attributeName); } public static void commit(OAApplicationModule am, boolean isClearEoCache) { OADBTransaction tsn = am.getOADBTransaction(); commit(tsn, isClearEoCache); } public static void commit(OADBTransaction tsn, boolean isClearEoCache) { boolean isClearCacheOnCommit = tsn.isClearCacheOnCommit(); // ClearCacheOnCommit tsn.setClearCacheOnCommit(isClearEoCache); try { tsn.commit(); } catch (OAException e) { throw e; } // 恢复原先设置 tsn.setClearCacheOnCommit(isClearCacheOnCommit); } public static void rollback(OAApplicationModule am, boolean isClearEoCache) { OADBTransaction tsn = am.getOADBTransaction(); rollback(tsn, isClearEoCache); } public static void rollback(OADBTransaction tsn, boolean isClearEoCache) { boolean isClearCacheOnRollback = tsn.isClearCacheOnRollback(); // ClearCacheOnRollback tsn.setClearCacheOnRollback(isClearEoCache); if (tsn.isDirty()) { tsn.rollback(); } // 恢复原先设置 tsn.setClearCacheOnRollback(isClearCacheOnRollback); } public static ViewObject createVo(ApplicationModule am, String voName, String voDefine) { ViewObject vo = am.findViewObject(voName); if (vo != null) { return vo; } return am.createViewObject(voName, voDefine); } public static Object[] getVoWhereClauseParams(ViewObject vo) { Object[] objects = vo.getWhereClauseParams(); if (objects == null) { return null; } Object[] result = new Object[objects.length]; for (int i = 0; i < objects.length; i++) { if (objects[i].getClass().isArray()) { Object[] params = (Object[])objects[i]; Object param$index = params[0]; Object param$value = params[1]; result[i] = param$value; } else { Object param$value = objects[i]; result[i] = param$value; } } return result; } public static void outputVo(ViewObject vo) { String[][] stra = { { "method", "outputVo" }, { "vo.getName()", vo.getName() }, { "vo.getFullName()", vo.getFullName() }, { "vo.getDefFullName()", vo.getDefFullName() }, { "vo.isExecuted()", vo.isExecuted() + "" }, { "vo.getRangeSize()", vo.getRangeSize() + "" }, { "vo.getRangeStart()", vo.getRangeStart() + "" }, { "vo.getFetchedRowCount()", vo.getFetchedRowCount() + "" }, { "vo.getCurrentRowIndex()", vo.getCurrentRowIndex() + "" }, { "vo.getMaxFetchSize()", vo.getMaxFetchSize() + "" }, { "vo.getWhereClause()", vo.getWhereClause() }, { "getVoWhereClauseParams(vo)", arrayToString(getVoWhereClauseParams(vo)) }, { "vo.getOrderByClause()", vo.getOrderByClause() }, { "vo.getQuery()", vo.getQuery() }, }; LogUtil.of(stra, CLASS_NAME).print(vo); } public static void outputVo(ApplicationModule am, String voName) { ViewObject vo = ModelUtil.findVo(am, voName); outputVo(vo); } public static String arrayToString(Object[] arrary) { if (arrary == null || arrary.length == 0) { return null; } String result = ""; for (int i = 0; i < arrary.length; i++) { result = result + arrary[i] + " , "; } return " [ " + result + " ] "; } public static void displayModels(ApplicationModule am) { String[] ams = am.getApplicationModuleNames(); for (int i = 0; i < ams.length; i++) { ApplicationModule am$i = am.findApplicationModule(ams[i]); String[][] stra = { { "method", "displayModels" }, { "for", i + " --> display ApplicationModule" }, { "parent AM", am.getFullName() }, { "child AM Name", am$i.getName() }, { "child AM FullName", am$i.getFullName() }, { "child AM DefFullName", am$i.getDefFullName() }, }; LogUtil.of(stra, CLASS_NAME).print(am); } String[] vos = am.getViewObjectNames(); for (int i = 0; i < vos.length; i++) { ViewObject vo$i = am.findViewObject(vos[i]); String[][] stra = { { "method", "displayModels" }, { "for", i + " --> display ViewObject" }, { "parent AM", am.getFullName() }, { "child VO Name", vo$i.getName() }, { "child VO FullName", vo$i.getFullName() }, { "child VO DefFullName", vo$i.getDefFullName() }, }; LogUtil.of(stra, CLASS_NAME).print(am); } String[] vls = am.getViewLinkNames(); for (int i = 0; i < vls.length; i++) { ViewLink vl$i = am.findViewLink(vls[i]); String[][] stra = { { "method", "displayModels" }, { "for", i + " --> display ViewLink" }, { "parent AM", am.getFullName() }, { "child VL Name", vl$i.getName() }, { "child VL FullName", vl$i.getFullName() }, { "child VL DefFullName", vl$i.getDefFullName() }, }; LogUtil.of(stra, CLASS_NAME).print(am); } for (int i = 0; i < ams.length; i++) { displayModels(am.findApplicationModule(ams[i])); } } }
输出VO对象信息:ModelUtil.outputVo(voInstance);