MVC 란?

mvc는 model-view-controller의 약자로 소프트웨어 공학에서 어플리케이션을 구성하는 패턴(pattern) 중 하나이다.

eclipse 에서 Spring MVC 기본 예제 작성

먼저 New -> Other -> Spring Legacy Project -> Spring MVC Project 로 프로젝트를 생성한다.


패키지 이름을 설정한다. com.bbo.[]에서 []에 해당하는 부분이 어플리케이션을 구분하는 Context가 된다.

기본 예제를 통해 MVC의 동작 방식을 살펴보도록 하자.


Spring에서 MVC의 동작 방식과 코드 살펴보기

우선 다음의 사진을 보도록 하자.


(1) 우선 유저로부터의 HTTP Request는 DispatcherServlet이 담당한다.

아래 코드는 web.xml에서 DispatcherServlet을 매핑하는 코드이다. 요청된 url로 / 와 매칭이 되면 appServlet 이라는 이름의 DispatcherServlet으로 넘겨준다.

// web.xml
 
<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

(2) 넘겨받은 DispatcherServlet은 요청된 URL을 HandlerMapping 오브젝트에 넘기고 호출 대상의 Controller를 얻어 URL에 해당하는 메서드를 실행한다.

아래 코드는 HandlerMapping을 동작하게 하는 코드이다.

/WEB-INF/spring/appServlet/servlet-context.xml 을 이용하여 필요한 컨트롤러를 찾는다.

// web.xml
 
<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

com.bbo.myapp 이라는 패키지에서 컨트롤러를 스캔하는 것을 의미한다.

// servlet-context.xml
 
<context:component-scan base-package="com.bbo.myapp" />

(3) Controller 오브젝트는 비즈니스 로직을 처리하고 뷰에 전달할 오브젝트를 Model 오브젝트에 저장한다. 끝으로 Controller 오브젝트는 처리 결과에 맞는 View 이름을 반환한다.

아래 예제는 http://localhost:8181/myapp 이라고 HTTP Request 가 오면 value="/"이므로 아래 로직을 수행하게 된다.(처음에 패키지 이름 설정시 com.bbo.myapp라면 myapp이 어플리케이션을 구분하는 context가 되므로 myapp까지 root url 이 된다.)

model.addAttribute 를 이용하여 serverTime이라는 Key 에 formattedDate Value를 설정하여 Model 오브젝트에 저장한다.

마지막으로 home 이라는 View name 을 return 한다.

// HomeController.java
 
@RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(Locale locale, Model model) {
        logger.info("Welcome home! The client locale is {}.", locale);
 
        Date date = new Date();
        DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
 
        String formattedDate = dateFormat.format(date);
 
        model.addAttribute("serverTime", formattedDate );
 
        return "home";
    }

(4) ViewResolver 로부터 해당 View 오브젝트를 얻는다.

예시를 통해 View의 path는 아래 정의된 prefix + "home" + suffix 가 된다.

// servlet-context.xml
 
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/" />
    <beans:property name="suffix" value=".jsp" />
</beans:bean>

(5) View 오브젝트에 화면 표시를 Request 한다.

(6) View는 Model 오브젝트로부터 화면 표시에 필요한 오브젝트를 가져와 화면 처리를 실행한다.

정리

eclipse를 통해 spring MVC 프로젝트를 생성하고 기본 예제를 통해 기본적인 동작 방식에 대해 알아보았다.

DispatcherServlet, HandlerMapping, ViewResolver, Model, View 오브젝트는 Spring MVC가 제공해주는 오브젝트이다.

이것저것 구조에 대해서 설명하기 위해 코드를 살펴보았지만 우리는 Controller와 해당 로직, 그리고 뷰를 코딩하면 된다.

'Framework > Spring' 카테고리의 다른 글

Bean의 Scope 지정  (0) 2017.12.28
bean 파일을 이용한 AOP 적용  (0) 2017.12.28
Environment를 이용한 bean 설정  (0) 2017.12.27
Bean 파일을 이용한 DI  (0) 2017.12.27

Scope란?

