AspectJ能否穿过sun.net.*包?

5

我正在使用AspectJ来拦截java.net.Socket的调用。

我创建了一个非常简单的切面。

after(): call(* java.net.Socket.connect(..)) {
    System.out.println("Connect intercepted!");
}

以及aop.xml文件

<aspectj>

    <aspects>
        <aspect name="com.iggroup.lightstreamer.nwtp.SocketExceptionLoggingAspect"/>
    </aspects>

    <weaver options="-Xlint:ignore -Xset:weaveJavaxPackages=true -Xset:weaveJavaPackages=true">
    </weaver>

</aspectj>

当调用栈像这样时,我可以看到控制台输出:
java.lang.Exception
    at com.iggroup.lightstreamer.nwtp.SocketExceptionLoggingAspect.ajc$after$com_iggroup_lightstreamer_nwtp_SocketExceptionLoggingAspect$2$6e16217c(SocketExceptionLoggingAspect.aj:39)
    at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:337)
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:134)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
    at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
    at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:91)
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:596)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:557)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:475)
    at com.iggroup.lightstreamer.nwtp.users.SsoRestClientImpl.lambda$0(SsoRestClientImpl.java:68)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

但是当调用栈像这样时,没有任何日志被记录:
Caused by: java.net.ConnectException: Connection refused: connect
                at java.net.DualStackPlainSocketImpl.connect0(Native Method) ~[na:1.8.0_65]
                at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) ~[na:1.8.0_65]
                at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[na:1.8.0_65]
                at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) ~[na:1.8.0_65]
                at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) ~[na:1.8.0_65]
                at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) ~[na:1.8.0_65]
                at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_65]
                at java.net.Socket.connect(Socket.java:589) ~[na:1.8.0_65]
                at sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:668) ~[na:1.8.0_65]
                at sun.security.ssl.BaseSSLSocketImpl.connect(BaseSSLSocketImpl.java:173) ~[na:1.8.0_65]
                at sun.net.NetworkClient.doConnect(NetworkClient.java:180) ~[na:1.8.0_65]
                at sun.net.www.http.HttpClient.openServer(HttpClient.java:432) ~[na:1.8.0_65]
                at sun.net.www.http.HttpClient.openServer(HttpClient.java:527) ~[na:1.8.0_65]
                at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:781) ~[na:1.8.0_65]
                at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:647) ~[na:1.8.0_65]
                at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1536) ~[na:1.8.0_65]
                at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1441) ~[na:1.8.0_65]
                at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254) ~[na:1.8.0_65]
                at com.lightstreamer.ls_client.HttpProvider.connectAndGetAnswer(HttpProvider.java:244) ~[lightstreamer-se-client-2.5.2-1110.jar:na]

我想知道是否因为某些安全管理器限制而导致无法在加载时织入sun.net.*包。

有人知道如何让sun.net.*包正常工作吗?

更新1

我确认可以拦截ls_client.HttpProvider.connectAndGetAnswer调用,但无法拦截sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream上面的调用。

使用AspectJ能否编织sun.*包?


你是否正在使用Java代理进行加载时编织? - Nándor Előd Fekete
顺便说一下,这是一个使用 sun.* 包的第三方库,我无法控制(我完全同意没有理智的人应该使用 sun.*)。 - stackoverflower
请查看我在此处的另一个答案,了解如何启用加载时编织的调试输出。这应该会揭示很多关于发生情况的信息。最好将标准输出和标准错误重定向到文件中以便更轻松地进行分析。 - Nándor Előd Fekete
哦,而且那个第三方库可能不会直接使用 sun.* 类,因为该类实际上是来自 javajavax 包的公共 API 类的子类,所以它可能只是从某个工厂获取了它,这就是 JDK 给他的。在 JDK 中经常发生这种情况。 - Nándor Előd Fekete
@NándorElődFekete,您能否详细解释一下这是什么意思?谢谢。 - stackoverflower
显示剩余3条评论
2个回答

5

我还没有看到过成功加载时间编织JRE(引导程序)类的设置。如果您需要此功能进行调试,则应选择构建时编织JRE类。

以下简短的代码片段将为您编织JRE jar文件,并将编织后的类放入单个输出jar文件中。它需要添加 org.aspectj/aspectjtools 作为依赖项。此外,它会跳过 JRE 的 ext 子文件夹中的 jar 文件,因为这些 jar 文件将包含一些重复的类,创建一个包含重复文件的 jar 文件将导致错误。我还跳过了较新版本的 jfxswt.jar,因为它会由于缺少类而失败。

