转:FindBugs,第 2 部分: 编写自定义检测器

2023-11-11

FindBugs ,第 2 部分 : 编写自定义检测器
如何编写自定义检测器以查找特定于应用程序的问题
FindBugs 是一种可以扩展和定制以满足自己团队独特要求的静态分析工具。在本系列的第 2 部分中,高级软件工程师 Chris Grindstaff 向您展示如何创建特定于应用程序的缺陷检测器。
在本系统的 第一篇文章 中,我展示了如何设置和执行 FindBugs 。现在我们将分析 FindBugs 最强大的功能 —— 自定义检测器。首先,我将说明为什么自定义缺陷检测器很有用,然后我将引导读者完成一个详细的例子。
为什么要编写自定义缺陷检测器?我在被要求对一个小组的性能问题进行检查时遇到了这个问题。很显然小组自已开发的日志框架(像所有日志框架一样)随着时间而增大。原来是随意地大量调用 Logger 。不幸的是,随着小组的扩大,他们的应用程序的性能也在变差,因为他们总是产生昂贵的日志消息 —— 而当日志框架发现禁用日志后,这些消息只能是被它抛弃。解决这个问题的标准方式是在构造昂贵的日志消息之前,首先检查是否启用了日志。换句话说,使用一个像清单 1 这样的监护子句:


清单 1. 监护日志示例
if(Logger.isLogging()) {
    Logger.log("perf", anObjectWithExpensiveToString + anotherExpensiveToString);
}
这个小组认定这是一种恰当的日志做法,并对现有的代码加以改变以体现新的做法。在这个非常大的项目的截止时间快到时听到还有很多地方未改完是不会让人感到意外的。小组需要有更好的方法找出尚未修改的地方。因为本文讨论的是 FindBugs ,所以我们将用 FindBugs 解决这个问题。
目标是编写一个 FindBugs 检测器,它将找出代码中调用日志框架而未被包装在监护子句中的所有地方。
当初编写这个检测器时,我将问题分解为几个单独的步骤:
  1. 首先用一条未监护的日志语句编写一个测试案例。
  2. 其次,查看 FindBugs 源代码以查找类似于我要编写的检测器类似的检测器。
  3. 然后创建正确打包的 JAR 文件(使用编译脚本),使 FindBugs 知道如何装载未监护检测器。
  4. 运行这个测试案例并实现代码使测试通过。
  5. 最后,加入更多测试案例,继续这个过程直到最后完成。
在浏览代码时,我特别检查了 BytecodeScanningDetector ByteCodePatternDetector 的子类型。实现扫描检测器要做更多工作,但是它们能检测更一般类型的问题。如果所要检测的问题可以表述为一组字节码模式,则模式检测器是一种好的选择。它的一个好例子是 BCPMethodReturnCheck 检测器 —— 它查找那些不同方法的返回类型有可能被忽略的地方。 BCPMethodReturnCheck 可以很容易地描述为一组模式,它查找在 POP 或者 POP2 指令后面调用某些方法的地方。绝大多数检测器目前被编写为扫描检测器,尽管我认为这仅仅是因为开发人员还没有足够的时间转移到 ByteCodePatternDetector
我决定使用 FindRunInvocations 作为例子,主要是因为它是最小的一种检测器。对我来说如何实现使用一组模式的检测器还不是很明确。
FindBugs 利用了 Byte Code Engineering Library ,或称为 BCEL (请参阅 参考资料 ),以实现其检测器。所有字节码扫描检测器都基于 visitor 模式, FindBugs 实现了这个模式。它提供了这些方法的默认实现,在实现自定义检测器时要覆盖这些方法。请分析 BetterVisitor 及其子类以获得更多细节。出于我们的目的,我们将侧重于两个方法 —— visit(Code) sawOpcode(int) 。在 FindBugs 分析类时,它会在分析方法内容时调用 visit(Code) 方法。与此类似, FindBugs 在分析方法正文中的每一个操作码时调用 sawOpcode(int) 方法。
有了这些背景知识,让我们分析这些用于构建未监护日志检测器的方法的实现,如清单 2 所示:
清单 2. 未监护日志检测器: visit() 方法
18      public void visit(Code code) {
19          seenGuardClauseAt = Integer.MIN_VALUE;
20          logBlockStart = 0;
21          logBlockEnd = 0;
22          super.visit(code);
23      }
 
