현실적인 개발자 로드맵(7) - Spring Boot으로 Spring을 더욱 편리하게..

# 포스팅에서 사용된 코드의 전체 코드는 https://github.com/ITVillage-Kevin/roadmap-springboot 에서 다운로드 받으실 수 있습니다.
 
# 해당 포스팅은 IT Village 유튜브 채널(https://www.youtube.com/channel/UCSIvsntWA8aJ3Apoc7kTxig)에서도 시청하실 수 있습니다.
 
 
 
안녕하세요, 오랜만에 찾아뵙게 되었네요. Kevin이라고 합니다. ^^
 
거의 석 달만에 올리는 글이 되겠습니다. 저희 아기가 탄생하시는 바램에 석 달 동안 참 바빴고 여러가지 많은 일이 있었더랬습니다. 새 생명이 탄생하는 경이로움을 경험했다고나 할까요. 아무튼 직장 다니랴 아기 돌보느라 이래 저래 바빠서 포스팅 한다는것은 꿈에도 생각하지 못했네요. 다만 짬짬이 유튜브 강의 영상만 녹화 하면서 간간히 소식을 전해드렸는데요. 포스팅 한 내용만큼 유튜브 영상 업로드가 끝나서 다시 포스팅을 진행하게 되었네요. Term이 조금 길긴했지만 그래도 이 다음 이야기를 궁금해 하실 초보 개발자분들이 있을거라고 생각하기에 다시 이야기를 진행해보도록 하겠습니다.
 
지난 시간에는 Spring 프레임워크에 대한 얘기를 나누어보았는데요. 앞서 보셨듯이 Spring은 그야말로 자바 개발자들에게는 없어서는 안될 그런 대세가 되어버렸습니다. 하지만 그 대단한 Spring 프레임워크에도 개발자들이 아주 난색을 표하는 단점이 하나 있었는데요. 그것은 바로 Spring 프레임워크를 사용하기 위한 설정의 어려움이었습니다. 프레임워크라는 편리한 도구를 사용해서 비즈니스 로직의 개발에 집중을 해야될 판에 정작 Spring Framework를 사용하기 위한 설정에 개발자들이 꽤나 상당한 시간을 투자해야하는 아이러니한 상황이 발생하게 되었던 것입니다.
 
물론 Spring이 버전업되면서 이런 문제들이 개선이 되긴했지만 여전히 설정의 복잡함은 남아 있었는데요. 
 
이러한 문제를 해결하기 위해서 등장한것이 바로 Spring Boot 이라는 놈입니다. 이 Spring Boot이 등장함으로 인해서 개발자들은 더이상 Spring의 설정을 위해 머리 싸매면서 고민하던 시간을 접고 비즈니스 로직을 구현하는데 집중할 수 있게 해주었습니다. 
 
그럼, Spring Boot 이라는 놈은 어떤 놈인지 지금부터 한번 살펴 볼까요?
 
우리가 Spring을 사용하여 웹 애플리케이션을 만들기위해서는 어떤 것들이 필요할까요? 
우선 메이븐이나 그레이들을 사용하는 프로젝트 구조를 사용하기 위해서 Spring에 필요한 의존 관계 라이브러리들을 지정해야합니다.
그리고 DispatcherServlet 설정을 위한 web.xml 설정도 해야하구요.
또한 Spring MVC를 사용하기 위해서는 핸들러 매핑이나 핸들러 어댑터, 뷰 리졸버 등의 설정을 최소한 해주어야합니다.
마지막으로 웹 애플리케이션을 배포하기 위한 Tomcat 등의 웹 애플리케이션 서버가 필요하겠죠.
 
이러한 준비 작업들이 끝나고나면 자 드디어 컨트롤러 클래스를 작성하면서 개발을 시작하면 되겠습니다.
자, 어떻게 된게 개발을 시작하기도 전에 해야할 일이 너무 많다는 생각이 들지 않으신가요? ^^; 개발자가 개발에 집중해야함에도 불구하고 개발을 하기 위한 사전 작업을 너무나 많이 하게되는 셈이 되는데요.
 
