본문 바로가기
Spring

제어의 역행(역전) : IoC(DI)

by 요리하다그만둠 2022. 7. 25.

1. IoC(제어의 역전(역행) : Inversion of Control)

일정한 다른 프로그램(프레임웍 등)에 프로그램에 대한 제어권을 이양(넘김)으로써 코드 제어권 관리 책임 부담을 덜어줌.

참고) 마틴 파울러(Martin Powler)의 정의 : https://martinfowler.com/bliki/InversionOfControl.html

프레임워크의 중요한 특성은 사용자가 정의한 메소드가 사용자의 어플리케이션 코드가 호출 하기보다 종종 프레임워크로 부터 호출 되어 진다는 것이다. 프레임워크 종종 어플리케이션 행위를 재배치 하고, 순서를 정하는 메인프로그램의 역할을 담당한다. 역전된 제어는 프레임워크가 확장된 뼈대를 제공하는 힘을 제공한다. 사용자는 특정 어플리케이션을 위한 커스터마이즈된 알고리즘 메소드를 프레임워크에 전달한다.

One important characteristic of a framework is that the methods defined by the user to tailor the framework will often be called from within the framework itself, rather than from the user's application code. The framework often plays the role of the main program in coordinating and sequencing application activity. This inversion of control gives frameworks the power to serve as extensible skeletons. The methods supplied by the user tailor the generic algorithms defined in the framework for a particular application.

-- Ralph Johnson and Brian Foote

2. 의존 객체(Dependency Object)/의존 관계(Dependency Relation)

: 참고로 UML에서는 의존관계를 점선 화살표로 표시한다.

모든 어플리케이션은 비지니스 로직을 수행하기 위해 서로 협업하는 둘 또는 그 이상의 클래스들로 이뤄진다.

전통적으로 각 객체는 협업할 객체의 참조(reference)를 취득해야 하는 책임이 있다. 이것이 의존성(dependency)이다. 이는 결합도가 높으며 테스트하기 어려운 코드를 만들어 낸다.

IoC(제어의 역전)를 적용함으로써 객체들은 시스템 내의 각 객체를 조정하는 어떤 외부의 존재에 의해 생성 시점에서 의존성 부여 받는다. 즉 의존성이 객체로 주입(inject)된다는 말이다.

IoC(제어의 역전)는 한 객체가 협업해야 하는 다른 객체의 참조관계를 이루는 방법에 대한 책임(제어권)의 역행이라고 부르기도 한다.

★ 의존 역전(역행)의 법칙(DIP) : https://sossms.tistory.com/m/176?category=169611

 
SW 설계의 원칙 5 의존 관계 역전의 원칙

출처 : http://vandbt.tistory.com/42#recentTrackback  포스트의 주제는 이전의 S.O.L.I.D 의  LSP에 이어 의존 역전의 원칙 Dependency Inversion Principle (DIP) 입니다. 포스팅의 동기 또한 LSP의 동기와..

sossms.tistory.com

: SOLID객체지향 프로그래밍의 5대 원칙은 매우 중요 !

: 간략하게 말하자면 사회(프로그램)가 원활하게 돌아가려면 잘 갖추어지고 제대로 된 지도층(추상:interface, 메타데이터 등)이 백성들(구체: concrete class)를 잘 지도하고 통제하도록 설계되고 실현되어야 범죄없이 질서가 잘 유지되고 살기좋은 프로그램(사회)가 된다는 것으로 이해해도 좋다. 이것이 의존 정보(객체)에 대한 제어의 역전 혹은 의존 역전(역행)의 법칙이다.

※ DIP(Dependency Inversion Principle)

- 자주 변경되는 구상클래스(Concrete class)에 의존하지 않는다

- 어떤 클래스를 상속받아야 한다면 , 기반 클래스를 추상 클래스로 만들어라

- 인터페이스를 만들어서 이 인터페이스에 의존하라

※ 나쁜 품질의 코드

- 경직성 : 어떤 하나를 바꿀 때 마다 연쇄적으로 연결된 다른 것도 바꿔야 한다.