Scope란 프로그래밍 할 때 가장 중요한 개념이라고 생각된다.

Scope란 변수 혹은 객체가 존재가능한 유효 범위를 의미한다.

이번 포스팅에서는 Spring에서의 Bean 객체의 scope에 대해 알아볼 것이다.

Bean의 범위

우선 Bean의 Scope는 bean 속성값에 따라서 다르다.

속성설명
singleton(Default) 하나의 bean 정의가 DI 컨테이너마다 하나의 객체를 유지한다.
prototype하나의 bean 정의가 다수의 객체 인스턴스 범위를 가진다.
request하나의 bean 정의가 하나의 HTTP 요청이 들어올때 생성되고 완료시 소멸된다.
session하나의 bean 정의가 HTTP session의 라이프 사이클의 범위를 가지며, ApplicationContext의 컨텍스트에서만 유효하다.
global session하나의 빈 정의가 전역적인 HTTP session의 라이프 사이클의 범위를 가진다. ApplicationContext의 컨텍스트에서만 유효하다.

이번 포스팅에서는 singleton과 prototype에 대해서만 다뤄보고자 한다.

singleton

scope 속성을 지정하지 않으면 기본적으로 singleton 범위가 적용된다.

<bean id="person" class="com.bbo.example.Person">
    <constructor-arg>
        <value>bbo</value>
    </constructor-arg>
    <constructor-arg>
        <value>23</value>
    </constructor-arg>
</bean>

아래 예제는 singleton의 범위를 가지는 person이라는 빈 객체를 가져와서 수정 전과 수정 후의 값을 비교해본다.

public static void main(String[] args) {
    AbstractApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationCTX.xml");
 
    Person person1 = ctx.getBean("person", Person.class);
    System.out.println(person1.getName());
    System.out.println(person1.getAge());
 
    Person person2 = ctx.getBean("person", Person.class);
    person2.setName("john");
    person2.setAge(22);
 
  // TEST 부분 - person2를 수정했는데 person1이 수정이 될까? 말까?
 
  System.out.println(person1.getName());
    System.out.println(person1.getAge());
}

결론적으로는 하나의 인스턴스만 유지하기 때문에 person1과 person2는 같은 객체이기 때문에 값이 변경된다.

prototype

scope를 prototype으로 값을 지정해보자.

<bean id="person" class="com.bbo.example.Person" scope="prototype">
    <constructor-arg>
        <value>bbo</value>
    </constructor-arg>
    <constructor-arg>
        <value>23</value>
    </constructor-arg>
</bean>

결론적으로는 singleton에서의 예제와는 달리 person1과 person2는 다른 객체이기 때문에 수정이 되지 않는다.

정리

bean의 범위는 scope 속성 값에 따라 달라진다. 이번 포스팅에서는 간략하게 singleton과 prototype 만을 다루었는데, request와 session에 대해서도 다루어보길 바란다.

참고 자료

Outsider's Dev story

AOP란?

AOP는 관점 지향 프로그래밍이다. 이 관점이라는 것에 대해 설명해보겠다.

OOP는 핵심단위를 클래스로 보아 객체로써 모듈화를 시키지만, AOP는 다양한 타입 혹은 객체에 걸친 트랜젝션을 모아서 관심으로 모듈화 시킨다.

AOP를 이용하면 공통 기능과 핵심 기능을 분리하여 공통된 처리를 제거할 수 있다.

즉, AOP란 핵심 코드를 바라본다라는 패러다임으로 이해하면 될 것 같다.

AOP는 언제 사용하는가?

우선 실제 업무 처리에는 사용하는 것은 좋지 않다. 가동적이 현저히 떨어지기 때문이다.

AOP로 분리해도 되는 처리는 로그 출력이나 트랜잭션 처리 등 공통화 할 수 있는 처리에 사용된다.

AOP의 용어 정리