Spring Boot의 자동 구성과 스타터 의존성이라는 방법으로 이러한 문제들을 해결 해주게되었습니다. 짝!짝!짝! 자 그럼 Spring Boot의 자동 구성과 스타터 의존성이 무엇인지 잠깐 알아볼까요?
 

자동 구성

우선 Spring Boot은 Tomcat을 내장하고 있기때문에 별도의 웹 애플리케이션 서버를 설치할 필요가 없습니다. 내장된 Tomcat에 빠르게 웹 애플리케이션을 배포할 수 있습니다. 
그리고 자동 구성 덕택에 DispatcherServlet이나 핸들러 매핑, 핸들러 어댑터 등의 Spring MVC 설정도 따로 필요가 없습니다. Spring Boot이 애플리케이션의 클래스 패스에서 관련 클래스들을 발견하고는 알아서 척척 구성을 해주는 것이죠. 또한 H2 같은 특정 데이터베이스 라이브러리를 발견한다면 해당 라이브러리를 사용하기위한 데이터베이스 설정을 자동으로 구성합니다. JPA를 사용하고 싶다면? 네, 맞습니다. Spring Boot이 JPA를 사용하기 위한 설정을 역시 자동으로 구성해줍니다. 웹 애플리케이션의 보안 설정을 하고 싶다면? 네, 역시 Spring Boot이 Spring Security를 사용하기 위한 설정을 자동으로 구성해줍니다. 
Spring Boot을 통해 Spring이 더 똑똑해졌다는 생각이 드시죠?
 

스타터 의존성

하나의 애플리케이션을 만들기 위해서 의존 관계에 있는 라이브러리들을 정확하게 추가하는일은 쉽지가 않습니다. JSON 형식의 응답 데이터를 반환하는 Spring MVC 애플리케이션을 만들어서 내장된 Tomcat으로 애플리케이션을 실행해야 한다면 대략 다음과 같은 라이브러리들을 직접 메이븐이나 그레이들 설정에 추가해주어야 합니다.
spring-core, spring-web, spring-webmvc, jackson-databind, tomcat-embed-core, tomcat-embed-el
하지만 스타터 의존성을 이용하면 개발자가 이런 의존관계에 있는 라이브러리들을 일일이 추가할 필요가 없습니다. 단순히 spring-boot-starter-web 스타터를 추가해주면 알아서 의존 위에 열거한 라이브러리들을 자동으로 추가해줍니다. 개발자들이 어떤 스타터를 추가해야 되는지도 직관적으로 알 수 있습니다. 
웹 애플리케이션을 개발하기 위해서는 spring-boot-starter-web 스타터를 추가하면 되고, JPA 영속성을 사용하려면 spring-boot-starter-jpa를, 애플리케이션 보안이 필요하다면 spring-boot-starter-security를 추가하면 됩니다.
 
말로 설명하면 Spring Boot의 장점이 직접적으로 와닿지 않을수도 있으니 코드를 한번 살펴보도록 하겠습니다.
 
지난 시간의 Spring MVC 프로젝트의 구조에서는 설정해야되는 부분이 엄청 많았습니다. 아래 이미지를 보시죠.

[전통적인 Spring MVC 설정]

스프링 MVC 프로젝트에 필요한 라이브러리들을 설정해놓은 pom.xml, 웹 애플리케이션 개발을 위한 dispatcher-servlet.xml과 web.xml 설정을 개발자가 직접 해주어야 했습니다.

 

물론 Spring Boot에서도 설정을 하긴합니다. 아래와 같이 말이죠.

 

[Spring Boot의 설정]

build.gradle 파일에서 Spring MVC 웹 애플리케이션 설정을 위해서 dependencies 항목에 필요한 라이브러리들을 설정하는데 단 5줄밖에 되지 않습니다. web.xml이나 dispatcher-servlet.xml 같은 설정은 아예 사라져버린것이죠.

이게 끝입니다. 더 이상 설정할게 없으며, 이 5줄만으로 기존의 Spring MVC 웹 애플리케이션에 JPA와 H2 데이터베이스 기능까지 포함한 설정이 자동으로 구성이 되는것입니다. 엄청나지 않나요? 저만 그렇게 느끼는건 아닌지.ㅎ ^^;

 

