1. 협업의 어려움
- A, B 두 회사에서 프로그램을 개발 중
- A 회사에서는 프로토콜에 따른 응답을 보낼 수 있도록 메서드를 연결하는 프로그램을 제작
- B 회사에서는 연결을 통해 사람들에게 응답을 보내는 서버 프로그램을 제작
- A 회사가 자사 프로그램을 B 회사에게 판매
- B 회사는 직접 메서드들을 서버와 연결할 수고가 없어짐
- B 회사는 A 회사가 설계해둔 구조(인터페이스)를 받아서, 메서드들을 구현
- A 회사는 어떤 회사가 기능을 어떻게 사용할지 모르므로 인터페이스로 메서드 구현

- B 회사에서 프로그램에 기능을 추가하고 싶으면 A 회사와 contact 필요
- A 회사는 B가 추가할 기능을 연결하고, 인터페이스에 이를 연결하는 번거로움 발생
- B 회사에서는 기능을 추가할 때마다 A 회사와 contact 해야 하는 번거로움 발생
- A 회사의 경우, 이 프로그램을 다른 회사에 판매하였을 때에도 똑같은 문제 발생 가능

2. Reflection & Annotation
- 동적으로 메서드에 접근할 수 있는 기술
- 메서드 이름, 매개 변수, 리턴 값 등 다양한 요소를 가지고 활용 가능
- 요구 (Request)에 따른 동적인 반응(Response)이 가능하므로 유연성 있는 프로그램 제작 가능
- 어노테이션 (Annotation)과 함께 사용하여 시간 절약, 코드 간소화 등에 큰 효과를 볼 수 있음
Example
- 큰 도로변의 가로수를 정리하는 데, 기둥에 상처가 난 나무만 정리하려고 한다.
- 기존 방법의 경우, 모든 나무들을 일일이 찾아다니면서 상처가 났는지 확인해야 한다.
- 나무에 상처가 났다면, 어느 나무가 상처가 났는지 기억해두고 다음 나무를 찾아야 한다.
- 나무를 추가로 심었는데 그 나무에 상처가 났다면, 다시 새로 심은 나무에 찾아가 상처가 났는지 확인하고, 그 위치를 외워둬야 한다.
- 상처가 난 나무를 다 확인했다면 벌목 담당자에게 나무의 위치를 알려준다.
- 벌목 담당자는 그 위치를 기억해서 나무를 정리한다.
- 나무를 자를 때에도 Full Scan이 필요 : 시간이 매우 오래 걸림

- 어노테이션 (Annotation)을 활용하면, 이를 더욱 손쉽게 처리할 수 있다.
- 상처가 난 나무의 위치를 외우지 않고, 이 나무를 정리하면 된다는 표시로 깃발을 걸어둔다.
- 나무를 새로 심을 때에 나무에 상처가 있는지 확인하고, 상처가 나 있다면 깃발을 걸어둔다.
- 깃발을 다 걸고 난 후, 벌목 담당자에게 깃발이 달린 나무만 정리하라고 알려준다.
- 벌목 담당자는 깃발이 걸린 나무만 정리한다.
- 깃발만 따라가면 되므로 모든 위치를 찾아 갈 필요가 없음

