Skip to content

Commit 2c998b9

Browse files
carsagokang-hyungu
andauthored
[MVC 미션 1단계] 오리(오현서) 미션 제출합니다. (#355)
* 패키지 위치 변경 및 코드 정리 * 서블릿 학습 테스트 코드 개선 * feat: annotation을 통한 Handler mapping * refactor: 핸들러 생성 과정 리팩토링 --------- Co-authored-by: kang-hyungu <[email protected]>
1 parent b16924a commit 2c998b9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+369
-203
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ jobs:
3434
env:
3535
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
3636
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
37-
run: ./gradlew build sonar --info -x :study:build
37+
run: ./gradlew clean build codeCoverageReport --info -x :study:build

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,5 @@ Temporary Items
171171

172172
tomcat.*
173173
tomcat.*/**
174+
175+
**/WEB-INF/classes/**

app/src/main/java/com/techcourse/AppWebApplicationInitializer.java

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package com.techcourse;
22

3-
import org.apache.catalina.connector.Connector;
4-
import org.apache.catalina.startup.Tomcat;
53
import org.slf4j.Logger;
64
import org.slf4j.LoggerFactory;
75

8-
import java.io.File;
6+
import java.io.IOException;
97
import java.util.stream.Stream;
108

119
public class Application {
@@ -16,22 +14,11 @@ public class Application {
1614

1715
public static void main(final String[] args) throws Exception {
1816
final int port = defaultPortIfNull(args);
19-
20-
final var tomcat = new Tomcat();
21-
tomcat.setConnector(createConnector(port));
22-
final var docBase = new File("app/src/main/webapp/").getAbsolutePath();
23-
tomcat.addWebapp("", docBase);
24-
log.info("configuring app with basedir: {}", docBase);
17+
final var tomcat = new TomcatStarter(port);
18+
log.info("configuring app with basedir: {}", TomcatStarter.WEBAPP_DIR_LOCATION);
2519

2620
tomcat.start();
27-
tomcat.getServer().await();
28-
}
29-
30-
private static Connector createConnector(final int port) {
31-
final var connector = new Connector();
32-
connector.setPort(port);
33-
connector.setProperty("bindOnInit", "false");
34-
return connector;
21+
stop(tomcat);
3522
}
3623

3724
private static int defaultPortIfNull(final String[] args) {
@@ -40,4 +27,16 @@ private static int defaultPortIfNull(final String[] args) {
4027
.map(Integer::parseInt)
4128
.orElse(DEFAULT_PORT);
4229
}
30+
31+
private static void stop(final TomcatStarter tomcat) {
32+
try {
33+
// make the application wait until we press any key.
34+
System.in.read();
35+
} catch (IOException e) {
36+
log.error(e.getMessage(), e);
37+
} finally {
38+
log.info("web server stop.");
39+
tomcat.stop();
40+
}
41+
}
4342
}

mvc/src/main/java/nextstep/mvc/DispatcherServlet.java renamed to app/src/main/java/com/techcourse/DispatcherServlet.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
1-
package nextstep.mvc;
1+
package com.techcourse;
22

33
import jakarta.servlet.ServletException;
44
import jakarta.servlet.http.HttpServlet;
55
import jakarta.servlet.http.HttpServletRequest;
66
import jakarta.servlet.http.HttpServletResponse;
7-
import nextstep.mvc.controller.asis.Controller;
8-
import nextstep.mvc.view.JspView;
97
import org.slf4j.Logger;
108
import org.slf4j.LoggerFactory;
9+
import webmvc.org.springframework.web.servlet.view.JspView;
1110

1211
public class DispatcherServlet extends HttpServlet {
1312

1413
private static final long serialVersionUID = 1L;
1514
private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class);
1615

17-
private HandlerMapping handlerMappings;
16+
private ManualHandlerMapping manualHandlerMapping;
1817

19-
public void addHandlerMapping(final HandlerMapping handlerMapping) {
20-
this.handlerMappings = handlerMapping;
18+
public DispatcherServlet() {
2119
}
2220

2321
@Override
2422
public void init() {
25-
this.handlerMappings.initialize();
23+
manualHandlerMapping = new ManualHandlerMapping();
24+
manualHandlerMapping.initialize();
2625
}
2726

2827
@Override
2928
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException {
30-
log.debug("Method : {}, Request URI : {}", request.getMethod(), request.getRequestURI());
29+
final String requestURI = request.getRequestURI();
30+
log.debug("Method : {}, Request URI : {}", request.getMethod(), requestURI);
3131

3232
try {
33-
final var controller = (Controller) handlerMappings.getHandler(request);
33+
final var controller = manualHandlerMapping.getHandler(requestURI);
3434
final var viewName = controller.execute(request, response);
3535
move(viewName, request, response);
3636
} catch (Throwable e) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.techcourse;
2+
3+
import jakarta.servlet.ServletContext;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
import web.org.springframework.web.WebApplicationInitializer;
7+
8+
/**
9+
* Base class for {@link WebApplicationInitializer}
10+
* implementations that register a {@link DispatcherServlet} in the servlet context.
11+
*/
12+
public class DispatcherServletInitializer implements WebApplicationInitializer {
13+
14+
private static final Logger log = LoggerFactory.getLogger(DispatcherServletInitializer.class);
15+
16+
private static final String DEFAULT_SERVLET_NAME = "dispatcher";
17+
18+
@Override
19+
public void onStartup(final ServletContext servletContext) {
20+
final var dispatcherServlet = new DispatcherServlet();
21+
22+
final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet);
23+
if (registration == null) {
24+
throw new IllegalStateException("Failed to register servlet with name '" + DEFAULT_SERVLET_NAME + "'. " +
25+
"Check if there is another servlet registered under the same name.");
26+
}
27+
28+
registration.setLoadOnStartup(1);
29+
registration.addMapping("/");
30+
31+
log.info("Start AppWebApplication Initializer");
32+
}
33+
}

