Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DI - Step 1] 효오 미션 제출합니다. #11

Merged
merged 16 commits into from
Nov 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
74 changes: 72 additions & 2 deletions nextstep-di/src/main/java/nextstep/di/factory/BeanFactory.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package nextstep.di.factory;

import com.google.common.collect.Maps;
import nextstep.stereotype.Controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class BeanFactory {
private static final Logger logger = LoggerFactory.getLogger(BeanFactory.class);
Expand All @@ -14,16 +20,80 @@ public class BeanFactory {

private Map<Class<?>, Object> beans = Maps.newHashMap();

public BeanFactory(Set<Class<?>> preInstanticateBeans) {
this.preInstanticateBeans = preInstanticateBeans;
public BeanFactory(Object... basePackage) {
BeanScanner beanScanner = new BeanScanner(basePackage);
this.preInstanticateBeans = beanScanner.getTypesAnnotated();
}

@SuppressWarnings("unchecked")
public <T> T getBean(Class<T> requiredType) {
if (!beans.containsKey(requiredType)) {
registerBean(requiredType);
}
return (T) beans.get(requiredType);
}

public void initialize() {
for (Class<?> preInstanticateBean : preInstanticateBeans) {
registerBean(preInstanticateBean);
}
}

private void registerBean(Class<?> preInstanticateBean) {
validateClassType(preInstanticateBean);

if (!beans.containsKey(preInstanticateBean)) {
beans.put(preInstanticateBean, createBean(preInstanticateBean));
Copy link

@kingbbode kingbbode Oct 31, 2019

Choose a reason for hiding this comment

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

(장황하게 작성하여 코멘트를 수정합니다!)
의도한 Bean 의 유형과 범위에서만 생성되고 있나요?
유형 : Controller, Service, Repository Annotation 이 작성된 class
범위 : package

이에 대한 테스트케이스도 작성이 되면 좋겠어요.

}
}

private void validateClassType(Class<?> preInstanticateBean) {
Class<?> requiredType = preInstanticateBean;
if (requiredType.isInterface()) {
requiredType = BeanFactoryUtils.findConcreteClass(preInstanticateBean, preInstanticateBeans);
}

if (!preInstanticateBeans.contains(requiredType)) {
throw new IllegalArgumentException("해당 클래스를 찾을 수 없습니다.");

Choose a reason for hiding this comment

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

조금 더 상세한 로그를 남겨주는 것이 추적하기 쉬울 것 같아요 :)

}
}

private Object createBean(Class<?> preInstanticateBean) {
try {
if (preInstanticateBean.isInterface()) {
return createBean(BeanFactoryUtils.findConcreteClass(preInstanticateBean, preInstanticateBeans));
}
return getInstance(preInstanticateBean);
} catch (Exception e) {
logger.error("### Bean create fail : ", e);
throw new IllegalArgumentException("Bean create fail!");
}
}

private Object getInstance(Class<?> preInstanticateBean) throws IllegalAccessException, InstantiationException, InvocationTargetException {
Constructor<?> injectedConstructor = BeanFactoryUtils.getInjectedConstructor(preInstanticateBean);
if (injectedConstructor == null) {
return createDefaultConstructorInstance(preInstanticateBean);
}
return createInstance(injectedConstructor);
}

private Object createDefaultConstructorInstance(Class<?> preInstanticateBean) throws IllegalAccessException, InstantiationException {
return preInstanticateBean.newInstance();
}

private Object createInstance(Constructor<?> injectedConstructor) throws IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?>[] parameterTypes = injectedConstructor.getParameterTypes();
List<Object> parameters = new ArrayList<>();
for (Class<?> parameterType : parameterTypes) {
parameters.add(getBean(parameterType));
}
return injectedConstructor.newInstance(parameters.toArray());
}

public Map<Class<?>, Object> getControllers() {
return beans.values().stream()
.filter(bean -> bean.getClass().isAnnotationPresent(Controller.class))
.collect(Collectors.toMap(Object::getClass, bean -> bean));
}
}
34 changes: 34 additions & 0 deletions nextstep-di/src/main/java/nextstep/di/factory/BeanScanner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package nextstep.di.factory;

