[boostcourse] 3.7. Spring Core - BE

1. Spring이란?

- Spring Framework. JAVA 기반의 오픈소스 애플리케이션 프레임워크

- 원하는 부분만 가져다 사용할 수 있도록 모듈화가 잘 되어있음

- 가벼운 솔루션. 경량 컨테이너로 자바 객체를 담고 직접 관리함

- IoC (Inversion of Control), 제어의 역전. 객체의 생성부터 소멸까지 객체 생성주기 관리를 사용자가 아닌 특별한 객체에게 맡기는 것

- DI (Dependency Injection), 의존성 주입. 객체를 외부(Spring)에서 생성해서 사용하려는 주체 객체에 주입시키는 방식

- AOP (Aspect Oriented Programming), 관점 지향 프로그래밍. 여러 모듈에서 공통적으로 사용하는 기능을 분리하여 관리하고, 재사용성을 높여주는 프로그래밍 기법

- MVC (Model-View-Controller) 패턴

- WAS에 독립적인 개발 환경. Tomcat과 같은 WAS가 내장되어 있어 자바 웹 애플리케이션을 구동할 수 있음

etc-image-0
Spring Framework Modules

2. IoC / DI Container

컨테이너(Container)란?

- 인스턴스의 생명주기를 관리함

- 생성된 인스턴스들에게 추가적인 기능을 제공함

- WAS는 Servlet 컨테이너를 갖고 있음. 웹 브라우저로부터 Servlet URL에 해당하는 요청을 받으면, 해당 Servlet을 메모리에 올린 후 실행함. 개발자가 Servlet 클래스를 작성했지만, 실제로 Servlet을 메모리에 올리고 실행하는 것은 WAS가 갖고 있는 Servlet 컨테이너가 수행함. 기존에 메모리에 올라간 Servlet은 다시 메모리에 올리지 않고 등등 실행해서 결과를 웹 브라우저에 전달함. JSP도 마찬가지. 얘가 Servlet으로 바뀌어 똑같이 수행됨.

 

IoC란?

- Inversion of Control. 제어의 역전

- 개발자는 프로그램의 흐름을 제어하는 코드를 작성함. 근데 이 흐름의 제어를 개발자가 하는 것이 아니라, 다른 프로그램이 그 흐름을 제어하는 것을 IoC라고 함

 

DI란?

- Dependency Injection. 의존성 주입

- 공장이 인스턴스를 만들었다면, 그 인스턴스를 사용해야 함. 어떻게 그 인스턴스를 가져올까? -> DI

- 클래스 사이의 의존 관계를 Bean 설정 정보를 바탕으로 컨테이너가 자동으로 연결해 주는 것을 의미

 

Spring에서 제공하는 IoC / DI Container

- 공장 역할

- BeanFactory: IoC / DI에 대한 기본 기능을 갖고 있음

- Application Context: BeanFactory의 모든 기능을 포함하며, 일반적으로 BeanFactory보다 추천됨. 트랜잭션 처리, AOP에 대한 처리 등을 할 수 있음.

 

3. IoC / DI 예제 - xml 파일 기반

예제 1

# UserBean.java

package com.example.diexam01;

public class UserBean {

    /*
    * 일반적인 Java 클래스 == Bean 클래스
    * 기본 생성자를 갖고 있음
    * 필드는 private으로 선언함
    * getter, setter 메소드를 가짐
    */

    private String name;
    private int age;
    private boolean male;

    public UserBean() {}

    public UserBean(String name, int age, boolean male) {
        this.name = name;
        this.age = age;
        this.male = male;
    }

    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;
    }

    public boolean isMale() {
        return male;
    }

    public void setMale(boolean male) {
        this.male = male;
    }
}

 

# DiExam01Application

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userBean" class="com.example.diexam01.UserBean"></bean>

</beans>

 

# ApplicationContextExam01

package com.example.diexam01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationContextExam01 {

    public static void main(String[] args) {

        // 스프링 공장
        ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        System.out.println("초기화 완료");

        UserBean userBean = (UserBean)ac.getBean("userBean");
        userBean.setName("Jeon");
        System.out.println(userBean.getName());

        // 싱글톤 패턴 확인
        UserBean userBean2 = (UserBean)ac.getBean("userBean");
        System.out.println(userBean.getName());

    }
}

 

예제 2

Engine.java

package com.example.diexam01;

public class Engine {

    public Engine() {
        System.out.println("Engine 생성자");
    }

    public void exec() {
        System.out.println("엔진 동작");
    }
}

Car.java

package com.example.diexam01;

public class Car {

