Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ bin/

### Mac OS ###
.DS_Store\ntomcat.8080/

## tomcat
**/tomcat.8080
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ dependencies {
implementation("org.apache.tomcat.embed:tomcat-embed-core:8.5.42")
implementation("org.apache.tomcat.embed:tomcat-embed-jasper:8.5.42")

// 리플렉션 의존성 주입
implementation("org.reflections:reflections:0.10.2")

implementation(kotlin("stdlib-jdk8"))
}

Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/diy/app/Lecture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.diy.app;

import java.math.BigDecimal;

public class Lecture {

private Long id;
private String name;
private BigDecimal price;

public Lecture() {
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public BigDecimal getPrice() {
return price;
}

public void setId(Long id) {
this.id = id;
}
}
23 changes: 23 additions & 0 deletions src/main/java/com/diy/app/LectureApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.diy.app;

import com.diy.framework.context.ApplicationContext;
import com.diy.framework.web.mvc.Controller;
import com.diy.framework.web.server.TomcatWebServer;
import com.diy.framework.web.servlet.DispatcherServlet;

import java.util.HashMap;
import java.util.Map;

public class LectureApplication {
public static void main(String[] args) {
final Map<String, Controller> controllerMapping = new HashMap<>();
controllerMapping.put("/lectures", new LectureController());

final ApplicationContext ac = new ApplicationContext(LectureApplication.class.getPackageName());
ac.initialize();

final DispatcherServlet servlet = new DispatcherServlet(controllerMapping);
final TomcatWebServer tomcatWebServer = new TomcatWebServer(servlet);
tomcatWebServer.start();
}
}
52 changes: 52 additions & 0 deletions src/main/java/com/diy/app/LectureController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.diy.app;

import com.diy.framework.web.mvc.Controller;
import com.diy.framework.web.mvc.view.ModelAndView;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class LectureController implements Controller {

private final Map<Long, Lecture> lectureRepository = new HashMap<>();

@Override
public ModelAndView handleRequest(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
if ("POST".equals(request.getMethod())) {
return doPost(request, response);
} else if ("GET".equals(request.getMethod())) {
return doGet(request, response);
}

throw new RuntimeException("404 Not Found");
}

private ModelAndView doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
final byte[] bodyBytes = req.getInputStream().readAllBytes();
final String body = new String(bodyBytes, StandardCharsets.UTF_8);

final Lecture lecture = new ObjectMapper().readValue(body, Lecture.class);

final long id = lectureRepository.size();
lectureRepository.put(id, lecture);
lecture.setId(id);

return new ModelAndView("redirect:/lectures");
}

private ModelAndView doGet(final HttpServletRequest req, final HttpServletResponse resp) throws Exception {
final Collection<Lecture> lectures = lectureRepository.values();
final Map<String, Object> model = new HashMap<>();
final Object lectureModels = lectures.stream().map(lecture -> Map.of("id", lecture.getId(), "name", lecture.getName(), "price", lecture.getPrice())).toList();
model.put("lectures", lectureModels);

return new ModelAndView("lecture-list", model);
}
}
6 changes: 0 additions & 6 deletions src/main/java/com/diy/app/Main.java

This file was deleted.

59 changes: 59 additions & 0 deletions src/main/java/com/diy/framework/beans/factory/BeanScanner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.diy.framework.beans.factory;

import com.diy.framework.context.annotation.Bean;
import org.reflections.Reflections;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static java.beans.Introspector.decapitalize;

