2012년 3월 12일 월요일

[tomcat] servlet invoker 사용하기

1. web.xml 에서 아래 부분 주석 해제
------------------------------------------------------------------------------
    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>


    <servlet-mapping>
        <servlet-name>invoker</servlet-name>
        <url-pattern>/servlet/*</url-pattern>
    </servlet-mapping>
------------------------------------------------------------------------------



2. context.xml에서 <Context>태그 부분 수정
------------------------------------------------------------------------------
<Context reloadable="true" privileged="true"> 
------------------------------------------------------------------------------



** tomcat7.0부터는 servlet3.0을 사용하는 이유로 invoker 기능이 사라졌다.
그래서 찾아보니 그렇게 할 수 있는 java를 만들어 listener로 추가 하는 방법을 구현한 외국분의 사이트를 발견
http://blogs.nologin.es/rickyepoderi/index.php?/archives/44-Tomcat-7-and-the-Invoker-Servlet.html

방법을 정리하자면..
1. InvokerLoadListener.java를 Download
2. web.xml 수정(여기서 말하는 web.xml은 war파일로 묶일 곳의 web.xml, tomcat/conf에 있는 web.xml이 아니다!)

-----------------------------------------------------------------------------

<listener>
        <listener-class>
            sample.invoker.ctxlistener.InvokerLoadListener
        </listener-class>
    </listener>

    <context-param>
        <param-name>invoker.packages</param-name>
        <param-value>
            sample.invoker.servlet,
            sample.invoker.other,
            sample.invoker.another
        </param-value>
        <description>List of packages to check for servlets (comma separated)</description>
    </context-param>
-----------------------------------------------------------------------------
고쳐질 web.xml에 대해서 간단한 설명

listener로 지정한 sample.invoker.ctxlistener.InvokerLoadListener
- 당연히 sample.invoker.ctxlistener패키지에 InvokerLoadListener.java를 넣으라는 것
<param-value>안에는 servlet이 들어가 있는 package를 넣을 것(한 개가 아니고 여러개라면 위처럼 ,를 이용해서 구분하자)



블로거의 단점!
왜!! 첨부가 안되는 것이냐.....
혹시나 나중에 링크가 작동안 할 때를 대비한 InvokerLoadListener.java
-----------------------------------------------------------------------------
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package sample.invoker.ctxlistener;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRegistration;
import javax.servlet.http.HttpServlet;
import org.apache.catalina.ContainerServlet;

/**
 *
 * http://snippets.dzone.com/posts/show/4831
 * 
 * @author ricky
 */
public class InvokerLoadListener implements ServletContextListener {

    /**
     * Invoker parameter that defines the packages to search servlets.
     * Comma separated list of packages
     */
    public static final String PACKAGES_PARAMETER = "invoker.packages";
    
    /**
     * Invoker parameter to setup the mapping name. By default is "/servlet/"
     */
    public static final String INVOKER_PREFIX_PARAMETER = "invoker.prefix";

    /**
     * Scans all classes accessible from the context class loader which 
     * belong to the given package and subpackages.
     * 
     * @param packageName
     * @return The list of classes found
     */
    private Set<Class> getClasses(String packageName) {
        Set<Class> classes = new HashSet<Class>();
        try {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            String path = packageName.replace('.', '/');
            Enumeration<URL> resources = classLoader.getResources(path);
            while (resources.hasMoreElements()) {
                URL resource = resources.nextElement();
                if (resource.getProtocol().equals("jar")) {
                    // inside a jar => read the jar files and check
                    findClassesJar(resource, path, classes);
                } else if (resource.getProtocol().equals("file")) {
                    // read subdirectories and find
                    findClassesFile(new File(resource.getFile()), packageName, classes);
                } else {
                    System.err.println("Unknown protocol connection: " + resource);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classes;
    }

    /**
     * Reads a jar file and checks all the classes inside it with the package
     * name specified.
     * 
     * @param resource The resource url
     * @param path
     * @param classes
     * @return 
     */
    private Set<Class> findClassesJar(URL resource, String path, Set<Class> classes) {
        JarURLConnection jarConn = null;
        JarFile jar = null;
        try {
            jarConn = (JarURLConnection) resource.openConnection();
            jar = jarConn.getJarFile();
            Enumeration<JarEntry> e = jar.entries();
            while (e.hasMoreElements()) {
                JarEntry entry = e.nextElement();
                if ((entry.getName().startsWith(path + "/")
                        || entry.getName().startsWith(path + "."))
                        && entry.getName().endsWith(".class")) {
                    String name = entry.getName().replace('/', '.');
                    name = name.substring(0, name.length() - 6);
                    checkClass(name, classes);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                jar.close();
            } catch (IOException e) {
            }
        }
        return classes;
    }

    /**
     * Recursive method used to find all classes in a given file (file
     * or directory).
     *
     * @param file   The base directory
     * @param packageName The package name for classes found inside the base directory
     * @ classes The list of classes
     * @return The same classes
     * @throws ClassNotFoundException
     */
    private Set<Class> findClassesFile(File file, String packageName, Set<Class> classes) {
        if (file.isFile() && file.getName().endsWith(".class")) {
            //classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
            checkClass(packageName.substring(0, packageName.length() - 6), classes);
        } else {
            File[] files = file.listFiles();
            for (File f : files) {
                findClassesFile(f, packageName + "." + f.getName(), classes);
            }
        }
        return classes;
    }

    private Set<Class> checkClass(String name, Set<Class> classes) {
        try {
            Class clazz = Class.forName(name);
            if (HttpServlet.class.isAssignableFrom(clazz)
                    && ContainerServlet.class.isAssignableFrom(clazz)) {
                classes.add(clazz);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return classes;
    }

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized(ServletContextEvent e)");
        ServletContext sc = sce.getServletContext();
        String list = sc.getInitParameter(PACKAGES_PARAMETER);
        String prefix = sc.getInitParameter(INVOKER_PREFIX_PARAMETER);
        if (prefix == null) {
            prefix = "/servlet/";
        }
        if (list != null) {
            String[] packages = list.split(",");
            for (int i = 0; i < packages.length; i++) {
                String packageName = packages[i].trim();
                if (packageName.length() > 0) {
                    System.out.println("Checking package: " + packageName);
                    // load classes under servlet.invoker
                    Set<Class> classes = getClasses(packageName);
                    System.out.println("size: " + classes.size());
                    for (Class clazz : classes) {
                        String mapping = prefix + clazz.getName();
                        System.out.println("Adding '" + clazz.getName() + "' in mapping '" + mapping + "'");
                        ServletRegistration sr = sc.addServlet(clazz.getName(), clazz.getName());
                        sr.addMapping(mapping);
                    }
                }
            }
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed(ServletContextEvent e)");
    }
}
-----------------------------------------------------------------------------





댓글 3개:

  1. war 파일로 묶어서 쓰라는 말씀이신가요?? 제가 완전 초보라 이해를 못하겠어요. web.xml이 수정된 파일과 말씀하신 외국사이트에서 제공하는 파일을 받은 상태예요. 자세히 설명좀 부탁드려도 될까요?????

    답글삭제
  2. 딱 필요한 부분이였는데, 쉬운 설명 감사합니다.

    답글삭제
  3. 딱 필요한 부분이였는데, 쉬운 설명 감사합니다.

    답글삭제