그리고 추가적으로 기존의 Spring MVC 기반의 Todo 애플리케이션을 Spring Boot 기반으로 변경하면서 구현 코드는 어떻게 변경이 되었는지 잠깐 살펴보도록 하겠습니다.

 

이번 Spring Boot 기반 Todo 애플리케이션에는 할일 목록을 DB에 저장하기 위한 기술로 JPA(Java Persistence API)를 사용하였습니다. JPA는 데이터베이스의 테이블과 자바의 Entity 객체를 매핑하여 데이터를 손쉽게 DB에 저장하는 ORM(Object Relational Mapping) 기술중에 하나로써 Spring을 사용하여 JPA를 손쉽게 사용할 수 있습니다.

아래 코드를 잠깐 보시겠습니다.

==== Todo.java ====

DB의 todo 테이블과 매핑되는 Todo 엔티티 클래스인데요. @Entity 애노테이션을 붙여서 엔티티 클래스라는것을 명시했고, @Id 애노테이션을 붙여서 기본키를 설정했습니다. 그리고 10번 라인에 @Data 애노테이션을 추가해서 Todo 클래스의 멤버 변수들에 대한 getter/setter 메서드를 간편하게 추가하였습니다. 

참고로 Lombok이라는 라이브러리를 사용하여 클래스의 생성자, toString(), getter/setter 등의 다양한 설정을 간편하게 할 수 있기때문에 Lombok은 개발자들이 많이 사용하는 라이브러리중의 하나입니다.

 

==== TodoRepository.java ====

이 클래스는 Todo 클래스의 객체를 DB에 저장해주는 기능을 하는 Repository 클래스입니다. JpaRepository를 상속받고 있는 인터페이스인데요. 구현된게 아무것도 없음에도 불구하고 할일을 저장해주고 조회해주는 CRUD 기능을 모두 다 할 수 있습니다.

 

마지막으로 할일에 대한 요청을 받아들이는 TodoController 클래스를 한번 보실까요?

==== TodoController.java ====

이전 포스팅에서는 @RequestParam 애노테이션을 사용하여 할일 데이터를 register()의 파라미터로 받았는데 이번에는 todoName과 todoDate 파라미터의 값이 Todo 객체에 바로 매핑이 되도록 한 후, TodoRepository 클래스의 save()를 사용하여 DB에 저장하는 로직으로 변경되었습니다.

28번 라인에서는 TodoRepository 클래스의 findAll()을 사용하여 저장된 모든 할일 데이터를 조회하도록 변경되었습니다.

실제 DB에 저장을 하는 로직으로 변경되었지만 여전히 소스코드가 필요한 비즈니스 로직만 포함하는 깔끔함을 유지하는것을 보실 수 있습니다.

 

자, 간단하게나마 Spring Boot의 편리함에 대해서 살펴보았는데요. Spring Boot이 개발자가 비즈니스 로직 개발에 집중하도록 얼마나 많은 노력을 하였는지 여전히 현실적으로 와닿지 않는 분들께서는 저처럼 Spring Boot을 사용하지 않고 Spring MVC 애플리케이션 구성을 해보신다음 Spring Boot으로 개선해보는 작업을 한번쯤 해보셨으면 하는 바램을 가져보겠습니다.

 

자, 그럼 이번 시간에 새롭게 추출해야 될 기술들에는 무엇이 있는지 살펴보겠습니다.

[Spring Boot이 추가된 로드맵]

개발자 로드맵에 새롭게 추가된 기술입니다. ^^ Spring Boot은 당연히 추가가 되었구요. 그 외에 JPA와 H2를 추가하였는데요. 

JPA는 데이터베이스에 데이터를 추가하는 대표적인 ORM 기술로써 괜찮은 회사(여기서 괜찮은 회사라함은 기술 트렌드를 적극적으로 받아들이는 회사로 칭하겠습니다.^^;)에서 일을 하고 싶다면 꼭 알아두셨으면 하는 기술중에 하나입니다.