    private Engine v8;

    public Car() {
        System.out.println("Car 생성자");
    }

    public void setEngine(Engine e) {
        this.v8 = e;
    }

    public void run() {
        System.out.println("엔진을 이용하여 자동차 달림");
        v8.exec();
    }

    public static void main(String[] args) {
        // 제어의 역전으로, Spring IoC 컨테이너가 아래와 같은 일들을 수행함
//        Engine e = new Engine();
//        Car c = new Car();
//        c.setEngine(e);
//        c.run();
    }
}

# applicationContext.xml

    <bean id="e" class="com.example.diexam01.Engine" />
    <bean id="c" class="com.example.diexam01.Car">
        <property name="engine" ref="e"></property>     <!-- getter, setter가 property. Car를 사용하기 위해 Engine을 set하기 위해 -->
    </bean>

 

예제 3

# ApplicationContextExam02.java

package com.example.diexam01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationContextExam02 {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        Car car = (Car)ac.getBean("c");
        car.run();
    }
}

etc-image-1

DI

- 객체에게 객체를 주입함

- 사용자는 Engine을 모르고 Car 클래스만 알고 있어도 Spring 컨테이너가 자동으로 DI를 해줌

 

4. IoC / DI 예제 - Java Config 기반

예제 1

# ApplicationConfig.java

package com.example.diexam01;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApplicationConfig {

    @Bean
    public Car car(Engine e) {
        Car c = new Car();
        return c;
    }

    @Bean
    public Engine engine() {
        return new Engine();
    }
}

- xml 파일이 아닌 @Configuration 을 통해 Bean 정보를 보관함

- 싱글톤으로 관리할 수 있게 함

 

# ApplicationContextExam03.java

package com.example.diexam01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationContextExam03 {

    public static void main(String[] args) {
        // 설정 정보를 불러 읽어옴
        ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);

//        Car car = (Car)ac.getBean("Car");
        Car car = (Car)ac.getBean(Car.class);
        car.run();
    }

}

 

예제 2

# ApplicationConfig2.java

package com.example.diexam01;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan      // 알아서 Bean 정보를 등록해
public class ApplicationConfig2 {


}

# Engine.java

package com.example.diexam01;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component		// 추가됨
public class Engine {

    public Engine() {
        System.out.println("Engine 생성자");
    }

    public void exec() {
        System.out.println("엔진 동작");
    }
}

 

# Car.java

package com.example.diexam01;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Car {

    @Autowired      // Car 객체 내 Engine 객체를 알아서 주입해줘. 이제 setter는 없어도 됨
    // @Qualifier("engine") // Engine이라는 인터페이스를 상속받은 클래스가 여러 개라면 명시해야 함
    private Engine v8;

    public Car() {
        System.out.println("Car 생성자");
    }

    public void run() {
        System.out.println("엔진을 이용하여 자동차 달림");
        v8.exec();
    }

    public static void main(String[] args) {
        // 제어의 역전으로, Spring IoC 컨테이너가 아래와 같은 일들을 수행함
//        Engine e = new Engine();
//        Car c = new Car();
//        c.setEngine(e);
//        c.run();
    }
}

- @ComponentScan, @Component, @Autowired를 사용하여 DI

 

5. 정리

지극히 주관적인 정리

 

Car 객체가 Engine 객체에 의존한다면?!

 

객체들을 생성하고 관리하는 것을 Spring 컨테이너에게 맡김

-> IoC. 제어의 역전. 프로그램의 흐름을 개발자가 아닌 다른 프로그램에게 맡김

 

이때 Spring 컨테이너가 관리하는 자바 객체를 Bean이라고 함

 

그럼 Spring 컨테이너는 관리할 자바 객체 정보를 어떻게 얻을까?

1. @Configuration을 통해 해당 파일 하위에 @Bean을 사용하여 등록하고자 하는 클래스를 찾음

2. @ComponentScan을 통해 @Component, @Controller, @Service, @Repository 등을 모두 찾아 Bean으로 등록해 줌

3. Car 클래스와 Engine 클래스를 정의할 때 미리 @Component를 붙여줘야겠지~

4. Car의 경우에는 Engine에 의존하기에 @Autowired로 알려줌

5. 그럼 이제 Spring 컨테이너인 ApplicationContext(Bean Factory를 상속받은)를 통해 위와 같이 설정한 정보들을 불러 읽어온다.

 

각 클래스 사이에 필요한 의존관계를 Bean 설정 정보를 바탕으로 Spring 컨테이너가 자동으로 연결해 줌

이게 DI. 의존성 주입.