- 부서지기쉬움 : 시스템에서 한 부분을 변경하면 그것과 전혀 상관없는 다른 부분에서 오류가 발생

- 부동성 : 시스템을 여러 컴포넌트로 분해해서 재사용하기 어려움

- 장시간의 코드 빌딩(compile & link) : 개발환경이 편집-컴파일-테스트 순환(cycle)을 한번 도는데 시간이 엄청나게 길다.

- 복잡함/불투명함 : 이해와 분석이 어려운 복잡한 코드들

- 불필요한 반복 : 복사&붙이기

3. IoC(제어의 역전) 사례

ex) Tomcat DBCP/JNDI 프로그래밍

...(중략)...

 

<Resource name="jdbc/xe"

auth="Container"

type="javax.sql.DataSource"

driverClassName="oracle.jdbc.OracleDriver"

factory="org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory"

url="jdbc:oracle:thin:@localhost:1521:xe"

username="hr"

password="hr"

maxActive="20"

maxIdle="10"

maxWait="-1" />

 

...(중략)...

 

<Context docBase="javateam_project" path="/javateam_project" reloadable="true"

source="org.eclipse.jst.jee.server:javateam_project">

<WatchedResource>WEB-INF/web.xml</WatchedResource>

<ResourceLink global="jdbc/xe" name="jdbc/xe" type="javax.sql.DataSource"/>

</Context>

 

...(중략)....

 

<!-- DBCP JNDI local setting -->

<resource-ref>

<description>oracle 11g</description>

<res-ref-name>jdbc/xe</res-ref-name>

<res-type>javax.sql.DataSource</res-type>

<res-auth>Container</res-auth>

</resource-ref>

 

...(중략)...

 

try {

Context initContext = new InitialContext();

DataSource ds = (DataSource)initContext.lookup("java:/comp/env/jdbc/xe");

 

con=ds.getConnection();

 

...(후략)...

4. Spring에서의 IoC(DI) : Spring은 기본적으로 DI(IoC) 컨테이너(Container) 기능을 있음.

※ 전자정부 문서에서의 개요

: http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte2:fdl:ioc_container:basics

※ Auto-wiring & Injection : XML정보와 annotation 정보를 좀더 최소화하여 자동적으로 자바빈을 와이어링(하는 좀더 편리한 기능

1) 첨부 예제 : https://cafe.naver.com/djjava24/517

 
 
package bean.test;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import bean.MsgBean;

public class MsgApp {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		Resource resource 
			= new ClassPathResource("bean/applicationContext.xml");
		
		@SuppressWarnings("deprecation")
		BeanFactory factory = new XmlBeanFactory(resource);
		MsgBean bean = (MsgBean)factory.getBean("msgBean");
		bean.setMsg();
	}

}
package bean.test;

import org.springframework.context.support.GenericXmlApplicationContext;

import bean.MsgBean;

public class MsgApp2 {

	public static void main(String[] args) {

		GenericXmlApplicationContext ctx 
			= new GenericXmlApplicationContext();
		ctx.load("classpath:bean/applicationContext.xml");
		ctx.refresh(); 
		// Context 정보 갱신 메소드 추가 :  
		// 이 정보가 없으면 위의 xml 정보 변경후 제대로 반영 안될 수 있습니다.
		
		MsgBean bean = (MsgBean)ctx.getBean("msgBean");
		bean.setMsg();
	}

}

※ iBatis DI(IoC)

 

sqlMapper.insert("add", user);

 

xml)

<insert id="add" parameterClass="model.User">

insert into INSA_TEMP values (#userId#, #userPW#, #userName#)

</insert>

 

MsgBean bean = (MsgBean)ctx.getBean("msgBean");

bean.setMsg();

 

xml)

< bean id="msgBean" class="bean.MsgBeanImpl">

<constructor-arg>

<value>Spring</value>

</constructor-arg>

<property name="msg">

<value>Hi !</value>

</property>

</bean>

2) 첨부 예제-2