H2는 그 자체로는 단순히 데이터베이스의 한 종류입니다. 일반적인 데이터베이스와의 차이점이라면 In memory DB 즉, 애플리케이션 실행을 종료하면 저장된 데이터는 사라지는 내장 DB라는것입니다. 제가 말씀드리고 싶은것은 H2 자체를 배우라기보다는 In memory DB를 활용할 줄 알아야한다는 것입니다. 나중에 Spring으로 개발을 진행하다보면 단위 테스트 코드를 짜야 되는 경우가 많은데 이 경우에 H2 같은 In memory DB를 활용하면 편리하게 단위 테스트 코드를 DB와 연계할 수 있습니다. 

 

자, 이번 시간에는 Java 개발자(웹 개발자) 로드맵에 Spring Boot을 추가를 해보았는데요. 다음 시간에는 '현실적인 개발자 로드맵 시리즈'의 마지막 편을 포스팅 해보도록 하겠습니다. 시리즈로 계속해서 포스팅을 할 수 있지만 다른 기술들에 대해서 구체적인 얘기들을 많이 해야되겠기에 어쨌든 다음 시간에 '현실적인 개발자 로드맵 시리즈'를 마무리 하겠습니다. ^^;

 

오늘이 2019년의 첫날인데 다들 새해에 목표하신바를 잘 이룰 수 있도록 좋은 출발하시기를 바래보겠습니다. 저도 올 한해 열심히 열심히 살아보도록 하겠습니다.

그럼 다음 시간에 다시 뵐게요~ 새해 복 많이 받으세요!(Happy New Year!)

 

[Kevin의 알기 쉬운 Java 로드맵 이야기] 인프런 강의 바로 가기

 

 

 

 

현실적인 개발자 로드맵(6) - Spring의 탄생

# 포스팅에서 사용된 코드의 전체 코드는 https://github.com/ITVillage-Kevin/roadmap-spring-mvc 에서 다운로드 받으실 수 있습니다.

# 해당 포스팅은 IT Village 유튜브 채널(https://www.youtube.com/channel/UCSIvsntWA8aJ3Apoc7kTxig)에서도 시청하실 수 있습니다.


안녕하세요, Kevin입니다. 

지난 시간까지 Ajax와 jQuery가 등장함으로 인해서 웹 애플리케이션 개발이 Front-end와 Back-end로 나누어 지기 시작했다고 말씀을 드렸는데요. 지난 시간에 Front-end쪽을 잠깐 살펴보았으니 이번 시간에는 Servlet에서 멈춰 있는 Back-end 쪽의 기술의 변화를 다시 살펴 보도록 하겠습니다.

Servlet을 사용하여 Back-end쪽을 불편하고 복잡하게 개발을 하던 시절의 무렵, 혜성 같이 등장한 구원투수가 있었으니 그것은 바로 Spring이라는 놈이었습니다. 마치 야근을 밥 먹듯이 하는 개발자들에게 봄날이 오길 바라는 마음에서 였을까 아무튼 자바 개발자들에게 Spring은 봄(spring)이 오듯이 찾아왔습니다. ^^;

Java 웹 개발의 이전과 이후는 Spring을 기준으로 나뉘어 진다고해도 과언이 아니라고 볼 수 있습니다. 마치 예능 프로그램이 무한도전 이전과 이후로 나뉜다고 볼 수 있는것 처럼 말이죠. ^^

아무튼 Spring이라는 프레임워크가 등장한 이후로 웹 애플리케이션을 개발에 있어서 커다란 변화가 생겼음은 Java 개발자라면 누구나 부정할 수 없을것이라고 봅니다. 

Spring 1.0 버전이 2004년에 나온것으로 알고 있는데 저는 개인적으로 2009년에 Spring 2.5를 처음 접하게 되었습니다.

2018년 10월 현재 Spring 5 버전이 나오면서 Spring 프레임워크의 기술에도 많은 변화가 있었으나 IOC, DI, AOP 같은 Spring의 근본 기술들은 Spring의 핵심 기술로 변함없이 자리를 지키고 있습니다.

이 포스팅은 Spring에 대한 구체적인 사용방법 등에 대해서 이야기를 하는것이 아니기때문에 Spring에 대한 이야기들은 추후에 다시 포스팅할 수 있는 기회가 있었으면 좋겠습니다.