在读取现有检测器代码时,需要做的一件事是关注检测器是否需要在分析时建立状态。换句话说,检测器是否需要记住它在方法、类、层次结构或者整个程序级别上看到了什么?例如, Inconsistent Synchronization 检测器构建整个程序的状态,这样它就可确定在同步方面,什么时候以非一致性的方式对字段进行了访问。我们的检测器只需要在字节码扫描阶段维护状态,因为我们查找的是方法级的问题。
可以在方法 visit(Code) 中刷新或者重新设置检测器存储的、特定于方法的状态(如清单 2 所示)。在这里,检测器维护了一个使用三位( bit )的状态:
  • seenGuardClauseAt:在所分析的代码中发现日志监护子句时,程序计数器的值
  • logBlockStart:监护子句开始处的索引
  • logBlockEnd:监护子句后面的指令的索引
关于 visit(Code) 方法的实现有两点很重要。要注意的第一件事是对 super.visit() 的调用,它是关键,因为这个方法的父类实现负责访问我们要分析的方法的内容。如果我们没有调用父类的实现,那么就不会检查所分析的方法。
第二点是在调用父类的实现之前重新设置存储的状态,这很重要,因为我们将分析的下一个方法 —— sawOpcode() 方法 —— 将要使用这些变量。我们希望保证在这之前对它们作了重新设置。清单 3 显示了 sawOpcode() 方法的实现:


清单 3. 未监护日志检测器: sawOpcode() 方法
25      public void sawOpcode(int seen) {
26          if ("cbg/app/Logger".equals(classConstant) &&
27                  seen == INVOKESTATIC &&
28                  "isLogging".equals(nameConstant) && "()Z".equals(sigConstant)) {
29              seenGuardClauseAt = PC;
30              return;
31          }
32          if (seen == IFEQ && (PC >= seenGuardClauseAt + 3 && PC < seenGuardClauseAt + 7)) {
33              logBlockStart = branchFallThrough;
34              logBlockEnd = branchTarget;
35          }
36          if (seen == INVOKEVIRTUAL && "log".equals(nameConstant)) {
37              if (PC < logBlockStart || PC >= logBlockEnd) {
38                  bugReporter.reportBug(
39                          new BugInstance("CBG_UNPROTECTED_LOGGING", HIGH_PRIORITY)
40                          .addClassAndMethod(this).addSourceLine(this));
41              }
42          }
43      }
 
如前所述,当 FindBugs 分析一个方法时,它会对方法中包含的每一个字节码指令调用 sawOpcode() 。这个方法做三件事。事实上,原来的代码被重构为三个方法,但是在本文的讨论中,我把它排在一行以减少所占用的空间。这个方法做三件事:
  1. 确定是否调用了 static 方法Logger.isLogging(),如果调用了,程序计数器PC的值是什么
  2. 确定在调用Logger.isLogging()后是否有一个if指令
  3. 寻找在监护子句外部调用log()方法的情况
清单 4 更详细地分别显示了每一部分:


清单 4. 未监护日志检测器:调用了 sawOpcode() isLogging()
25      public void sawOpcode(int seen) {
26          if ("cbg/app/Logger".equals(classConstant) &&
27                  seen == INVOKESTATIC &&
28                  "isLogging".equals(nameConstant) && "()Z".equals(sigConstant)) {
29              seenGuardClauseAt = PC;
30              return;
31          }
 
classConstant nameConstant sigConstant 字段是检测器从其父类继承的 protected 字段。它们包含有关当前操作码的细节。在编写自己的检测器时,打印出它们的值通常是有用的。浏览 BytecodeScanningDetector 层次结构以寻找 DismantleBytecode 类中更有用的字段和方法。另一个编写检测器可以使用的非常有用的工具是永久的 javap 。对于理解编写检测器的逻辑流程和方法名, Java 反汇编程序是非常有用的工具。一般的方式是编写要查找的模式(在这里是编写 Java 文件中的监护子句)、保存它、再编译它。然后使用 javap -c 以查看反汇编的字节码,并学习如何构造自己的 sawOpcode(int) 方法。例如,清单 5 显示了对我的测试实例中使用的类运行 javap 后的输出(这是一个正确使用日志监护子句的方法):


清单 5. 反汇编的监护子句及源代码
public void methodWithLogging_guarded();
  Code:
   0:   invokestatic    #28; //Method cbg/app/Logger.isLogging:()Z
   3:   ifeq    18
   6:   new     #16; //class Logger
   9:   dup
   10:  invokespecial   #17; //Method cbg/app/Logger."<init>":()V
   13:  ldc     #19; //String bob
   15:  invokevirtual   #23; //Method cbg/app/Logger.log:(Ljava/lang/Object;)V
   18:  aload_0
   19:  invokespecial   #31; //Method doWork:()V
   22:  return
 
   corresponds to the Java source code
   public void methodWithLogging_guarded() {
        if (Logger.isLogging()) {
            new Logger().log("bob");
        }
        doWork();
    }
 
分析 javap 的输出有助于理解方法的控制流程序以及如何构建需要在 sawOpcode() 方法中指定的类、签名和名称常量。例如,清单 6 显示清单 5 javap 的第一行代码


清单 6. 反汇编的方法调用
0:   invokestatic    #28; //Method cbg/app/Logger.isLogging:()Z
 
如果仔细观察 清单 4 sawOpcode() 方法的第 26 28 行,将会看到它们描述了一种与我们在 清单 5 中用 javap 见到的内容相匹配的方式。在确定如何匹配这些格式( form )时, javap 是一个有用的工具。
确定已经调用了 Logger.isLogging() 方法后,我们要保存程序计数器的值,如清单 7 所示。需要用程序计数器确定在调用 Logger.isLogging() 后,是否有一个 if 子句,这将我们带入下一节的代码。


清单 7. 保存程序计数器的值
32          if (seen == IFEQ && (PC >= seenGuardClauseAt + 3 && PC < seenGuardClauseAt + 7)) {
33              logBlockStart = branchFallThrough;
34              logBlockEnd = branchTarget;
35          }
 
这段摘自 清单 3 的代码检查在上述对 Logger.isLogging() 的调用后面 3 7 字节码之间是否有 if 分支语句。这些值是通过查看 javap 的输出和通过试验确定的。你是说试验?没错,有些时候必须借助于反复试验才能找到伪错误和有用的结果之间的平衡点。将这个过程想像为使用试探法的计算机工程而不是计算机科学。确定了这个语句是 if(Logger.isLogging()) 语句后,我们需要找出 if 代码块的边界。这是通过保存 branchFallThrough branchTarget 而做到的。 branchFallThrough if 子句的开始,而 branchTarget 代表 if 子句外面的第一行。有了这些信息,我们现在就可以进入这个方法的最后一部分了,如清单 8 所示:


清单 8. 检查对 log() 的调用
36          if (seen == INVOKEVIRTUAL && "log".equals(nameConstant)) {
37              if (PC < logBlockStart || PC >= logBlockEnd) {
38                  bugReporter.reportBug(
39                          new BugInstance("CBG_UNPROTECTED_LOGGING", HIGH_PRIORITY)
40                          .addClassAndMethod(this).addSourceLine(this));
41              }
42          }
 
这段代码也取自 清单 3 ,查找对 Logger log() 方法的调用。找到对 log() 方法的调用后,我们检查程序计数器是否在前面确定的 if 块外面。如果是的话,我们就通过创建一个新的 bug 实例报告一个缺陷,指定 bug 的类型(我们将在后面详细讨论)和其优先级。在 bug 中加入类、方法和源代码会提供很大方便,这样用户就知道在什么地方修复这个问题。
编写了代码后,需要创建一个特别打包的 JAR 文件, FindBugs 将它识别为插件程序 JAR 。清单 9 显示了我用来创建这个 JAR 文件并将它拷贝到正确位置的编译脚本:


清单 9. 打包 FindBugs 检测器的脚本
<property name="FindBugs.home" value="C:/apps/FindBugs-0.7.3"></property>
<target name="build">
         <jar destfile="cbgFindbugsPlugin.jar">
                 <fileset dir="bin"/>
                 <fileset dir="src"/>
                 <zipfileset dir="etc" includes="*.xml" prefix=""></zipfileset>
         </jar>
         <copy file="cbgFindbugsPlugin.jar" todir="${FindBugs.home}/plugin" />
</target>
 
这段代码创建一个包含源文件、类文件、 FindBugs.xml message.xml JAR 文件。清单 10 11 显示了这两个 XML 文件的内容:


清单 10. FindBugs.xml 的内容
<FindbugsPlugin>
  <Detector class="cbg.FindBugs.FindUnprotectedLogging" speed="fast" />
  <BugPattern abbrev="CBGL" type="CBG_UNPROTECTED_LOGGING" category="PERFORMANCE" />
</FindbugsPlugin>
 
对于每一个新的检测器,在 FindBugs.xml 文件中增加一个 Detector 元素和一个 BugPattern 元素。 Detector 元素指定用于实现检测器的类以及它是快速还是慢速检测器。在 UI 中查看检测器时就会用到 speed 属性,如图 1 所示。 speed 属性的可能值有 slow moderate fast

BugPattern 元素指定三个属性。 abbrev 属性定义检测器的缩写。缩写用于标识用命令行客户运行时检测到的缺陷。可以用同一个缩写将几个相关的检测器组织到一起。 .
type 属性是惟一标识符,有两个用途。在使用 Ant 版本或者命令行版本的 FindBugs 且输出格式设置为 XML 时,用 type 属性标识问题。 type 属性也是在检测器的 Java 代码中指定的,用以创建缺陷的正确类型。注意这里列出的类型与在 清单 8 中第 39 行使用的名字相匹配。 .
category 属性是枚举类型。它是以下类型中的一种:
  • CORRECTNESS:一般正确性问题
  • MT_CORRECTNESS:多线程正确性问题
  • MALICIOUS_CODE:如果公开给恶意代码,有可能成为攻击点
  • PERFORMANCE:性能问题
FindBugs.xml 文件就是这些了。清单 11 显示了 messages.xml 文件的内容:


清单 11. messages.xml 的内容
<MessageCollection>
  <Detector class="cbg.FindBugs.FindUnprotectedLogging">
    <Details>
<![CDATA[
<p> This detector finds logs statements that aren't contained in an if-logging block.
It is a fast detector.
]]>
    </Details>
  </Detector>
  <BugPattern type="CBG_UNPROTECTED_LOGGING">
    <ShortDescription>Found unprotected logging</ShortDescription>
    <LongDescription>Found unprotected logging in {1}</LongDescription>
    <Details>
<![CDATA[
<p> This method logs without first checking that logging is enabled; for example
... more text omitted...
]]>
    </Details>
  </BugPattern>
 
  <BugCode abbrev="CBGL">Found unprotected logging</BugCode>
</MessageCollection>
 
messages.xml 文件由三个元素组成: Detector BugPattern BugCode
检测器的 class 属性应当指定检测器的类名。 Details 元素包含检测器的简单 HTML 描述,因而应当包含在 CDATA 部分中。 UI 使用这些描述,如图 2 所示:


2. FindBugs UI 突出显示未监护日志检测器
BugPattern 元素类似于在 FindBugs.xml 中定义的 BugPattern 元素。需要 type 属性,并且它应当匹配在 FindBugs.xml 和在检测器的 Java 代码中使用的相同惟一标识符。 BugPattern 包含三个影响有关检测器的信息在 UI 中显示方式的元素: ShortDescription LongDescription Details —— 它们的意义都是相当直观的。
UI 中关闭 View > Full Descriptions 时,使用 ShortDescription 。同样,在启用 View > Full Descriptions 时,使用 LongDescription 。可以使用注释( annotation )将信息从缺陷检测器的 Java 代码中传递给完全描述。在描述中,用 {0} 表示第一个注释、 {1} 表示第二个注释等来指定变量。在运行时,如果发现缺陷,附加在缺陷实例上的注释将替换到描述中。注意在 清单 8 的第 40 行,类和方法注释添加到了 BugInstance 上。类注释在位置 0 ,方法注释在位置 1 。更多细节请查看 BugInstance 上的不同 add*() 方法。
与前面一样, Details 元素应当在 CDATA 部分中包含一个 HTML 描述。图 2 显示了我们的检测器细节的一个例子。 View > Full Descriptions 已经打开。
在使用 By Bug Type 选项卡时, UI 使用 BugCode 元素。这个元素的文字在树中作为红色节点出现,如 图 2 所示。公共检测器共享同一个缩写,因此 BugCode 元素必须用元素的属性指定这个缩写。
创建了这两个 XML 文件后,我们现在就可以打包完整的 JAR 了。在编译了 JAR 并将它放到 FIND_BUGS_HOME/plugin 目录中后,就可以测试新的检测器了。
FindBugs 会是您的装备库中一件有用的工具。但是,像所有工具一样,必须知道如何使用它。不过,静态分析工具应作为单元 / 系统测试和代码审查的补充。
除了其改进代码质量的作用, FindBugs 还有很多特定于应用程序的用法,我鼓励读者去探索它们。例如,可以编写一组检测器,它们可以查找新手容易出现的问题。也可以编写检查代码是否符合小组规则的检测器。也许您正在构建一个框架,并且需要保证包中的所有类都有零参数的构造函数、或者所有带下划线前缀的字段都有 getter 而没有 setter 。也许可以编写一组检测器,它们验证 J2EE 代码遵守适当的限制,如不创建 Thread 或者 Socket
未监护日志例子中的小组还有捕获异常的问题。值得称赞的是,他们没有简单忽略这些异常,相反,他们让它们打印自己的堆栈跟踪,这对于编译和调试应用程序很好,但是对于部署来说这不是很理想 —— 特别是当可能有数千个异常时。(当然,如果应用程序抛出数千个异常,您的问题就比大的日志文件要严重得多了,但是姑且容许我为了说明问题而这样说。)小组需要一个检测器来找出代码中捕获异常并要求打印其堆栈跟踪的地方。这样他们就可以改变代码,将异常改为传递给日志框架。
我创建了另外一个有趣的未监护日志检测器。这个检测器用于寻找代码中所有在监护子句以外生成要记录消息的地方 —— 如果使用了行为有些特别的 toString ,这会是一个非常常见的问题,并且代价有可能相当昂贵。
不管是刚接触 FindBugs 还是已经熟悉它了,我鼓励您用自己的特定于应用程序的检测器进行试验。同时,我希望本文提供如何实现自定义检测器的简洁例子,并鼓励您将这些思路应用到小组的特定情况中去。
Chris Grindstaff 是在北加利福尼亚 Research Triangle Park 工作的 IBM 高级软件工程师。 Chris 7 岁时编写了他的第一个程序,当时他让小学老师认识到 键入 句子与手写它们一样费力。 Chris 目前参与了不同的开放源代码项目。他大量使用 Eclipse 并编写了几个流行的 Eclipse 插件程序,可以在他的 网站 找到这些插件程序。可以通过 cgrinds@us.ibm.com 或者 chris@gstaff.org Chrise 联系。
 
1. 配置检测器 UI
 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

转:FindBugs,第 2 部分: 编写自定义检测器 的相关文章

  • Jackson - 反序列化嵌套 JSON

    我有一个 JSON 字符串 其格式如下 response execution status ready report cache hit true created on 2013 07 29 08 42 42 fact cache erro
  • R、Rcpp 与 Armadillo 中矩阵 rowSums() 与 colSums() 的效率

    背景 来自 R 编程 我正在扩展到 C C 形式的编译代码Rcpp 作为循环交换 以及一般的 C C 效果的实践练习 我实现了 R 的等效项rowSums and colSums 矩阵的函数Rcpp 我知道它们以 Rcpp 糖的形式存在 并
  • 使用 Java 在 WebDriver 中按 Ctrl+F5 刷新浏览器

    我已经使用 java 刷新了 WebDriver 中的浏览器 代码如下 driver navigate refresh 如何使用 Java 在 WebDriver 中按 Ctrl F5 来做到这一点 我认为您可以使用 WebDriver 和
  • 解决 Java Checkstyle 错误:名称 'logger' 必须匹配模式 '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'

    使用 Eclipse Checkstyle 插件我看到以下错误 名称 logger 必须匹配模式 A Z A Z0 9 A Z0 9 我通过更改解决了此错误 private static final Logger logger Logger
  • 在哪里可以获得有关 Java FitNesse 和 Slim 的一些教程? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何在远程 WebSphere 上进行 JNDI 查找期间解决 sun/io/MalformedInputException

    我使用 WebSphere 8 5 来托管我的应用程序 并在应用程序服务器上配置了一些 JDBC 资源 我还使用瘦客户端运行时库开发了一个客户端应用程序 当按以下方式执行 JNDI 查找时 env put Context INITIAL C
  • 根据 .NET Core 2.1 中的更改重新加载 Serilog JSON 配置

    我目前正在开发 ASP NET Core 2 1 应用程序 并使用 Serilog 进行日志记录 我想在运行时为我的 Serilog 实现重新加载应用程序设置文件 我的目标是在运行时更改日志级别 例如我写入minimumLevelDebug
  • 定期更新 SWT 会导致 GUI 冻结

    Problem 当 GUI 字段定期更新时 SWT 会冻结 我想要一个基于 SWT 的 GUI 其中文本字段的值会定期递增 最初我从单独的线程访问 textField 导致抛出异常 线程 Thread 0 org eclipse swt S
  • Java Microsoft Excel API [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • JFace ColumnWeigthData 导致父级增长

    我有一个 Eclipse RCP 应用程序 并且想要在TableViewer using ColumnWeigthData as ColumnLayoutData 问题是父表单 ScrolledForm在示例代码中 每当我布局表格时都会增加
  • 容器中的 JVM 计算处理器错误?

    最近我又做了一些研究 偶然发现了这一点 在向 OpenJDK 团队抱怨之前 我想看看是否有其他人观察到这一点 或者不同意我的结论 因此 众所周知 JVM 长期以来忽略了应用于 cgroup 的内存限制 众所周知 现在从 Java 8 更新某
  • 从 @JsonProperty 值获取枚举常量

    我有一个标有 JsonProperty 的枚举 用于使用 Jackson 进行 JSON 序列化 反序列化 并且希望获取给定字符串 JsonProperty 的枚举值 public enum TimeBucket JsonProperty
  • linux perf:如何解释和查找热点

    我尝试了linux perf https perf wiki kernel org index php Main Page今天很实用 但在解释其结果时遇到了困难 我习惯了 valgrind 的 callgrind 这当然是与基于采样的 pe
  • 了解 Spark 中的 DAG

    问题是我有以下 DAG 我认为当需要洗牌时 火花将工作划分为不同的阶段 考虑阶段 0 和阶段 1 有些操作不需要洗牌 那么为什么 Spark 将它们分成不同的阶段呢 我认为跨分区的实际数据移动应该发生在第 2 阶段 因为这里我们需要cogr
  • 使用 secp256r1 曲线和 SHA256 算法生成 ECDSA 签名 - BouncyCastle

    我正在尝试使用带有 secp256r1 曲线 P256 的 ECDSA 和用于消息哈希的 SHA256 算法生成签名 我也在使用 Bouncy Castle 库 下面的代码 public class MyTest param args pu
  • 公共方法与公共 API

    在干净的代码书中 有一个观点是 公共 API 中的 Javadocs 同样 Effective java 一书也有这样的内容 项目 56 为所有公开的 API 元素编写文档注释 所以这就是我的问题 所有公共方法都被视为公共 API 吗 它们
  • 用于网络服务器日志分析的软件? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我可以得到一些关于 Apache 2 2 访问日志文件的优秀日志分析软件的建议 最好有一些理由 吗 我听说过韦巴利泽 http www w
  • 对于当前月份和日期但年份不同的日期,经过的月份计算未给出正确的结果

    我正在尝试计算自特定日期以来经过的月份 该函数工作正常 尽管如果我将今天的日期与过去的不同年份放在一起 它会给我一个月的差异 不到一个月 假设对于所有日期 该函数都运行良好 除了 如果今天是 2014 03 06 YYYY MM DD 并且
  • 防止Java实例化的正确方法[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 为什么范围为“provided”的依赖项会隐藏 Maven 中的传递依赖项?

    我的 Maven 项目中有三个模块 这稍微简化了 model包含JPA注释的实体类 坚持实例化一个实体管理器并调用它的方法 应用创建类的实例model 设置一些值并将它们传递给坚持 model and 坚持显然取决于javax persis

随机推荐

  • 串口服务器网页进不去怎么办,路由器登录入口进不去怎么办?

    问 路由器登录入口进不去怎么办 答 如果在设置路由器的时候 进不去路由器的登录入口 无法对路由器进行设置 这多半是用户自己操作有误导致的 也可能是路由器或者其它客观原因引起的 具体的解决办法如下 温馨提示 1 如果是用手机设置路由器时 手机
  • clang 01.clang简介

    文章目录 前言 1 Clang的工作流程 前言 Clang的官方网站是 http clang llvm org 它被认为是C家族的LLVM前端 Clang可能指代三种不同的实体 前端 由Clang程序库实现 编译器驱动器 由Clang命令和
  • 本机如何传文件到VMware 中

    本机传文件到VMware 中可以使用2种方法 1 安装tools 直接拖拽过去 2 实现文件共享 在VMware中没有安装解压文件的应用时 使用tools会不再适用 这时可以选择共享文件夹的方式 直接在本机解压文件 共享文件夹到VMware
  • C#中的Dispose模式

    声明 本文中的内容属于个人总结整理而来 个人水平有限 对于部分细节难免有理解错误及遗漏之处 如果您在阅读过程中有所发现 希望您能指正 同时文章中的部分内容也参考了其它大神的文章 如果文章中的内容侵犯了您的权益 表示非常歉意 请您指出 我将尽
  • C++职工管理系统

    C 演讲比赛流程管理系统 1 职工管理系统的需求 2 功能实现 2 1 创建管理类 2 2退出功能 2 3增加联系人信息 2 4显示职工信息 2 5删除离职职工 2 6修改职工信息 2 7查找职工信息 2 8按照编号排序 2 9清空所有文档
  • access建立er图_5G SA注册流程(2)- RRC连接建立

    导读 在正式讨论SA注册的相关NAS流程之前 笔者觉得有必要先讨论下SA下的RRC连接的建立流程 毕竟这是终端与网络交互的连接基础 同时也会讨论下不同场景下的RRC建立流程中信令内容的异同 RRC连接建立流程 SA注册流程主要是终端与5GC
  • 8X8X8光立方整体框架设计&技术细节

    从一师兄那拿来的 东西是师兄自己做的 觉得特有才一人 只是进了互联网公司 感觉做嵌入式更适合他 Powered by lihui Liusheng 2012 Shenyang 太过技术了 写给自己留着看的 不懂的可绕行 确实有些头大 在对最
  • OA权限树搭建 代码

    ul ul
  • Android下拉刷新效果实现

    本文主要包括以下内容 自定义实现pulltorefreshView 使用google官方SwipeRefreshLayout 下拉刷新大致原理 判断当前是否在最上面而且是向下滑的 如果是的话 则加载数据 并更新界面 自定义实现pulltor
  • Matlab中dir使用中遇到的一些问题

    今天调程序时遇到一个bug 感觉有点意思 也许有人会遇到类似的问题吧 问题 说手上有一段代码 原本是希望在一个文件夹中读取出其中所有音频文件的 tdir dir fullfile SoundDir SoundFileName NumSoun
  • 出现command 'gcc' failed with exit status 1 解决方案

    在centos7 上用pip 安装psutil的时候很不幸的出现了如下错误 pip install psutil Collecting psutil Using cached psutil 5 3 1 tar gz Installing c
  • python 角度判断_大牛带你打牢Python基础,看看这10语法

    都说Python简单 易懂 但是有时候却又很深奥 许多人都觉的自己学会了 却老是写不出项目来 对很多常用包的使用也并不熟悉 学海无涯 我们先来了解一些Python中最基本的内容 1 数值 数值包括整型和浮点型 分别对应整数和浮点数 后者精度
  • Python3 列表笔记

    列表 使用 括起来的一个个元素的集合 1 列表的元素使用 进行分割 2 列表的元素可以是任意数据类型 1 创建列表 list huarzil 32 3 14 True zhuangsan lisi 32 29 30 name height
  • linux学习笔记--网络编程

    目录 概念 协议 网络应用设计模式 分层模型 协议格式 TCP状态 网络名词 socket编程 套接字 字节序 函数 socket bind listen accept connect C S模型 server client 封装 高并发服
  • iview 数据表格 固定列拉倒底部后刷新出现错行问题

    很多小伙伴肯定遇到过这个组件问题 下面只需要一行即可搞定 vue方法 首先我们在mixin js里封装一个方法 pageSizeChange pageSize 每页显示数量变更 this searchParams limit pageSiz
  • C#中,浮点数的比较和decimal

    浮点数 C 的浮点数类型 float double 当我们定义一个浮点数可以 可以使用var 关键字 可以做类型推断 定义float类型 数字末尾需要加上 F或者是f 定义一个double类型 double a1 1 1 var a2 1
  • <转>企业应用架构 --- 分层

    系统架构师 基础到企业应用架构 分层 上篇 一 前言 大家好 接近一年的时间没有怎么书写博客了 一方面是工作上比较忙 同时生活上也步入正轨 事情比较繁多 目前总算是趋于稳定 可以有时间来完善以前没有写完的系列 也算是对自己这段时间工作和生活
  • 程序流程图是什么?基本流程图讲解

    程序流程图是什么 程序流程图是流程图的其中一种分类 又称程序框图 指用特定图形符号加上对应的文字描述表示程序中所需要的各项操作或判断的图示 程序流程图除了说明程序的流程顺序外 着重于说明程序的逻辑性 一 程序流程图特点 当程序流程中有较多循
  • 动态规划经典例题-国王的金矿问题

    金矿问题 问题概述 有一位国王拥有5座金矿 每座金矿的黄金储量不同 需要参与挖掘的工人人数也不同 例如有的金矿储量是500kg黄金 需 要5个工人来挖掘 有的金矿储量是200kg黄金 需要3个工人来挖 掘 如果参与挖矿的工人的总数是10 每
  • 转:FindBugs,第 2 部分: 编写自定义检测器

    FindBugs 第 2 部分 编写自定义检测器 如何编写自定义检测器以查找特定于应用程序的问题 FindBugs 是一种可以扩展和定制以满足自己团队独特要求的静态分析工具 在本系列的第 2 部分中 高级软件工程师 Chris Grinds