- root-context.xml (metadata)

 
<?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">
	
	<!-- Root Context: defines shared resources visible to all other web components -->
	<bean id="javaBean" class="com.javateam.springIoC.bean.JavaBean">
		<constructor-arg type="String" name="name" value="스프링" />
		<!-- <property name="name" value="spring 3.1.1" /> -->
		<property name="bean2" ref="javaBean2" />
	</bean>
	
	<bean id="javaBean2" class="com.javateam.springIoC.bean.JavaBean2">
		<property name="name" value="제어의 역전 프로그래밍" />
	</bean>
		
</beans>
/**
 * 
 */
package com.javateam.springIoC.bean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * Java Bean
 * @author javateam
 *
 */
// @Component
public class JavaBean {
	
	private static final Logger log 
		= LoggerFactory.getLogger(JavaBean.class); 
	
	public String name;
	
	public JavaBean2 bean2;

	public JavaBean(String name) {
		
		log.info("생성자 인자 : " + name);
		this.name = name;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public JavaBean2 getBean2() {
		return bean2;
	}

	public void setBean2(JavaBean2 bean2) {
		this.bean2 = bean2;
	}

	public void print(String arg) {
		
		log.info("JavaBean print");
		log.info("arg : "+arg);
	}

}
/**
 * 
 */
package com.javateam.springIoC.bean;

import org.springframework.stereotype.Component;

/**
 * @author javateam
 *
 */
// @Component
public class JavaBean2 {
	
	public String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
}
※ 단위 테스트 (Unit Test)

/**
 * 
 */
package com.javateam.spring_IoC.test;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;

import javax.inject.Inject;

import org.hamcrest.CoreMatchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import com.javateam.springIoC.bean.JavaBean;

import lombok.extern.slf4j.Slf4j;

/**
 * Unit Test
 * @author javateam
 *
 */
// @Slf4j // lombok
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"file:src/main/webapp/WEB-INF/spring/root-context.xml",
					"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@WebAppConfiguration
public class IoCTest {
	
	private static final Logger log = LoggerFactory.getLogger(IoCTest.class);
	
	// @Inject
	@Autowired
	private JavaBean bean;

	@Test
	public void test() {
		
		log.info("--------------------------------");
		log.info("unit test");
		
		assertNotNull(bean.bean2.name);
		assertEquals(bean.bean2.name, "제어의 역전 프로그래밍");
		// assertThat(bean.bean2.name, CoreMatchers.is("제어의 역전 프로그래밍"));
		// assertThat(bean.bean2.name, is("제어의 역전 프로그래밍"));
		
		log.info("--------------------------------");
		
	} //
}

 

 

1. applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:lang="http://www.springframework.org/schema/lang"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
		http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.2.xsd">
 
 <!-- bean id="msgBean" class="bean.MsgBeanImpl">
   <constructor-arg>
     <value>Spring</value>
   </constructor-arg>
   <property name="msg">
   	<value>Hi !</value>
   </property>
 </bean-->
 
 <!-- 애너테이션(annotation)을 활용한 Bean 설정 : since Spring 2.5-->

 <context:annotation-config/>
 
 <context:component-scan base-package="bean"/>
 <!-- bean 패키지(폴더)에서 annotation 정의를 검색하도록 조치 -->
 
</beans>
​

2. MsgBean.java

package bean;

public interface MsgBean {
	
	void setMsg(); 

}
3. MsgBeanImpl.java

package bean;

import org.springframework.beans.factory.annotation.Autowired; // 생성자 주입(injection) Autowired annotation..
import org.springframework.beans.factory.annotation.Value; // 생성자 및 단순 값("Hi !") 주입에 따른 인자값(Value) annoatation.
import org.springframework.stereotype.Service; // (전체적인) Service annotation 지원을 위해 추가됨.

@Service("msgBean") // annotation 형태의 Bean 설정(wiring)
public class MsgBeanImpl implements MsgBean {
	
	private String name;
	
	// @Value("Hi !") 
	// 값 주입을 할 경우 멤버 필드 위에서 직접 주입할 수도 있고
	// 아래의 setMsg 메소드와 같이 setter에서 값을 주입(injection)할 수도 있다.
	// 또한 다른 방법은 이러한 값을 할당하는 별도의 클래스(MsgBeanValues) 
	// 를 두어서 SpEL(since Spring 3.0)
	// 방식으로 별도로 값을 호출하는 방법을 이용할 수도 있다. 
	// 그렇게 한다면 고정값을 두지 않고 값을 조정할 수 있는 장점이 있다.
	@Value("#{msgBeanValues.msg}")
	private String msg;
	
