DI, AOP
스프링 전용 기술이 아니고 스프링에서 도입한 기술 중 하나이다.
면접 시 자주 묻는 질문 중 하나이다.
🔻Spring DI
DI : Dependency Injection 의 약자로 우리말로는 의존(성) 주입 이라고 한다.
스프링에서 중요한 개념으로 스프링 내의 모든 객체 관리에 사용된다.
정의 : 프로그래밍에서 구성 요소간의 의존 관계가 소스 내부가 아닌 외부 환경에서 정의되게하는 디자인 패턴
객체 간의 결합도를 낮추고 코드의 유지보수성을 높이기 위해 사용한다.
DI 예제
src/main/java > "com.test.spring.di01" 패키지 생성 후 하위에는 Main, Hong, Pen 클래스 생성한다.
Main.java
package com.test.spring.di01;
public class Main {
public static void main(String[] args) {
//main()을 실행하면 hong에게 업무를 위임하게된다.
Hong hong = new Hong();
hong.run();
}
}
Hong.java
package com.test.spring.di01;
public class Hong {
public void run() {
//Hong()을 실행하면 Pen에게 업무를 위임하게된다.
Pen p = new Pen();
p.write();
}
}
Pen.java
package com.test.spring.di01;
public class Pen {
public void write() {
System.out.println("Main > Hong > Pen");
}
}
Main > Hong > Pen 관계 살피기
Main은 Hong없이 업무를 실행할 수 없을 때 Hong을 의존한다고 한다. 또한 객체 hong을 Main의 의존객체 (Dependency Object)라고 한다.
Hong은 Pen없이 업무를 수행할 수 없으므로 의존관계라고 한다. p는 Hong의 의존 객체이다.
의존 관계 형성 방법
1. 기존 방법
- 의존 객체를 직접 생성한다. (Main에서의 Hong 객체 생성)
2. DI 패턴 방법
- 의존 주입 도구(생성자, setter)로 형성한다.
- 의존 주입 도구 : 의존 객체를 스스로 생성하지 않고, 외부로부터 건내받는 도구
- 생성자 : 딱 1번만 호출 가능하여 setter 보다 안정성이 높다.
- setter : 언제든 호출 가능하다.
Main.java
package com.test.spring.di01;
public class Main {
public static void main(String[] args) {
//Main 객체가 필요로하는 객체가 아니지만 Lee가 필요로 하는 객체를 생성한다.
Brush brush = new Brush();
//의존 주입(DI)이 발생했다.
Lee lee = new Lee(brush);
lee.run();
}
}
Main은 Lee에게 업무를 위임하고 Lee은 Brush에게 업무를 위임한다.
Main 업무를 위해 Lee 객체를 생성하는데 Lee 객체 생성자에 Brush 객체를 의존 주입하여 의존 관계를 만들어준다.
Lee.java
package com.test.spring.di01;
public class Lee {
private Brush brush;
//의존 주입 발생
public Lee(Brush brush) {
this.brush = brush;
}
public void setBrush(Brush brush) {
this.brush = brush;
}
public void run() {
brush.draw();
}
}
1. brush 변수를 멤버 변수로 승격
Lee 클래스에는 Brush 타입의 brush를 멤버 변수로 선언하며, 이 변수는 Lee 클래스가 의존하는 Brush 객체를 저장한다.
2. 생성자를 통한 의존 주입
Lee 클래스는 생성자를 통해 Brush 객체를 받아온다. 생성자를 통한 의존 주입은 의존성 주입의 한 형태이다.
생성자를 통해 의존성을 주입받으면, Lee 객체를 생성할 때 외부에서 필요한 Brush 객체를 전달해줄 수 있다.
3. setBrush 메서드를 통한 의존 주입
setBrush 메서드를 통해서도 Brush 객체를 주입할 수 있으며 이것은 setter를 이용한 의존성 주입이다.
이렇게 하면 객체 생성 후에도 필요에 따라 의존성을 변경할 수 있다.
4. run 메서드
run 메서드는 Brush 객체의 draw 메서드를 호출하여 작업을 수행한다.
이 메서드를 통해 Lee 클래스는 외부에서 주입된 Brush 객체를 사용하여 작업을 할 수 있다.
Brush.java
package com.test.spring.di01;
public class Brush {
public void draw() {
System.out.println("Main > Lee > Brush");
}
}
Spring DI 구현 (xml 방식)
Spring DI를 구현하는 방법으로는 1. XML 설정, 2. 어노테이션 설정, 3. Java 설정 세가지가 있다.
가장 고전적인 방법인 xml 방법으로 예제를 진행한다.
Bean 설정 파일 작성 (di02.xml)
bean 태그는 자바 객체 1개를 의미한다. 수많은 bean을 관리하는 부모 태그이자 스프링 프레임워크가 관리하는 객체이다.
1. bean 설정 코드
bean 태그를 사용하기 위해 아래 코드가 필요하다.
아래 코드는 Spring Framework에서 XML 파일이 Spring 설정 파일임을 나타내고, 해당 XML 파일에서 사용할 수 있는 스프링의 태그들을 선언한다. 이로써 스프링 컨테이너는 XML 파일을 읽고 이 안에 정의된 빈들과 설정들을 해석하여 애플리케이션을 구성할 수 있게한다.
<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">
2. bean 등록하기
스프링이 관리하는 대상에 등록되었다. 오브젝트 생성된 단계가 아닌 대상에 등록만 된 상태이다.
<bean id="pen" class="com.test.spring.di02.Pen"></bean>
id 속성: id 속성은 해당 빈을 식별하는 데 사용된다. 다른 부분에서 이 id를 참조하여 해당 빈을 가져와 사용할 수 있다.
class 속성: 빈이 어떤 클래스를 인스턴스화할지를 나타낸다. 여기서는 "cohttp://m.test.spring.di02.Pen" 클래스를 사용하는 것이다.
이 방법은 순수 자바로 구현한 DI이다.
Spring DI로 구현하는 방법은 아래와 같다.
방법1. 정석
hong이 pen에게 업무를 위임하여 의존하듯이 hong의 bean를 정의할 때 <constructor-arg>로 hong에게 pen 의존성 주입을 한다.
<bean id="hong" class="com.test.spring.di02.Hong">
<constructor-arg>
<bean class="com.test.spring.di02.Pen"></bean>
</constructor-arg>
</bean>
방법2. 참조
ref 속성으로 위에서 생성한 pen의 bean를 참조한다.
<bean id="hong" class="com.test.spring.di02.Hong">
<constructor-arg ref="pen"></constructor-arg>
</bean>
Main > (의존) > Park > Choi > Brush
1. 정석방식
<bean class="com.test.spring.di02.Park">
<constructor-arg>
<bean class="com.test.spring.di02.Choi">
<property name="brush">
<bean class="com.test.spring.di02.Brush"></bean>
</property>
</bean>
</constructor-arg>
</bean>
2. 참조를 하면 가독성과 재사용성이 높아진다.
<bean class="com.test.spring.di02.Brush"></bean>
<bean id="choi" class="com.test.spring.di02.Choi">
<property name="brush" ref="brush"></property>
</bean>
<bean id="park" class="com.test.spring.di02.Park">
<constructor-arg ref="choi"></constructor-arg>
</bean>
di02.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 순수 자바로 구현하는 DI -->
<bean id="pen" class="com.test.spring.di02.Pen"></bean>
<bean id="brush" name="b1" class="com.test.spring.di02.Brush"></bean>
<!-- Spring DI -->
<!-- 정석
<bean id="hong" class="com.test.spring.di02.Hong">
<constructor-arg>
<bean class="com.test.spring.di02.Pen"></bean>
</constructor-arg>
</bean>
-->
<!-- 참조 -->
<bean id="hong" class="com.test.spring.di02.Hong">
<constructor-arg ref="pen"></constructor-arg>
</bean>
<!-- Main > (의존) > Park > Choi > Brush -->
<!-- 정석
<bean class="com.test.spring.di02.Park">
<constructor-arg>
<bean class="com.test.spring.di02.Choi">
<property name="brush">
<bean class="com.test.spring.di02.Brush"></bean>
</property>
</bean>
</constructor-arg>
</bean> -->
<!-- 참조(가독성, 재사용성이 높아짐) -->
<bean class="com.test.spring.di02.Brush"></bean>
<bean id="choi" class="com.test.spring.di02.Choi">
<property name="brush" ref="brush"></property>
</bean>
<bean id="park" class="com.test.spring.di02.Park">
<constructor-arg ref="choi"></constructor-arg>
</bean>
</beans>
Main.java
package com.test.spring.di02;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
//ApplicationContext context = new ClassPathXmlApplicationContext("file:/src/main/java/com/test/spring/di02/di02.xml"); //절대 경로
ApplicationContext context = new ClassPathXmlApplicationContext("com/test/spring/di02/di02.xml"); //상대 경로
Pen p2 = (Pen)context.getBean("pen");
p2.write();
}
}
1. xml 읽기
위에서 생성한 di02.xml을 읽어온다. xml 파일의 경로는 절대경로, 상대경로로 표현 가능하다.
ApplicationContext context = new ClassPathXmlApplicationContext("xml 파일의 경로");
2. xml에서 빈 가져오기
빈을 1개 가져온다는건 객체를 1개 생성 요청하는 것과 같다. == new Pen();
context.getBean 으로 가져오며, 반드시 다운캐스팅을 해야한다.
Pen p2 = (Pen)context.getBean("pen");
스프링 프레임워크는 객체의 생성부터 소멸까지의 생명주기를 자동으로 관리해주는 특징을 가지고 있다. 이는 개발자가 직접 객체의 생명주기를 관리할 필요 없이, 스프링이 필요한 객체를 생성하고 필요에 따라 소멸시키는 과정을 대신 수행해준다.
또한, 스프링은 의존 주입(Dependency Injection)이라는 기능을 통해 객체 간의 관계를 중앙 집중적으로 관리한다. 이는 객체들 간의 의존 관계를 한 곳에서 설정하고 주입함으로써 코드의 유연성을 높이고, 유지보수를 용이하게 만든다. 개발자는 객체 간의 의존 관계를 직접 코드로 구현하는 것이 아니라, 스프링의 설정 파일(XML 또는 Java Config)을 통해 간단하게 정의할 수 있다.
이러한 특징들로 스프링을 사용하면 개발자는 주로 비즈니스 로직에 집중할 수 있으며, 객체의 생성과 관계 설정에 대한 부분은 스프링이 자동으로 처리해주기 때문에 생산성이 향상된다.
'Spring' 카테고리의 다른 글
[Spring] @Controller 기초 및 데이터 송수신하기 (0) | 2023.11.25 |
---|---|
[Spring] 스프링 프로젝트 설정 일괄 적용 (0) | 2023.11.24 |
[Spring] Spring AOP 기초 및 예제 (0) | 2023.11.23 |
[Spring] Spring Framework란 (0) | 2023.11.21 |
[Spring] Spring Tool Suite (STS3) 사용법 (0) | 2023.11.21 |