이름설명
관점(Aspect)여러 클래스에 걸친 관심사의 모듈이다. 즉 공통 기능으로 볼 수 있다.
조인 포인트(Join Point)프로그램이 실행되는 중의 특정 지점이다.(특정 지점은 메서드의 실행 혹은 예외처리 부분이라 생각하면 된다. 즉, 핵심 기능 하나하나를 의미한다.)
어드바이스(Advice)Aspect의 기능으로(공통 기능) 특정 조인포인트에서 관점이 취하는 행동을 나타낸다.
포인트컷(Point cut)Join Point의 부분으로 실제 Advice가 적용되는 부분이다. (Advice는 point cut의 expression과 매칭되고, point cut이 매칭한 join point에서 실행된다.)
AOP Proxy클라이언트의 요청을 받아주어 처리한다.(target에 접근하는 방법을 제어할 때 사용된다.)
WeavingAdvice를 핵심 기능(point cut)에 적용하는 행위이다.

AOP 적용 방법

AOP 적용 방법에는 Bean 파일을 이용한 방법과 어토네이션을 이용한 방법이 있다. 이번 포스팅에서는 Bean 파일을 이용한 방법을 다루겠다.

아래 예제는 LogAop라는 클래스에서 로깅을 다룬다. 우선 bean 파일에 LogAop 클래스의 객체를 생성해준다.

// applicationCTX.xml
 
<bean id="logAop" class="com.bbo.ex.LogAop" />

다음 부분은 aop를 적용한다.

pointcut 을 적용하기 위해 expression을 지정해준다. 아래 예제는 com.bbo.ex 에 있는 모든 클래스의 함수 호출에 적용하겠다라는 것을 의미한다.

만약 특정 클래스로 한정 짓고 싶다면 expression의 범위를 수정하면 된다.

// applicationCTX.xml
 
<aop:config>
    <aop:aspect id="logger" ref="logAop">
        <aop:pointcut id="publicM" expression="within(com.bbo.ex.*)"  />
 
      <!-- advice 설정 부분 -->
        <aop:around pointcut-ref="publicM" method="loggerAop" />
    </aop:aspect>
</aop:config>

위의 코드중 다음 코드는 advice와 pointcut을 연결하는 과정이다.

pointcut-ref를 이용해 pointcut으로 설정한 id를 참조하고 method 속성을 이용해 advice의 메소드를 LogAop의 loggerAop라는 함수로 지정한다.

<!-- advice 설정 부분 -->
<aop:around pointcut-ref="publicM" method="loggerAop" />

마지막으로 AOP를 적용할 클래스를 생성한다.

중요한 부분은 Advice method의 첫 파라미터는 반드시 ProceedingJoinPoint 타입이어야 한다. ProceedingJoinPoint의 proceed()를 호출하면 기반하는 메소드를 호출한다.

//LogAop.java
 
public class LogAop {
 
    public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable {
 
        try {
            Object obj = joinpoint.proceed();
            return obj;
        }
 
    }
 
}

정리

bean 파일을 이용한 AOP의 적용방법에 대해 알아보았다. 실제 적용할 때는 LogAop 클래스에 Log를 찍어볼 수 있다. 예를 들어 Person이라는 클래스를 만들고 내부적으로 멤버 함수를 호출할 때 pointcut의 expression 의 범위에 해당하는 클래스라면 AOP가 적용되어 loggerAop라는 함수를 거치게 될 것이다.

그리고 proceed() 함수 호출을 통해 기반하는 메소드가 호출될 것이다.

추가적으로 advice 타입에 대해 간략히 정리한다. 자세한 내용은 검색을 통해 알아보면 좋을 것 같다.

속성내용
aop:before메소드 실행 전(Join point 이전) advice를 실행
aop:after메소드 실행 중 exception이 발생 하여도 advice를 실행
aop:after-returning메소드가 정상적으로 실행 된 후에 advice를 실행
aop:around메서드 실행 전, 후, 예외 발생 등 모든 시점에서 advice를 실행
aop:after-throwing메소드가 실행 중 exception이 발생 시 advice 실행

참고 자료

I's Story

Outsider's Dev Story

Spring 4 입문(한빛미디어)

인프런 강좌(신입 프로그래머를 위한 자바 스프링 프레임워크 강좌)