	// annotation 사용시 반드시 기본 생성자를 정의해주도록 한다 : 에러(error) 방지
	// 여러 곳에 생성자에 Autowired를 하게 되면 에러 발생 !
	// @Autowired
	public MsgBeanImpl() {
		
	}
	
	// (주의사항) 생성자에 Autowired는 단지 한 곳에서만 적용이 가능하다 !
	@Autowired
	//public MsgBeanImpl(@Value("Spring") String name) {
	 public MsgBeanImpl(@Value("#{msgBeanValues.name}") String name) {
		// super();
		this.name = name;
	}

	@Override
	public void setMsg() {
		System.out.println(msg + " " + name);
	}
	
	public String getMsg() {
		return msg;
	}
	
	// @Autowired
	//public void setMsg(@Value("Hi !") String msg) {
	public void setMsg(String msg) {			
		this.msg = msg;
	}

}
​

4. MsgBeanImpl2.java

package bean;

public class MsgBeanImpl2 implements MsgBean {
	
	private String name;
	private String msg;
	
	public MsgBeanImpl2(String name) {
		super();
		this.name = name;
	}

	@Override
	public void setMsg() {
		System.out.println(msg + " " + name);
	}
	
	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

}
​

5. MsgBeanValues.java

package bean;

import org.springframework.stereotype.Component; // Component annotation
// import org.springframework.stereotype.Service; // Service annotation

// Service annotation은 Component 에서 특화된 클래스이기 때문에
// 동일한 표현으로 사용할 수 있다.
// 그러나, 비즈니스 로직 서비스(Business Logic Service)를 제공하는 것이 아닌, 
// application에 대한 설정 정보(context, metadata)를 저장하는 클래스인 경우는
// 논리적으로는 Component를 사용하는 것이 합당하다.
@Component("msgBeanValues") 
// @Service("msgBeanValues")
public class MsgBeanValues {
	
  // 아래의 필드에서 private을 하지 않도록 유의하자! (참조 불가 Error 유발!)
  // 호출하여 사용하는 값이기 때문에 public으로 공유해주는 것이 좋겠다.

  public String name = "Spring ~~~ !"; 
  
  public String msg = "Hi ~~~~ !"; 

}
6. Test : MsgApp.java

package bean.test;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import bean.MsgBean;

public class MsgApp {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		Resource resource 
			= new ClassPathResource("bean/applicationContext.xml");
		
		@SuppressWarnings("deprecation")
		BeanFactory factory = new XmlBeanFactory(resource);
		MsgBean bean = (MsgBean)factory.getBean("msgBean");
		bean.setMsg();
	}

}
7. Test  : MsgApp2.java

package bean.test;

import org.springframework.context.support.GenericXmlApplicationContext;

import bean.MsgBean;

public class MsgApp2 {

	public static void main(String[] args) {

		GenericXmlApplicationContext ctx 
			= new GenericXmlApplicationContext();
		ctx.load("classpath:bean/applicationContext.xml");
		ctx.refresh(); 
		// Context 정보 갱신 메소드 추가 :  
		// 이 정보가 없으면 위의 xml 정보 변경후 제대로 반영 안될 수 있습니다.
		
		MsgBean bean = (MsgBean)ctx.getBean("msgBean");
		bean.setMsg();
	}

}

 

 

제어의 역행(역전) : IoC(DI)-3 : 자바빈 의존성 정보 주입(injection) 애너테이션 적용

1. @Autowired

가장 많이 사용되는 애너테이션으로 주입(injection)하려는 객체 타입이 일치하는 객체를 검색하여 자동(auto)으로 주입합니다. 아래의 API 레퍼런스에서 볼 수 있는 바와 같이 부착 대상은 생성자, 메서드, 인자, 멤버 필드, 다른 애너테이션에 불일 수 있습니다. 단, setter(set method)에 붙일 경우는 반드시 기본 생성자(default constructor)가 정의가 전제되어야 합니다.

