是否有可能从javaagent动态注册servlet?

时间:2016-04-18 09:51:08

标签: java tomcat tomcat7 servlet-3.0 javaagents

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

然而,servlet似乎没有被加载,并且在尝试访问servlet时出现404错误。

这甚至可能吗?

1 个答案:

答案 0 :(得分:2)

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/