Java 개발자 로드맵에 이미 Spring이 추가가 될 것이라는 결론은 난 셈이 되지만 그래도 이대로 글을 마치게되면 너무 싱거우니 여태까지 해온대로 이전 포스팅에서 Servlet 기반으로 구현했던 TODO 애플리케이션을 Spring 프레임워크를 사용하여 개선을 해보도록 하겠습니다. Servlet 기반의 TODO 애플리케이션 구현에 대한 상세한 내용은 아래 포스팅을 참고해주세요.

2018/09/07 - [현실적인 개발자 로드맵] - 현실적인 개발자 로드맵(3) - JSP 모델2


자, 그럼 결론적으로 Servlet으로 구현한 TODO 애플리케이션의 Servlet 코드와 Spring으로 구현한 Spring Controller의 코드를 비교해서 잠깐 살펴보겠습니다. 아래는 TodoAjaxServlet의 코드입니다.

==== ToDoAjaxServlet.java ====

package com.itvillage.servlet;

import com.google.gson.Gson;
import com.itvillage.vo.ToDo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@WebServlet(name = "ToDoAjaxServlet", value = "/todoAjax")
public class ToDoAjaxServlet extends HttpServlet {
// Database를 대신한다.
private List<ToDo> todoList;

@Override
public void init() throws ServletException {
super.init();
this.todoList = new ArrayList<>();
}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");

String todoName = request.getParameter("todoName");
String todoDate = request.getParameter("todoDate");

todoList.add(new ToDo(todoName, todoDate));

Gson gson = new Gson();
String json = gson.toJson(todoList);

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
response.getWriter().write(json);

}
}


다음은 Spring으로 구현한 ToDoController입니다.

==== ToDoController.java ====

package com.itvillage;

import com.itvillage.vo.ToDo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;

@Controller
public class ToDoController {
@RequestMapping(value = "/todoAjax", method = RequestMethod.POST)
@ResponseBody
public List<ToDo> todoAjax(@RequestParam("todoName")String todoName,
@RequestParam("todoDate")String todoDate) {
ToDo.todoList.add(new ToDo(todoName, todoDate));
return ToDo.todoList;
}
}


ToDoAjaxServlet 클래스의 doPost() 메서드와 ToDoController 클래스의 todoAjax() 메서드는 클라이언트측으로부터 Ajax 요청을 처리하는 메서드인데요. 일단 눈으로 보이는 소스 코드의 길이만 봐도 ToDoController의 todoAjax() 메서드가 확연히 짧고 간결한것이 보이시죠?

ToDoAjaxSevlet의 doPost()에서는 캐릭터 셋 설정이나 JSON으로의 포맷팅 설정, response의 설정까지 직접 해주어야 했으나 ToDoController의 todoAjax() 메서드에는 그런 설정들이 전혀 없습니다. 

Spring 설정파일에 별도의 설정을 하긴 하지만 구현 코드상에서는 모두 제거되어 Servlet 보다 실제로 깔끔한 소스 코드를 유지하는것이 가능해졌습니다.

사실 왜 Spring을 사용하여야 되는지에 대해서 구체적으로 설명하려면 저는 내일 출근을 못합니다.^^; 

Spring에 대해서는 다음에 더 많은 얘기를 나눌 수 있는 기회가 있을것이라고 생각을 하고, 우선 지금은 이것 하나만 기억하시면 되겠습니다.

Spring은 Java 개발자가 되기 위해서 반드시 알아야되는 핵심 기술중에 하나이다 라는것을 말이죠.


자, 이미 앞에서 이미 다 결론이 난 상태이지만 그래도 로드맵을 정리하는 차원에서 다시 한번 로드맵을 그려보면 다음과 같습니다.

[Spring 프레임워크가 추가된 개발자 로드맵]


짠~ 예상했던대로 Spring 프레임워크가 개발자 로드맵에 추가가 되었습니다.^^;


Spring에 대해서는 몇십번을 강조해도 지나치지 않을만큼 Java 개발자가 되기위해서 필수적으로 알아야 되는 기술입니다. 

더 정확하게 말하자면 Spring을 몰라도 당연히 Java 개발자가 될 수 있지만 조금 더 스마트하고 미래지향적인 Java 개발자가 되기위해서는 배워야 될 기술입니다.