https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/annotation/Autowired.html

 

Autowired (Spring Framework 5.3.22 API)

Marks a constructor, field, setter method, or config method as to be autowired by Spring's dependency injection facilities. This is an alternative to the JSR-330 Inject annotation, adding required-vs-optional semantics. Autowired Constructors Only one cons

docs.spring.io

 

org.springframework.beans.factory.annotation
Annotation Type Autowired

@Target(value={CONSTRUCTOR,METHOD,PARAMETER,FIELD,ANNOTATION_TYPE})
@Retention(value=RUNTIME)
@Documented
public @interface Autowired
 
org.springframework.beans.factory.annotation
Annotation Type Qualifier

@Target(value={FIELD,METHOD,PARAMETER,TYPE,ANNOTATION_TYPE})
 @Retention(value=RUNTIME)
 @Inherited
 @Documented
public @interface Qualifier

대개 @Autowired와 같이 사용되는데 XML로 정의되었을 경우에 가령, <qualifier value="demoBean"> 태그를 <bean> 태그 내부에 삽입하여 등록하였을 경우, 자바에서 이를 호출할 때 아래와 같이 구체적인 명칭으로 와이어링(주입)할 수 있도록 도와줍니다. 같은 타입의 자바빈이 다수 있을 경우 특정 자바빈을 우선적으로 주입(injection, wiring) 할 수 있습니다.

<bean id="demoBean2" class="com.javateam.spring.bean.DemoBean2">
      <qualifier value="demoBean" />
</bean>

 

public class CallDemoBean {

     @Autowired 
     @Qualifier("demoBean")
     private DemoBean demoBean;
}

3. @Resource

이 애너테이션을 사용하려면 아래의 라이브러리 의존성 정보(아래는 maven 사례)가 있어야 합니다.

기본적으로 Java 측에서 제공하는 애너테이션으로써 아래와 같은 spec을 가지고 있습니다. (생성자에는 주입 불가). "name" 이라는 속성(attribute)가 있어서, 자바빈 주입시 자바빈의 이름으로 인식할 수 있습니다.

https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api/1.3.2

<!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

https://docs.oracle.com/javaee/7/api/javax/annotation/Resource.html

 

Resource (Java(TM) EE 7 Specification APIs)

The Resource annotation marks a resource that is needed by the application. This annotation may be applied to an application component class, or to fields or methods of the component class. When the annotation is applied to a field or method, the container

docs.oracle.com

 

javax.annotation
Annotation Type Resource

@Target(value={TYPE,FIELD,METHOD})
 @Retention(value=RUNTIME)
public @interface Resource

 

public class SimpleMovieLister {
 
    private MovieFinder movieFinder;
 
    @Resource(name="myMovieFinder")
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

 

4. @Inject

@Autowired 처럼 주입하려는 자바빈의 타입과 일치하는 객체를 알아서 자동으로 검색하여 주입합니다. 이도 역시 JEE 요소 중 하나로 아래와 같은 스펙을 가집니다.

역시 아래와 같은 라이브러리 의존성 정보가 필요합니다. (maven 사례)

https://mvnrepository.com/artifact/javax.inject/javax.inject

<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

https://docs.oracle.com/javaee/7/api/javax/inject/Inject.html

 

Inject (Java(TM) EE 7 Specification APIs)

Identifies injectable constructors, methods, and fields. May apply to static as well as instance members. An injectable member may have any access modifier (private, package-private, protected, public). Constructors are injected first, followed by fields,

docs.oracle.com

javax.inject
Annotation Type Inject

@Target(value={METHOD,CONSTRUCTOR,FIELD})
 @Retention(value=RUNTIME)
 @Documented
public @interface Inject

'Spring' 카테고리의 다른 글

AOP 01  (0) 2022.07.29
log4j2  (0) 2022.07.27
STS IoC컨테이너  (0) 2022.07.25
프레임워크 개념  (0) 2022.07.25
Spring 시작  (0) 2022.07.25