현재 프로젝트에서 생성된 모든 클래스들을 스캔(Scan)하도록 하겠습니다.
클래스 스캔(Scan)은 웹 서버가 구동될때 시작되도록 "ServletContextListener"의 "contextInitialized"에서 처리하고 베이스 패키지를 기준으로 모든 클래스들을 스캔하도록 출력하겠습니다.
1. "web.xml"에 "context-param"으로 베이스 패키지 경로를 설정합니다.
<context-param>
<param-name>ComponentScan-BasePackage</param-name>
<param-value>com.home.project.test2</param-value>
</context-param>
현재 "test2" 프로젝트가 클래스들이 "com.home.project.test2"를 기준으로 생성되어 있습니다.
2. 이전에 생성한 "ServletContextListener"(서블릿 컨텍스 리스너)인 "TestServletContextListener.java"의 "contextInitialized"에서 "getServletContext().getInitParameter"로 베이스 패키지 경로를 읽어옵니다.
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext 초기화 실행");
String basePackage = sce.getServletContext().getInitParameter("ComponentScan-BasePackage");
System.out.println("basePackage : " + basePackage);
}
이전 자바 서블릿 콘텍스트 리스너 만들기(carrotweb.tistory.com/38)를 참고하세요.
3. "test2" 프로젝트의 "Java Resources"에서 "src/main/java"의 "com.home.project.test2"를 선택한 후 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Package]를 클릭하여 "Name"의 기존 패키지명에 ".scanner"를 추가하고 "Finish"버튼을 클릭합니다. 추가된 "com.hom.project.test2.scanner"에서 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Class]를 클릭하고 "Constructors from superclass"를 선택하여 "TestClassScanner.java"를 생성합니다.
베이스 패키지 경로로 프로젝트의 리소스를 스캔하도록 메소드를 생성합니다.
현재 스레드를 기준으로 "ClassLoader"를 가져옵니다.
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
프로젝트 리소스를 기준으로 검색하기 위해 베이스 패키지 경로에서 '.'(도트)를 '/'(슬래시)로 변환합니다.
String path = basePackageName.replace('.', '/');
"ClassLoader"의 "getResources"메소드는 검색하는 리소스 명으로 된 모든 리소스를 찾고 리소스 명은 리소스를 식별하는 '/'(슬래시)로 구분 된 경로들로 열거형 데이터인 "Enumeration"(이뉴머레이션)를 리턴합니다.
Enumeration<URL> resources = classLoader.getResources(path);
"Enumeration"(이뉴머레이션)는 열거형 데이터(배열로 이해하셔도 됨)로 cursor(커서)의 위치를 통해 객체를 반환합니다. cursor(커서)의 위치는 0부터 시작하고 "hanMoreElement()"를 통해 cursor(커서)의 다음 위치(0 -> 1)에 객체가 있는지 확인합니다. 그리고 "nextElement()"를 통해 현재 cursor(커서)의 위치를 이동(0 -> 1)시키고 객체를 리턴합니다.
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
}
"getResources"메소드가 여러개를 리턴하는 리소스의 URL를 File로 변환(실제 시스템 경로)하여 File 배열에 추가합니다.
List<File> files = new ArrayList<File>();
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
files.add(new File(resource.getFile()));
}
File 배열에서 디렉토리인 것만 출력합니다. 디렉토리가 패키지의 경로입니다.
for (File file : files) {
if (file.isDirectory()) {
System.out.println("[Directory] " + file.getAbsolutePath());
}
}
"scan"메소드 전체 소스입니다.
/**
* 베이스 패키지 경로로 리소스를 스캔합니다.
* @param basePackageName 베이스 패키지 경로
*/
public static void scan(String basePackageName) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = basePackageName.replace('.', '/');
try {
List<File> files = new ArrayList<File>();
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
files.add(new File(resource.getFile()));
}
for (File file : files) {
if (file.isDirectory()) {
System.out.println("[Directory] " + file.getAbsolutePath());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
4. "Servers"탭에서 "tomcat8"를 선택하고 "start"버튼(start the server)을 클릭합니다.
"Console"탭을 클릭하면 베이스 패키지 경로를 찾아 시스템 경로로 출력한 것을 확인할 수 있습니다.
[Console]
ServletContext 초기화 실행
basePackage : com.home.project.test2
[Directory] C:\workspaces\projects\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\test2\WEB-INF\classes\com\home\project\test2
"Servers"탭에서 "Tomcat8"를 선택하고 "stop"버튼(stop the server)을 클릭합니다.
5. 베이스 패키지 경로를 찾아 시스템 경로를 기준으로 클래스 파일(.class)를 찾도록 메소드를 생성합니다.
현재 경로를 기준으로 디렉토리와 파일을 찾아 File 배열로 가져옵니다.
File[] files = directory.listFiles();
File배열을 for문으로 돌면서 디렉토리이면 재귀호출(recursive call)로 다시 "findClasses"를 호출하게 합니다.
for (File file : files) {
if (file.isDirectory()) {
findClasses(file);
}
}
File배열을 for문으로 돌면서 클래스 파일(.class)이면 출력합니다.
for (File file : files) {
if (file.getName().endsWith(".class")) {
System.out.println("[File] " + file.getAbsolutePath());
}
}
"findClasses"메소드 전체 소스입니다.
/**
* 모든 리소스 디렉토리를 검색하여 클래스 파일(.class)을 찾습니다.
* @param directory 리소스 디렉토리
*/
private static void findClasses(File directory) {
if (!directory.exists()) {
return;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
System.out.println("[Directory] " + file.getAbsolutePath());
findClasses(file);
} else if (file.getName().endsWith(".class")) {
System.out.println("[File] " + file.getAbsolutePath());
}
}
}
"scan"메소드에 "findClasses"메소드를 추가합니다.
for (File file : files) {
if (file.isDirectory()) {
System.out.println("[Directory] " + file.getAbsolutePath());
findClasses(file);
}
}
6. "Servers"탭에서 "tomcat8"를 선택하고 "start"버튼(start the server)을 클릭합니다.
"Console"탭을 클릭하면 지금까지 "com.home.project.test2"를 베이스로 만들어진 모든 디렉토리(패키지) 경로와 클래스 파일(.class) 경로가 출력된 것을 확인할 수 있습니다.
[Console]
ServletContext 초기화 실행
basePackage : com.home.project.test2
[Directory] C:\workspaces\projects\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\test2\WEB-INF\classes\com\home\project\test2
[Directory] C:\workspaces\projects\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\test2\WEB-INF\classes\com\home\project\test2\config
[Directory] C:\workspaces\projects\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\test2\WEB-INF\classes\com\home\project\test2\controller
[File] C:\workspaces\projects\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\test2\WEB-INF\classes\com\home\project\test2\controller\ITestController.class
[File] C:\workspaces\projects\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\test2\WEB-INF\classes\com\home\project\test2\controller\ModelAndView.class
[File] C:\workspaces\projects\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\test2\WEB-INF\classes\com\home\project\test2\controller\TestController.class
[File] C:\workspaces\projects\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\test2\WEB-INF\classes\com\home\project\test2\controller\TestRootController.class
출력 내용이 많아 앞부분 표시하였습니다.
"test2"프로젝트에서 "com.home.project.test2"를 기준으로 생성된 모든 클래스 파일(.class)들을 스캔(Scan)하였습니다.
이어서, 스캔(scan)한 모든 클래스 파일(.class)들을 클래스 배열로 리턴하도록 수정하여 처리하도록 하겠습니다.