java mock 模拟接口_agent探针[通俗易懂]

java mock 模拟接口_agent探针[通俗易懂]在了解javaagent的创建后,今天将尝试一种更高级的用法——类替换,并用其实现Http请求地址的记录功能

在了解javaagent的创建后,今天将尝试一种更高级的用法——类替换,并用其实现Http请求地址的记录功能。javaagent允许我们在项目启动时的类加载阶段或者项目运行后进行类的替换,两者的替换方式相同,都是借助入口函数Instrumentation对象进行操作,回顾下两种方式的入口函数:

1.perman入口函数,由JVM参数配置在程序启动时的类加载阶段引入

详见《Java探针-javaagent由浅入深(一)》


 

import java.lang.instrument.Instrumentation;

 

public class MyAgent{

    public static void premain(String agentOps,Instrumentation inst){

        System.out.println("MyAgent:Hello Main Method");

    }

} 

2.agentmain函数,可在程序运行时动态引入

详见《Java探针-javaagent由浅入深(二)》

 

/*
 * Copyright (c) 1994, 2003, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*-
 *      HTTP stream opener
 */

package sun.net.www.protocol.http;

import java.io.IOException;
import java.net.URL;
import java.net.Proxy;

/** open an http input stream given a URL */
public class Handler extends java.net.URLStreamHandler {
    protected String proxy;
    protected int proxyPort;

    protected int getDefaultPort() {
        return 80;
    }

    public Handler () {
        proxy = null;
        proxyPort = -1;
    }

    public Handler (String proxy, int port) {
        this.proxy = proxy;
        this.proxyPort = port;
    }

    protected java.net.URLConnection openConnection(URL u)
    throws IOException {
        return openConnection(u, (Proxy)null);
    }

    protected java.net.URLConnection openConnection(URL u, Proxy p)
        throws IOException {
        return new HttpURLConnection(u, p, this);
    }
}

 可以借助Instrumentation的addTransformer方法添加ClassFileTransformer对象对指定的类进行替换,因为需求是记录http请求的地址,我们需要找到一个合适的切入点加入我们自己的代码,同时要保证其原有的功能不受影响,经过一番考虑我决定从sun.net.www.protocol.http下的Handler类入手,这个类的作用是根据给定的URL创建HttpsURLConnection对象,我们就在这个类的openConnection方法中进行请求地址的记录,该类的原始代码如下:

 

/*
 * Copyright (c) 2001, 2003, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*-
 *      HTTP stream opener
 */

package sun.net.www.protocol.https;

import java.io.IOException;
import java.net.URL;
import java.net.Proxy;

/** open an http input stream given a URL */
public class Handler extends sun.net.www.protocol.http.Handler {
    protected String proxy;
    protected int proxyPort;

    protected int getDefaultPort() {
        return 443;
    }

    public Handler () {
        proxy = null;
        proxyPort = -1;
    }

    public Handler (String proxy, int port) {
        this.proxy = proxy;
        this.proxyPort = port;
    }

    protected java.net.URLConnection openConnection(URL u)
    throws IOException {
        return openConnection(u, (Proxy)null);
    }

    protected java.net.URLConnection openConnection(URL u, Proxy p)
        throws IOException {
        return new HttpsURLConnectionImpl(u, p, this);
    }
}

修改后的代码如下:

package sun.net.www.protocol.http;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.Proxy;
import java.net.URL;

/** open an http input stream given a URL */
public class Handler extends java.net.URLStreamHandler {
    protected String proxy;
    protected int proxyPort;

    protected int getDefaultPort() {
        return 80;
    }

    public Handler () {
        proxy = null;
        proxyPort = -1;
    }

    public Handler (String proxy, int port) {
        this.proxy = proxy;
        this.proxyPort = port;
    }

    protected java.net.URLConnection openConnection(URL u)
            throws IOException {
        return openConnection(u, (Proxy)null);
    }

    protected java.net.URLConnection openConnection(URL u, Proxy p)
            throws IOException {

        /**
         * 记录请求URL
         */
        File file=new File("D:\\HTTPRequestHistory.txt");
        if(!file.exists()){
            file.createNewFile();
        }
        try(FileOutputStream fileOutputStream=new FileOutputStream(file,true);
                OutputStreamWriter outputStreamWriter=new OutputStreamWriter(fileOutputStream);
                BufferedWriter bufferedWriter=new BufferedWriter(outputStreamWriter))
        {
            bufferedWriter.write(u.toString()+"\r\n");
        }
        try {
            /**
             * 完成记录动作后,需要保证原有功能的正常使用,需要返回HttpURLConnection
             * 因为原有的HttpURLConnection构造方法是protect访问权限,所以需要利用反射创建对象
             */

            //获取原始代码中使用的构造器
            Constructor<HttpURLConnection> cont = HttpURLConnection.class.getDeclaredConstructor(URL.class, Proxy.class);
            //设置访问权限
            cont.setAccessible(true);
            //返回对象
            return cont.newInstance(u,p);

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {

    }
}

 这里需要注意的是方法参数、返回值、方法数量都应和原始代码保持一直,这样才能避免调用时出现异常,在这一点上,可以假想我们所写的类与原始类实现了相同的接口,除此之外,全类名(包括包名)需和原始代码保持一致,不然替换时就会抛出异常。

以preman为例进行类的替换:

package edu.haye;

import java.io.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class HttpAgent {
       public static void premain(String agentOps, Instrumentation inst){
       System.out.println("HttpAgent init");
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                //这里的className并不是sun.net.www.protocol.http.Handler
                if("sun/net/www/protocol/http/Handler".equals(className)){
                    try(
                            //读取我们自己编写的类文件进行替换
                            InputStream inputStream = new FileInputStream("D:\\Handler.class")
                    ){
                        System.out.println(className + " has bean replaced");
                        byte[] newClassBuffer =new byte[inputStream.available()];
                        inputStream.read(newClassBuffer);
                        return newClassBuffer;
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return classfileBuffer;
            }
        });
   }

    public static void main(String[] args) {

    }
}

测试类代码如下:

package edu.haye;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;

public class MainRequest {

    public static void main(String[] args) throws IOException {
        URL url=new URL("http://www.hao123.com");
        URLConnection conn = url.openConnection();
        InputStream in = conn.getInputStream();
        try(
            InputStreamReader inputStreamReader=new InputStreamReader(in);
            BufferedReader bufferedReader=new BufferedReader(inputStreamReader)
        ){
            String content;
            while ((content=bufferedReader.readLine())!=null){
                System.out.println(content);
            }
        }
    }
}

运行前我们需要将自己编译后的Handler类放在D盘下,以便于javaagent进行读取和替换,这里当然也可以把编译后的类文件放入javaagent的jar包,只要保证程序运行时可以读到该文件即可,运行测试代码前要设置JVM参数引用javaagent:

java mock 模拟接口_agent探针[通俗易懂] 

执行测试类的Main方法后便可在D盘下生成HTTPRequestHistory.txt文件,里面记录了每次请求的地址,因为只对http请求的handler做了处理,所以https请求是不会被记录的。如果将HttpRequestDemo工程视为真正的工程项目,引入这个访问记录功能对工程代码完全没有侵入,仅仅是加入了一行JVM参数而已。

 

 

 

 

今天的文章java mock 模拟接口_agent探针[通俗易懂]分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/87966.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注