这是我生命中的第一次,我发现自己处于一个可以开源的Java API的位置.希望被包括在许多其他项目中.
对于日志记录我(以及与我一起工作的人)总是使用JUL(java.util.logging)并且从未遇到任何问题.但是现在我需要更详细地了解我应该为我的API开发做些什么.我已经对此做了一些研究,并且我得到的信息让我感到更加困惑.因此这篇文章.
由于我来自JUL,我对此持偏见态度.我对其余的知识并不是那么大.
从我所做的研究中我得出了人们不喜欢JUL的原因:
"在Sun发布JUL之前,我开始使用Java进行开发,而且我更容易继续使用logging-framework-X而不是学习新东西".嗯.我不是在开玩笑,这实际上就是人们所说的.有了这个论点,我们都可以做COBOL.(但我当然可以说这是一个懒惰的家伙)
"我不喜欢JUL中日志记录级别的名称".好吧,说真的,这还不足以成为引入新依赖的理由.
"我不喜欢JUL输出的标准格式".嗯.这只是配置.你甚至不需要做任何代码方面的事情.(确实,过去你可能不得不创建自己的Formatter类来实现它).
"我使用其他也使用logging-framework-X的库,所以我觉得使用那个更容易".这是一个循环论证,不是吗?为什么'每个人'都使用logging-framework-X而不是JUL?
"其他人都在使用logging-framework-X".这对我来说只是上面的一个特例.多数并不总是正确的.
所以真正的大问题是为什么不是JUL?.我错过了什么?伐木立面的存在理由(SLF4J,JCL)是历史上存在多种伐木实施,其原因可以追溯到JUL之前的时代,正如我所看到的那样.如果JUL是完美的那么伐木外墙将不存在,或者什么?我们不应该首先质疑为什么它们是必要的而不是拥抱它们?(看看这些原因是否仍然存在)
好吧,到目前为止我的研究已经导致我可以看到的一些事情可能是JUL的真正问题:
表现.有人说SLF4J的表现优于其他表现.在我看来,这是一个过早优化的案例.如果你需要每秒记录数百兆字节,那么无论如何我都不确定你是否在正确的路径上.JUL也在不断发展,你在Java 1.4上做的测试可能不再适用.你可以在这里阅读它,这个修复已经成为Java 7.许多人还谈到了日志记录方法中字符串连接的开销.但是,基于模板的日志记录可以避免这种成本,并且它也存在于JUL中.我个人从来没有真正编写基于模板的日志记录 太懒了.例如,如果我使用JUL执行此操作:
log.finest("Lookup request from username=" + username
+ ", valueX=" + valueX
+ ", valueY=" + valueY));
Run Code Online (Sandbox Code Playgroud)
我的IDE会警告我并请求允许它将其更改为:
log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}",
new Object[]{username, valueX, valueY});
Run Code Online (Sandbox Code Playgroud)
..我当然会接受.许可授予 !谢谢您的帮助.
所以我自己并没有自己编写这样的语句,这是由IDE完成的.
关于性能问题的结论我没有发现任何迹象表明JUL的表现与竞争对手相比并不好.
从类路径配置.开箱即用的JUL无法从类路径加载配置文件.要做到这一点,需要几行代码.我可以看出为什么这可能很烦人,但解决方案简短而简单.
输出处理程序的可用性.JUL带有5个开箱即用的输出处理程序:控制台,文件流,套接字和内存.这些可以扩展或可以编写新的.例如,这可能是写入UNIX/Linux Syslog和Windows事件日志.我个人从来没有这个要求,也没有看过它,但我当然可以说明为什么它可能是一个有用的功能.例如,Logback附带了Syslog的附加程序.我仍然会争辩
我真的很担心我忽略了一些东西.除了JUL之外,使用伐木外墙和伐木实施是如此普遍,我必须得出结论,我只是不明白.那恐怕不是第一次.:-)
那我该怎么办?我希望它成功.我当然可以"顺其自然"并实施SLF4J(这些日子似乎最受欢迎)但是为了我自己的缘故,我仍然需要明白今天的JUL究竟出了什么问题才能保证所有的模糊?我会为我的图书馆选择JUL来破坏自己吗?
(nolan600于2012年7月7日添加的部分)
下面有一篇来自Ceki的参考文献,其中提到SLF4J的参数化比JUL快10倍或更快.所以我开始做一些简单的测试.乍一看,这种说法肯定是正确的.以下是初步结果(但请继续阅读!):
上面的数字是msecs,所以越少越好.因此,10倍的性能差异实际上非常接近.我最初的反应:这是很多!
这是测试的核心.可以看出,整数和字符串是在循环中构造的,然后在log语句中使用:
for …Run Code Online (Sandbox Code Playgroud) 你很快就会发现JDK8在Javadoc方面要严格得多(默认情况下).(链接 - 见最后一点)
如果你从来没有生成任何Javadoc那么你当然不会遇到任何问题,但Maven发布过程之类的事情以及可能你的CI版本会突然失败,因为它们与JDK7一起工作得很好.任何检查Javadoc工具退出值的东西现在都会失败.warnings与JDK7相比,JDK8 Javadoc可能也更冗长,但这不是这里的范围.我们在谈论errors!
存在这个问题是为了收集有关如何处理的提案.什么是最好的方法?是否应该在源代码文件中一劳永逸地修复这些错误?如果你有一个巨大的代码库,这可能是很多工作.还有哪些其他选择?
您也可以评论以前通过的失败的故事.
wsimport工具是用于创建Web服务使用者的代码生成器.它包含在JDK中.即使您使用wsimportJDK8中的工具,它仍然会生成无法使用JDK8中的javadoc编译器编译的源代码.
我打开3-4岁的源代码文件,看到这个:
/**
* My very best class
* @author John <john.doe@mine.com>
*/
Run Code Online (Sandbox Code Playgroud)
现在由于<字符而失败.严格来说,这是合理的,但不是很宽容.
Javadoc中的HTML表格?考虑这个有效的HTML:
/**
*
* <table>
* <tr>
* <td>Col1</td><td>Col2</td><td>Col3</td>
* </tr>
* </table>
*/
Run Code Online (Sandbox Code Playgroud)
这现在失败并显示错误消息no summary or caption for table.一个快速解决方法是这样做:
/**
*
* <table summary="">
* <tr>
* <td>Col1</td><td>Col2</td><td>Col3</td>
* </tr>
* </table>
*/
Run Code Online (Sandbox Code Playgroud)
但为什么这必须是一个来自Javadoc工具的停止世界错误击败我?
{@link notexist}always returns <code>true<code> …有一个创建仅限时间的Date对象的函数.(为什么这是必需的是一个长篇故事,在这种情况下是无关紧要的,但我需要与XML世界中的一些东西进行比较,其中TIME(即仅时间)是一个有效的概念).
private static final SimpleDateFormat DF_TIMEONLY = new SimpleDateFormat("HH:mm:ss.SSSZ");
public static Date getCurrentTimeOnly() {
String onlyTimeStr = DF_TIMEONLY.format(new Date()); // line #5
Date onlyTimeDt = null;
try {
onlyTimeDt = DF_TIMEONLY.parse(onlyTimeStr); // line #8
} catch (ParseException ex) {
// can never happen (you would think!)
}
return onlyTimeDt;
}
Run Code Online (Sandbox Code Playgroud)
可能至少有几种方法可以在Java中创建一个仅限日期的日期(或者更确切地说,日期部分是1970-01-01),但我的问题实际上并非如此.
我的问题是这段代码在生产运行很长时间后开始在#8行上随机抛出NumberFormatException.从技术上讲,我会说这应该是不可能的,对吧?
以下是来自上面一段代码的随机NumberFormatExceptions的摘录:
java.lang.NumberFormatException: multiple points
java.lang.NumberFormatException: For input string: ".11331133EE22"
java.lang.NumberFormatException: For input string: "880044E.3880044"
java.lang.NumberFormatException: For input string: "880044E.3880044E3"
Run Code Online (Sandbox Code Playgroud)
首先,我希望我们能够正式认为这应该是不可能的吗?代码使用相同的format(DF_TIMEONLY)作为输出然后输入.如果你不同意它应该是不可能的,请告诉我.
我无法在独立环境中重新生成问题.当JVM运行很长时间(> 1周)时,问题似乎就出现了.我无法找到问题的模式,即夏令时/冬令时,上午/下午等.错误是零星的,这意味着一分钟它将抛出NumberFormatException,下一分钟它将运行正常. …
这个问题似乎非常简单,但我很尴尬地说我无法弄清楚它是如何工作的.
我希望我的Java SE应用程序从操作系统中配置的任何内容中获取其数字格式.就像该OS上的任何其他应用程序一样.
我的目标主要是Windows.在Windows中考虑此示例:

我用这个小例子来检查结果:
public class MyMain {
public static void main(String[] args) {
DecimalFormat decFormat = new DecimalFormat();
DecimalFormatSymbols decSymbols = decFormat.getDecimalFormatSymbols();
System.out.println("Decimal separator is : " + decSymbols.getDecimalSeparator());
System.out.println("Thousands separator is : " + decSymbols.getGroupingSeparator());
}
}
Run Code Online (Sandbox Code Playgroud)
通过上面的截图示例,我的小Java示例打印:
Decimal separator is : ,
Thousands separator is : .
Run Code Online (Sandbox Code Playgroud)
我原以为它会说小数点分隔符是一个点而千位分隔符是一个逗号 - 因为这就是我告诉Windows的内容,而这正是所有其他Windows应用程序所接受的.
我的印象是,在Windows中,这些设置会对新进程生效.我试过重启我的IDE.没有帮助.
我究竟做错了什么?
与JDK7相比,我发现在JDK8 javadoc中难以阅读新的外观.这是一个并排的例子.
JDK7:

JDK8:

JDK8占用了相当多的空间.它现在使用之前使用Arial的DejaVu字体.可能有充分的理由.不知道.
我最大的问题是在"参数"和"引发"部分中,参数与其描述之间不再存在任何视觉差异.它们都是单声道间隔字体.我认为用单声道间隔字体书写描述性文字只是丑陋.单调间隔字体用于标识符,源代码列表等的名称.(随意不同意).
在使用JDK8 javadoc工具的同时,我可以恢复JDK7样式吗?
我希望这样的事情javadoc -stylesheet jdk7.css在那里jdk7.css被包含在JDK8东西.此外,如果我决定自己定制css(不是我的东西,但可能没有其他解决方案),我不愿意在我们企业的每个构建服务器上确保新样式表的可用性.也许有一个Maven解决方案呢?
有人建议(下面)使用JDK7 javadoc css和JDK8 javadoc工具来查看是否会带回一些符合条件的Javadoc.
我通过检查Apache Commons Lang项目的源代码完成了我的测试.我只使用源代码,而不是他们的POM.这是为了确保我知道我在正确的基础上工作.
好的,首先 - 供参考 - 这是由所有JDK7工具链(JDK7 javadoc工具,JDK7 css)生成的Javadoc.这是POM片段:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<stylesheetfile>${basedir}/src/main/css/jdk7javadoc.css</stylesheetfile>
<javadocExecutable>C:/Program Files/Java/jdk1.7.0_55/bin</javadocExecutable>
</configuration>
</plugin>
</plugins>
</build>
Run Code Online (Sandbox Code Playgroud)
以及由此产生的Javadoc:

接下来,尝试将JDK7 css与JDK8 javadoc工具一起使用.这是POM片段:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<stylesheetfile>${basedir}/src/main/css/jdk7javadoc.css</stylesheetfile>
<javadocExecutable>C:/Program Files/Java/jdk1.8.0_05/bin</javadocExecutable>
</configuration>
</plugin>
</plugins>
</build>
Run Code Online (Sandbox Code Playgroud)
以及由此产生的Javadoc:

所以,正如你所看到的,这个策略对我没有用.
我刚刚意识到这种变化的结果是在参数描述上使用{@code }(或<code>)标记变得毫无意义.它无论如何都没有显示出来.换句话说,如果你喜欢在过去喜欢这样做:
/**
* ...
* @param …Run Code Online (Sandbox Code Playgroud) 要在Windows上使用Java进行客户端HTTP SPNEGO身份验证,您需要设置Windows注册表项allowtgtsessionkey.这是有据可查的.我不明白的是人们如何解决这个问题?为了单个软件,大多数企业站点永远不会接受在Windows中更改此注册表项.如果需要在组织中的每个工作站上更改它,请考虑一下麻烦.但这只是理论,因为我到目前为止还无法说服我们的任何客户更改此注册表项.
我不怪他们.大多数企业管理员会认为这样可以放松安全性并因此反对它.
我已经读过: 在Java或命令行工具中是否有办法使用本机SSPI API获取服务的Kerberos票证?
但它现在已经很老了.
所以我真的,真的不明白人们如何使Windows + Java客户端+ Kerberos可以在除大学环境,家庭用户等之外的任何地方工作.
我从公司管理员那里得到的问题是"当IE和Firefox等应用程序在没有设置此密钥的情况下执行SPNEGO时没有问题时,我们为什么需要设置此注册表项?".好吧,我知道答案是什么.这是因为(很可能)IE和Firefox等应用程序基于Windows本机GSS API(SSPI),而Sun的Java使用自己的实现.
我假设使用像WAFFLE这样的东西可以解决问题,但我赞成纯Java解决方案.我还假设使用基于Java的解决方案(如Spring安全性或Apache HttpClient)无济于事,因为它们都会遇到这个问题.
任何帮助或指针将不胜感激.
更新1:
我发现Oracle的bug数据库中有一个RFE.还有一个由Oracle员工就此问题提交的补丁以及有关此功能的JDK邮件列表的讨论.除了我能理解的这一点在目前的Java 7中是不可用的,甚至不是实验性的,都不会让我更加明智.对?
更新2:
现在问题在OpenJDK Security Dev邮件列表上再次存在.
我不确定如何从SQL执行中获取受影响的行数.
我喜欢这个:
boolean isResultSet = statement.execute(arbitrarySQLCommand);
Run Code Online (Sandbox Code Playgroud)
我可以从该getUpdateCount()方法获得受影响的行数.这一切都很好.我遇到的问题是更新计数为零时.这可能意味着:
这是一个DML语句,但它不会影响任何行.受影响的零行是有效的响应.我只是意味着某些条件没有得到满足.
这是一个非DML语句(最有可能是DDL语句)..根据定义,它不会更改行,因此更新计数始终为零(呃!).或者换句话说:更新计数的概念对于这些陈述毫无意义.
我想要的是能够区分上面的情况1和2.怎么样?
我对产生输出的语句不感兴趣所以我也可以使用executeUpdate()但是我看到它的返回值有同样的缺陷:
返回:
(1)SQL数据操作语言(DML)语句的行数或(2)0表示不返回任何内容的SQL语句
Arghhh!
我希望它是:
返回:
(1)SQL数据操作语言(DML)语句的行计数或(2)-1表示不返回任何内容的SQL语句
(注意:我arbitrarySQLCommand事先不知道内容)
对于这个问题,似乎没有真正的JDBC解决方案.在我看来,JDBC的设计者在getUpdateCount使用值0(零)来表示一个没有(按定义)影响行的语句时犯了一个严重错误,因为受影响的零行也是一个完全有效的结果值. DML声明.
唯一可能的解决方案似乎是在SQL语句上进行某种模式匹配,以确定它是否是DML语句(INSERT,UPDATE,DELETE)或其他类型的SQL语句.像这样的东西:
arbitrarySQLCommand.单词由空格或EOL行字符终止.getUpdateCount()相关,否则输出getUpdateCount()是无关紧要的.丑陋且容易出错.但是这个问题出来的唯一可能的解决方案.:-(
我需要以编程方式为某些JDK7内部类启用日志记录.
这是我在我的应用程序初始化中所做的事情:
httpLogger = Logger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
httpLogger.setLevel(Level.FINEST);
Run Code Online (Sandbox Code Playgroud)
哪里httpLogger是强引用(为了避免记录器被垃圾收集).我还将ConsoleHandler的级别设置为ALL.但是我无法获得任何输出.
如果我通过记录配置文件来执行它,它按预期工作.
我可能错了,但我认为这与我有所关系,不了解PlatformLoggerJava 7中引入的内容,以及 - afaik - 现在用于所有JDK内部日志记录.或许我只是不太了解JUL.
我猜它会起作用,如果我这样做:
httpLogger = PlatformLogger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
httpLogger.setLevel(Level.FINEST);
Run Code Online (Sandbox Code Playgroud)
但是PlatformLogger类是在一个我无法参考的包中.
PlatformLogger?这是JavaDoc:
Platform logger provides an API for the JRE components to log
messages. This enables the runtime components to eliminate the
static dependency of the logging facility and also defers the
java.util.logging initialization until it is enabled.
In addition, the PlatformLogger API can be used if the logging
module does not …Run Code Online (Sandbox Code Playgroud) 我有一个非常简单(现有)的Web服务,我想生成一个反对使用JDK8的Web服务客户端.
我正在使用纯JDK8工具链,这意味着我使用了JDK8目录中的wsimport工具.
现在来看问题:JDK8中wsimport工具生成的Java源代码不符合JDK8 Javadoc.您可能知道Javadoc工具在JDK8中变得更加严格.
请考虑以下简单架构:
<xs:schema version="1.0" targetNamespace="http://mavenwsserver.ws.mytest.org/">
<xs:element name="operation" type="tns:operation"/>
<xs:element name="operationResponse" type="tns:operationResponse"/>
<xs:complexType name="operation">
<xs:sequence>
<xs:element name="person" type="tns:person" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="person">
<xs:sequence>
<xs:element name="firstName" type="xs:string" minOccurs="0"/>
<xs:element name="lastName" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="operationResponse">
<xs:sequence>
<xs:element name="return" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Run Code Online (Sandbox Code Playgroud)
为此,wsimport工具将生成Java代码,如下所示:
package org.mytest.ws.mavenwsclient;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for person complex type.
*
* <p>The following schema fragment specifies the expected content contained …Run Code Online (Sandbox Code Playgroud) 如何避免在ProxySelector与URLConnection Java知道的任何代理无关的情况下获得连接或者更确切地说如何获得连接?
我认为这就是Proxy.NO_PROXY的用途.引用Javadoc:
代表DIRECT连接的代理设置,基本上告诉协议处理程序不使用任何代理
然而,这样的连接仍将通过ProxySelector.我不明白吗?
我做了一个小测试来证明我的观点:
public static void main(String[] args) throws MalformedURLException, IOException {
ProxySelector.setDefault(new MyProxySelector());
URL url = new URL("http://foobar.com/x1/x2");
URLConnection connection = url.openConnection(Proxy.NO_PROXY);
connection.connect();
}
Run Code Online (Sandbox Code Playgroud)
和一个虚拟的ProxySelector,除了记录正在发生的事情之外什么都不做:
public class MyProxySelector extends ProxySelector {
@Override
public List<Proxy> select(URI uri) {
System.out.println("MyProxySelector called with URI = " + uri);
return Collections.singletonList(Proxy.NO_PROXY);
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {}
}
Run Code Online (Sandbox Code Playgroud)
打印:
"MyProxySelector called with URI = socket://foobar.com:80"
Run Code Online (Sandbox Code Playgroud)
(注意如何协议已经从http至socket) …