Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
22072d5
test: 학습 테스트 완성
Hyeon9mak Sep 22, 2021
d9cfaa1
refactor: User 클래스 멤버 변수 설정 리팩토링
Hyeon9mak Sep 24, 2021
6d62613
refactor: UserDao.insert시 id(primary key)를 포함한 User를 반환하도록 변경
Hyeon9mak Sep 24, 2021
80140bd
feat: UserDao.update 구현
Hyeon9mak Sep 24, 2021
c158788
feat: UserDao.findAll, UserDao.deleteAll 구현
Hyeon9mak Sep 24, 2021
2d3fb19
feat: UserDao.findById, UserDao.findByAccount 구현
Hyeon9mak Sep 24, 2021
91695f5
refactor: UserDao.deleteAll, UserDao.update 책임 분리
Hyeon9mak Sep 24, 2021
acf57da
refactor: JdbcTemplate 생성 및 데이터베이스 작업 수행 책임 분리
Hyeon9mak Sep 24, 2021
d9225b1
refactor: queryForObject, query 구현 및 JdbcTemplate 패키지 위치 변경
Hyeon9mak Sep 25, 2021
54898b1
docs: Todo 리스트 업데이트
Hyeon9mak Sep 25, 2021
8fab7ae
feat: 커스텀 예외처리 적용
Hyeon9mak Sep 25, 2021
4e4415a
feat: SimpleJdbcInsert 구현
Hyeon9mak Sep 25, 2021
4b17405
refactor: SimpleJdbcInsert 커스텀 예외 추가 및 변수명 리팩토링
Hyeon9mak Sep 25, 2021
308315d
refactor: DatabasePopulatorUtils 메서드 분리
Hyeon9mak Sep 25, 2021
dbf9765
refactor: mvc 모듈 리팩토링
Hyeon9mak Sep 25, 2021
1f0c15d
refactor: jdbcTemplate 에서 Optional을 반환하도록 변경
Hyeon9mak Sep 25, 2021
37087bf
refactor: 사용되지 않는 import 제거
Hyeon9mak Sep 25, 2021
832fb74
refactor: app 모듈 리팩토링
Hyeon9mak Sep 25, 2021
e85d4d8
refactor: App 컴포넌트 초기화 단계에서 Datasource 설정을 초기화 한 후 사용하도록 변경
Hyeon9mak Sep 26, 2021
85c9ffa
refactor: Handler URL 메소드 변경, Conflict 핸들링 페이지 추가
Hyeon9mak Sep 26, 2021
a6111c0
chore: jacoco 테스트 설정 추가
Hyeon9mak Sep 26, 2021
f1b96c1
refactor: ComponentContainer 초기화시 필요한 DataSource 모킹
Hyeon9mak Sep 26, 2021
e0e3b96
refactor: JdbcTemplate에서 커스텀 예외를 사용하도록 변경
Hyeon9mak Sep 27, 2021
86eb046
refactor: JdbcTemplate for문 기준 변수를 미리 추출해서 사용하도록 변경
Hyeon9mak Sep 29, 2021
9cb151c
refactor: JdbcTemplate 에서 단일 오브젝트 조회 결과가 1개를 초과할 때 예외 추가
Hyeon9mak Sep 29, 2021
a7fa076
refactor: Optional을 사용할 때 isPresent 대신 ifPresent를 사용하도록 변경
Hyeon9mak Sep 29, 2021
7defbd9
refactor: VO 불변 유지를 위한 원시값 포장
Hyeon9mak Sep 29, 2021
92a2d11
refactor: 커스텀 예외에 메세지를 포함하도록 수정
Hyeon9mak Sep 29, 2021
ced9630
refactor: JsonView 에서 복수/단수 데이터 조회시 예외 상황 변경
Hyeon9mak Sep 29, 2021
4d36e3e
refactor: ComponentContainer 중복 메서드 제거
Hyeon9mak Sep 29, 2021
5d1022d
refactor: Autowired 어노테이션을 이용한 Component 초기화 변경
Hyeon9mak Oct 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,6 @@ public class AppWebApplicationInitializer implements WebApplicationInitializer {

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 클래스에 불필요한 import 문들이 있는데 제거하면 좋을 것 같아요 👀

Copy link
Member Author

@Hyeon9mak Hyeon9mak Oct 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗... 꼼꼼한 리뷰 감사합니다 ㅠㅠ 요즘 120자 제한을 두고도 가독성을 위해 조금씩 넘겨서 코드를 짜다보니
코드 리포맷팅을 사용하면 코드 라인이 넘어가는게 싫어서 리포맷팅을 잘 안누르게 되네요.. 이상한 버릇이 생겨버렸어요..
넘길 땐 넘길 줄 알아야 하는데!!!!!

@Override
public void onStartup(ServletContext servletContext) {
try {
LOG.info("Start Components Initializer");
DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
ComponentContainer.initialize(DataSourceConfig.getInstance(), BASE_PACKAGE);
} catch (Exception e) {
LOG.error("Initialize Components Error: {}", e.getMessage());
throw new ComponentContainerException(e.getMessage());
}

final DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.addHandlerMapping(new AnnotationHandlerMapping(BASE_PACKAGE));
dispatcherServlet.addHandlerAdapter(new AnnotationHandlerAdapter());
Expand Down
13 changes: 9 additions & 4 deletions app/src/main/java/com/techcourse/config/DataSourceConfig.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package com.techcourse.config;

import javax.sql.DataSource;
import nextstep.web.annotation.Configuration;
import nextstep.web.annotation.Initialize;
import org.h2.jdbcx.JdbcDataSource;

import java.util.Objects;

@Configuration
public class DataSourceConfig {

private static javax.sql.DataSource INSTANCE;
private static DataSource INSTANCE;

public static javax.sql.DataSource getInstance() {
private DataSourceConfig() {}

@Initialize
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 단순히 @Bean 애너테이션을 사용했었는데, @Configuration@Initialize가 더 적절했겠네요 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Configuration은 피케이 코드를 리뷰하다가 영감을 얻었고, @Initialize는 '초기화를 하면서 딱 한 번만 수행될 메서드를 표현할게 뭐가 있을까...' 고민하다가 직관적인 이름을 쓰자는 생각으로 접근했어요! 👍

public static DataSource getInstance() {
if (Objects.isNull(INSTANCE)) {
INSTANCE = createJdbcDataSource();
}
Expand All @@ -22,6 +29,4 @@ private static JdbcDataSource createJdbcDataSource() {
jdbcDataSource.setPassword("");
return jdbcDataSource;
}

private DataSourceConfig() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import jakarta.servlet.http.HttpSession;
import nextstep.mvc.view.JspView;
import nextstep.mvc.view.ModelAndView;
import nextstep.web.annotation.Autowired;
import nextstep.web.annotation.Controller;
import nextstep.web.annotation.RequestMapping;
import nextstep.web.support.RequestMethod;
Expand All @@ -22,6 +23,7 @@ public class LoginController {

private final LoginService loginService;

@Autowired
public LoginController(LoginService loginService) {
this.loginService = loginService;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.servlet.http.HttpServletResponse;
import nextstep.mvc.view.JspView;
import nextstep.mvc.view.ModelAndView;
import nextstep.web.annotation.Autowired;
import nextstep.web.annotation.Controller;
import nextstep.web.annotation.RequestMapping;
import nextstep.web.support.RequestMethod;
Expand All @@ -20,6 +21,7 @@ public class RegisterController {

private final RegisterService registerService;

@Autowired
public RegisterController(RegisterService registerService) {
this.registerService = registerService;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.servlet.http.HttpServletResponse;
import nextstep.mvc.view.JsonView;
import nextstep.mvc.view.ModelAndView;
import nextstep.web.annotation.Autowired;
import nextstep.web.annotation.Controller;
import nextstep.web.annotation.RequestMapping;
import nextstep.web.support.RequestMethod;
Expand All @@ -20,6 +21,7 @@ public class UserController {

private final UserService userService;

@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
Expand Down
9 changes: 5 additions & 4 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@
import nextstep.jdbc.RowMapper;
import nextstep.jdbc.SimpleJdbcInsert;
import nextstep.jdbc.exception.DataAccessException;
import nextstep.web.annotation.Autowired;
import nextstep.web.annotation.Repository;

@Repository
public class UserDao {

private static final RowMapper<User> MAPPER = (rs) -> new User(
private final JdbcTemplate jdbcTemplate;
private final SimpleJdbcInsert jdbcInsert;
private final RowMapper<User> MAPPER = (rs) -> new User(
rs.getLong("id"),
rs.getString("account"),
rs.getString("password"),
rs.getString("email")
);

private final JdbcTemplate jdbcTemplate;
private final SimpleJdbcInsert jdbcInsert;

@Autowired
public UserDao(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.jdbcInsert = new SimpleJdbcInsert(dataSource, "users", "id");
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/techcourse/service/LoginService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import com.techcourse.domain.User;
import com.techcourse.exception.UnauthorizedException;
import jakarta.servlet.http.HttpSession;
import nextstep.web.annotation.Autowired;
import nextstep.web.annotation.Service;

@Service
public class LoginService {

private final UserDao userDao;

@Autowired
public LoginService(UserDao userDao) {
this.userDao = userDao;
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/techcourse/service/RegisterService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.techcourse.dao.UserDao;
import com.techcourse.domain.User;
import com.techcourse.exception.DuplicateAccountException;
import nextstep.web.annotation.Autowired;
import nextstep.web.annotation.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -16,6 +17,7 @@ public class RegisterService {

private final UserDao userDao;

@Autowired
public RegisterService(UserDao userDao) {
this.userDao = userDao;
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import com.techcourse.dao.UserDao;
import com.techcourse.domain.User;
import com.techcourse.exception.UserNotFoundException;
import nextstep.web.annotation.Autowired;
import nextstep.web.annotation.Service;

@Service
public class UserService {

private final UserDao userDao;

@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import jakarta.servlet.http.HttpServletRequest;
import java.util.Set;
import nextstep.mvc.HandlerMapping;
import nextstep.mvc.exception.ComponentContainerException;
import nextstep.web.ComponentContainer;
import nextstep.web.annotation.Controller;
import nextstep.web.support.RequestMethod;
import org.reflections.Reflections;
Expand All @@ -25,6 +27,21 @@ public AnnotationHandlerMapping(Object... basePackage) {

@Override
public void initialize() {
initializeComponentContainer();
initializeHandlerExecutions();
}

private void initializeComponentContainer() {
try {
LOG.info("Start Components Initializer");
ComponentContainer.initialize(basePackage);
} catch (Exception e) {
LOG.error("Initialize Components Error: {}", e.getMessage());
throw new ComponentContainerException(e.getMessage());
}
}

private void initializeHandlerExecutions() {
try {
Reflections reflections = new Reflections(basePackage, new TypeAnnotationsScanner(), new SubTypesScanner());
Set<Class<?>> annotatedHandlers = reflections.getTypesAnnotatedWith(Controller.class);
Expand Down
63 changes: 44 additions & 19 deletions mvc/src/main/java/nextstep/web/ComponentContainer.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package nextstep.web;

import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import java.util.stream.Collectors;
import nextstep.mvc.exception.ComponentContainerException;
import nextstep.web.annotation.Autowired;
import nextstep.web.annotation.Configuration;
import nextstep.web.annotation.Controller;
import nextstep.web.annotation.Initialize;
import nextstep.web.annotation.Repository;
import nextstep.web.annotation.Service;
import org.reflections.Reflections;
Expand All @@ -17,39 +23,58 @@ public class ComponentContainer {

private ComponentContainer() {}

public static void initialize(DataSource dataSource, Object... basePackage) throws Exception {
public static void initialize(Object... basePackage) throws Exception {
Reflections reflections = new Reflections(basePackage);
initializeRepositories(reflections.getTypesAnnotatedWith(Repository.class), dataSource);
initializeConfigurations(reflections.getTypesAnnotatedWith(Configuration.class));
initializeComponents(reflections.getTypesAnnotatedWith(Repository.class));
initializeComponents(reflections.getTypesAnnotatedWith(Service.class));
initializeComponents(reflections.getTypesAnnotatedWith(Controller.class));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그럴 일이 있을지는 모르겠지만 새로운 애너테이션이 생성된다면 계속해서 한 줄 한 줄 코드가 추가될 것 같아요.
Repository, Service, Controller class 를 리스트로 두는 상수 (ex) ANNOTATION_COMPONENTS를 두고 추후에는 아래와 같은 구조로 변경을 해볼 수도 있을 것 같네요 😃

        for (Class<? extends Annotation> clazz : ANNOTATION_COMPONENTS) {
            initializeComponents(reflections.getTypesAnnotatedWith(clazz));
        }

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아마도 이 코드는

Controller와 Service를 생성하는 메서드가 중복이 됨에도 현상을 유지한 가장 큰 이유는 가독성 때문이었어요!
말씀해주신대로 Repositories는 DataSource의 존재로 Controller, Service와 조금 다른 로직을 수행하게 되는데
통일성을 유지 시키고 싶었어요.

라던 현구막의 생각이 반영된 코드가 아닌가 싶어서 나중에는 이렇게 바뀔 수도 있겠네요~ 라고 언급하고 넘어가는 정도로 할게요 😉😃

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리스트는 순서를 보장할 수 있으니 충분히 가능하겠네요!!!
initializeComponents(reflections.getTypesAnnotatedWith()); 자체도 사실 따지고 보면 코드 중복이니...
리팩토링 해보겠습니다!!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀해주신대로 상수로 관리하려하다가, 당장은 사용하는 곳이 한 곳 뿐이라 지역변수로 두었습니다!

    public static void initialize(Object... basePackage) throws Exception {
        Reflections reflections = new Reflections(basePackage);
        initializeConfigurations(reflections.getTypesAnnotatedWith(Configuration.class));

        List<Class<? extends Annotation>> annotations = List.of(Repository.class, Service.class, Controller.class);
        for (Class<? extends Annotation> annotation : annotations) {
            initializeComponents(reflections.getTypesAnnotatedWith(annotation));
        }
    }

}

private static void initializeRepositories(Set<Class<?>> repositoryTypes, DataSource dataSource) throws Exception {
for (Class<?> repository : repositoryTypes) {
Object instance = repository.getConstructor(DataSource.class).newInstance(dataSource);
COMPONENTS.put(repository, instance);
private static void initializeConfigurations(Set<Class<?>> configurationTypes) throws Exception {
for (Class<?> configuration : configurationTypes) {
List<Method> initializeMethods = Arrays.stream(configuration.getMethods())
.filter(method -> method.isAnnotationPresent(Initialize.class))
.collect(Collectors.toList());

Constructor<?> constructor = configuration.getDeclaredConstructor();
constructor.setAccessible(true);
initializeConfigurationMethods(constructor.newInstance(), initializeMethods);
}
}

private static void initializeConfigurationMethods(Object instance, List<Method> initializeMethods) throws Exception {
for (Method method : initializeMethods) {
COMPONENTS.put(method.getReturnType(), method.invoke(instance));
}
}

private static void initializeComponents(Set<Class<?>> componentTypes) throws Exception {
for (Class<?> type : componentTypes) {
Class<?>[] fieldTypes = getFieldTypes(type);
Object[] fieldObjects = getFieldObjects(fieldTypes);
Constructor<?> constructor = getConstructor(type);
Object[] parameterObjects = getConstructorParameterObjects(constructor);

Object instance = type.getConstructor(fieldTypes).newInstance(fieldObjects);
COMPONENTS.put(type, instance);
COMPONENTS.put(type, constructor.newInstance(parameterObjects));
}
}

private static Class<?>[] getFieldTypes(Class<?> classTypes) {
return Arrays.stream(classTypes.getDeclaredFields())
.map(Field::getType)
.filter(COMPONENTS::containsKey)
.toArray(Class<?>[]::new);
private static Constructor<?> getConstructor(Class<?> type) {
return Arrays.stream(type.getConstructors())
.filter(constructor -> constructor.isAnnotationPresent(Autowired.class))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런식으로 빈들을 주입해줄 (특정한) 생성자를 지정하고 그 생성자들에 빈을 주입하는거군요 ? 🤔
생각해보니 저희가 Spring프레임워크를 쓸 때도 하나의 생성자만 존재하는 경우에는 예외적으로 @Autowired를 생략하는 것이지 실제로는 @Autowired가 붙어있는거였죠?
이런 관점으로 생각하니 현구막이 구현한 방식이 어쩌면 더 스프링스러운거 같네요 😃

.findAny()
.orElseGet(() -> getDefaultConstructor(type));
}

private static Constructor<?> getDefaultConstructor(Class<?> type) {
try {
return type.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new ComponentContainerException("기본 생성자 탐색에 실패했습니다.");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우와... 👍 이 예외는 진짜 현구막의 꼼꼼함이 느껴지네요 😧

}
}

private static Object[] getFieldObjects(Class<?>[] fieldTypes) {
return Arrays.stream(fieldTypes)
private static Object[] getConstructorParameterObjects(Constructor<?> constructor) {
return Arrays.stream(constructor.getParameterTypes())
.map(COMPONENTS::get)
.toArray(Object[]::new);
}
Expand Down
11 changes: 11 additions & 0 deletions mvc/src/main/java/nextstep/web/annotation/Autowired.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.web.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
11 changes: 11 additions & 0 deletions mvc/src/main/java/nextstep/web/annotation/Configuration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.web.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 {
}
11 changes: 11 additions & 0 deletions mvc/src/main/java/nextstep/web/annotation/Initialize.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.web.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 Initialize {
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ class AnnotationHandlerMappingTest {
private AnnotationHandlerMapping handlerMapping;

@BeforeEach
void setUp() throws Exception {
ComponentContainer.initialize(mock(DataSource.class), "samples");
void setUp() {
handlerMapping = new AnnotationHandlerMapping("samples");
handlerMapping.initialize();
}
Expand Down