Web/Java+Spring

Spring IoC/DI 컨테이너

WakaraNai 2021. 6. 1. 00:26
728x90
반응형

Container

객체의 생명주기를 관리하며, 그 객체에 추가적인 기능을 제공

 

예로 Servlet을 실행해주는 WAS는 Servlet 컨테이너를 가지고 있다고 말함.

WAS는 웹 브라우저로부터 Servlet URL에 해당하는 요청을 받으면, Servlet을 메모리에 올린 후 실행

개발자가 Servlet 클래스를 작성했지만, 실제로 메모리에 올리고 실행하는 것은

WAS가 가지고 있는 Servlet 컨테이너.

 

Servlet 컨테이너는 동일한 Servlet에 해당하는 요청을 받으면,

메모리에 올리지 않고 기존에 메모리에 올라간 Servlet을 실행하여 그 결과를 웹브라우저에 전달

 

 

IoC (Inversion of Control)

Container가 코드 대신 객체의 제어권을 갖고 있어 이 상황을 IoC(제어의 역전)이라고 부름

개발자가 작성한 코드를 다른 프로그램이 실행해주는 것을 제어의 역전이라고 함

 

예로 TV 회사마다 미세하게 리모컨의 자판 배치는 다르지만 기능적으로 일치함

그래야 사용자 입장에서 어느 TV를 쓰던 편하게 사용할 수 있음

이런 껍데기를 인터페이스라고 하고 프로그래밍 코드에도 이런 것이 있음

지금처럼 인터페이스를 통일하면 TV객체가 달라져도 쉽게 다룰 수 있음

 

어노테이션을 통해 url을 간단하게 바꿀 수 있는 것처럼...

 

 

DI (Dependency Injection)

의존성 주입

공장에서 만든 객체를 사용하기 위해 내가 만든 프로그램으로 가져올 수 있는 방법 중 하나

 

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

 

DI 적용 X

개발자가 직접 객체 생성 - new를 써서 생성

class 엔진 {

}

class 자동차 {
     엔진 v5 = new 엔진();
}

 

 

DI 적용 by Spring

약속된 어노테이션으로 알려주기만 하면 Spring 컨테이너가 알아서 그 객체를 생성하여 내 코드에 넣어줌

객체 생성을 직접하지 않고 컨테이너에 맡김. new를 사용하지 않음.

@Component
class 엔진 {

}

@Component
class 자동차 {
     @Autowired
     엔진 v5;
}

 

 

Spring에서 제공하는 IoC/DI Container

Spring에서 제공하는 대표적인 공장 2개

  • BeanFactory : IoC/DI에 대한 기본 기능을 가지고 있습니다.
  • ApplicationContext : BeanFactory의 모든 기능을 포함하기 때문에 일반적으로 BeanFactory보다 추천됩니다.
    • 트랜잭션처리, AOP 등에 대한 처리를 할 수 있습니다. BeanPostProcessor, BeanFactoryPostProcessor등을 자동으로 등록하고, 국제화 처리, 어플리케이션 이벤트 등을 처리할 수 습니다.
  • BeanPostProcessor : 컨테이너의 기본로직을 오버라이딩하여 인스턴스화 와 의존성 처리 로직 등을 개발자가 원하는 대로 구현 할 수 있도록 합니다.
  • BeanFactoryPostProcessor : 설정된 메타 데이터를 커스터마이징 할 수 있습니다.

 

 

 

pom.xml 파일로 IoC/DI 동작 확인해보기

 

Maven Project 생성

1. 새로운 Maven Project 생성 

File -> New -> Maven Project -> Next -> maven-archtype-quickstart 선택 -> artifact id는 diexam1 -> finish

archtype 설정으로 프로젝트의 구조를 정함 

Group ID는 보통 회사의 도메인을 거꾸로 씀. 나중에 패키지가 되기에 패키지 이름적는 규칙처럼 하면 됨

그러니 소문자로 기입하기!

 

 

2. JDK 플러그인 xml 파일에 추가하기

혹시 모르니 properties에서 1.8로 수정

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

<build>/<pluginManagement> 에 아래 코드 추가

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        

 

!! 프로젝트 우클릭 -> Maven -> Update Project 필수로 해주기!

 

 

 

3. Run -> JUnit

src/test/java 아래의 AppTest.java 를 JUnit Test로 실행해보기

JUnit 창에서 이렇게 뜨면 성공

이를 통해 내가 만든 Maven 프로젝트가 제대로 동작하는 지 확인

 

 

Spring 설정하기

Spring이 대신 객체를 공장에서 만들어내 내 코드에 넣어주는지 확인하기

그 객체를 bean이라고 부름

