标签 java 下的文章

C / C++

先定义 Java native 接口:

package jni;

/**
 * @author syf
 * @date 2024/2/6
 **/
public class JniMath {

    static {
        System.load("/tmp/libzichecpp.so");
        System.load("/tmp/libzichego.so");
    }

    public static native long multiply(long x, long y);

    public static native long multiplygo(long x, long y);

    public static void main(String[] args) {
        System.out.println(JniMath.multiply(12345, 67890));
        System.out.println(JniMath.multiplygo(12345, 67890));
    }
}

javah生成关联的头文件

javah jni.JniMath

生成的jni_JniMath.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jni_JniMath */

#ifndef _Included_jni_JniMath
#define _Included_jni_JniMath
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jni_JniMath
 * Method:    multiply
 * Signature: (JJ)J
 */
JNIEXPORT jlong JNICALL Java_jni_JniMath_multiply
  (JNIEnv *, jclass, jlong, jlong);

#ifdef __cplusplus
}
#endif
#endif

编写代码实现头文件中定义的函数:

#include "jni_JniMath.h"

JNIEXPORT jlong JNICALL Java_jni_JniMath_multiply
  (JNIEnv * env, jclass clazz, jlong argX, jlong argY) {

  return argX * argY;
}

编译 cpp,这里 JAVA_HOME 的头文件目录换成你的系统环境对应的

gcc -shared -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/darwin src/jni/jni_JniMath.c -o /tmp/libzichecpp.so

调用见 Java 部分的代码,不重复了

Golang

golang 从 1.5 版本开始支持 c-shared 模式编译,可以作为动态链接库调用

同样这里注释中cgo引入的CFLAG,头文件目录换成你的系统环境对应目录

package main

// #cgo CFLAGS: -I/Users/syf/opt/zulu-jdk-8/include
// #cgo CFLAGS: -I/Users/syf/opt/zulu-jdk-8/include/darwin
// #include <jni.h>
import "C"

//export Java_jni_JniMath_multiplygo
func Java_jni_JniMath_multiplygo(env *C.JNIEnv, clazz C.jclass, x C.jlong, y C.jlong) C.jlong {
    return x * y
}

// main function is required, don't know why!
func main() {} // a dummy function

编译:

go build -buildmode=c-shared -o /tmp/libzichego.so src/jni/jni_math.go

调用见 Java 部分的代码,不重复了

背景

对于一些底层业务系统,同步信息表填充基本信息的操作比较常见,过去的做法多为跨服务同步基础信息表到当前业务表并持久化,需要时join基础表填充字段。这里设计一个简单的CacheService,以内存缓存的方式实现以下功能:

  1. 从外部系统rpc调用刷新缓存并持久化到DB(全量刷新)
  2. 从MQ拿到增量变更数据,刷新缓存并更新DB(增量刷新)
  3. 应用重启从DB拉取全量基础信息维护到内存(初始化)
  4. 无法推MQ的外部系统支持定时任务统一同步(定时刷新)
  5. 需要填充基本字段时直接从内存中可取(读取)

接口

Cacheable,从外部系统接收的数据结构实现转换方法,处理成系统内需要的结构T:

/**
 * @author sunyongfei
 * @date 2022/3/28
 */
public interface Cacheable<T> {

    /**
     * 获取缓存key
     * @return
     */
    String getCacheKey();

    /**
     * 获取缓存值
     * @return
     */
    T getCacheValue();
}

CacheService,单个缓存维护逻辑,可能从不同的数据源同步,统一约束行为

/**
 * @author sunyongfei
 * @genertic T: destination target class
 * @date 2022/3/28
 */
public interface CacheService<T> {

    /**
     * @PostConstruct needed in actual impl bean,
     * if you want to add annotation in the interface, use abstract class instead.
     */
    void initCacheFromDB();

    /**
     * refresh cache
     */
    boolean refreshCache();

    /**
     * refresh single cache item
     */
    boolean refreshCache(T t);

    /**
     * get cache by key
     * @param cacheKey
     * @return
     */
    T getCacheItem(String cacheKey);
}

使用例:

SysInfoSimpleDTO implements XXX, Cacheable<SysInfoDetailDTO>: 接收外部系统的DTO

SysInfoServiceImpl implements XXX, CacheService: 自定义逻辑

SyncServiceImpl: 公共逻辑,如简单的定时任务同步

@Resource
private List<CacheService<?>> cacheServices;

@Scheduled(cron = "0 0/5 * * * ?")
public void refreshCache() {
    if (CollectionUtils.isNotEmpty(cacheServices)) {
        cacheServices.forEach(CacheService::refreshCache);
    }
}

其他逻辑略

最近开发javaagent应用,参照jps命令引入了sun.jvmstat包下的一些工具写了些逻辑,Maven打包时确实有些tricky,很容易打包后执行时找不到sun.jvmstat包下的一些类,记录下解决过程

1. dependency 引入

引入jdk内部依赖方法:定位.jar文件位置,scope设置为system引入