String aspectFileName = "src/main/java/pckg/AspectName.aj";
String jreLibPath = "c:/Program Files/Java/jdk1.8.0_40/jre/lib";
String outputJar = "weavedjre.jar";

List<String> jars = new ArrayList<>();

File dir = new File(jreLibPath);
File[] files = dir.listFiles();
for (File file : files) {
    if (file.isFile() && file.getName().endsWith(".jar")
            && !file.getName().endsWith("jfxswt.jar")) {
        jars.add(file.getAbsolutePath());
    }
}

List<String> ajcArgs = new ArrayList<>(Arrays.asList("-showWeaveInfo"));
for (String jar : jars) {
    ajcArgs.add("-inpath");
    ajcArgs.add(jar);
}
ajcArgs.add(aspectFileName);
ajcArgs.add("-outjar");
ajcArgs.add(outputJar);

org.aspectj.tools.ajc.Main.main(ajcArgs.toArray(new String[] {}));

如果要使用编织后的JRE类(添加到引导类路径前缀),请使用以下VM参数运行程序:

-verbose:class -Xbootclasspath/p:path_to/weavedjre.jar

或者在Eclipse启动配置中:

-verbose:class -Xbootclasspath/p:${resource_loc:/project_name/weavedjre.jar}

我还添加了详细日志记录类加载VM参数,这样您就可以看到从哪里加载了哪个类。

我有同样的问题,我无法拦截 pointcut callUrlOpenConnection() : (execution (public * java.net.URL.openConnection())); - khouloud mejdoub

1

Nándor 是正确的。我无法在 JRE 库上执行 LTW,因为它们在引导类路径上并且在 AspectJ 可以编织它们之前被加载。我必须进行编译时编织并替换默认库。

这是我所做的:

你需要的文件

workspace
  |- Aspect.aj
  |- rt.jar
  |- aspectjrt-1.8.7.jar
  |- aspectjtools-1.8.7.jar

Aspect.aj

package java.net;

import sun.misc.SharedSecrets;

import java.io.FileDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public aspect Aspect {
    private static Class dualStackPlainSocketImplClass;
    private static Method localPort0;

    static {
        try {
            dualStackPlainSocketImplClass = Class.forName("java.net.DualStackPlainSocketImpl");
            localPort0 = dualStackPlainSocketImplClass.getDeclaredMethod("localPort0", Integer.TYPE);
            localPort0.setAccessible(true);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    pointcut connect(java.net.DualStackPlainSocketImpl s): target(s) && execution(* java.net.DualStackPlainSocketImpl.socketConnect(..));

    after(DualStackPlainSocketImpl s) throwing (Exception e): connect(s) {
        try {
            Field fdf = dualStackPlainSocketImplClass.getSuperclass().getSuperclass().getDeclaredField("fd");
            fdf.setAccessible(true);
            FileDescriptor fd = (FileDescriptor) fdf.get(s);
            int nativeFd = SharedSecrets.getJavaIOFileDescriptorAccess().get(fd);
            int localPort = (int) localPort0.invoke(dualStackPlainSocketImplClass, nativeFd);
            System.out.format("[local port=%s][exception=%s]\n", localPort, e.getMessage());
        } catch (InvocationTargetException e1) {
            e1.printStackTrace();
        } catch (IllegalAccessException e1) {
            e1.printStackTrace();
        } catch (NoSuchFieldException e1) {
            e1.printStackTrace();
        } catch (NullPointerException e1) {
            e1.printStackTrace();
        }
    }
}

方面文件非常简单,棘手的部分是如何通过反射获取本地端口。
请注意,package java.net;很重要,因为某些类/方法是受保护的。

然后从工作区运行

java -cp "aspectjrt-1.8.7.jar;aspectjtools-1.8.7.jar" org.aspectj.tools.ajc.Main -1.8 -inpath rt.jar Aspect.aj -outjar newrt.jar

你会得到一个newrt.jar


运行你的程序时,使用它。
java -cp <your_class_path> -Xbootclasspath/p:<path_to_newrt.jar> <main_class>

-Xbootclasspath/p会将newrt.jar添加到启动类路径中,并覆盖JDK默认设置。


我认为你不需要使用反射技巧,只需将你的方面声明为privileged即可。 - Nándor Előd Fekete
谢谢你的建议。我会尝试并更新这个答案。 - stackoverflower

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接