从此

Java 高级编程、I/O File、硬件OSHI 日志/Logger NetBeans CDI JSON

综合/最新

Java编程专题Nginx Jetty HTTP C/SJakarta EEAndroid应用开发JDK源码Gradle构建Spring
原则 - 首选内存用量小的Quarkus(GraalVM),次选嵌入式Jetty、Open Liberty InstantOn、Open Liberty非快速启动版。

重要

判 null:
  // 必不会为 null,仅为了抑制 NetBeans 警告 Dereferencing possible null pointer.
  Objects.requireNonNull(obj); obj.callMethod();
  // 运行时异常捕获 catch (IOException | RuntimeException ex) { }

转基类避免运行时异常:
  // HttpsURLConnection 可能报运行时 ClassCastException,故先转为基类用。
  var conn = (HttpURLConnection) url.openConnection();

IO File

方案:
  背压式双缓冲队列 - 将“不稳定的网络流”转化为“稳定和缓存较小的内存块”,同时避免了全量载入内存,比单缓冲的 BufferedInputStream 多一份。

尾参指初始容量,会扩充至 ReadLimit - new BufferedInputStream(stream, 64 * 1024).mark(_ReadLimit);

JSON

JSON-B: 序列化 Java Class 或 Record 对象,依赖包 tools.jackson.core:jackson-databind;Jackson 3 已融合 java.time.* 序列化包 jackson-datatype-jsr310。
JSON-P: 即 Jakarta JSON Processing,逐字段构造 JSON 对象,依赖包 org.eclipse.parsson:jakarta.json。

CDI

方案:
  Jetty + Weld、GraalVM + Jetty + Avaje-Inject(Weld无法AOT)、Quarkus + Arc。
  Android 则选用官方 com.google.dagger:hilt-android。

Weld SE:
  // gradle就地执行Java程序时,还没到jar打包beans.xml阶段,故将其拷贝至classes路径,使Weld能感知到它。
  val copyRes by tasks.registering(Copy::class) {
    from(layout.projectDirectory.file("src/main/resources/META-INF"))
    into(layout.buildDirectory.dir("classes/java/main/META-INF"))
  } // 亲测 bean-discovery-mode="all" 才能解决 WELD-ENV-002009。
  tasks.processResources { dependsOn(copyRes) }
  tasks.jar { // 避免因重复文件(beans.xml)而构建失败。
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
  }

  依赖库:
    implementation("org.jboss.weld.se:weld-se-core:6.0.3.Final")
    implementation("io.smallrye:jandex:3.4.0") // for weld

  var wc = new Weld().initialize();
  System.out.println("Weld - " + wc.isRunning());
  //wc.select(MyClass.class).get().method();
  wc.shutdown();

  // 测试CDI Events监听:
  调用 StartMain.main(null); // 或 java -cp app.jar:weld-se-shaded.jar org.jboss.weld.environment.se.StartMain
  @Singleton
  public class InitCDI {
    public void initCDI(@Observes ContainerInitialized event, @Parameters List parameters) {
        System.out.println("InitCDI " + parameters);
    }
  }
  未标注CDI范围的普通类可使用静态方法注入: 
    CDI.current().select(InterfaceCDI.class, new AnnotationLiteral() { }).get();
    //CDI.current().select(RequestContext.class, UnboundLiteral.INSTANCE).get().activate()

Weld Servlet:
  原理 - 
    实现了 ServletContainerInitializer 的 Listener 会在 Jetty Server 启动时触发,并:
      org.jboss.weld.environment.servlet.Listener.contextInitialized(ServletContextEvent sce)
      WeldServletLifecycle.initialize(ServletContext context)
      createDeployment(...) 负责扫描 CDI 注解并收集为 Beans 列表;strategy.performDiscovery().size() != 0。
      context.getAttribute(Listener.CONTAINER_ATTRIBUTE_NAME) instanceof ContainerInstanceFactory
      CONTAINER_ATTRIBUTE_NAME 属性无值则会 new WeldBootstrap().startContainer(...);

    可直接传入 sch.addEventListener(Listener.using(weld)),其内部进行了 setAttribute(Listener.CONTAINER_ATTRIBUTE_NAME, weld实例或工厂)

  WebAppBeanArchiveScanner.java scan() 底部可输出可用 beans.xml 路径和版本:
    results.stream().forEach(x -> { IO.println(x.getBeansXml().getVersion() + " | " + x.getBeansXml().getUrl()); });
    GraalVM 原生构建时,路径会加个 Weld 不识别的协议前缀 resource:/META-INF/beans.xml,故需要 registerHandler(BeanArchiveHandler handler) 兼容下;由于 Jandex 也不支持,故可禁之 -Dorg.jboss.weld.environment.deployment.discovery.jandex=false -Dorg.jboss.weld.discovery.disableJandexDiscovery=true。