public class BeanScanner {

private final Reflections reflections;

public BeanScanner(final String... basePackages) {
reflections = new Reflections(basePackages);
}

public Set<Class<?>> scanClassesTypeAnnotatedWith(final Class<? extends Annotation> annotation) {
return reflections.getTypesAnnotatedWith(annotation)
.stream()
.filter(type -> (!type.isAnnotation() && !type.isInterface()))
.collect(Collectors.toSet());
}

/**
* @Bean 어노테이션의 문자열 value 값으로 빈 이름-인스턴스 매핑
* */
public Map<String, Class<?>> scanBeansTypeAnnotatedWithName() {
Map<String, Class<?>> container = new HashMap<>();
Set<Class<?>> beanClasses = scanClassesTypeAnnotatedWith(Bean.class);

for(Class<?> clazz: beanClasses) {
try {
Bean bean = clazz.getAnnotation(Bean.class);
// Bean 어노테이션에 등록된 value값 추출
String beanName = bean.value();

// 빈 이름이 없으면 클래스 기본명을 소문자화하여 저장
if(beanName.isBlank()) {
beanName = decapitalize(clazz.getSimpleName());
}

// 중복 빈 이름 예외처리하기
if(container.containsKey(beanName)){
throw new IllegalArgumentException(beanName + "빈이 이미 등록되어 있습니다!");
}

container.put(beanName, clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return container;
}
}
106 changes: 106 additions & 0 deletions src/main/java/com/diy/framework/context/ApplicationContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.diy.framework.context;

import com.diy.framework.beans.factory.BeanScanner;
import com.diy.framework.context.annotation.Autowired;
import com.diy.framework.context.annotation.Bean;
import com.diy.framework.context.annotation.Component;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ApplicationContext {

private final String basePackage;
private final Set<Class<?>> beanClasses = new HashSet<>();
private final List<Object> beans = new ArrayList<>();

public ApplicationContext(final String basePackage) {
this.basePackage = basePackage;
}

public void initialize() {
final BeanScanner beanScanner = new BeanScanner(basePackage);
beanClasses.addAll(beanScanner.scanClassesTypeAnnotatedWith(Component.class));
beanClasses.addAll(beanScanner.scanBeansTypeAnnotatedWithName().values());

beanClasses.forEach(clazz -> {
if (isBeanInitialized(clazz)) {
return;
}

final Object bean = createInstance(clazz);
saveBean(bean);
});

// @Bean 어노테이션 코드도 저장
beanClasses.forEach(clazz -> {});
}

private Object createInstance(final Class<?> clazz) {
final Constructor<?> constructor = findConstructor(clazz);

try {
constructor.setAccessible(true);
final Object[] parameters = getConstructorParameters(constructor);

return constructor.newInstance(parameters);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
constructor.setAccessible(false);
}
}

private Constructor<?> findConstructor(final Class<?> clazz) {
final Constructor<?>[] constructors = clazz.getDeclaredConstructors();

if (constructors.length == 1) {
return constructors[0];
}

return findAutowiredConstructor(constructors);
}

private Constructor<?> findAutowiredConstructor(final Constructor<?>[] constructors) {
final Constructor<?>[] autowiredConstructors = Arrays.stream(constructors)
.filter(constructor -> constructor.isAnnotationPresent(Autowired.class))
.toArray(Constructor[]::new);

if (autowiredConstructors.length != 1) {
throw new RuntimeException("Autowired constructor not found");
}

return autowiredConstructors[0];
}

private Object[] getConstructorParameters(final Constructor<?> constructor) {
final List<Class<?>> parameterTypes = Arrays.stream(constructor.getParameterTypes()).toList();

if (!beanClasses.containsAll(parameterTypes)) {
throw new RuntimeException("parameter is not bean");
}

return parameterTypes.stream().map(parameterType -> {
if (isBeanInitialized(parameterType)) {
return beans.stream().findFirst().get();
}

final Object bean = createInstance(parameterType);
saveBean(bean);

return bean;
}).toArray();
}

private boolean isBeanInitialized(final Class<?> parameterType) {
return beans.stream().anyMatch(bean -> bean.getClass().equals(parameterType));
}

private void saveBean(final Object bean) {
beans.add(bean);
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/diy/framework/context/annotation/Autowired.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.diy.framework.context.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 {
}
10 changes: 10 additions & 0 deletions src/main/java/com/diy/framework/context/annotation/Bean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.diy.framework.context.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
String value() default "";
}
11 changes: 11 additions & 0 deletions src/main/java/com/diy/framework/context/annotation/Component.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.diy.framework.context.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 {
}
11 changes: 11 additions & 0 deletions src/main/java/com/diy/framework/web/mvc/Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.diy.framework.web.mvc;

import com.diy.framework.web.mvc.view.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@FunctionalInterface
public interface Controller {
ModelAndView handleRequest(final HttpServletRequest request, final HttpServletResponse response) throws Exception;
}
Loading