app/src/main/java/com/techcourse/ManualHandlerMapping.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
package com.techcourse;
22

33
import com.techcourse.controller.*;
4-
import jakarta.servlet.http.HttpServletRequest;
5-
import nextstep.mvc.HandlerMapping;
6-
import nextstep.mvc.controller.asis.Controller;
7-
import nextstep.mvc.controller.asis.ForwardController;
84
import org.slf4j.Logger;
95
import org.slf4j.LoggerFactory;
6+
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
7+
import webmvc.org.springframework.web.servlet.mvc.asis.ForwardController;
108

119
import java.util.HashMap;
1210
import java.util.Map;
1311

14-
public class ManualHandlerMapping implements HandlerMapping {
12+
public class ManualHandlerMapping {
1513

1614
private static final Logger log = LoggerFactory.getLogger(ManualHandlerMapping.class);
1715

1816
private static final Map<String, Controller> controllers = new HashMap<>();
1917

20-
@Override
2118
public void initialize() {
2219
controllers.put("/", new ForwardController("/index.jsp"));
2320
controllers.put("/login", new LoginController());
@@ -31,9 +28,7 @@ public void initialize() {
3128
.forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass()));
3229
}
3330

34-
@Override
35-
public Controller getHandler(HttpServletRequest request) {
36-
final String requestURI = request.getRequestURI();
31+
public Controller getHandler(final String requestURI) {
3732
log.debug("Request Mapping Uri : {}", requestURI);
3833
return controllers.get(requestURI);
3934
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.techcourse;
2+
3+
import org.apache.catalina.Context;
4+
import org.apache.catalina.LifecycleException;
5+
import org.apache.catalina.connector.Connector;
6+
import org.apache.catalina.core.StandardContext;
7+
import org.apache.catalina.startup.Tomcat;
8+
import org.apache.tomcat.util.scan.StandardJarScanner;
9+
10+
import java.io.File;
11+
12+
public class TomcatStarter {
13+
14+
public static final String WEBAPP_DIR_LOCATION = "app/src/main/webapp/";
15+
16+
private final Tomcat tomcat;
17+
18+
public TomcatStarter(final int port) {
19+
this(WEBAPP_DIR_LOCATION, port);
20+
}
21+
22+
public TomcatStarter(final String webappDirLocation, final int port) {
23+
this.tomcat = new Tomcat();
24+
tomcat.setConnector(createConnector(port));
25+
26+
final var docBase = new File(webappDirLocation).getAbsolutePath();
27+
final var context = (StandardContext) tomcat.addWebapp("", docBase);
28+
skipJarScan(context);
29+
skipClearReferences(context);
30+
}
31+
32+
public void start() {
33+
try {
34+
tomcat.start();
35+
} catch (LifecycleException e) {
36+
throw new UncheckedServletException(e);
37+
}
38+
}
39+
40+
public void stop() {
41+
try {
42+
tomcat.stop();
43+
tomcat.destroy();
44+
} catch (LifecycleException e) {
45+
throw new UncheckedServletException(e);
46+
}
47+
}
48+
49+
private Connector createConnector(final int port) {
50+
final var connector = new Connector();
51+
connector.setPort(port);
52+
return connector;
53+
}
54+
55+
private void skipJarScan(final Context context) {
56+
final var jarScanner = (StandardJarScanner) context.getJarScanner();
57+
jarScanner.setScanClassPath(false);
58+
}
59+
60+
private void skipClearReferences(final StandardContext context) {
61+
/**
62+
* https://tomcat.apache.org/tomcat-10.1-doc/config/context.html
63+
*
64+
* setClearReferencesObjectStreamClassCaches 번역
65+
* true인 경우 웹 응용 프로그램이 중지되면 Tomcat은 직렬화에 사용되는
66+
* ObjectStreamClass 클래스에서 웹 응용 프로그램에 의해 로드된
67+
* 클래스에 대한 SoftReference를 찾고 찾은 모든 SoftReference를 지웁니다.
68+
* 이 기능은 리플렉션을 사용하여 SoftReference를 식별하므로 Java 9 이상에서
69+
* 실행할 때 명령줄 옵션 -XaddExports:java.base/java.io=ALL-UNNAMED를 설정해야 합니다.
70+
* 지정하지 않으면 기본값인 true가 사용됩니다.
71+
*
72+
* ObjectStreamClass와 관련된 메모리 누수는 Java 19 이상, Java 17.0.4 이상 및
73+
* Java 11.0.16 이상에서 수정되었습니다.
74+
* 수정 사항이 포함된 Java 버전에서 실행할 때 확인이 비활성화됩니다.
75+
*
76+
* Amazon Corretto-17.0.6은 경고 메시지가 나옴.
77+
* 학습과 관련 없는 메시지가 나오지 않도록 관련 설정을 끈다.
78+
*/
79+
context.setClearReferencesObjectStreamClassCaches(false);
80+
context.setClearReferencesRmiTargets(false);
81+
context.setClearReferencesThreadLocals(false);
82+
}
83+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.techcourse;
2+
3+
public class UncheckedServletException extends RuntimeException {
4+
5+
public UncheckedServletException(Exception e) {
6+
super(e);
7+
}
8+
}

app/src/main/java/com/techcourse/controller/LoginController.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import com.techcourse.repository.InMemoryUserRepository;
55
import jakarta.servlet.http.HttpServletRequest;
66
import jakarta.servlet.http.HttpServletResponse;
7-
import nextstep.mvc.controller.asis.Controller;
7+
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
88
import org.slf4j.Logger;
99
import org.slf4j.LoggerFactory;
1010

@@ -31,8 +31,7 @@ private String login(final HttpServletRequest request, final User user) {
3131
final var session = request.getSession();
3232
session.setAttribute(UserSession.SESSION_KEY, user);
3333
return "redirect:/index.jsp";
34-
} else {
35-
return "redirect:/401.jsp";
3634
}
35+
return "redirect:/401.jsp";
3736
}
3837
}

0 commit comments

Comments
 (0)