NetBeans

IDE 注入的环境变量: if (System.getenv("NETBEANS_USERDIR") != null) { }

通用

静态方法里获取所在类:
  MethodHandles.lookup().lookupClass();
  带行号所在方法 - StackWalker.getInstance().walk(Stream::findFirst).get() // pkg.App.main(App.java:67)
    或 new Throwable().getStackTrace()[0];

守护线程:
        var es = Executors.newSingleThreadScheduledExecutor(runnable -> {
            var t = new Thread(runnable, "auto-shutdown-thread");
            t.setDaemon(true);
            return t;
        });
        es.schedule(() -> {
            System.out.println("setDaemon 守护线程不阻止 JVM 正常退出!");
        }, seconds, TimeUnit.SECONDS);

日志:
    static final LazyConstant<System.Logger> LOGGER = LazyConstant.of(() -> {
        var logger = System.getLogger("global"); // Logger.getGlobal()
        var loggerImpl = LogManager.getLogManager().getLogger("global");
        loggerImpl.setLevel(Level.ALL);

        // [可选]控制台输出
        var ch = new ConsoleHandler();
        ch.setLevel(Level.ALL);
        loggerImpl.addHandler(ch);

        try {
            var fh = new FileHandler("global.log");
            fh.setLevel(Level.ALL);
            fh.setFormatter(new SimpleFormatter()); // 默认 XML。
            loggerImpl.addHandler(fh);
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
        return logger;
    }); //用处 LOGGER.get().log(System.Logger.Level.ALL, "msg..."); LOGGER.get().log(Logger.Level.ALL, MethodHandles.lookup().toString(), ex);

    if (logger.isLoggable(Logger.Level.INFO)) {
      logger.log(Logger.Level.INFO, "Msg: {0}", Arrays.toString(args));
    }
    logger.log(Logger.Level.INFO, () -> "Msg: " + Arrays.toString(args)); // 日志级别不匹配则不执行 Lambda,特别是复杂日志内容。

JDK/JRE

JRE 自定义镜像:
    cd "C:\Program Files\Java\jdk-24\bin"
    ./jlink --help  只含 java.base 则生成后约 30-47MiB。
    [可选] 查看依赖的modules: jdeps HelloWorld.class

    不含应用: ./jlink --output $HOME/Desktop/my-jre/ --add-modules java.base
        ./jlink --output $HOME/Desktop/my-jre/ --add-modules java.base,jdk.localedata --include-locales en,zh-Hans-CN --strip-debug --compress=zip-9
        说明 - 不指定 --module-path 则默认取 JDK 目录 jmods/;可指定 jar(不指定模块名则取文件名) 和通过 jar 或 *.class 生成的 jmod 文件。
          其中 --strip-debug --compress=zip-9 参数均为可选,但建议加上,压缩似乎只针对 my-jre/lib/modules 文件。
    包含应用: ./jlink --output $HOME/Desktop/my-jre/ --module-path ../jmods/:path/to/your-modular.jar:your-modular.jmod --add-modules java.base
        说明 - 运行命令 java --module your.modular/HelloWorld.class

    其他参数:如果想重用参数,可通过配置文件指定 ./jlink @my.conf
        --add-modules java.base,javafx.base,javafx.graphics,javafx.controls --launcher start=java.base/java
        指定本地化语言(默认en_US) ./jlink --add-modules java.base,jdk.localedata --include-locales en,ja,zh-Hans-CN

    清理输出目录: rm -r $HOME/Desktop/my-jre/
    [可选] 创建jmod文件: jmod create --class-path mods/com.example.mymodule --output mymods/com.example.mymodule.jmod --module-version 1.0.0 --main-class com.example.mymodule.MyClass --resource-path resources

其他

OSHI 库获取"设备 ID"(重装会变) - RegistryUtil.getStringValue(WinReg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\SQMClient", "MachineID");
  主板 SMBIOS(DMTF格式/硬件固化): new SystemInfo().getHardware().getComputerSystem().getHardwareUUID();