diff --git a/src/main/java/client/Application.java b/src/main/java/client/Application.java new file mode 100644 index 0000000..d441c1a --- /dev/null +++ b/src/main/java/client/Application.java @@ -0,0 +1,15 @@ +package client; + +import spring.ioc.annotation.ComponentScan; +import spring.ioc.annotation.Configuration; +import spring.mvc.web.TomcatStarter; + +@Configuration +@ComponentScan(value = {"client"}) +public class Application { + public static void main(String[] args) { + final TomcatStarter tomcatStarter = new TomcatStarter(); + tomcatStarter.start(); + tomcatStarter.await(); + } +} diff --git a/src/main/java/client/controller/UserController.java b/src/main/java/client/controller/UserController.java index 11b0bde..1ed20a5 100644 --- a/src/main/java/client/controller/UserController.java +++ b/src/main/java/client/controller/UserController.java @@ -1,20 +1,19 @@ package client.controller; import client.controller.dto.CreatUserDto; -import client.model.User; +import client.domain.User; import client.service.UserService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import spring.mvc.annotation.Controller; import spring.mvc.annotation.RequestMapping; -import spring.mvc.controller.AbstractController; -import spring.mvc.handler.mapping.RequestMethod; +import spring.mvc.web.handler.mapping.RequestMethod; import spring.mvc.view.ModelAndView; import java.util.List; @Controller -public class UserController extends AbstractController { +public class UserController { private final UserService userService; @@ -22,27 +21,26 @@ public UserController(final UserService userService) { this.userService = userService; } - @RequestMapping("/users") + @RequestMapping(value = "/users/create", method = RequestMethod.POST) + public ModelAndView create(HttpServletRequest request, HttpServletResponse response) { + CreatUserDto requestDto = CreatUserDto.of(request); + User user = userService.createUser(requestDto); + + ModelAndView modelAndView = new ModelAndView(); + modelAndView.addObject("user", user); + return modelAndView; + } + + @RequestMapping(value = "/users", method = RequestMethod.GET) public ModelAndView getUsers(HttpServletRequest request, HttpServletResponse response) { int wannaSize = Integer.parseInt(request.getParameter("size")); List users = userService.getUsersWithSize(wannaSize); - ModelAndView modelAndView = jsonView(); - for(int i = 0; i < wannaSize; i++) { + ModelAndView modelAndView = new ModelAndView(); + for (int i = 0; i < users.size(); i++) { User user = users.get(i); modelAndView.addObject("user"+ i, user); } - - return modelAndView; - } - - @RequestMapping(value = "/users/create", method = RequestMethod.POST) - public ModelAndView create(HttpServletRequest request, HttpServletResponse response) { - CreatUserDto requestDto = CreatUserDto.of(request); - User user = userService.createUser(requestDto); - - ModelAndView modelAndView = jsonView(); - modelAndView.addObject("user", user); return modelAndView; } } diff --git a/src/main/java/client/controller/dto/CreatUserDto.java b/src/main/java/client/controller/dto/CreatUserDto.java index d0f9e5d..2b6154f 100644 --- a/src/main/java/client/controller/dto/CreatUserDto.java +++ b/src/main/java/client/controller/dto/CreatUserDto.java @@ -4,12 +4,10 @@ public record CreatUserDto(String name, String password, - String email, - String userId) { + String email) { public static CreatUserDto of(final HttpServletRequest request) { return new CreatUserDto(request.getParameter("name"), request.getParameter("password"), - request.getParameter("email"), - request.getParameter("userId")); + request.getParameter("email")); } } diff --git a/src/main/java/client/domain/User.java b/src/main/java/client/domain/User.java new file mode 100644 index 0000000..548773b --- /dev/null +++ b/src/main/java/client/domain/User.java @@ -0,0 +1,29 @@ +package client.domain; + +import client.controller.dto.CreatUserDto; + +public class User { + private long userId; + private String name; + private String password; + private String email; + + public User(long userId, String name, String password, String email) { + this.userId = userId; + this.name = name; + this.password = password; + this.email = email; + } + + public void updateUserId(long userId) { + this.userId = userId; + } + + public String getName() { + return name; + } + + public static User from(final CreatUserDto dto) { + return new User(0, dto.name(), dto.password(), dto.email()); + } +} diff --git a/src/main/java/client/domain/repsotiroy/UserRepository.java b/src/main/java/client/domain/repsotiroy/UserRepository.java new file mode 100644 index 0000000..ca0992b --- /dev/null +++ b/src/main/java/client/domain/repsotiroy/UserRepository.java @@ -0,0 +1,32 @@ +package client.domain.repsotiroy; + +import client.controller.dto.CreatUserDto; +import client.domain.User; +import spring.ioc.annotation.Repository; +import java.util.*; + +@Repository +public class UserRepository { + + private final Map temporaryDatabase = new HashMap<>(); + private Long SEQUENCE = 0L; + + public User save(User user) { + long id = SEQUENCE++; + user.updateUserId(id); + temporaryDatabase.put(id, user); + return user; + } + + public List getUsersWithLimit(int wannaSize) { + while(temporaryDatabase.size() < wannaSize) { + fixUser(); + } + return new ArrayList<>(temporaryDatabase.values()); + } + + private void fixUser() { + CreatUserDto dto = new CreatUserDto("김명준", "비밀번호", "skatks1016@gmail.com"); + save(User.from(dto)); + } +} diff --git a/src/main/java/client/model/User.java b/src/main/java/client/model/User.java deleted file mode 100644 index a4293aa..0000000 --- a/src/main/java/client/model/User.java +++ /dev/null @@ -1,52 +0,0 @@ -package client.model; - -import client.controller.dto.CreatUserDto; - -public class User { - private final String userId; - private final String password; - private final String name; - private final String email; - - public User(final String userId, - final String password, - final String name, - final String email) { - this.userId = userId; - this.password = password; - this.name = name; - this.email = email; - } - - public static User from(final CreatUserDto creatUserDto) { - return new User(creatUserDto.userId(), - creatUserDto.password(), - creatUserDto.name(), - creatUserDto.email()); - } - - public String getUserId() { - return userId; - } - - public String getPassword() { - return password; - } - - public String getName() { - return name; - } - - public String getEmail() { - return email; - } - - @Override - public String toString() { - return "\n----User----\n" + - "userId = " + userId + "\n" + - "password = " + password + "\n" + - "name = " + name + "\n" + - "email = " + email+ "\n"; - } -} diff --git a/src/main/java/client/repsotiroy/UserRepository.java b/src/main/java/client/repsotiroy/UserRepository.java deleted file mode 100644 index 51b7b21..0000000 --- a/src/main/java/client/repsotiroy/UserRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -package client.repsotiroy; - -import client.model.User; -import spring.mvc.annotation.Repository; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Repository -public class UserRepository { - private final Map temporaryDatabase = new HashMap<>(); - private Long SEQUENCE = 0L; - - public void save(final User user) { - temporaryDatabase.put(SEQUENCE++,user); - } - - public List getUsersWithLimit(int wannaSize) { - List allUsers = (List) temporaryDatabase.values(); - - return allUsers.stream() - .limit(wannaSize) - .toList(); - } -} diff --git a/src/main/java/client/service/UserService.java b/src/main/java/client/service/UserService.java index a98673e..6ee3879 100644 --- a/src/main/java/client/service/UserService.java +++ b/src/main/java/client/service/UserService.java @@ -1,9 +1,9 @@ package client.service; import client.controller.dto.CreatUserDto; -import client.model.User; -import client.repsotiroy.UserRepository; -import spring.mvc.annotation.Service; +import client.domain.User; +import client.domain.repsotiroy.UserRepository; +import spring.ioc.annotation.Service; import java.util.List; @@ -17,8 +17,7 @@ public UserService(final UserRepository userRepository) { public User createUser(final CreatUserDto creatUserDto) { User user = User.from(creatUserDto); - userRepository.save(user); - return user; + return userRepository.save(user); } public List getUsersWithSize(final int wannaSize) { diff --git a/src/main/java/http/Application.java b/src/main/java/http/Application.java deleted file mode 100644 index f512fe0..0000000 --- a/src/main/java/http/Application.java +++ /dev/null @@ -1,7 +0,0 @@ -package http; - -public class Application { - public static void main(String[] args) { - System.out.println("실행 되었습니다."); - } -} \ No newline at end of file diff --git a/src/main/java/http/RequestHandler.java b/src/main/java/http/RequestHandler.java deleted file mode 100644 index 1851be6..0000000 --- a/src/main/java/http/RequestHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -package http; - -import http.controller.Controller; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import http.view.request.HttpRequest; -import http.view.response.HttpResponse; - -import java.io.*; -import java.net.Socket; - -public class RequestHandler extends Thread{ - private static final Logger log = LoggerFactory.getLogger(RequestHandler.class); - private Socket connect; - - public RequestHandler(final Socket connect) { - this.connect = connect; - } - - public void run() { - try(InputStream in = connect.getInputStream(); - OutputStream out = connect.getOutputStream()) { - - HttpRequest request = new HttpRequest(in); - HttpResponse response = new HttpResponse(out); - - String path = request.getPath(); - log.info(path); - Controller controller = RequestMapping.getController(path); - if(controller == null) { - response.sendResponse("안녕하세요!"); - } - else { - controller.service(request, response); - } - - - } catch (IOException e) { - log.error(e.getMessage()); - } - } -} diff --git a/src/main/java/http/RequestMapping.java b/src/main/java/http/RequestMapping.java deleted file mode 100644 index dc9921e..0000000 --- a/src/main/java/http/RequestMapping.java +++ /dev/null @@ -1,20 +0,0 @@ -package http; - -import http.controller.Controller; -import http.controller.CreateUserController; -import http.controller.UserListController; - -import java.util.HashMap; -import java.util.Map; - -public class RequestMapping { - private static Map controllers = new HashMap<>(); - static { - controllers.put("/user/create", new CreateUserController()); - controllers.put("/users", new UserListController()); - } - - public static Controller getController(String requestUrl) { - return controllers.get(requestUrl); - } -} diff --git a/src/main/java/http/WebServer.java b/src/main/java/http/WebServer.java deleted file mode 100644 index 52ec6b5..0000000 --- a/src/main/java/http/WebServer.java +++ /dev/null @@ -1,26 +0,0 @@ -package http; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; - -public class WebServer { - private static final int DEFAULT_PORT = 8080; - public static void main(String[] args) throws IOException { - int port; - - if(args == null || args.length == 0) { - port = DEFAULT_PORT; - } else { - port = Integer.parseInt(args[0]); - } - - try(ServerSocket listenSocket = new ServerSocket(port)) { - Socket connection; - while((connection = listenSocket.accept()) != null) { - RequestHandler requestHandler = new RequestHandler(connection); - requestHandler.start(); - }; - } - } -} diff --git a/src/main/java/http/controller/AbstractController.java b/src/main/java/http/controller/AbstractController.java deleted file mode 100644 index a058c06..0000000 --- a/src/main/java/http/controller/AbstractController.java +++ /dev/null @@ -1,24 +0,0 @@ -package http.controller; - -import http.model.HttpRequestMethod; -import http.view.request.HttpRequest; -import http.view.response.HttpResponse; - -public abstract class AbstractController implements Controller{ - @Override - public void service(HttpRequest request, HttpResponse response) { - - if(HttpRequestMethod.POST.equals(request.getMethod())) { - doPost(request, response); - } - else { - doGet(request, response); - } - } - - protected void doGet(final HttpRequest request, final HttpResponse response) { - } - - protected void doPost(final HttpRequest request, final HttpResponse response) { - } -} diff --git a/src/main/java/http/controller/Controller.java b/src/main/java/http/controller/Controller.java deleted file mode 100644 index 9eeb563..0000000 --- a/src/main/java/http/controller/Controller.java +++ /dev/null @@ -1,8 +0,0 @@ -package http.controller; - -import http.view.request.HttpRequest; -import http.view.response.HttpResponse; - -public interface Controller { - void service(HttpRequest request, HttpResponse response); -} diff --git a/src/main/java/http/controller/CreateUserController.java b/src/main/java/http/controller/CreateUserController.java deleted file mode 100644 index 2716b14..0000000 --- a/src/main/java/http/controller/CreateUserController.java +++ /dev/null @@ -1,22 +0,0 @@ -package http.controller; - -import client.model.User; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import http.view.request.HttpRequest; -import http.view.response.HttpResponse; - -public class CreateUserController extends AbstractController { - private static final Logger log = LoggerFactory.getLogger(CreateUserController.class); - - @Override - public void doPost(final HttpRequest request, final HttpResponse response) { - User user = new User(request.getParameter("userId"), - request.getParameter("password"), - request.getParameter("name"), - request.getParameter("email")); - String result = user.toString(); - - response.sendResponse(result); - } -} diff --git a/src/main/java/http/controller/UserListController.java b/src/main/java/http/controller/UserListController.java deleted file mode 100644 index 2d5dc08..0000000 --- a/src/main/java/http/controller/UserListController.java +++ /dev/null @@ -1,18 +0,0 @@ -package http.controller; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import http.view.request.HttpRequest; -import http.view.response.HttpResponse; - -public class UserListController extends AbstractController { - private static final Logger log = LoggerFactory.getLogger(UserListController.class); - - @Override - public void doGet(final HttpRequest request, final HttpResponse response) { - int wannaSize = Integer.parseInt(request.getParameter("size")); - String result = wannaSize + "만큼의 유저 정보를 읽기 원합니다."; - - response.sendResponse(result); - } -} diff --git a/src/main/java/http/model/HttpRequestMethod.java b/src/main/java/http/model/HttpRequestMethod.java deleted file mode 100644 index d17fe83..0000000 --- a/src/main/java/http/model/HttpRequestMethod.java +++ /dev/null @@ -1,15 +0,0 @@ -package http.model; - -public enum HttpRequestMethod { - POST("POST"), GET("GET"), PATCH("PATCH"), DELETE("DELETE"); - - private final String value; - - HttpRequestMethod(final String value) { - this.value = value; - } - - public boolean equals(final String op) { - return op.equals(value); - } -} diff --git a/src/main/java/http/util/HttpRequestUtils.java b/src/main/java/http/util/HttpRequestUtils.java deleted file mode 100644 index e7358a6..0000000 --- a/src/main/java/http/util/HttpRequestUtils.java +++ /dev/null @@ -1,27 +0,0 @@ -package http.util; - -import java.util.HashMap; -import java.util.Map; - -public class HttpRequestUtils { - - private HttpRequestUtils() { - throw new IllegalStateException("Utility class"); - } - - public static Map parseQueryString(final String queryString) { - Map result = new HashMap<>(); - String[] splintedQuery = queryString.split("&"); - - for(String sentence : splintedQuery) { - String[] query = sentence.split("="); - - String key = query[0]; - String value = query[1]; - - result.put(key, value); - } - - return result; - } -} diff --git a/src/main/java/http/util/IOUtils.java b/src/main/java/http/util/IOUtils.java deleted file mode 100644 index 904266d..0000000 --- a/src/main/java/http/util/IOUtils.java +++ /dev/null @@ -1,32 +0,0 @@ -package http.util; - -import java.io.BufferedReader; -import java.io.IOException; - - -public class IOUtils { - private IOUtils() { - throw new IllegalStateException("Utility class"); - } - - public static String readData(final BufferedReader br, final int contentLength) throws IOException { - char[] body = new char[contentLength]; - br.read(body, 0, contentLength); - return convertJsonToUrl(String.copyValueOf(body)); - } - - public static String convertJsonToUrl(final String json) { - StringBuffer sb = new StringBuffer(); - String[] keyValuePairs = json.replaceAll("[{}\"]", "").split(","); - - for (String pair : keyValuePairs) { - String[] keyValue = pair.split(":"); - String key = keyValue[0].trim(); - String value = keyValue[1].trim(); - sb.append(key).append("=").append(value).append("&"); - } - - sb.deleteCharAt(sb.length() - 1); - return sb.toString(); - } -} \ No newline at end of file diff --git a/src/main/java/http/view/request/HttpRequest.java b/src/main/java/http/view/request/HttpRequest.java deleted file mode 100644 index c4c7a12..0000000 --- a/src/main/java/http/view/request/HttpRequest.java +++ /dev/null @@ -1,66 +0,0 @@ -package http.view.request; - -import http.model.HttpRequestMethod; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import http.util.HttpRequestUtils; -import http.util.IOUtils; - -import java.io.*; -import java.util.HashMap; -import java.util.Map; - -public class HttpRequest { - private static final Logger log = LoggerFactory.getLogger(HttpRequest.class); - private final Map headers = new HashMap<>(); - private Map params = new HashMap<>(); - private RequestLine requestLine; - - public HttpRequest(final InputStream in) { - try { - BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); - String line = br.readLine(); - if (line == null) { - return; - } - - requestLine = new RequestLine(line); - - line = br.readLine(); - while (!line.equals("")) { - log.debug("header : {}", line); - String[] tokens = line.split(":"); - headers.put(tokens[0].trim(), tokens[1].trim()); - line = br.readLine(); - } - - if (HttpRequestMethod.POST.equals(getMethod())) { - String body = IOUtils.readData(br, Integer.parseInt(headers.get("Content-Length"))); - params = HttpRequestUtils.parseQueryString(body); - } - else { - params = requestLine.getParams(); - } - - } catch (IOException e) { - log.error(e.getMessage()); - } - } - - public String getMethod() { - return requestLine.getMethod(); - } - - public String getPath() { - return requestLine.getPath(); - } - - public String getHeader(final String key) { - return headers.get(key); - } - - public String getParameter(final String name) { - return params.get(name); - } - -} diff --git a/src/main/java/http/view/request/RequestLine.java b/src/main/java/http/view/request/RequestLine.java deleted file mode 100644 index d03b661..0000000 --- a/src/main/java/http/view/request/RequestLine.java +++ /dev/null @@ -1,52 +0,0 @@ -package http.view.request; - -import http.model.HttpRequestMethod; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import http.util.HttpRequestUtils; - -import java.util.HashMap; -import java.util.Map; - -public class RequestLine { - private static final Logger log = LoggerFactory.getLogger(RequestLine.class); - private final String method; - private final String path; - private Map params = new HashMap<>(); - - public RequestLine(String requestLine) { - log.debug("request line : {}", requestLine); - - String[] tokens = requestLine.split(" "); - if(tokens.length != 3) { - throw new IllegalArgumentException(requestLine + "이 형식에 맞지 않습니다."); - } - - method = tokens[0]; - - if(HttpRequestMethod.POST.equals(method)) { - path = tokens[1]; - return; - } - - int index = tokens[1].indexOf("?"); - if(index == 1) { - path = tokens[1]; - }else { - path = tokens[1].substring(0, index); - params = HttpRequestUtils.parseQueryString(tokens[1].substring(index + 1)); - } - } - - public String getMethod() { - return method; - } - - public String getPath() { - return path; - } - - public Map getParams() { - return params; - } -} diff --git a/src/main/java/http/view/response/HttpResponse.java b/src/main/java/http/view/response/HttpResponse.java deleted file mode 100644 index 6819bbc..0000000 --- a/src/main/java/http/view/response/HttpResponse.java +++ /dev/null @@ -1,46 +0,0 @@ -package http.view.response; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; - - -public class HttpResponse { - private static final Logger log = LoggerFactory.getLogger(HttpResponse.class); - private DataOutputStream dos = null; - - public HttpResponse(final OutputStream out) { - dos = new DataOutputStream(out); - } - - public void sendResponse(final String response) { - byte[] responseBytes = response.getBytes(StandardCharsets.UTF_8); - response200Header(dos, responseBytes.length); - responseBody(dos, responseBytes); - } - - private void responseBody(final DataOutputStream dos, final byte[] body) { - try { - dos.write(body, 0, body.length); - dos.writeBytes("\r\n"); - dos.flush(); - } catch (IOException e) { - log.error(e.getMessage()); - } - } - - private void response200Header(final DataOutputStream dos, final int length) { - try { - dos.writeBytes("HTTP/1.1 200 OK\r\n"); - dos.writeBytes("Content-Type: text/html; charset=utf-8\r\n"); - dos.writeBytes("Content-Length: " + length + "\r\n"); - dos.writeBytes("\r\n"); - } catch (IOException e) { - log.error(e.getMessage()); - } - } -} diff --git a/src/main/java/spring/AppInitServlet.java b/src/main/java/spring/AppInitServlet.java new file mode 100644 index 0000000..b1255e9 --- /dev/null +++ b/src/main/java/spring/AppInitServlet.java @@ -0,0 +1,30 @@ +package spring; + +import client.Application; +import jakarta.servlet.ServletContainerInitializer; +import jakarta.servlet.ServletContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spring.ioc.context.support.ApplicationContext; +import spring.ioc.context.support.AnnotationConfigApplicationContext; +import spring.mvc.web.DispatcherServlet; + +import java.util.Set; + +public class AppInitServlet implements ServletContainerInitializer { + + private static final String DEFAULT_SERVLET_NAME = "DispatcherServlet"; + private static final Logger log = LoggerFactory.getLogger(AppInitServlet.class); + + @Override + public void onStartup(final Set> c, final ServletContext servletContext) { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class); + + DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext); + + servletContext + .addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet) + .addMapping("/"); + log.info("서블릿 등록 : {}",dispatcherServlet.getClass().getName()); + } +} \ No newline at end of file diff --git a/src/main/java/spring/ioc/annotation/Bean.java b/src/main/java/spring/ioc/annotation/Bean.java new file mode 100644 index 0000000..d9e91fc --- /dev/null +++ b/src/main/java/spring/ioc/annotation/Bean.java @@ -0,0 +1,12 @@ +package spring.ioc.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Bean { + +} diff --git a/src/main/java/spring/ioc/annotation/Component.java b/src/main/java/spring/ioc/annotation/Component.java new file mode 100644 index 0000000..c242677 --- /dev/null +++ b/src/main/java/spring/ioc/annotation/Component.java @@ -0,0 +1,12 @@ +package spring.ioc.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Component { + String value() default ""; +} diff --git a/src/main/java/spring/ioc/annotation/ComponentScan.java b/src/main/java/spring/ioc/annotation/ComponentScan.java new file mode 100644 index 0000000..1d1da7c --- /dev/null +++ b/src/main/java/spring/ioc/annotation/ComponentScan.java @@ -0,0 +1,14 @@ +package spring.ioc.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ComponentScan { + String[] value() default {}; + + String[] basePackages() default {}; +} diff --git a/src/main/java/spring/ioc/annotation/Configuration.java b/src/main/java/spring/ioc/annotation/Configuration.java new file mode 100644 index 0000000..e5fadee --- /dev/null +++ b/src/main/java/spring/ioc/annotation/Configuration.java @@ -0,0 +1,12 @@ +package spring.ioc.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Configuration { + +} diff --git a/src/main/java/spring/ioc/annotation/Inject.java b/src/main/java/spring/ioc/annotation/Inject.java new file mode 100644 index 0000000..a274c96 --- /dev/null +++ b/src/main/java/spring/ioc/annotation/Inject.java @@ -0,0 +1,12 @@ +package spring.ioc.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Inject { + boolean required() default true; +} diff --git a/src/main/java/spring/ioc/annotation/PostConstruct.java b/src/main/java/spring/ioc/annotation/PostConstruct.java new file mode 100644 index 0000000..956c647 --- /dev/null +++ b/src/main/java/spring/ioc/annotation/PostConstruct.java @@ -0,0 +1,12 @@ +package spring.ioc.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface PostConstruct { + +} \ No newline at end of file diff --git a/src/main/java/spring/mvc/annotation/Repository.java b/src/main/java/spring/ioc/annotation/Repository.java similarity index 89% rename from src/main/java/spring/mvc/annotation/Repository.java rename to src/main/java/spring/ioc/annotation/Repository.java index 1b6e62d..cddeb45 100644 --- a/src/main/java/spring/mvc/annotation/Repository.java +++ b/src/main/java/spring/ioc/annotation/Repository.java @@ -1,4 +1,4 @@ -package spring.mvc.annotation; +package spring.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/spring/mvc/annotation/Service.java b/src/main/java/spring/ioc/annotation/Service.java similarity index 90% rename from src/main/java/spring/mvc/annotation/Service.java rename to src/main/java/spring/ioc/annotation/Service.java index 2e94f12..ffd26c9 100644 --- a/src/main/java/spring/mvc/annotation/Service.java +++ b/src/main/java/spring/ioc/annotation/Service.java @@ -1,4 +1,4 @@ -package spring.mvc.annotation; +package spring.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/spring/ioc/bean/BeanFactory.java b/src/main/java/spring/ioc/bean/BeanFactory.java new file mode 100644 index 0000000..a403d0e --- /dev/null +++ b/src/main/java/spring/ioc/bean/BeanFactory.java @@ -0,0 +1,11 @@ +package spring.ioc.bean; + +import java.util.Set; + +public interface BeanFactory { + Set> getBeanClasses(); + + T getBean(Class clazz); + + void clear(); +} \ No newline at end of file diff --git a/src/main/java/spring/ioc/bean/BeanUtils.java b/src/main/java/spring/ioc/bean/BeanUtils.java new file mode 100644 index 0000000..42c89e0 --- /dev/null +++ b/src/main/java/spring/ioc/bean/BeanUtils.java @@ -0,0 +1,76 @@ +package spring.ioc.bean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class BeanUtils { + private static final Logger log = LoggerFactory.getLogger(BeanUtils.class); + + public static T instantiate(Class clazz) { + if (clazz.isInterface()) { + throw new RuntimeException("인터페이스입니다."); + } + try { + Constructor constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (NoSuchMethodException ex) { + // 기본 생성자가 없는 경우, 다른 생성자를 사용 + return instantiateWithSuitableConstructor(clazz); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { + throw new RuntimeException("클래스 인스턴스화 오류: " + clazz.getName(), ex); + } + } + + private static T instantiateWithSuitableConstructor(Class clazz) { + Constructor[] constructors = clazz.getDeclaredConstructors(); + Constructor constructor = constructors[0]; // 첫 번째 생성자를 사용 + + // 생성자의 파라미터 타입에 맞는 기본 인스턴스를 생성하여 주입 + Object[] params = Arrays.stream(constructor.getParameterTypes()) + .map(BeanUtils::getDefaultInstance) + .collect(Collectors.toList()) + .toArray(); + + try { + constructor.setAccessible(true); + return (T) constructor.newInstance(params); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { + throw new RuntimeException("클래스 인스턴스화 오류: " + clazz.getName(), ex); + } + } + + private static Object getDefaultInstance(Class paramType) { + if (paramType.isPrimitive()) { + if (paramType == boolean.class) return false; + if (paramType == char.class) return '\0'; + if (paramType == byte.class || paramType == short.class || paramType == int.class || paramType == long.class) + return 0; + if (paramType == float.class || paramType == double.class) return 0.0; + } + try { + return instantiate(paramType); + } catch (RuntimeException e) { + throw new RuntimeException("파라미터 타입 인스턴스화 오류: " + paramType.getName(), e); + } + } + + + public static Object instantiateClass(final Constructor constructor, final Object[] args) { + try { + log.debug("Instantiating class using constructor: {}", constructor.getName()); + return constructor.newInstance(args); + } catch (InstantiationException e) { + throw new RuntimeException("클래스를 인스턴스화 할 수 없습니다: " + constructor.getDeclaringClass().getName(), e); + } catch (IllegalAccessException e) { + throw new RuntimeException("생성자에 접근할 수 없습니다: " + constructor.getDeclaringClass().getName(), e); + } catch (InvocationTargetException e) { + throw new RuntimeException("생성자 호출 중 예외가 발생했습니다: " + constructor.getDeclaringClass().getName(), e); + } + } +} diff --git a/src/main/java/spring/ioc/bean/ConfigurableListableBeanFactory.java b/src/main/java/spring/ioc/bean/ConfigurableListableBeanFactory.java new file mode 100644 index 0000000..34e8960 --- /dev/null +++ b/src/main/java/spring/ioc/bean/ConfigurableListableBeanFactory.java @@ -0,0 +1,5 @@ +package spring.ioc.bean; + +public interface ConfigurableListableBeanFactory extends BeanFactory { + void preInstantiateSingletons(); +} \ No newline at end of file diff --git a/src/main/java/spring/ioc/bean/config/BeanDefinition.java b/src/main/java/spring/ioc/bean/config/BeanDefinition.java new file mode 100644 index 0000000..ee522bf --- /dev/null +++ b/src/main/java/spring/ioc/bean/config/BeanDefinition.java @@ -0,0 +1,17 @@ +package spring.ioc.bean.config; + +import spring.ioc.bean.support.InjectType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.Set; + +public interface BeanDefinition { + + Constructor getInjectConstructor(); + + Set getInjectFields(); + + Class getBeanClass(); + + InjectType getResolvedInjectMode(); +} \ No newline at end of file diff --git a/src/main/java/spring/ioc/bean/support/BeanDefinitionReader.java b/src/main/java/spring/ioc/bean/support/BeanDefinitionReader.java new file mode 100644 index 0000000..7fbe5f5 --- /dev/null +++ b/src/main/java/spring/ioc/bean/support/BeanDefinitionReader.java @@ -0,0 +1,5 @@ +package spring.ioc.bean.support; + +public interface BeanDefinitionReader { + void loadBeanDefinitions(Class... annotatedClasses); +} diff --git a/src/main/java/spring/ioc/bean/support/BeanDefinitionRegistry.java b/src/main/java/spring/ioc/bean/support/BeanDefinitionRegistry.java new file mode 100644 index 0000000..fbc7dca --- /dev/null +++ b/src/main/java/spring/ioc/bean/support/BeanDefinitionRegistry.java @@ -0,0 +1,7 @@ +package spring.ioc.bean.support; + +import spring.ioc.bean.config.BeanDefinition; + +public interface BeanDefinitionRegistry { + void registerBeanDefinition(Class clazz, BeanDefinition beanDefinition); +} diff --git a/src/main/java/spring/ioc/bean/support/BeanFactoryUtils.java b/src/main/java/spring/ioc/bean/support/BeanFactoryUtils.java new file mode 100644 index 0000000..19c5b10 --- /dev/null +++ b/src/main/java/spring/ioc/bean/support/BeanFactoryUtils.java @@ -0,0 +1,77 @@ +package spring.ioc.bean.support; + +import static org.reflections.ReflectionUtils.getAllConstructors; +import static org.reflections.ReflectionUtils.getAllFields; +import static org.reflections.ReflectionUtils.getAllMethods; +import static org.reflections.ReflectionUtils.withAnnotation; +import static org.reflections.ReflectionUtils.withReturnType; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spring.ioc.annotation.Inject; + + +public class BeanFactoryUtils { + private static final Logger log = LoggerFactory.getLogger(BeanFactoryUtils.class); + + public static Set getInjectedMethods(Class clazz) { + return getAllMethods(clazz, withAnnotation(Inject.class), withReturnType(void.class)); + } + + public static Set getBeanMethods(Class clazz, Class annotation) { + return getAllMethods(clazz, withAnnotation(annotation)); + } + + public static Set getInjectedFields(Class clazz) { + return getAllFields(clazz, withAnnotation(Inject.class)); + } + + public static Set getInjectedConstructors(Class clazz) { + return getAllConstructors(clazz, withAnnotation(Inject.class)); + } + + public static Optional invokeMethod(Method method, Object bean, Object[] args) { + try { + return Optional.ofNullable(method.invoke(bean, args)); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + log.error(e.getMessage()); + return Optional.empty(); + } + } + + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Constructor getInjectedConstructor(Class clazz) { + Set injectedConstructors = getAllConstructors(clazz, withAnnotation(Inject.class)); + if (injectedConstructors.isEmpty()) { + return null; + } + return injectedConstructors.iterator().next(); + } + + + public static Optional> findConcreteClass(Class injectedClazz, Set> preInstanticateBeans) { + if (!injectedClazz.isInterface()) { + return Optional.of(injectedClazz); + } + + for (Class clazz : preInstanticateBeans) { + Set> interfaces = new HashSet<>(Arrays.asList(clazz.getInterfaces())); + if (interfaces.contains(injectedClazz)) { + return Optional.of(clazz); + } + } + + return Optional.empty(); + } +} \ No newline at end of file diff --git a/src/main/java/spring/ioc/bean/support/DefaultBeanDefinition.java b/src/main/java/spring/ioc/bean/support/DefaultBeanDefinition.java new file mode 100644 index 0000000..66baaf6 --- /dev/null +++ b/src/main/java/spring/ioc/bean/support/DefaultBeanDefinition.java @@ -0,0 +1,91 @@ +package spring.ioc.bean.support; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spring.ioc.bean.config.BeanDefinition; + +public class DefaultBeanDefinition implements BeanDefinition { + private static final Logger log = LoggerFactory.getLogger(DefaultBeanDefinition.class); + private Class beanClazz; + private Constructor injectConstructor; + private Set injectFields; + + public DefaultBeanDefinition(Class clazz) { + this.beanClazz = clazz; + injectConstructor = getInjectConstructor(clazz); + injectFields = getInjectFields(clazz, injectConstructor); + } + + private static Constructor getInjectConstructor(Class clazz) { + return BeanFactoryUtils.getInjectedConstructor(clazz); + } + + private Set getInjectFields(Class clazz, Constructor constructor) { + if (constructor != null) { + return new HashSet<>(); + } + + Set injectFields = new HashSet<>(); + Set> injectProperties = getInjectPropertiesType(clazz); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if (injectProperties.contains(field.getType())) { + injectFields.add(field); + } + } + return injectFields; + } + + private static Set> getInjectPropertiesType(Class clazz) { + Set> injectProperties = new HashSet<>(); + Set injectMethod = BeanFactoryUtils.getInjectedMethods(clazz); + for (Method method : injectMethod) { + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 1) { + throw new IllegalStateException("DI할 메소드 인자는 하나여야 합니다."); + } + + injectProperties.add(paramTypes[0]); + } + + Set injectField = BeanFactoryUtils.getInjectedFields(clazz); + for (Field field : injectField) { + injectProperties.add(field.getType()); + } + return injectProperties; + } + + @Override + public Constructor getInjectConstructor() { + return injectConstructor; + } + + @Override + public Set getInjectFields() { + return this.injectFields; + } + + @Override + public Class getBeanClass() { + return this.beanClazz; + } + + @Override + public InjectType getResolvedInjectMode() { + if (injectConstructor != null) { + return InjectType.INJECT_CONSTRUCTOR; + } + + if (!injectFields.isEmpty()) { + return InjectType.INJECT_FIELD; + } + + return InjectType.INJECT_NO; + } +} diff --git a/src/main/java/spring/ioc/bean/support/DefaultBeanFactory.java b/src/main/java/spring/ioc/bean/support/DefaultBeanFactory.java new file mode 100644 index 0000000..0639830 --- /dev/null +++ b/src/main/java/spring/ioc/bean/support/DefaultBeanFactory.java @@ -0,0 +1,141 @@ +package spring.ioc.bean.support; + +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spring.ioc.bean.BeanUtils; +import spring.ioc.bean.ConfigurableListableBeanFactory; +import spring.ioc.bean.config.BeanDefinition; +import spring.ioc.context.annotation.AnnotatedBeanDefinition; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.*; + +public class DefaultBeanFactory implements BeanDefinitionRegistry, ConfigurableListableBeanFactory { + private static final Logger log = LoggerFactory.getLogger(DefaultBeanFactory.class); + + private Map, Object> beans = new HashMap<>(); + + private Map, BeanDefinition> beanDefinitions = new HashMap<>(); + + @Override + public void preInstantiateSingletons() { + for (Class clazz : getBeanClasses()) { + getBean(clazz); + } + for(var bean : beans.values()) { + log.debug(bean.getClass().getName()); + } + } + + @Override + public Set> getBeanClasses() { + return beanDefinitions.keySet(); + } + + @Override + public T getBean(Class clazz) { + Object bean = beans.get(clazz); + if (bean != null) { + return (T) bean; + } + + BeanDefinition beanDefinition = beanDefinitions.get(clazz); + if (beanDefinition instanceof AnnotatedBeanDefinition) { + Optional optionalBean = createAnnotatedBean(beanDefinition); + optionalBean.ifPresent(b -> beans.put(clazz, b)); + initialize(bean, clazz); + return (T) optionalBean.orElse(null); + } + + Optional> concreteClazz = BeanFactoryUtils.findConcreteClass(clazz, getBeanClasses()); + if (concreteClazz.isEmpty()) { + return null; + } + + beanDefinition = beanDefinitions.get(concreteClazz.get()); + bean = inject(beanDefinition); + beans.put(concreteClazz.get(), bean); + initialize(bean, concreteClazz.get()); + return (T) bean; + } + + private void initialize(Object bean, Class beanClass) { + Set initializeMethods = BeanFactoryUtils.getBeanMethods(beanClass, PostConstruct.class); + if (initializeMethods.isEmpty()) { + return; + } + for (Method initializeMethod : initializeMethods) { + log.debug("@PostConstruct Initialize Method : {}", initializeMethod); + BeanFactoryUtils.invokeMethod(initializeMethod, bean, + populateArguments(initializeMethod.getParameterTypes())); + } + } + + private Optional createAnnotatedBean(BeanDefinition beanDefinition) { + AnnotatedBeanDefinition abd = (AnnotatedBeanDefinition) beanDefinition; + Method method = abd.getMethod(); + Object[] args = populateArguments(method.getParameterTypes()); + return BeanFactoryUtils.invokeMethod(method, getBean(method.getDeclaringClass()), args); + } + + private Object[] populateArguments(Class[] paramTypes) { + List args = new ArrayList<>(); + for (Class param : paramTypes) { + Object bean = getBean(param); + if (bean == null) { + throw new NullPointerException(param + "에 해당하는 Bean이 존재하지 않습니다."); + } + args.add(getBean(param)); + } + return args.toArray(); + } + + private Object inject(BeanDefinition beanDefinition) { + if (beanDefinition.getResolvedInjectMode() == InjectType.INJECT_NO) { + return BeanUtils.instantiate(beanDefinition.getBeanClass()); + } else if (beanDefinition.getResolvedInjectMode() == InjectType.INJECT_FIELD) { + return injectFields(beanDefinition); + } else { + return injectConstructor(beanDefinition); + } + } + + private Object injectConstructor(BeanDefinition beanDefinition) { + Constructor constructor = beanDefinition.getInjectConstructor(); + Object[] args = populateArguments(constructor.getParameterTypes()); + return BeanUtils.instantiateClass(constructor, args); + } + + private Object injectFields(BeanDefinition beanDefinition) { + Object bean = BeanUtils.instantiate(beanDefinition.getBeanClass()); + Set injectFields = beanDefinition.getInjectFields(); + for (Field field : injectFields) { + injectField(bean, field); + } + return bean; + } + + private void injectField(Object bean, Field field) { + log.debug("Inject Bean : {}, Field : {}", bean, field); + try { + field.setAccessible(true); + field.set(bean, getBean(field.getType())); + } catch (IllegalAccessException | IllegalArgumentException e) { + log.error(e.getMessage()); + } + } + + @Override + public void clear() { + beanDefinitions.clear(); + beans.clear(); + } + + @Override + public void registerBeanDefinition(Class clazz, BeanDefinition beanDefinition) { + beanDefinitions.put(clazz, beanDefinition); + } +} \ No newline at end of file diff --git a/src/main/java/spring/ioc/bean/support/InjectType.java b/src/main/java/spring/ioc/bean/support/InjectType.java new file mode 100644 index 0000000..7af51a5 --- /dev/null +++ b/src/main/java/spring/ioc/bean/support/InjectType.java @@ -0,0 +1,5 @@ +package spring.ioc.bean.support; + +public enum InjectType { + INJECT_NO, INJECT_FIELD, INJECT_CONSTRUCTOR; +} diff --git a/src/main/java/spring/ioc/context/annotation/AnnotatedBeanDefinition.java b/src/main/java/spring/ioc/context/annotation/AnnotatedBeanDefinition.java new file mode 100644 index 0000000..dd4ccbb --- /dev/null +++ b/src/main/java/spring/ioc/context/annotation/AnnotatedBeanDefinition.java @@ -0,0 +1,18 @@ +package spring.ioc.context.annotation; + +import spring.ioc.bean.support.DefaultBeanDefinition; + +import java.lang.reflect.Method; + +public class AnnotatedBeanDefinition extends DefaultBeanDefinition { + private Method method; + + public AnnotatedBeanDefinition(Class clazz, Method method) { + super(clazz); + this.method = method; + } + + public Method getMethod() { + return method; + } +} \ No newline at end of file diff --git a/src/main/java/spring/ioc/context/annotation/AnnotatedBeanDefinitionReader.java b/src/main/java/spring/ioc/context/annotation/AnnotatedBeanDefinitionReader.java new file mode 100644 index 0000000..0336e68 --- /dev/null +++ b/src/main/java/spring/ioc/context/annotation/AnnotatedBeanDefinitionReader.java @@ -0,0 +1,39 @@ +package spring.ioc.context.annotation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spring.ioc.annotation.Bean; +import spring.ioc.bean.support.BeanDefinitionReader; +import spring.ioc.bean.support.BeanDefinitionRegistry; +import spring.ioc.bean.support.BeanFactoryUtils; +import spring.ioc.bean.support.DefaultBeanDefinition; + +import java.lang.reflect.Method; +import java.util.Set; + +public class AnnotatedBeanDefinitionReader implements BeanDefinitionReader { + private static final Logger log = LoggerFactory.getLogger(AnnotatedBeanDefinitionReader.class); + + private BeanDefinitionRegistry beanDefinitionRegistry; + + public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) { + this.beanDefinitionRegistry = beanDefinitionRegistry; + } + + @Override + public void loadBeanDefinitions(Class... annotatedClasses) { + for (Class annotatedClass : annotatedClasses) { + registerBean(annotatedClass); + } + } + + private void registerBean(Class annotatedClass) { + beanDefinitionRegistry.registerBeanDefinition(annotatedClass, new DefaultBeanDefinition(annotatedClass)); + Set beanMethods = BeanFactoryUtils.getBeanMethods(annotatedClass, Bean.class); + for (Method beanMethod : beanMethods) { + log.debug("@Bean method : {}", beanMethod); + AnnotatedBeanDefinition abd = new AnnotatedBeanDefinition(beanMethod.getReturnType(), beanMethod); + beanDefinitionRegistry.registerBeanDefinition(beanMethod.getReturnType(), abd); + } + } +} \ No newline at end of file diff --git a/src/main/java/spring/ioc/context/annotation/ClasspathBeanDefinitionScanner.java b/src/main/java/spring/ioc/context/annotation/ClasspathBeanDefinitionScanner.java new file mode 100644 index 0000000..a488484 --- /dev/null +++ b/src/main/java/spring/ioc/context/annotation/ClasspathBeanDefinitionScanner.java @@ -0,0 +1,39 @@ +package spring.ioc.context.annotation; + +import org.reflections.Reflections; +import spring.ioc.annotation.Component; +import spring.mvc.annotation.Controller; +import spring.ioc.annotation.Repository; +import spring.ioc.annotation.Service; +import spring.ioc.bean.support.BeanDefinitionRegistry; +import spring.ioc.bean.support.DefaultBeanDefinition; + +import java.lang.annotation.Annotation; +import java.util.HashSet; +import java.util.Set; + +public class ClasspathBeanDefinitionScanner { + private final BeanDefinitionRegistry beanDefinitionRegistry; + + public ClasspathBeanDefinitionScanner(BeanDefinitionRegistry beanDefinitionRegistry) { + this.beanDefinitionRegistry = beanDefinitionRegistry; + } + + public void doScan(Object... basePackages) { + Reflections reflections = new Reflections(basePackages); + Set> beanClasses = getTypesAnnotatedWith(reflections, Controller.class, Service.class, + Repository.class, Component.class); + for (Class clazz : beanClasses) { + beanDefinitionRegistry.registerBeanDefinition(clazz, new DefaultBeanDefinition(clazz)); + } + } + + @SafeVarargs + private Set> getTypesAnnotatedWith(Reflections reflections, Class... annotations) { + Set> preInstantiatedBeans = new HashSet<>(); + for (Class annotation : annotations) { + preInstantiatedBeans.addAll(reflections.getTypesAnnotatedWith(annotation)); + } + return preInstantiatedBeans; + } +} \ No newline at end of file diff --git a/src/main/java/spring/ioc/context/support/AnnotationConfigApplicationContext.java b/src/main/java/spring/ioc/context/support/AnnotationConfigApplicationContext.java new file mode 100644 index 0000000..ebac02d --- /dev/null +++ b/src/main/java/spring/ioc/context/support/AnnotationConfigApplicationContext.java @@ -0,0 +1,61 @@ +package spring.ioc.context.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spring.ioc.annotation.ComponentScan; +import spring.ioc.bean.support.BeanDefinitionReader; +import spring.ioc.bean.support.DefaultBeanFactory; +import spring.ioc.context.annotation.AnnotatedBeanDefinitionReader; +import spring.ioc.context.annotation.ClasspathBeanDefinitionScanner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +public class AnnotationConfigApplicationContext implements ApplicationContext { + private static final Logger log = LoggerFactory.getLogger(AnnotationConfigApplicationContext.class); + + private DefaultBeanFactory beanFactory; + + public AnnotationConfigApplicationContext(Class... clazzs) { + Object[] basePackages = findBasePackages(clazzs); + beanFactory = new DefaultBeanFactory(); + BeanDefinitionReader abdr = new AnnotatedBeanDefinitionReader(beanFactory); + abdr.loadBeanDefinitions(clazzs); + + if (basePackages.length > 0) { + ClasspathBeanDefinitionScanner scanner = new ClasspathBeanDefinitionScanner(beanFactory); + scanner.doScan(basePackages); + } + beanFactory.preInstantiateSingletons(); + } + + private Object[] findBasePackages(Class[] annotatedClasses) { + List basePackages = new ArrayList<>(); + + for (Class annotatedClass : annotatedClasses) { + + ComponentScan componentScan = annotatedClass.getAnnotation(ComponentScan.class); + if (componentScan == null) { + continue; + } + + for (String basePackage : componentScan.value()) { + log.info("컴포넌트 스캔 범위, basePackage : {}", basePackage); + } + basePackages.addAll(Arrays.asList(componentScan.value())); + } + return basePackages.toArray(); + } + + @Override + public T getBean(Class clazz) { + return beanFactory.getBean(clazz); + } + + @Override + public Set> getBeanClasses() { + return beanFactory.getBeanClasses(); + } +} \ No newline at end of file diff --git a/src/main/java/spring/ioc/context/support/ApplicationContext.java b/src/main/java/spring/ioc/context/support/ApplicationContext.java new file mode 100644 index 0000000..382c53a --- /dev/null +++ b/src/main/java/spring/ioc/context/support/ApplicationContext.java @@ -0,0 +1,9 @@ +package spring.ioc.context.support; + +import java.util.Set; + +public interface ApplicationContext { + T getBean(Class clazz); + + Set> getBeanClasses(); +} \ No newline at end of file diff --git a/src/main/java/spring/mvc/annotation/RequestMapping.java b/src/main/java/spring/mvc/annotation/RequestMapping.java index 83ca424..d4c179d 100644 --- a/src/main/java/spring/mvc/annotation/RequestMapping.java +++ b/src/main/java/spring/mvc/annotation/RequestMapping.java @@ -1,16 +1,16 @@ package spring.mvc.annotation; -import spring.mvc.handler.mapping.RequestMethod; +import spring.mvc.web.handler.mapping.RequestMethod; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target({ ElementType.METHOD, ElementType.TYPE }) +@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RequestMapping { String value() default ""; - RequestMethod method() default RequestMethod.GET; + RequestMethod[] method() default {}; } diff --git a/src/main/java/spring/mvc/controller/AbstractController.java b/src/main/java/spring/mvc/controller/AbstractController.java deleted file mode 100644 index d609ccf..0000000 --- a/src/main/java/spring/mvc/controller/AbstractController.java +++ /dev/null @@ -1,10 +0,0 @@ -package spring.mvc.controller; - -import spring.mvc.view.JsonView; -import spring.mvc.view.ModelAndView; - -public abstract class AbstractController { - protected ModelAndView jsonView() { - return new ModelAndView(new JsonView()); - } -} diff --git a/src/main/java/spring/mvc/handler/excution/HandlerAdapter.java b/src/main/java/spring/mvc/handler/excution/HandlerAdapter.java deleted file mode 100644 index df0d76d..0000000 --- a/src/main/java/spring/mvc/handler/excution/HandlerAdapter.java +++ /dev/null @@ -1,11 +0,0 @@ -package spring.mvc.handler.excution; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import spring.mvc.view.ModelAndView; - -public interface HandlerAdapter { - boolean supports(Object handler); - - ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; -} diff --git a/src/main/java/spring/mvc/handler/excution/HandlerExecution.java b/src/main/java/spring/mvc/handler/excution/HandlerExecution.java deleted file mode 100644 index 873c131..0000000 --- a/src/main/java/spring/mvc/handler/excution/HandlerExecution.java +++ /dev/null @@ -1,31 +0,0 @@ -package spring.mvc.handler.excution; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spring.mvc.view.ModelAndView; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public class HandlerExecution { - private static final Logger logger = LoggerFactory.getLogger(HandlerExecution.class); - - private Object declaredObject; - private Method method; - - public HandlerExecution(Object declaredObject, Method method) { - this.declaredObject = declaredObject; - this.method = method; - } - - public ModelAndView handle(HttpServletRequest request, HttpServletResponse response) { - try { - return (ModelAndView) method.invoke(declaredObject, request, response); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - logger.error("{} method invoke fail. error message : {}", method, e.getMessage()); - throw new RuntimeException(e); - } - } -} \ No newline at end of file diff --git a/src/main/java/spring/mvc/handler/excution/HandlerExecutionHandlerAdapter.java b/src/main/java/spring/mvc/handler/excution/HandlerExecutionHandlerAdapter.java deleted file mode 100644 index 5bbb872..0000000 --- a/src/main/java/spring/mvc/handler/excution/HandlerExecutionHandlerAdapter.java +++ /dev/null @@ -1,17 +0,0 @@ -package spring.mvc.handler.excution; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import spring.mvc.view.ModelAndView; - -public class HandlerExecutionHandlerAdapter implements HandlerAdapter { - @Override - public boolean supports(Object handler) { - return handler instanceof HandlerExecution; - } - - @Override - public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) { - return ((HandlerExecution) handler).handle(request, response); - } -} \ No newline at end of file diff --git a/src/main/java/spring/mvc/handler/mapping/AnnotationHandlerMapping.java b/src/main/java/spring/mvc/handler/mapping/AnnotationHandlerMapping.java deleted file mode 100644 index bd1a11d..0000000 --- a/src/main/java/spring/mvc/handler/mapping/AnnotationHandlerMapping.java +++ /dev/null @@ -1,62 +0,0 @@ -package spring.mvc.handler.mapping; - -import jakarta.servlet.http.HttpServletRequest; -import org.reflections.ReflectionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spring.mvc.handler.excution.HandlerExecution; -import spring.mvc.annotation.RequestMapping; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class AnnotationHandlerMapping implements HandlerMapping { - - private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); - private Object[] basePackage; - private Map handlerExecutions = new HashMap<>(); - - public AnnotationHandlerMapping(Object... basePackage) { - this.basePackage = basePackage; - } - - public void initialize() { - ControllerScanner controllerScanner = new ControllerScanner(basePackage); - Map, Object> controllers = controllerScanner.getControllers(); - Set methods = getRequestMappingMethods(controllers.keySet()); - - for (Method method : methods) { - RequestMapping rm = method.getAnnotation(RequestMapping.class); - log.debug("register handlerExecution : url is {}, method is {}", rm.value(), rm.method()); - handlerExecutions.put(createHandlerKey(rm), - new HandlerExecution(controllers.get(method.getDeclaringClass()), method)); - } - - log.info("Initialized AnnotationHandlerMapping!"); - } - - private HandlerKey createHandlerKey(RequestMapping rm) { - return new HandlerKey(rm.value(), rm.method()); - } - - @SuppressWarnings("unchecked") - private Set getRequestMappingMethods(Set> controlleers) { - Set requestMappingMethods = new HashSet<>(); - for (Class clazz : controlleers) { - requestMappingMethods - .addAll(ReflectionUtils.getAllMethods(clazz, ReflectionUtils.withAnnotation(RequestMapping.class))); - } - return requestMappingMethods; - } - - @Override - public HandlerExecution getHandler(HttpServletRequest request) { - String requestUri = request.getRequestURI(); - RequestMethod rm = RequestMethod.valueOf(request.getMethod().toUpperCase()); - log.debug("requestUri : {}, requestMethod : {}", requestUri, rm); - return handlerExecutions.get(new HandlerKey(requestUri, rm)); - } -} \ No newline at end of file diff --git a/src/main/java/spring/mvc/handler/mapping/ControllerScanner.java b/src/main/java/spring/mvc/handler/mapping/ControllerScanner.java deleted file mode 100644 index b8f5afc..0000000 --- a/src/main/java/spring/mvc/handler/mapping/ControllerScanner.java +++ /dev/null @@ -1,39 +0,0 @@ -package spring.mvc.handler.mapping; - -import org.reflections.Reflections; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spring.mvc.annotation.Controller; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public class ControllerScanner { - private static final Logger log = LoggerFactory.getLogger(ControllerScanner.class); - - private Reflections reflections; - - public ControllerScanner(Object... basePackage) { - reflections = new Reflections(basePackage); - } - - public Map, Object> getControllers() { - Set> preInitiatedControllers = reflections.getTypesAnnotatedWith(Controller.class); - log.debug("Scanned Controllers: " + preInitiatedControllers.toString()); - return instantiateControllers(preInitiatedControllers); - } - - Map, Object> instantiateControllers(Set> preInitiatedControllers) { - Map, Object> controllers = new HashMap<>(); - try { - for (Class clazz : preInitiatedControllers) { - controllers.put(clazz, clazz.newInstance()); - } - } catch (InstantiationException | IllegalAccessException e) { - log.error(e.getMessage()); - } - - return controllers; - } -} diff --git a/src/main/java/spring/mvc/handler/mapping/HandlerMapping.java b/src/main/java/spring/mvc/handler/mapping/HandlerMapping.java deleted file mode 100644 index ee9c692..0000000 --- a/src/main/java/spring/mvc/handler/mapping/HandlerMapping.java +++ /dev/null @@ -1,7 +0,0 @@ -package spring.mvc.handler.mapping; - -import jakarta.servlet.http.HttpServletRequest; - -public interface HandlerMapping { - Object getHandler(HttpServletRequest request); -} diff --git a/src/main/java/spring/mvc/view/ModelAndView.java b/src/main/java/spring/mvc/view/ModelAndView.java index 3374902..90a4afc 100644 --- a/src/main/java/spring/mvc/view/ModelAndView.java +++ b/src/main/java/spring/mvc/view/ModelAndView.java @@ -8,8 +8,8 @@ public class ModelAndView { private View view; private Map model = new HashMap<>(); - public ModelAndView(View view) { - this.view = view; + public ModelAndView() { + this.view = new JsonView(); } public ModelAndView addObject(String attributeName, Object attributeValue) { diff --git a/src/main/java/spring/mvc/web/DispatcherServlet.java b/src/main/java/spring/mvc/web/DispatcherServlet.java new file mode 100644 index 0000000..4553175 --- /dev/null +++ b/src/main/java/spring/mvc/web/DispatcherServlet.java @@ -0,0 +1,62 @@ +package spring.mvc.web; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spring.ioc.context.support.ApplicationContext; +import spring.mvc.web.handler.excution.Handler; +import spring.mvc.web.handler.mapping.AnnotationHandlerMapping; +import spring.mvc.web.handler.mapping.HandlerMapping; + +import java.io.IOException; + + +public class DispatcherServlet extends HttpServlet { + + private static Logger log = LoggerFactory.getLogger(DispatcherServlet.class); + private final ApplicationContext applicationContext; + private HandlerMapping handlerMapping; + + public DispatcherServlet(final ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public void init() { + this.handlerMapping = new AnnotationHandlerMapping(applicationContext, "client"); + handlerMapping.initialize(); + } + + @Override + protected void service(final HttpServletRequest request, + final HttpServletResponse response) { + log.info("Request : {} {}", request.getMethod(), request.getRequestURI()); + if(!handlerMapping.supports(request)) { + handleNotSupported(request, response); + return; + } + + Handler handler = handlerMapping.getHandler(request); + try { + handler.handle(request,response); + } catch (ServletException e) { + e.printStackTrace(); + handleNotSupported(request, response); + } + + } + + private void handleNotSupported(final HttpServletRequest request, + final HttpServletResponse response) { + log.warn("지원하지 않는 요청 URI: {} ", request.getRequestURI()); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + try { + response.getWriter().write("해당 URI에 대한 접근은 지원하지 않는 요청입니다. : " + request.getRequestURI()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/spring/web/TomcatStarter.java b/src/main/java/spring/mvc/web/TomcatStarter.java similarity index 85% rename from src/main/java/spring/web/TomcatStarter.java rename to src/main/java/spring/mvc/web/TomcatStarter.java index f9de673..1183462 100644 --- a/src/main/java/spring/web/TomcatStarter.java +++ b/src/main/java/spring/mvc/web/TomcatStarter.java @@ -1,4 +1,4 @@ -package spring.web; +package spring.mvc.web; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Connector; @@ -13,14 +13,10 @@ public class TomcatStarter { private final Tomcat tomcat; public TomcatStarter() { - this(WEBAPP_DIR_LOCATION); - } - - public TomcatStarter(final String webappDirLocation) { this.tomcat = new Tomcat(); tomcat.setConnector(createConnector()); - String docBase = new File(webappDirLocation).getAbsolutePath(); + String docBase = new File(WEBAPP_DIR_LOCATION).getAbsolutePath(); StandardContext context = (StandardContext) tomcat.addWebapp("", docBase); } @@ -51,4 +47,3 @@ private Connector createConnector() { return connector; } } - diff --git a/src/main/java/spring/web/filter/CharacterEncodingFilter.java b/src/main/java/spring/mvc/web/filter/CharacterEncodingFilter.java similarity index 95% rename from src/main/java/spring/web/filter/CharacterEncodingFilter.java rename to src/main/java/spring/mvc/web/filter/CharacterEncodingFilter.java index 762e09f..f731414 100644 --- a/src/main/java/spring/web/filter/CharacterEncodingFilter.java +++ b/src/main/java/spring/mvc/web/filter/CharacterEncodingFilter.java @@ -1,4 +1,4 @@ -package spring.web.filter; +package spring.mvc.web.filter; import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; diff --git a/src/main/java/spring/web/filter/JsonToUrlEncodedFilter.java b/src/main/java/spring/mvc/web/filter/JsonToUrlEncodedFilter.java similarity index 95% rename from src/main/java/spring/web/filter/JsonToUrlEncodedFilter.java rename to src/main/java/spring/mvc/web/filter/JsonToUrlEncodedFilter.java index c6e7831..46a5be7 100644 --- a/src/main/java/spring/web/filter/JsonToUrlEncodedFilter.java +++ b/src/main/java/spring/mvc/web/filter/JsonToUrlEncodedFilter.java @@ -1,4 +1,4 @@ -package spring.web.filter; +package spring.mvc.web.filter; import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; diff --git a/src/main/java/spring/web/filter/JsonToUrlEncodedRequestWrapper.java b/src/main/java/spring/mvc/web/filter/JsonToUrlEncodedRequestWrapper.java similarity index 98% rename from src/main/java/spring/web/filter/JsonToUrlEncodedRequestWrapper.java rename to src/main/java/spring/mvc/web/filter/JsonToUrlEncodedRequestWrapper.java index 8d1a0ee..854aae9 100644 --- a/src/main/java/spring/web/filter/JsonToUrlEncodedRequestWrapper.java +++ b/src/main/java/spring/mvc/web/filter/JsonToUrlEncodedRequestWrapper.java @@ -1,4 +1,4 @@ -package spring.web.filter; +package spring.mvc.web.filter; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ReadListener; diff --git a/src/main/java/spring/mvc/web/handler/excution/AnnotationHandler.java b/src/main/java/spring/mvc/web/handler/excution/AnnotationHandler.java new file mode 100644 index 0000000..fb0b415 --- /dev/null +++ b/src/main/java/spring/mvc/web/handler/excution/AnnotationHandler.java @@ -0,0 +1,31 @@ +package spring.mvc.web.handler.excution; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import spring.mvc.view.ModelAndView; +import spring.mvc.view.View; +import java.util.Map; + +public class AnnotationHandler implements Handler { + + private final HandlerExecution handlerExecution; + + public AnnotationHandler(final HandlerExecution handlerExecution) { + this.handlerExecution = handlerExecution; + } + + @Override + public void handle(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { + try { + ModelAndView modelAndView = handlerExecution.handle(request, response); + + View view = modelAndView.getView(); + Map model = modelAndView.getModel(); + + view.render(model, request, response); + } catch (final Exception e) { + throw new ServletException(e.getMessage()); + } + } +} diff --git a/src/main/java/spring/mvc/web/handler/excution/Handler.java b/src/main/java/spring/mvc/web/handler/excution/Handler.java new file mode 100644 index 0000000..9464485 --- /dev/null +++ b/src/main/java/spring/mvc/web/handler/excution/Handler.java @@ -0,0 +1,9 @@ +package spring.mvc.web.handler.excution; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public interface Handler { + void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException; +} diff --git a/src/main/java/spring/mvc/web/handler/excution/HandlerExecution.java b/src/main/java/spring/mvc/web/handler/excution/HandlerExecution.java new file mode 100644 index 0000000..6f8d64e --- /dev/null +++ b/src/main/java/spring/mvc/web/handler/excution/HandlerExecution.java @@ -0,0 +1,30 @@ +package spring.mvc.web.handler.excution; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spring.mvc.view.ModelAndView; +import java.lang.reflect.Method; + + +public class HandlerExecution { + private static final Logger log = LoggerFactory.getLogger(HandlerExecution.class); + + private final Object invoker; + private final Method method; + + public HandlerExecution(final Object invoker, final Method method) { + this.invoker = invoker; + this.method = method; + } + + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response) { + try { + return (ModelAndView) method.invoke(invoker, request, response); + } catch (Exception e) { + log.error("{} 메서드 호출 실패 : {}", method, e.getMessage()); + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/spring/mvc/web/handler/mapping/AnnotationHandlerMapping.java b/src/main/java/spring/mvc/web/handler/mapping/AnnotationHandlerMapping.java new file mode 100644 index 0000000..b951dba --- /dev/null +++ b/src/main/java/spring/mvc/web/handler/mapping/AnnotationHandlerMapping.java @@ -0,0 +1,43 @@ +package spring.mvc.web.handler.mapping; + +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spring.ioc.context.support.ApplicationContext; +import spring.mvc.web.handler.excution.Handler; + +import java.util.*; + +public class AnnotationHandlerMapping implements HandlerMapping { + private static final Logger logger = LoggerFactory.getLogger(AnnotationHandlerMapping.class); + + private final ApplicationContext applicationContext; + private final Object[] basePackage; + private final Map handlerMap; + + public AnnotationHandlerMapping(ApplicationContext applicationContext, Object... basePackage) { + this.basePackage = basePackage; + this.handlerMap = new HashMap<>(); + this.applicationContext = applicationContext; + } + + public void initialize() { + ControllerScanner sc = ControllerScanner.withScanRange(basePackage); + sc.putHandlersToHandlerMap(handlerMap, applicationContext); + for(HandlerKey key: handlerMap.keySet()) { + logger.debug("Mapped : {}",key.toString()); + } + } + + @Override + public boolean supports(final HttpServletRequest request) { + HandlerKey handlerKey = HandlerKey.from(request); + return handlerMap.containsKey(handlerKey); + } + + @Override + public Handler getHandler(final HttpServletRequest request) { + HandlerKey handlerKey = HandlerKey.from(request); + return handlerMap.get(handlerKey); + } +} \ No newline at end of file diff --git a/src/main/java/spring/mvc/web/handler/mapping/ControllerScanner.java b/src/main/java/spring/mvc/web/handler/mapping/ControllerScanner.java new file mode 100644 index 0000000..37137b2 --- /dev/null +++ b/src/main/java/spring/mvc/web/handler/mapping/ControllerScanner.java @@ -0,0 +1,59 @@ +package spring.mvc.web.handler.mapping; + +import org.reflections.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spring.ioc.context.support.ApplicationContext; +import spring.mvc.annotation.Controller; +import spring.mvc.annotation.RequestMapping; +import spring.mvc.web.handler.excution.AnnotationHandler; +import spring.mvc.web.handler.excution.Handler; +import spring.mvc.web.handler.excution.HandlerExecution; + +import java.lang.reflect.Method; +import java.util.*; + +public class ControllerScanner { + private final Reflections reflections; + private final Logger log = LoggerFactory.getLogger(ControllerScanner.class); + + public ControllerScanner(Object... basePackage) { + reflections = new Reflections(basePackage); + } + + public static ControllerScanner withScanRange(final Object[] basePackage) { + return new ControllerScanner(basePackage); + } + + public void putHandlersToHandlerMap(Map handlerExecutions, ApplicationContext applicationContext) { + for (Class controllerType : reflections.getTypesAnnotatedWith(Controller.class)) { + Object controller = getControllerInstance(controllerType, applicationContext); + List controllerMethods = getControllerMethods(controllerType); + controllerMethods.forEach(method -> addHandlerExecutions(controller, method, handlerExecutions)); + } + } + + private Object getControllerInstance(final Class controllerType, final ApplicationContext applicationContext){ + try { + return applicationContext.getBean(controllerType); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private List getControllerMethods(final Class controllerType) { + return Arrays.stream(controllerType.getMethods()) + .filter(method -> method.isAnnotationPresent(RequestMapping.class)) + .toList(); + } + + private void addHandlerExecutions(final Object controller, final Method method, Map handlerExecutions) { + RequestMapping request = method.getAnnotation(RequestMapping.class); + HandlerExecution handlerExecution = new HandlerExecution(controller, method); + AnnotationHandler handler = new AnnotationHandler(handlerExecution); + + Arrays.stream(request.method()) + .map(httpMethod -> new HandlerKey(request.value(), httpMethod)) + .forEach(handlerKey -> handlerExecutions.put(handlerKey, handler)); + } +} diff --git a/src/main/java/spring/mvc/handler/mapping/HandlerKey.java b/src/main/java/spring/mvc/web/handler/mapping/HandlerKey.java similarity index 61% rename from src/main/java/spring/mvc/handler/mapping/HandlerKey.java rename to src/main/java/spring/mvc/web/handler/mapping/HandlerKey.java index 9d420ba..d31ffbe 100644 --- a/src/main/java/spring/mvc/handler/mapping/HandlerKey.java +++ b/src/main/java/spring/mvc/web/handler/mapping/HandlerKey.java @@ -1,5 +1,6 @@ -package spring.mvc.handler.mapping; +package spring.mvc.web.handler.mapping; +import jakarta.servlet.http.HttpServletRequest; import java.util.Objects; public class HandlerKey { @@ -11,9 +12,16 @@ public HandlerKey(String url, RequestMethod method) { this.method = method; } + public static HandlerKey from(final HttpServletRequest request) { + String uri = request.getRequestURI(); + RequestMethod method = RequestMethod.valueOf(request.getMethod()); + + return new HandlerKey(uri, method); + } + @Override public String toString() { - return "HandlerKey [url= {}" + url + ", requestMethod=" + method + "]"; + return "HandlerKey [url= " + url + ", requestMethod=" + method + "]"; } @Override diff --git a/src/main/java/spring/mvc/web/handler/mapping/HandlerMapping.java b/src/main/java/spring/mvc/web/handler/mapping/HandlerMapping.java new file mode 100644 index 0000000..846b9ce --- /dev/null +++ b/src/main/java/spring/mvc/web/handler/mapping/HandlerMapping.java @@ -0,0 +1,12 @@ +package spring.mvc.web.handler.mapping; + +import jakarta.servlet.http.HttpServletRequest; +import spring.mvc.web.handler.excution.Handler; + +public interface HandlerMapping { + Handler getHandler(HttpServletRequest request); + + boolean supports(HttpServletRequest request); + + void initialize(); +} diff --git a/src/main/java/spring/mvc/handler/mapping/RequestMethod.java b/src/main/java/spring/mvc/web/handler/mapping/RequestMethod.java similarity index 68% rename from src/main/java/spring/mvc/handler/mapping/RequestMethod.java rename to src/main/java/spring/mvc/web/handler/mapping/RequestMethod.java index 0ff13fb..5f16be4 100644 --- a/src/main/java/spring/mvc/handler/mapping/RequestMethod.java +++ b/src/main/java/spring/mvc/web/handler/mapping/RequestMethod.java @@ -1,4 +1,4 @@ -package spring.mvc.handler.mapping; +package spring.mvc.web.handler.mapping; public enum RequestMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE diff --git a/src/main/java/spring/web/DispatcherServlet.java b/src/main/java/spring/web/DispatcherServlet.java deleted file mode 100644 index 74eaeb7..0000000 --- a/src/main/java/spring/web/DispatcherServlet.java +++ /dev/null @@ -1,62 +0,0 @@ -package spring.web; - -import jakarta.servlet.annotation.WebServlet; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spring.mvc.handler.excution.HandlerAdapter; -import spring.mvc.handler.excution.HandlerExecutionHandlerAdapter; -import spring.mvc.handler.mapping.AnnotationHandlerMapping; -import spring.mvc.handler.mapping.HandlerMapping; -import spring.mvc.view.ModelAndView; -import spring.mvc.view.View; -import java.util.ArrayList; -import java.util.List; - - -@WebServlet(name = "dispatcher", urlPatterns = "/", loadOnStartup = 1) -public class DispatcherServlet extends HttpServlet { - private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); - - private HandlerMapping handlerMapping; - private List handlerAdapters = new ArrayList<>(); - - @Override - public void init() { - AnnotationHandlerMapping ahm = new AnnotationHandlerMapping("client.controller"); - ahm.initialize(); - this.handlerMapping = ahm; - handlerAdapters.add(new HandlerExecutionHandlerAdapter()); - } - - @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) { - String requestUri = req.getRequestURI(); - log.debug("Method : {}, Request URI : {}", req.getMethod(), requestUri); - - Object handler = handlerMapping.getHandler(req); - if (handler == null) { - throw new IllegalArgumentException("존재하지 않는 URL입니다."); - } - - try { - ModelAndView mav = execute(handler, req, resp); - View view = mav.getView(); - view.render(mav.getModel(), req, resp); - } catch (Exception e) { - log.error(e.getMessage()); - } - } - - - private ModelAndView execute(Object handler, HttpServletRequest req, HttpServletResponse resp) throws Exception { - for (HandlerAdapter handlerAdapter : handlerAdapters) { - if (handlerAdapter.supports(handler)) { - return handlerAdapter.handle(req, resp, handler); - } - } - return null; - } -} \ No newline at end of file diff --git a/src/main/java/spring/web/ServletApplication.java b/src/main/java/spring/web/ServletApplication.java deleted file mode 100644 index 3ecd485..0000000 --- a/src/main/java/spring/web/ServletApplication.java +++ /dev/null @@ -1,9 +0,0 @@ -package spring.web; - -public class ServletApplication { - public static void main(String[] args) { - final TomcatStarter tomcatStarter = new TomcatStarter(); - tomcatStarter.start(); - tomcatStarter.await(); - } -} diff --git a/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer b/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer new file mode 100644 index 0000000..c0595be --- /dev/null +++ b/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer @@ -0,0 +1 @@ +spring.AppInitServlet