3. 코드 작성
- Dispatcher : Reflection, Annotation을 활용하여 메서드를 동적으로 연결
- UserController의 메서드를 전부 읽어서 methods 배열에 저장
- UserController에서 Annotation이 붙은 메서드만 체크
- 경로까지 일치하면 메서드 invoke ( 서버 연결 )
- 프로그램을 배포한 후의 유지/보수에서 자유롭다는 이점
package ex02;
import java.lang.reflect.Method;
public class Dispatcher {
UserController con;
public Dispatcher(UserController con) {
this.con = con;
}
public void routing(String path) { // /login
Method[] methods = con.getClass().getMethods();
for (Method method : methods) {
RequestMapping rm = method.getAnnotation(RequestMapping.class);
if (rm == null) continue; // 다음 for문으로 바로 넘어감
if (rm.value().equals(path)) {
try {
method.invoke(con);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
- UserController : Annotation을 붙여서, 서버에 연결한 메서드들만 정의하면 자동(동적) 연결
- 새로 메서드를 추가해도, Annotation을 부착하여 path와 동작만 정의하면 자동 연결 가능
- 다른 클래스와 직접 연결하지 않아도 되는 간편함
package ex02;
public class UserController {
@RequestMapping("/login")
public void login() {
System.out.println("login call");
}
@RequestMapping("/join")
public void join() {
System.out.println("join call");
}
@RequestMapping("/logout")
public void logout() {
System.out.println("logout call");
}
@RequestMapping("/userinfo")
public void userinfo() {
System.out.println("userinfo call");
}
}
- App : 메인 서버 프로그램
- 가동 시 클라이언트 측으로부터 요청을 받아 응답
- 리플렉션을 적용해둔 메서드를 사용
package ex02;
public class App {
public static void main(String[] args) {
Dispatcher ds = new Dispatcher(new UserController());
// path : Scanner로 받으면 됨
ds.routing("/userinfo");
}
}
- RequestMapping : 요청에 대한 응답을 태그(@)처럼 미리 지정
- 작동 대상, 작동 시간 결정 가능 : Dynamic
package ex02;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 메서드 위에만 붙일 수 있음 : ElementType.METHOD
// 클래스 위에 붙이고 싶으면 : ElementType.TYPE
// 작동 시간 결정(Timing) (RUNTIME, SOURCE(COMPILE))
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {
String value();
}
결과

4. 활용
- App
- Component Scan 활용 : 패키지 내의 @Component가 달린 클래스를 Search
package ex04;
import java.io.File;
import java.net.URL;
public class App {
public static void main(String[] args) {
// 1. @Component가 붙으면 new해서 컬렉션에 담기
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
URL packageUrl = classLoader.getResource("ex04");
File packageDir = new File(packageUrl.getFile());
for (File file : packageDir.listFiles()) {
if (file.getName().endsWith(".class")) {
String className = "ex04." + file.getName().replace(".class", "");
System.out.println(className);
}
}
}
}
- DispatcherServlet
package ex04;
import java.lang.reflect.Method;
public class DispatcherServlet {
UserController con;
public DispatcherServlet(UserController con) {
this.con = con;
}
public void routing(String path) { // /login
Method[] methods = con.getClass().getMethods();
for (Method method : methods) {
RequestMapping rm = method.getAnnotation(RequestMapping.class);
if (rm == null) continue; // 다음 for문으로 바로 넘어감
if (rm.value().equals(path)) {
try {
method.invoke(con);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
- UserController
package ex04;
@Component
public class UserController {
@RequestMapping("/login")
public void login() {
System.out.println("login call");
}
@RequestMapping("/join")
public void join() {
System.out.println("join call");
}
@RequestMapping("/logout")
public void logout() {
System.out.println("logout call");
}
@RequestMapping("/userinfo")
public void userinfo() {
System.out.println("userinfo call");
}
}
- BoardController
package ex04;
@Component
public class BoardController {
@RequestMapping("/write")
public void write() {
System.out.println("write call");
}
@RequestMapping("/delete")
public void delete() {
System.out.println("delete call");
}
}
- Component
package ex04;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
}
결과


- App 수정
package ex04;
import java.io.File;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
public class App {
public static void main(String[] args) {
// 1. @Component가 붙으면 new해서 컬렉션에 담기
Set<Object> instances = new HashSet();
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
URL packageUrl = classLoader.getResource("ex04");
File packageDir = new File(packageUrl.getFile());
for (File file : packageDir.listFiles()) {
if (file.getName().endsWith(".class")) {
String className = "ex04." + file.getName().replace(".class", "");
//System.out.println(className);
try {
Class cls = Class.forName(className);
if (cls.isAnnotationPresent(Component.class)) {
Object instance = cls.getDeclaredConstructor().newInstance();
instances.add(instance);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} // for 종료
for (Object instance : instances) {
System.out.println(instance.getClass().getName());
}
}
}
결과

5. 코드 완성
- App에서는 path를 받아 요청 및 응답만 수행
- Component Scan과 routing은 DispatcherServlet에서 수행
- App
package ex04;
import java.util.Scanner;
import java.util.Set;
public class App {
public static void main(String[] args) {
// RequestMapping, Component, DispatcherServlet (돈 주고 삼 = SpringWeb)
Scanner sc = new Scanner(System.in);
String path = sc.nextLine();
DispatcherServlet ds = new DispatcherServlet();
Set<Object> instances = ds.componentScan("ex04");
ds.routing(instances, path);
}
}
- DispatcherServlet
package ex04;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
public class DispatcherServlet {
public Set<Object> componentScan(String packageName) {
// 1. @Component가 붙으면 new해서 컬렉션에 담기
Set<Object> instances = new HashSet();
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
URL packageUrl = classLoader.getResource(packageName);
File packageDir = new File(packageUrl.getFile());
for (File file : packageDir.listFiles()) {
if (file.getName().endsWith(".class")) {
String className = "ex04." + file.getName().replace(".class", "");
//System.out.println(className);
try {
Class cls = Class.forName(className);
if (cls.isAnnotationPresent(Component.class)) {
Object instance = cls.getDeclaredConstructor().newInstance();
instances.add(instance);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
return instances;
}
public void routing(Set<Object> instances, String path) { // /login
for (Object instance : instances) {
Method[] methods = instance.getClass().getMethods();
for (Method method : methods) {
RequestMapping rm = method.getAnnotation(RequestMapping.class);
if (rm == null) continue; // 다음 for문으로 바로 넘어감
if (rm.value().equals(path)) {
try {
method.invoke(instance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
}
- 나머지는 동일
결과
- BoardController에 존재하는 write 메서드를 동적 호출 가능

Share article