import com.google.common.collect.Sets;
import nextstep.stereotype.Controller;
import nextstep.stereotype.Repository;
import nextstep.stereotype.Service;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

public class BeanScanner {
private static final Logger logger = LoggerFactory.getLogger(BeanScanner.class);
private static final List<Class<? extends Annotation>> ANNOTATIONS = Arrays.asList(Controller.class, Service.class, Repository.class);

private Reflections reflections;

public BeanScanner(Object... basePackage) {
reflections = new Reflections(basePackage);
}

public Set<Class<?>> getTypesAnnotated() {
Set<Class<?>> beans = Sets.newHashSet();
for (Class<? extends Annotation> annotation : ANNOTATIONS) {
beans.addAll(reflections.getTypesAnnotatedWith(annotation));
}
logger.debug("Scan Beans Type : {}", beans);
return beans;
}
}
56 changes: 32 additions & 24 deletions nextstep-di/src/test/java/nextstep/di/factory/BeanFactoryTest.java
Original file line number Diff line number Diff line change
@@ -1,39 +1,30 @@
package nextstep.di.factory;

import com.google.common.collect.Sets;
import nextstep.di.factory.example.QnaController;
import nextstep.stereotype.Controller;
import nextstep.stereotype.Repository;
import nextstep.stereotype.Service;
import nextstep.di.factory.example.MyQnaService;
import nextstep.di.factory.example.QnaController;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.util.Set;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class BeanFactoryTest {
private static final Logger log = LoggerFactory.getLogger( BeanFactoryTest.class );

private Reflections reflections;
private BeanFactory beanFactory;

@BeforeEach
@SuppressWarnings("unchecked")
public void setup() {
reflections = new Reflections("nextstep.di.factory.example");
Set<Class<?>> preInstanticateClazz = getTypesAnnotatedWith(Controller.class, Service.class, Repository.class);
beanFactory = new BeanFactory(preInstanticateClazz);
beanFactory = new BeanFactory("nextstep.di.factory.example");
beanFactory.initialize();
}

@Test
public void di() throws Exception {
public void di() {
QnaController qnaController = beanFactory.getBean(QnaController.class);

assertNotNull(qnaController);
Expand All @@ -44,13 +35,30 @@ public void di() throws Exception {
assertNotNull(qnaService.getQuestionRepository());
}

@SuppressWarnings("unchecked")
private Set<Class<?>> getTypesAnnotatedWith(Class<? extends Annotation>... annotations) {
Set<Class<?>> beans = Sets.newHashSet();
for (Class<? extends Annotation> annotation : annotations) {
beans.addAll(reflections.getTypesAnnotatedWith(annotation));
}
log.debug("Scan Beans Type : {}", beans);
return beans;
@Test
@DisplayName("해당 패키지에 있는 클래스를 찾는다.")
void getControllers() {
Map<Class<?>, Object> controllers = beanFactory.getControllers();
assertThat(controllers.containsKey(QnaController.class)).isTrue();
assertThat(controllers.size()).isEqualTo(1);
}

@Test
@DisplayName("해당 패키지에 없는 클래스는 찾지 못한다.")
void getControllersFail() {
Map<Class<?>, Object> controllers = beanFactory.getControllers();
assertThat(controllers.containsKey(BeanScanner.class)).isFalse();
}

@Test
@DisplayName("해당 패키지에 있는 클래스의 빈을 찾는다.")
void getBean() {
assertNotNull(beanFactory.getBean(MyQnaService.class));
}

@Test
@DisplayName("다른 패키지에 있는 클래스의 빈을 찾는 경우 예외가 발생한다.")
void getBeanFail() {
assertThrows(IllegalArgumentException.class, () -> beanFactory.getBean(BeanScanner.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,15 @@
public class AnnotationHandlerMapping implements HandlerMapping {
private static final Logger logger = LoggerFactory.getLogger(AnnotationHandlerMapping.class);

private Object[] basePackage;
private Map<Class<?>, Object> controllers;

private Map<HandlerKey, HandlerExecution> handlerExecutions = Maps.newHashMap();

public AnnotationHandlerMapping(Object... basePackage) {
this.basePackage = basePackage;
public AnnotationHandlerMapping(Map<Class<?>, Object> controllers) {
this.controllers = controllers;
}

public void initialize() {
ControllerScanner controllerScanner = new ControllerScanner(basePackage);
Map<Class<?>, Object> controllers = controllerScanner.getControllers();
Set<Method> methods = getRequestMappingMethods(controllers.keySet());
for (Method method : methods) {
RequestMapping rm = method.getAnnotation(RequestMapping.class);
Expand Down Expand Up @@ -69,7 +67,6 @@ private Set<Method> getRequestMappingMethods(Set<Class<?>> controlleers) {
return requestMappingMethods;
}


public Object getHandler(HttpServletRequest request) {
String requestUri = request.getRequestURI();
RequestMethod rm = RequestMethod.valueOf(request.getMethod().toUpperCase());
Expand Down

This file was deleted.

8 changes: 6 additions & 2 deletions slipp/src/main/java/slipp/SlippWebApplicationInitializer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package slipp;

import nextstep.di.factory.BeanFactory;
import nextstep.mvc.DispatcherServlet;
import nextstep.mvc.asis.ControllerHandlerAdapter;
import nextstep.mvc.tobe.AnnotationHandlerMapping;
Expand All @@ -12,13 +13,16 @@
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class SlippWebApplicationInitializer implements WebApplicationInitializer {
public class SlippWebApplicationInitializer implements WebApplicationInitializer {
private static final Logger log = LoggerFactory.getLogger(SlippWebApplicationInitializer.class);

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
BeanFactory beanFactory = new BeanFactory("slipp");
beanFactory.initialize();

DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.addHandlerMpping(new AnnotationHandlerMapping("slipp.controller"));
dispatcherServlet.addHandlerMpping(new AnnotationHandlerMapping(beanFactory.getControllers()));

dispatcherServlet.addHandlerAdapter(new HandlerExecutionHandlerAdapter());
dispatcherServlet.addHandlerAdapter(new ControllerHandlerAdapter());
Expand Down
9 changes: 7 additions & 2 deletions slipp/src/main/java/slipp/controller/ApiUserController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package slipp.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import nextstep.annotation.Inject;
import nextstep.mvc.JsonView;
import nextstep.mvc.ModelAndView;
import nextstep.stereotype.Controller;
Expand All @@ -19,12 +20,16 @@

@Controller
public class ApiUserController {
private static final Logger logger = LoggerFactory.getLogger( ApiUserController.class );
private static final Logger logger = LoggerFactory.getLogger(ApiUserController.class);

private ObjectMapper objectMapper = new ObjectMapper();

private UserDao userDao = UserDao.getInstance();
private final UserDao userDao;

@Inject
public ApiUserController(UserDao userDao) {
this.userDao = userDao;
}

@RequestMapping(value = "/api/users", method = RequestMethod.POST)
public ModelAndView create(HttpServletRequest request, HttpServletResponse response) throws Exception {
Expand Down
13 changes: 11 additions & 2 deletions slipp/src/main/java/slipp/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package slipp.controller;

import nextstep.annotation.Inject;
import nextstep.mvc.ModelAndView;
import nextstep.mvc.tobe.AbstractNewController;
import nextstep.stereotype.Controller;
Expand All @@ -10,6 +11,7 @@
import slipp.dao.UserDao;
import slipp.domain.User;
import slipp.dto.UserUpdatedDto;
import slipp.service.UserService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand All @@ -19,7 +21,14 @@
public class UserController extends AbstractNewController {
private static final Logger log = LoggerFactory.getLogger(UserController.class);

private UserDao userDao = UserDao.getInstance();
private final UserService userService;
private final UserDao userDao;

@Inject
public UserController(UserService userService, UserDao userDao) {
this.userService = userService;
this.userDao = userDao;
}

@RequestMapping(value = "/users", method = RequestMethod.GET)
public ModelAndView list(HttpServletRequest request, HttpServletResponse response) throws Exception {
Expand Down Expand Up @@ -50,7 +59,7 @@ public ModelAndView create(HttpServletRequest request, HttpServletResponse respo
User user = new User(request.getParameter("userId"), request.getParameter("password"),
request.getParameter("name"), request.getParameter("email"));
log.debug("User : {}", user);
userDao.insert(user);
userService.signUp(user);
return jspView("redirect:/");
}

Expand Down
Loading