Java 高级编程、I/O File、硬件OSHI 日志/Logger NetBeans CDI JSON
重要
判 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();