JAVA字节码插桩技术

字节码插桩听起来高大上,实际是通过无侵入方式修改字节码,通常干的还是动态代理的事,有两种方式。

一、agent模式

java进程启动的时候指定agent (-javaagent:demo-agent-1.0-SNAPSHOT.jar),方法入口名必须是premain,在类加载阶段修改class,实现代理。

以下是alibaba TTL库agent的入口示例。

public static void premain(final String agentArgs, @NonNull final Instrumentation inst) {        try {                      final List transformletList = new ArrayList<>();            transformletList.add(new TtlExecutorTransformlet(disableInheritableForThreadPool));            transformletList.add(new TtlPriorityBlockingQueueTransformlet());            transformletList.add(new TtlForkJoinTransformlet(disableInheritableForThreadPool));            if (isEnableTimerTask()) transformletList.add(new TtlTimerTaskTransformlet());//实现字节码替换            final ClassFileTransformer transformer = new TtlTransformer(transformletList);            inst.addTransformer(transformer, true);        } catch (Exception e) {                   }    }

二、attach模式

attach模式是在java进程已经启动,然后再加载agent。

public static void main(String[] args) {    VirtualMachine vm = null;    try {        //进程ID        vm = VirtualMachine.attach("76293");        //java agent jar包路径        vm.loadAgent("gent-demo-jar-with-dependencies.jar");    } catch (Exception e) {        e.printStackTrace();    }}

agent入口方法必须是agentmain

public static void agentmain(String agentArgs, Instrumentation inst)

由于java进程启动后,类已经加载完成,要想实现动态修改,必须重新加载类。如下

public static void agentmain(String agentArgs, Instrumentation inst) {   System.out.println("loading dynamic agent ...");   MyClassFileTransformer tf = new MyClassFileTransformer();   inst.addTransformer(tf,true); // 要传递true,否则不生效      if(inst.isRetransformClassesSupported()){      Class<?>[] classes = inst.getAllLoadedClasses();      for (Class<?> c : classes) {         if (c.getName().contains("TestDemo")) {            try {               inst.retransformClasses(c);            } catch (UnmodifiableClassException e) {               e.printStackTrace();            }            break;         }      }   }}

三、实战spring cloud + feign+nacos实现灰度发布

灰度的核心是流量染色,把带有指定版本的请求,路由到对应版本的服务上。所以我们要做两件事:

第一把版本透传(类似链路跟踪)

第二服务路由的时候,路由到指定的版本服务。

agent实现思路如下(通过bytebuddy实现):

1.要拦截HTTP请求,然后把请求头的版本信息放到ThreadLocal。

public class ServletAdvice {    @Advice.OnMethodEnter()    public static void enter(@Advice.Argument(value = 0, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object req,                             @Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object resp) {        HttpServletRequest request = (HttpServletRequest) req;        String tag = request.getHeader(TagContext.TAG);        if (StringUtils.isNotEmpty(tag)) {            System.out.println("有对应的tag标记" + tag);            TagContext.setTag(tag);        }    }    @Advice.OnMethodExit(onThrowable = Throwable.class)    public static void exit() {        TagContext.remove();    }}

2.feign服务调用的时候,服务列表只返回对应版本的服务。

public class NacosBalancerAdvice {    @Advice.OnMethodExit    private static void filterServer(@Advice.Return(readOnly = false) List instances) {        if (instances == null) {            return;        }        String tag = TagContext.getTag();        if (StringUtils.isEmpty(tag)) {            return;        }        List newList = new ArrayList<>();        for (Server server : instances) {            Map metadata = ((NacosServer) server).getMetadata();            if (metadata == null) {                continue;            }            if (tag.equals(metadata.get(TagContext.TAG))) {                newList.add(server);            }        }        //发现有对应tag的服务        if(!newList.isEmpty()){            instances = newList;        }    }}

3.feign的Http请求发出去之前,把获取到的版本信息放到Header里面,进行透传。

public class URLConnectionAdvice {    @Advice.OnMethodExit(onThrowable = Throwable.class)    public static void exit(@Advice.Return(readOnly = false) HttpURLConnection urlConnection) {        System.out.println("url connection");        String tag = TagContext.getTag();        if (StringUtils.isNotEmpty(tag)) {            // log.info("透传tag{}", tag);            System.out.println("透传tag:" + tag);            urlConnection.addRequestProperty(TagContext.TAG, tag);        }    }}
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章