(근래에 들어 일반적인 자바 클래스를 Bean 클래스라고 함)

 

 

1. UserBean 클래스 파일 생성

diexam01/src/main/java -> kr.or.connect.diexam01에

UserBean 이름의 클래스 파일 생성

 

!! Bean Class가 필수로 가지고 있어야 하는 것 !!

  • 기본 생성자 한 개
  • 필드가 private로 선언
  • getter, setter 메소드 필수 -> 이 메소드는 해당 필드의 property로 부름

Bean에 이러한 규칙이 필요한 이유는,

사용자가 직접 생성하는 것이 아니라 누군가 대신 해주기에 반드시 규칙이 필요함

 

UserBean.java

package kr.or.connect.diexam01;

public class UserBean {
	
	// 필드는 private로 선언
	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;
	}

	// getter, setter
	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;
	}
}

 

 

 

2. Spring Framework 추가하기 -> pom.xml

 

<properties>에 아래 코드 추가

버전을 바꾸고 싶다면 4.2.5 쪽 내용을 바꾸면 됨

  <org.springframework-version>4.2.5.RELEASE</org.springframework-version> <!-- wanted Spring version -->
  <hibernate.version>5.1.0.Final</hibernate.version> <!-- wanted Hibernate version -->
  

 

<dependencies>에 아래 코드 추가 후 Update Project

    <version>의 ${}안의 값을 properties에서 찾아야 인식 가능

    이 이름으로 상수처럼 사용 가능

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${org.springframework-version}</version>
	</dependency>

 

이런 내용은 Google에 "maven spring" 검색해서 나오는

Maven Repository 사이트에서 찾을 수 있음

현재는 Spring 4를 사용하기에 4.3.x에서 가장 최신의 것을 선택

 

Maven 코드 블럭 안에 dependency에 적을 코드가 나타남

 

 

 

3. Spring 설정 파일 작성하기

즉, UserBean 클래스를 Spring에 알려주기

 

프로젝트 우클릭 -> New -> Folder -> src/main 경로에 'resources' 이름으로 생성

Spring에 건넬 정보만 이곳에 담을 예정

 

resource 우클릭 -> New -> FIle -> xml 파일의 이름을 applicationContext로 하여 파일 생성 (applicationContext.xml)

어플리케이션의 context들에게 설정을 공장에게 알려주겠다는 의미.

생성한 파일에 아래 코드 기입 후 저장

<?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="kr.or.connect.diexam01.UserBean"></bean>

</beans>

 

xml 파일로 Spring 설정 파일을 만들게 되면 

가장 바깥쪽 태그를 루트 element라고 부름

이게 반드시 beans로 되어 있어야 함

 

그리고 xml 스키마에 대한 설정도 추가해야 하는데

해당 설정 파일은 Spring 컨테이너가 읽어 들인다.

만약 beans 태그 안에 아무것도 없으면 아무 일도 일어나지 않음

 

지금 하려고 하는 것은 Spring 컨테이너한테 사용할 객체를 대신 생성하게 하고 싶은 것이므로,

반드시 Spring 컨테이너에 정보를 줘야 함.

이 때 사용되는 element가 beans라는 element다.

beans가 자바의 일반적인 클래스이기에 beans라는 element에다가 몇 가지 속성을 부여할 수 있음

id = "userBean"으로, 클래스는 내가 진짜 만들고 싶은 클래스인 "kr.or.connect.diexam01.UserBean"를 적어줌

 

id는 객체 생성 시 부여하는 레퍼런스 변수명.

클래스는 Spring이 인식할 수 있도록 패키지명까지 적음

 

+) 이렇게 객체를 딱 하나만 가지고 있는 것을

싱글턴 패턴이라고 함

메모리에 하나만 생성.

이 때 해당 객체를 여러 사람이 동시에 이용한다면 동기화문제(?)/충돌하여 데이터가 변조됨

 

+) 디자인 패턴도 한 번 찾아보기

+) Spring에서 bean 생성 시 scope를 지정하여 싱글톤 외에 다른 방법으로도 객체 생성 가능

 

4. Spring 설정 파일을 읽어들일 객체 한 개 생성하기

ApplicationContextExam01 클래스 파일 생성

경로 :  diexam01/src/main/java  /  kr.or.connect.diexam01

 

package kr.or.connect.diexam01;

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