Environment 객체란?

외부 설정 파일들을 가져와서 프로퍼티를 추가하거나 추출하는 역할을 한다.

Environment 객체를 사용하는 이유?

Environment를 이용하는 경우를 생각해보자. DB 이전을 할 경우 해당 정보는 자바 코드 상에 없을 때, 외부 파일에 정보를 담아 관리를 해야 할 경우이다.

Environment 사용 방법

ConfigurableApplicationContext 객체는 AbstractApplicationContext 클래스의 상위 클래스이다.

ConfigurableApplicationContext 객체에 GenericXmlApplicationContext() 를 생성 후 getEnvironment()로 Environment 객체를 얻어올 수 있다.

아래 예시는 resources 폴더에 user.properties 라는 파일을 읽어와 user.id와 user.pw 라는 속성 값을 출력한 결과다.

// MainClass.java
 
public class MainClass {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = new GenericXmlApplicationContext();
        ConfigurableEnvironment env = ctx.getEnvironment();
 
        MutablePropertySources propertySources = env.getPropertySources();
 
        try {
            propertySources.addLast(new ResourcePropertySource("classpath:user.properties"));
 
            System.out.println(env.getProperty("user.id"));
            System.out.println(env.getProperty("user.pw"));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

'=' 를 기준으로 key, value를 구분한다. '=' 이외에도 ':'으로도 테스트를 해보니 가능함을 알 수 있다.

// user.properties
 
user.id=konan
user.pw=12345

bean 객체 생성후 Environment 값을 전달하기

ctx는 ConfigurableApplicationContext 형이고, 이는 GenericXmlApplicationContext의 상위 클래스이므로 형변환이 가능하다.

생명주기에 따라 gCtx 객체는 해당 bean 파일을 로드하고 refresh()를 해줘야 설정값이 적용된다.

// MainClass.java
 
...
 
GenericXmlApplicationContext gCtx = (GenericXmlApplicationContext) ctx;
 
gCtx.load("applicationCTX.xml");
gCtx.refresh();
 
AdminConnection adminConnection = gCtx.getBean("adminConnection", AdminConnection.class);

AdminConnection 이라는 객체를 BeanFactory로부터 가져오기 위해 다음과 같이 설정을 하여 객체를 DI Container에서 생성한다.

// applicationCTX.xml
 
<bean name="adminConnection" class="com.bbo.example.AdminConnection">
 
</bean>

이제 Environment 값을 adminConnection 객체에 전달해야 한다. AdminConnection 클래스에 EnvironmentAware 인터페이스를 추가후 setEnvironment(Environment env) 함수를 재정의한다.

흐름을 다시 생각해보면, ctx에는 기존의 Environment 객체 값을 가지고 있고 이 ctx를 GenericXmlApplicationContext 타입으로 gCtx라는 객체로 형변환하였다. gCtx로부터 BeanFactory에 대한 정보를 가지고 있는 applicationCTX.xml 를 load하여 refresh()를 호출하여 적용시켰다. 적용 후에 applicationCTX.xml 내에 adminConnection이라는 객체를 생성된다.

즉, 객체 생성시 이미 Environment에 대한 정보를 가지고 있으니 전달이 가능하다는 이론이다.

// AdminConnection.java
 
public class AdminConnection implements EnvironmentAware {
    private Environment env;
    private String adminId;
    private String adminPw;
 
    @Override
    public void setEnvironment(Environment env) {
        // TODO Auto-generated method stub
        setEnv(env);
    }
 
  public void setEnv(Environment env) {
        this.env = env;
    }
}

env로부터 값을 가져올 때는 getProperty(key)를 통해 value를 가져올 수 있다. 다음 예시는 InitializingBean을 통해 bean 객체 생성시 각 멤버변수에 대해 초기화한다.

// AdminConnection.java
 
public class AdminConnection implements EnvironmentAware, InitializingBean, DisposableBean {
    private Environment env;
    private String adminId;
    private String adminPw;
 
    @Override
    public void setEnvironment(Environment env) {
        // TODO Auto-generated method stub
        setEnv(env);
    }
 
  ...
 
  @Override
    public void afterPropertiesSet() throws Exception {
        // TODO Auto-generated method stub
        setAdminId(env.getProperty("user.id"));
        setAdminPw(env.getProperty("user.pw"));
    }
 
    @Override
    public void destroy() throws Exception {
        // TODO Auto-generated method stub
 
    }
}

정리

이번 포스팅에서는 Environment 객체가 무엇인지, 언제 사용되는지, 어떻게 사용하는지에 대해 간략하게 알아보았다.

이렇게 외부 파일을 이용한 bean 객체 설정 방법은 Environment 외에도 프로퍼티 파일을 이용한 방법과 java 코드에 어노테이션을 이용한 방법, 그리고 프로파일 속성을 이용한 설정도 있다.

관련 강의는 검색을 통해 알아보거나 추후 포스팅하도록 하겠다.

DI란 무엇인가?

DI란 말 그대로 Dependency Injection으로 의존성을 주입시킨다는 뜻이다.

의존성 주입을 설명할 가장 간단한 예를 들 수 있다. 아래와 같이 객체에서 new 연산자를 사용하여 포함시킬 수 있다.

//A객체
B b = new B();
C c = new C();

하지만 스프링에서 객체를 포함시키는 방법은 코드 내부에서가 아닌 외부에서 객체를 얻어오는 방식이다.

AbstractApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationCTX.xml");
 
PersonService personService = ctx.getBean("personService", PersonService.class);

추후 설명하겠지만 외부 파일(xml)에서 생성된 객체를 가져오는 방식이다.

DI 방식의 이점은 무엇인가?

간단하게 말하겠다. 코드 상에서 변경이 아니므로 쉽게 설정파일만을 수정하여 부품(각 기능을 담당하는 클래스)들을 생성 및 조립이 가능하다.


DI 적용 방법

우선 세가지의 방식이 있다.

  1. 어노테이션 이용한 방식

  2. Bean 파일을 이용한 방식

  3. JavaConfig 파일을 이용한 방식


가장 많이 사용되는 2번 방식만을 이번 포스팅에서 다루려고 한다. 각 방법마다 장점과 단점이 있으니 추후 포스팅할 기회가 있다면 하겠다.

먼저 BeanFactory를 알자

DI 컨테이너의 핵심은 BeanFactory이다. BeanFactory는 실행 시 건네지는 Bean 정의 파일(기본적으로 applicationCTX.xml로 명명)을 바탕으로 인스턴스를 생성하고 인스턴스의 인젝션을 처리한다.


DI 컨테이너로부터 인스턴스를 얻는 다는 말은 BeanFactory 로부터 인스턴스를 얻는 다는 것을 의미한다.

전체 구조

예제를 위한 전체 구조는 아래 사진과 같다.


중간에 구현클래스와 인터페이스를 구분하였는데 DI를 이용할 때는 원칙적으로 인터페이스에 의존하고 구현 클래스에서는 의존하면 안된다. 이를 인터페이스 기반의 컴포넌트화라고 하는데 PersonService와 PersonDao는 인터페이스화 시키고 그 구현 클래스들은 인터페이스 클래스 이름에 Impl를 덧붙였다.

PersonDaoImpl은 PersonServiceImpl을 인젝션함을 알 수 있다. 이는 내부적으로 PersonServiceImpl가 PersonDao를 포함하고 있고, 이를 bean 파일에 정의함으로써 설정을 해야 한다.(곧 배운다.)

Bean 파일 정의

src/main/resources에서 오른쪽 마우스를 클릭하여 New -> Other 에서 Spring Bean Configuration File을 클릭하여 생성한다.(applicationCTX.xml로 생성)


bean 태그에는 여러가지가 있지만 이번 포스팅에서 사용할 주요 태그는 아래와 같다.

속성의미
id오브젝트를 유일하게 구분하는 값
name오브젝트명을 의미
classid의 실체로 패키지명 + 클래스명 으로 구성
autowireno, byName, byType, constructor 값을 통해 인젝션 방법 설정

personService 인스턴스는 내부적으로 personDao를 포함하는데 autowire 의 byType이라는 속성을 통해 프로퍼티형과 일치하는 Bean이 인젝션된다.

// applicationCTX.xml
 
<bean id="personDao" class="com.bbo.example.PersonDaoImpl"/>
 
<bean id="personService" class="com.bbo.example.PersonServiceImpl"
    autowire="byType"/>

단, 인젝션을 위해서는 personService 내에서 personDao에 대한 setter 함수가 꼭!! 필요하다.

// PersonServiceImpl.java
 
private PersonDao personDao;
 
public void setPersonDao(PersonDao personDao)
{
    this.personDao = personDao;
}

생성된 객체를 Bean 으로부터 가져오기

bean 파일에 대한 path를 통해 생성된 bean 파일에서 생성한 id를 통해 personService를 가져온다.

// PersonSampleRun.java
 
AbstractApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationCTX.xml");
 
PersonService personService = ctx.getBean("personService", PersonService.class);

personService는 DI 컨테이너에 의해 만들어진 PersonServiceImpl 클래스이기에 멤버함수를 호출하여 사용할 수 있다.

// PersonSampleRun.java
 
Person person = new Person("bbo", 22);
 
personService.addPerson(person);
 
System.out.println(person.getName());
 
Person loaded_person = personService.findByPerson("bbo");
 
System.out.println(loaded_person.getName());

전체 소스

// Person.java
 
public class Person {
    private String name;
    private int age;
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}
// PersonDao.java
 
public interface PersonDao {
    public void addPerson(Person person);
    public Person findByPerson(String name);
}
// PersonDaoImpl.java
 
package com.bbo.example;
 
import java.util.HashMap;
import java.util.Map;
 
public class PersonDaoImpl implements PersonDao{
 
    private Map<String, Person> storage = new HashMap<String, Person>();
 
    @Override
    public void addPerson(Person person) {
        // TODO Auto-generated method stub
        storage.put(person.getName(), person);
    }
 
    @Override
    public Person findByPerson(String name) {
        // TODO Auto-generated method stub
        return storage.get(name);
    }
 
}
// PersonService.java
 
public interface PersonService {
    void addPerson(Person person);
    Person findByPerson(String name);
}
// PersonServiceImpl.java
 
public class PersonServiceImpl implements PersonService{
 
    private PersonDao personDao;
 
 
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }
 
    @Override
    public void addPerson(Person person) {
        // TODO Auto-generated method stub
        personDao.addPerson(person);
    }
 
    @Override
    public Person findByPerson(String name) {
        // TODO Auto-generated method stub
        return personDao.findByPerson(name);
    }
 
}
// PersonSampleRun.java
 
package com.bbo.example;
 
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
 
import ex00.MyCalculator;
 
public class PersonSampleRun {
    public static void main(String[] args) {
        AbstractApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationCTX.xml");
 
        PersonService personService = ctx.getBean("personService", PersonService.class);
 
        Person person = new Person("bbo", 22);
 
        personService.addPerson(person);
 
        System.out.println(person.getName());
 
        Person loaded_person = personService.findByPerson("bbo");
 
        System.out.println(loaded_person.getName());
 
        ctx.close();
    }
}

정리

Bean파일을 이용한 DI 방법에 대해 알아보았다. bean 파일을 이용하여 의존성 주입하는 방법에는 setter 방식외에도 생성자를 이용한 방식도 있다.

setter 이용시에는 <property> 태그를 이용하지만 생성자를 이용한 방식에는 <constructor-arg> 태그를 이용한다. 이 방식에 대해서는 자료가 많으니 찾아보면 좋을 것 같다.

DI 가 정말 중요한 개념인만큼 프로젝트 진행시 여러 방법에 대해 탐구해보며 공부해봐야 할 것 같고 느낀점에 대해 계속 포스팅해야겠다.

참고서적

Spring 4 입문(한빛미디어)

인프런 강좌(신입 프로그래머를 위한 자바 스프링 프레임워크 강좌)

+ Recent posts