SpringMVC 구조는 다음과 같다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseBody {
}
// 매핑 정보
public class MappingRegistry {
private Object handler;
private Method method;
...
}
public interface HandlerAdapter {
boolean supports(Object handler);
String handle(HttpServletRequest request, HttpServletResponse response, MappingRegistry handler);
}
public class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler.getClass().isAnnotationPresent(Controller.class);
}
@Override
public String handle(HttpServletRequest request, HttpServletResponse response, MappingRegistry handler) {
Object result = null;
try {
Method method = handler.getMethod();
result = method.invoke(handler.getHandler());
if (method.isAnnotationPresent(ResponseBody.class)) {
if (result instanceof String) {
response.setContentType("text/plain");
response.getWriter().println(result);
}
return null;
}
} catch (InvocationTargetException | IllegalAccessException | IOException e) {
e.printStackTrace();
}
return (String) result;
}
}
public interface ViewResolver {
View resolveViewName(String viewName) throws Exception;
}
public class InternalResourceViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName) {
return viewName.endsWith(".jsp") ? new InternalResourceView() : null;
}
}
public interface View {
void render(HttpServletRequest request, HttpServletResponse response, String path);
}
public class InternalResourceView implements View {
@Override
public void render(HttpServletRequest request, HttpServletResponse response, String path) {
try {
request.getRequestDispatcher(path).forward(request, response);
} catch (ServletException | IOException e) {
e.printStackTrace();
}
}
}
public class WebApplicationContext {
public static WebApplicationContext instance;
private final Map<String, MappingRegistry> handlerMapping = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
private final List<ViewResolver> viewResolvers = new ArrayList<>();
// 싱글톤
public static WebApplicationContext getInstance() {
if (instance == null) {
instance = new WebApplicationContext();
}
return instance;
}
// 객체 생성 시 리소스 등록
private WebApplicationContext() {
initResources();
}
...
getter 메소드
...
private void initResources() {
String packageName = "com.example.dispatcherservlet";
Set<Class<?>> classes = new HashSet<>();
setClasses(classes, packageName);
for (Class<?> aClass : classes) {
try {
Object instance = aClass.getConstructor().newInstance();
// 핸들러 어댑터 등록
if (instance instanceof MyHandlerAdapter) {
handlerAdapters.add((MyHandlerAdapter) instance);
}
// 뷰 리졸버 등록
if (instance instanceof ViewResolver) {
viewResolvers.add((ViewResolver) instance);
}
// 각 요청 URL에 맞는 핸들러와 메소드 등록
if (aClass.isAnnotationPresent(Controller.class)) {
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
RequestMapping annotation = declaredMethod.getAnnotation(RequestMapping.class);
handlerMapping.put(annotation.value(), new MappingRegistry(instance, declaredMethod));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 패키지 내부의 모든 클래스 찾기
private void setClasses(Set<Class<?>> classes, String packageName) {
String directoryString = getDirectoryString(packageName);
File directory = new File(directoryString);
if(directory.exists()){
String[] files = directory.list();
for(String fileName : files){
if (isExcluded(fileName)) continue;
// .class 파일 인 경우
if (fileName.endsWith(".class")) {
fileName = fileName.substring(0, fileName.length() - 6);
try{
// 해당 이름의 클래스 찾기
Class<?> c = Class.forName(packageName + "." + fileName);
// 인터페이스 제외
if (!c.isInterface()) {
classes.add(c);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 디렉토리 일 경우 하위 디렉토리의 클래스 찾기
} else {
setClasses(classes, packageName + "." + fileName);
}
}
}
}
// 스캔 제외 대상
private boolean isExcluded(String fileName) {
String[] exclude = {"WebApplicationContext", "MyDispatcherServlet"};
for (String s : exclude) {
if (fileName.contains(s)) return true;
}
return false;
}
// 디렉토리 경로
private String getDirectoryString(String packageName) {
String packageNameSlashed =
"./" + packageName.replace(".", "/");
URL packageDirURL =
Thread.currentThread().getContextClassLoader().getResource(packageNameSlashed);
return packageDirURL.getFile();
}
}
@WebServlet(name = "dispatcherServlet", urlPatterns = "/")
public class DispatcherServlet extends HttpServlet {
private List<ViewResolver> viewResolvers;
private List<HandlerAdapter> handlerAdapters;
private Map<String, MappingRegistry> handlerMapping;
// 서블릿 생성 시 WebApplicationContext 초기화
public DispatcherServlet() {
WebApplicationContext resources = WebApplicationContext.getInstance();
this.viewResolvers = resources.getViewResolvers();
this.handlerMapping = resources.getHandlerMapping();
this.handlerAdapters = resources.getHandlerAdapters();
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
try {
// 핸들러 찾기
MappingRegistry handler = getHandler(request);
if (handler != null) {
// 핸들러 어댑터 찾기
HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
if (handlerAdapter != null) {
// 핸들러 수행
String result = handlerAdapter.handle(request, response, handler);
if (result != null) {
// 렌더링
render(request, response, result);
}
}
} else {
// 없으면 404.jsp
request.getRequestDispatcher("/404.jsp").forward(request, response);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private MappingRegistry getHandler(HttpServletRequest request) {
return handlerMapping.get(request.getRequestURI());
}
private HandlerAdapter getHandlerAdapter(MappingRegistry handler) {
for (HandlerAdapter handlerAdapter : handlerAdapters) {
if (handlerAdapter.supports(handler.getHandler())) {
return handlerAdapter;
}
}
return null;
}
private void render(HttpServletRequest request, HttpServletResponse response, String result) throws Exception {
View view = resolveViewName(result);
if (view != null) {
view.render(request, response, result);
}
}
private View resolveViewName(String path) throws Exception {
for (ViewResolver viewResolver : viewResolvers) {
View view = viewResolver.resolveViewName(path);
if (view != null) {
return view;
}
}
return null;
}
}
@Controller
public class TestController {
@ResponseBody
@RequestMapping("/hi")
public String hi() {
return "hi";
}
@RequestMapping("/main")
public String main() {
return "main.jsp";
}
}
localhost:8080/hi -> body에 데이터 출력
localhost:8080/main -> main.jsp
없는 요청 -> 404.jsp
컨트롤러 추가해보기
@Controller
public class HelloController {
@ResponseBody
@RequestMapping("/hello")
public String hello() {
return "hello";
}
}
너무 좋은 글 감사합니다 잘 배우고 갑니다.