public class ApplicationContextExam01 {
	// 이 프로그램을 시작시킬 시작점이 필요하기에 main() 메소드 적음
	public static void main(String[] args) {
		
		// Spring 공장 중에서 ApplicationContext 공장(인터페이스)을 이용. 
		// ApplicationContext 구현한 여러 객체(컨테이너) 중 ClassPathXmlApplicationContext 객체 사용
		// classpath로 applicationContext.xml을 지정하여 Bean 정보를 공장에 넘겨줌
		ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		// classpath는 resources 폴더에서 생성한 xml 파일을 자동으로 가리킴
		// ClassPathXmlApplicationContext 객체가 생성될 때 지정한 설정 파일(xml)을 읽어서
		// 그 안에 선언된 bean에 설정된 객체들을 '전부' 생성하여 메모리에 올려줌
		// 만약 이때 문제가 발생하면 해당 어플리케이션은 종료됨
		
		// 잘 동작하는 지 확인하기 위한 메세지
		System.out.println("초기화 완료!!");
		
		
		// 개발자가 UserBean 객체를 생성하지 않고
		// 공장(ac)의 getBean() 메소드에 id를 넣어 얻어냄
		// 이 메소드는 Object 타입으로 리턴하기 때문에 형변환 필요
		UserBean userBean = (UserBean) ac.getBean("userBean");
		
		// ac는 applicationContext.xml에서
		// "userBean"이라는 id를 탐색
		// 같은 것이 있다면 등록된 클래스를 생성하여 리턴
		// 이 일을 getBean() 메소드가 수행
		
		
		// 얻어온 bean에다가 userBean.setName()메소드를 실행하여
		// getName()도 해보면 그 값을 받아서 출력하는 것을 볼 수 있음
		userBean.setName("Lee");
		System.out.println(userBean.getName());
		
		
		
		// 객체를 하나 더 얻어내기 위해 새롭게 생성하더라도 의미없음
		// getBean()을 계속 요청해도 하나 만든 bean을 재사용함
		UserBean userBean2 = (UserBean) ac.getBean("userBean");
		if (userBean == userBean2)
			System.out.println("같은 객체 입니다."); // 일치하기에 출력됨
		// bean 공장이 싱글턴 패턴을 이용해서 bean들을 생성하기 때문
		
		// 객체를 대신 생성해주고 싱글턴으로 관리해주는 기능 등을 
		// IoC, 제어의 역전이라고 함

	}

}

 

 

 

DI 테스트

1. Car, Engine 클래스 생성하기

경로 :  diexam01/src/main/java  /  kr.or.connect.diexam01

 

일단 개발자가 직접 생성하여 실행시킬 때의 코드

 

 

Engine.java

package kr.or.connect.diexam01;

public class Engine {
	// 기본 생성자
	public Engine() {
		System.out.println("Engine 생성자");
	}
	
	public void exec() {
		System.out.println("엔진이 동작합니다.");
	}

}

 

 

Car.java

package kr.or.connect.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) {
		// 개발자가  생성
		Engine e = new Engine();
		Car c = new Car();
		c.setEngine(e);
		c.run();
	}
}

 

 

2. Car, Engine 정보를 Spring에 알려주기

resources의 applicationCentext.xml에 아래 코드 추가

	<bean id="e" class="kr.or.connect.diexam01.Engine"></bean>
	<bean id="c" class="kr.or.connect.diexam01.Car">

		<property name="engine" ref="e"></property>
		<!-- setEngine(), getEngine()을 의미 -->
		<!-- setEngine()은 Engine e를 파라미터로 받기에 -->
			<!-- e라는 bean을 여기서 참조, 사용하겠다는 의미 -->
	</bean>
	

 

3. Spring으로 객체 생성하여 실행해보기

ApplictaionContextExam02.java 파일 생성

경로 :  diexam01/src/main/java  /  kr.or.connect.diexam01

!! 일단 Car 클래스에서 main 메소드 삭제해두기 !!

그래야 ApplictaionContextExam02에서 제대로 실행되는지 확인 가능

 

package kr.or.connect.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"); // c라고 등록된 클래스에서 객체 생성
		car.run();
	}
}

 

Car 클래스만 등장

Engine 클래스는 등장하지 않아도 모두 실행됨

 

즉, 사용자는 Car 클래스만 알고 있으면 된다.

나중에 Car를 상속받고 있는 Bus 클래스가 생성되도록 xml 파일만 바꿔주고

Bus가 상속받고 있는 Engine 클래스도 Electric Engine으로 주입받도록 바꿔준다면

실행 클래스의 코드는 하나도 바뀌지 않고 전기 버스가 동작하도록 할 수 있음

 

 

 

728x90
반응형

'Web > Java+Spring' 카테고리의 다른 글

Spring JDBC  (0) 2021.06.02
JAVA Config  (0) 2021.06.02
Spring Framework  (0) 2021.05.31
Web API 실습  (0) 2021.05.14
REST API 와 Web API, 상태 코드  (0) 2021.05.14