이렇게 Spring에 대한 칭찬만 하다가 끝나도 사실 상관은 없지만 그래도 Spring에 대한 단점에 대해서 언급은 하고 가는것이 맞는거 같아 제가 Spring을 처음 접하면서 겪었던 불편함을 잠시 말씀드리겠습니다.

처음 Spring을 접하면서 가장 불편했던점은 바로 Spring의 개발 환경을 구축하는것이었습니다. 

Spring의 기술을 사용하기 위해서 해야되는 설정이 생각보다 복잡했고, Spring에 대한 근본 지식이 없는 상태에서 설정을 하다가 에러를 만나게 되면 해결하는것이 그리 만만치 않았다는 것입니다.

이번 포스팅을 위해서 오랜만에 Spring 개발 환경 설정을 해보았는데 역시나 설정하는데 오래 걸리더군요. 보통 밤 12시 전에 잠을 자는데 설정하고 정상적으로 동작하는지 확인하느라 12시를 넘겨버리고 말았습니다. ^^;


그럼 Spring 프레임워크를 사용하여 개발하는데 있어서 어떤 설정을 해주어야 하는지에 대해서 간단하게만 말씀 드리고 이번 포스팅을 마치도록 하겠습니다.

==== dispatcher-servlet.xml ===



    
        
            
        
    
    


    
        
        
    

dispatcher-servlet.xml에는 웹 프리젠테이션 계층의 빈(Bean)을 등록하는 설정 파일입니다. ToDo 애플리케이션의 경우 샘플 애플리케이션이다 보니 많은 빈(Bean)이 등록되지 않았으나 애플리케이션의 기능이 늘어날수록 빈 설정의 수는 늘어날 것입니다. 현재는 응답 데이터를 JSON으로 변환해주는 Converter와 JSP 뷰를 리졸빙해주는 Resolver만 설정되어 있습니다.

==== web.xml ====



    
        contextConfigLocation
        /WEB-INF/spring-config/applicationContext.xml
    
    
        org.springframework.web.context.ContextLoaderListener
    
    
        dispatcher
        org.springframework.web.servlet.DispatcherServlet
        
            contextConfigLocation
            /WEB-INF/spring-config/dispatcher-servlet.xml
        
        1
    
    
        dispatcher
        /
    
    
        CORSFilter
        com.itvillage.filter.CORSFilter
    
    
        CORSFilter
        /*
    


Servlet 사용 시 필요한 web.xml 파일에 Spring 연동을 위한 설정들이 추가로 포함되었습니다. ContextLoaderListener를 통해 Root Web Application Context를 등록하고, 앞서 보았던 dispatcher-servlet.xml 파일의 경로를 추가함으로써 Servlet Web Application Context를 등록하였습니다. 그리고 추가적으로 CORS(Cross Origin Resource Sharing) 문제를 해결하기 위해서 Filter 설정이 포함되었습니다. 

이 포스팅에서는 이런 설정들이 정확하게 무엇을 하는지 몰라도 상관없습니다. 이런 설정들을 해야지만 Spring을 사용할 수 있다라는 정도만 알고 계시면 될것 같습니다.


설정이 간단해 보이지만 직접 한번 해보시면 다양한 에러를 만나실 수 도 있을것입니다. ^^; 제 경우는 Spring 설정 + IDE 설정 + CORS 문제 등등으로 시간이 좀 걸렸습니다.


아무튼 Spring의 이런 설정상의 불편함때문에 개발자들이 정작 핵심 개발에 집중을 하지 못하고 Spring 설정에서부터 벽에 부딪치는 경우도 종종 있다고 알고 있는데요. 아시는 분들은 아시겠지만 이런 불편함이 추후에 개선이 되었더랬죠.

이 부분에 대해서는 다음 시간에 말씀을 드리도록하고, 오늘은 이만 여기서 포스팅을 마무리해야겠습니다. 그럼 다음 시간에 다시 찾아뵐게요~


[Kevin의 알기 쉬운 Java 개발자 로드맵 이야기] 인프런 강의 바로가기


+ Recent posts

출처: http://large.tistory.com/23 [Large]