java - 是否可以从 javaagent 动态注册 servlet?

标签 java tomcat tomcat7 servlet-3.0 javaagents

我有一个 java 代理,它有一个用 @WebServlet 注释的类。我通过在 JAVA_OPTS 中指定 -javaagent:/path/to/agent/jar 将此代理附加到使用 Servlet 3.0 的基于 servlet 的应用程序。

但是,servlet 似乎没有加载,我在尝试访问 servlet 时收到 404 错误。

这可能吗?

最佳答案

TLDR:https://github.com/tsabirgaliev/tomcat-agent

Tomcat 8.0 的快速而肮脏的解决方案

public class Agent {
    public static class Target {

        public static final String GLOBAL_SERVLET_PATTERN = "/globalServlet";
        public static final String GLOBAL_SERVLET_NAME = "globalServlet";

        public boolean intercept(
                @SuperCall Callable<Boolean> zuper
                , @Argument(0) InputSource source
                , @Argument(1) Object dest
                , @Argument(2) boolean fragment
                , @This Object self
        ) {
            try {
                boolean ok = zuper.call();

                if (!fragment) {
                    Method getServletMappings = dest.getClass().getMethod("getServletMappings");
                    Map<String, String> mappings = (Map<String, String>)getServletMappings.invoke(dest);

                    if (!mappings.containsKey(GLOBAL_SERVLET_PATTERN)) {
                        ClassLoader loader = self.getClass().getClassLoader();

                        Class<?> servletDefClass = Class
                        .forName("org.apache.tomcat.util.descriptor.web.ServletDef", true, loader);

                        Object servletDef = servletDefClass.newInstance();

                        servletDefClass.getMethod("setServletClass", String.class)
                        .invoke(servletDef, "io.tair.myagent.GlobalServlet");

                        servletDefClass.getMethod("setServletName", String.class)
                        .invoke(servletDef, GLOBAL_SERVLET_NAME);

                        dest.getClass().getMethod("addServlet", servletDefClass)
                        .invoke(dest, servletDef);

                        dest.getClass().getMethod("addServletMapping", String.class, String.class)
                        .invoke(dest, GLOBAL_SERVLET_PATTERN, GLOBAL_SERVLET_NAME);
                    }

                }

                return ok;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }

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

        new AgentBuilder.Default()
            .type(named("org.apache.tomcat.util.descriptor.web.WebXmlParser"))
            .transform((builder, typeDescription, classLoader) ->
                builder.method (
                    named("parseWebXml")
                    .and(takesArgument(0, InputSource.class))
                )
                .intercept(MethodDelegation.to(new Target()))
            )
            .installOn(inst);

    }
}

想法是拦截对 org.apache...WebXmlParser#parseWebXml(InputSource, WebXml, boolean) 的调用,并在解析 web.xml 文件后立即添加必要的 servlet 映射。

重的部分由优秀的 ByteBuddy [1] 处理。要使此代理工作,您必须包含 ByteBuddy 类。

如果 GlobalServlet 打包在代理中,您还必须包含 ServletApi 类。这肯定是可以避免的,但我不知道足够的 ByteBuffer 魔法来实现它。

Tomcat 7.0 更新

除了 servlet-api 之外,以下解决方案还需要 jsp-api。

public class Agent {
    public static class Target {

        public static final String GLOBAL_SERVLET_PATTERN = "/globalServlet";
        public static final String GLOBAL_SERVLET_NAME = "globalServlet";

        public void intercept(
                @SuperCall Callable<Void> zuper
                , @This Object self
        ) {
            try {
                Method getServletMappings = self.getClass().getMethod("getServletMappings");
                Map<String, String> mappings = (Map<String, String>)getServletMappings.invoke(self);

                if (!mappings.containsKey(GLOBAL_SERVLET_PATTERN)) {
                    ClassLoader loader = self.getClass().getClassLoader();

                    Class<?> servletDefClass = Class
                            .forName("org.apache.catalina.deploy.ServletDef", true, loader);

                    Object servletDef = servletDefClass.newInstance();

                    servletDefClass.getMethod("setServletClass", String.class)
                            .invoke(servletDef, "io.tair.myagent.GlobalServlet");

                    servletDefClass.getMethod("setServletName", String.class)
                            .invoke(servletDef, GLOBAL_SERVLET_NAME);

                    self.getClass().getMethod("addServlet", servletDefClass)
                            .invoke(self, servletDef);

                    self.getClass().getMethod("addServletMapping", String.class, String.class)
                            .invoke(self, GLOBAL_SERVLET_PATTERN, GLOBAL_SERVLET_NAME);
                }

                zuper.call();

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }

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

        new AgentBuilder.Default()
            .type(named("org.apache.catalina.deploy.WebXml"))
            .transform((builder, typeDescription, classLoader) ->
                builder.method (
                    named("configureContext")
                )
                .intercept(MethodDelegation.to(new Target()))
            )
            .installOn(inst);

    }
}

[1] http://bytebuddy.net/

关于java - 是否可以从 javaagent 动态注册 servlet?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36690601/

相关文章:

Java 字体呈现 : Must general AA really be turned off for subpixel AA?

java - 我们如何在 GWT 中从一页导航到另一页

java - 重装系统后"The specified Tomcat installation directory does not exist"

spring-boot - spring-boot项目需要单独安装Tomcat吗?

java - 如何在 Java 中创建要打印到 JFrame 的 JLabel 数组

java - Android:简单的 Listview 不使用 startActivityForResult() 显示数组中的字符串

tomcat - 从未知来源创建的 ActiveMQ 消费者

apache - 编译 apache APR 时出错,找不到生成文件

java - 更改 Eclipse 中的默认动态 Web 模块版本

java - Applet 因 JNLP MissingFieldException <jnlp> 停止工作