<!-- for jdk internal lib -->
<dependency>
    <groupId>com.sun</groupId>
    <artifactId>tools</artifactId>
    <version>1.8.0</version>
    <scope>system</scope>
    <systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>

2. Assembly Plugin 配置

正常maven-compiler-plugins是没问题的,但是如果要打单个包,使用Assembly Plugin或Shade Plugin直接打包会有问题,因为system scope的缘故tools.jar内的库还是打不进去,此时需要添加descriptor配置:

<assembly
        xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
    <id>jar-with-all-dependencies</id>
    <formats>
        <format>jar</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/</outputDirectory>
            <useProjectArtifact>true</useProjectArtifact>
            <unpack>true</unpack>
            <scope>runtime</scope>
        </dependencySet>
        <dependencySet>
            <outputDirectory>/</outputDirectory>
            <unpack>true</unpack>
            <scope>system</scope>
        </dependencySet>
    </dependencySets>
</assembly>

保存为assembly.xml,在pom.xml的assembly-plugin插件配置段添加如下配置引入discriptor:

<configuration>
    <appendAssemblyId>false</appendAssemblyId>
    <descriptors>
        <descriptor>${basedir}/assembly.xml</descriptor>
    </descriptors>
</configuration>

再执行assembly打包,可以看到打出来的.jar文件内已经包含sun.jvmstat包内的相关class,可以正常执行了

参考

  1. https://stackoverflow.com/questions/3080437/jdk-tools-jar-as-maven-dependency

一、从arthas-spring-boot-starter开始

官方提供的Arthas Spring Boot Starter,以maven依赖的方式启动了arthas,attach自身进程后常驻;

以starter包为切入点查看arthas是如何进行attach和执行命令等一系列操作的

1.1 starter的configuration拉起AttachArthasClassloader

starter中的ArthasConfiguration类,指定在配置开启时加载attach.ArthasAgent并加载其init方法

init方法内的主要逻辑为启动AttachArthasClassloader:

com.taobao.arthas.agent.attach.ArthasAgent#init

if (instrumentation == null) {
    instrumentation = ByteBuddyAgent.install();
}
 
// 检查 arthasHome
if (arthasHome == null || arthasHome.trim().isEmpty()) {
    // 解压出 arthasHome
    URL coreJarUrl = this.getClass().getClassLoader().getResource("arthas-bin.zip");
    if (coreJarUrl != null) {
        File tempArthasDir = createTempDir();
        ZipUtil.unpack(coreJarUrl.openStream(), tempArthasDir);
        arthasHome = tempArthasDir.getAbsolutePath();
    } else {
        throw new IllegalArgumentException("can not getResources arthas-bin.zip from classloader: "
                + this.getClass().getClassLoader());
    }
}
 
// find arthas-core.jar
File arthasCoreJarFile = new File(arthasHome, ARTHAS_CORE_JAR);
if (!arthasCoreJarFile.exists()) {
    throw new IllegalStateException("can not find arthas-core.jar under arthasHome: " + arthasHome);
}
AttachArthasClassloader arthasClassLoader = new AttachArthasClassloader(
        new URL[] { arthasCoreJarFile.toURI().toURL() });
 
/**
 * <pre>
 * ArthasBootstrap bootstrap = ArthasBootstrap.getInstance(inst);
 * </pre>
 */
Class<?> bootstrapClass = arthasClassLoader.loadClass(ARTHAS_BOOTSTRAP);
Object bootstrap = bootstrapClass.getMethod(GET_INSTANCE, Instrumentation.class, Map.class).invoke(null,
        instrumentation, configMap);
boolean isBind = (Boolean) bootstrapClass.getMethod(IS_BIND).invoke(bootstrap);
if (!isBind) {
    String errorMsg = "Arthas server port binding failed! Please check $HOME/logs/arthas/arthas.log for more details.";
    throw new RuntimeException(errorMsg);
}

解压arthas-bin.zip包后通过AttachArthasClassloader加载,可以看到对应属性中bootstrap类是com.taobao.arthas.core.server.ArthasBootstrap,方法是getInstance,跳转到core中的Bootstrap类:

对应getInstance方法调了构造方法ArthasBootstrap,到这里arthas-core模块才真正起起来:

https://github.com/alibaba/arthas/blob/arthas-all-3.5.5/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java#L350

其中最重要的流程是bind方法,启动了arthas server,关键逻辑在ShellServer的初始化:

1.2 ArthasBootstrap启动ShellServer

- 阅读剩余部分 -

最近写代码时发现将枚举类暴露到 dict 接口时总需要做一些重复的装配工作,于是写了这么个工具类来提供枚举类的自动暴露功能。通过在想要暴露的枚举类属性上引用@DictApi注解,不需要再写额外的逻辑即可将对应枚举类和字段暴露出来。

期望

  1. 添加枚举类时可以低成本或无成本地添加到已有的枚举字典接口,不需要写额外的逻辑
  2. 可以指定枚举类哪些字段是需要暴露的,且可以指定字典接口中的属性别名
  3. 支持私有属性通过公有的Getter()方法获取属性值

实现

- 阅读剩余部分 -