1. 의존성 주입하기
의존성 주입(DI, Dependency Injection)이란?
클래스의 연관 관계를 개발자가 직접 코딩을 통해 컴포넌트(클래스)에 부여하는 것이 아니라 컨테이너가 연관 관계를 직접 규정하는 것이다.
코드에서 직접적인 연관 관계가 발생하지 않으므로 각 클래스들의 변경이 자유로워 진다.(loosely coupled, 약한 결합)
의존성 주입이 필요한 이유
기존의 자바 코드에서는 직접 객체를 생성하는 것(tightly coupled)은 복잡한 문제를 일으킨다. 다른 클래스의 변경 사항이 연속적으로 다른 부분에 영향을 미친다면 이 방법은 좋은 방법이 아니다.
문제 해결 방법 1) 인터페이스 적용
인터페이스를 사용하면 각 클래스를 구현한 후 클래스의 객체를 사용할때는 인터페이스 타입으로 선언한 참조 변수로 접근해서 사용하면 된다. 그러나 인터페이스를 사용해도 소스코드를 직접 수정할 일이 생긴다.
문제 해결 방법 2) 의존성 주입을 적용
의존성 주입을 적용했을 때의 장점
- 클래스들 간의 의존관계 최소화하여 코드 단순화
- 유지 및 관리가 쉬워짐
- 기존 구현 방법은 개발자가 직접 코드 안에서 객체의 생성과 소멸을 제어했지만, 의존성 주입은 객체의 생성, 소멸과 객체 간의 의존 관계를 컨테이너가 제어
DI는 객체의 생성, 소멸, 의존 관계를 개발자가 직접 설정하는 것이 아니라 XML이나 애너테이션 설정을 통해 스프링 프레임워크가 제어한다.(제어의 역전, IoC)
예시 코드
public class BoardServiceImpl implements BoardService{
private BoardDAO boardDAO;
public BoardServiceImpl(BoardDAO boardDAO){
this.boardDAO = boardDAO;
}
...
BoardServiceImpl 클래스는 의존하는 BoardDAOImpl 객체를 전달받기 위해 new키워드를 사용해 객체를 생성하지 않고 생성자를 호출 할 때 외부에서 객체를 주입 받아 사용했다.
이것이 바로 DI를 적용한 예이다.
소스 코드에서 new를 사용해 객체를 생성하는 것이 아니라 BoardServiceImpl 생성자를 호출할 때 컨테이너에 의해 주입되는 객체로 boardDAO 변수를 초기화 한것이다.
위의 그림처럼 스프링에서는 의존(dependency)하는 객체를 컨테이너 실행 시 주입(inject)하기 때문에 DI(Dependency Injection)라고 부른다.
2. 의존성 주입 실습하기
스프링 의존 객체 주입 방식은 두가지가 있다. setter을 이용한 주입과 생성자를 이용한 주입이다.
우선 setter을 이용한 실습을 해보자
1) setter을 이용한 DI 기능
<bean> 태그에 사용되는 여러 속성들
속성이름 | 설명 |
id | 빈 객체의 고유 이름으로, 빈 id를 이용해 빈에 접근합니다. |
name | 객체의 별칭입니다 |
class | 생성할 클래스입니다. 패키지 이름까지 입력해야 합니다. |
constructor-arg | 생성자를 이용해 값을 주입할 때 사용합니다. |
property | setter를 이용해 값을 주입할 때 사용합니다. |
lazy-init | • 빈 생성을 톰캣 실행 시점이 아닌 해당 빈 요청 시 메모리에 생성할 수 있습니다. • true, false, default 세가지 값을 지정할 수 있습니다. • 설정하지 않거나 false로 설정 시 톰캣 실행 시 빈이 생성됩니다. • true로 설정 시 해당 빈 사용시 빈이 생성됩니다. |
스프링관련 라이브러리 패스 설정하고 진행해야한다. (https://sap518.tistory.com/84 참고)
person.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="personService" class="com.spring.ex01.PersonServiceImpl">
<property name="name">
<value>홍길동</value>
</property>
</bean>
</beans>
<bean> 태그를 이용해 com.spring.ex01.PersonServiceImpl클래스에 대한 빈을 생성한다.
그리고 이 빈에 접근할 수 있는 빈 id를 personService로 지정한 후 <property>태그를 이용해 PersonServiceImple 클래스 객체의 name속성에 <value>태그의 값으로 초기화 한다.
소스 코드에서 new를 이용해 직접 객체를 생성하던 것을 person.xml에서 설정한 것이다
PersonService.java
public interface PersonService {
public void sayHello();
}
인터페이스에 추상메서드 sayHello()를 선언한다
PersonServiceImpl.java
public class PersonServiceImpl implements PersonService {
private String name;
private int age;
//<value> 태그의 값을 setter를 이용해 설정
public void setName(String name) {
this.name = name;
}
@Override
public void sayHello() {
System.out.println("이름: " + name);
System.out.println("나이: " + age);
}
}
PersonServiceImpl에서 인터페이스 PersonService를 구현하고 setter를 이용해 person.xml에서 <value>태그로 설정한 값을 name속성에 주입한다.
단, age속성은 setter가 없으므로 빈이 생성되더라도 값이 초기화 되지 않는다
PersonTest.java
public class PersonTest {
public static void main(String[] args) {
// 실행시 person.xml을 읽어 들여 빈을 생성한다
BeanFactory factory = new XmlBeanFactory(new FileSystemResource("person.xml"));
//id가 personService인 빈을 가져온다
PersonService person = (PersonService) factory.getBean("personService");
// 생성된 빈을 이용해 name 값을 출력한다
person.sayHello();
}
}
콘솔에 name속성 값은 person.xml에서 <value>태그로 설정한 값이 출력되지만 age속성은 0이 출력된다
2) 생성자를 이용한 DI 기능
person.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="personService1" class="com.spring.ex02.PersonServiceImpl">
<constructor-arg value="이순신" />
</bean>
<bean id="personService2" class="com.spring.ex02.PersonServiceImpl">
<constructor-arg value="손흥민" />
<constructor-arg value="23" />
</bean>
</beans>
<bean>태그를 이용해 id가 personService1인 빈을 생성하고 <constructor-arg>태그를 이용해 생성자 호출 시 생성자 매개변수로 <value> 태그의 값을 전달하여 속성을 초기화한다.
이처럼 PersonServiceImpl 클래스의 인자가 한 개인 생성자를 이용하여 name 속성에 <value>태그 값이 주입된다.
두번째 <bean>태그에서는 빈 생성 시 인자가 두 개인 생성자를 호출하면서 두 개의 값을 전달하여 각각의 속성을 초기화한다.
즉, PersonSerciceImpl 클래스의 인자가 두 개인 생성자를 이용해 name과 age속성에 <value>태그 값이 차례대로 주입된다
PersonServiceImpl.java
public class PersonServiceImpl implements PersonService {
private String name;
private int age;
public PersonServiceImpl(String name) {
this.name = name;
}
public PersonServiceImpl(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sayHello() {
System.out.println("이름: " + name);
System.out.println("나이: " + age + "살");
}
}
인자가 한개인 생성자와 두 개인 생성자를 구현한다.
PersonTest.java
public class PersonTest {
public static void main(String[] args) {
// 실행시 person.xml을 읽어 들여 빈을 생성한다
BeanFactory factory = new XmlBeanFactory(new FileSystemResource("person.xml"));
//id가 personService인 빈을 가져온다
PersonService person = (PersonService) factory.getBean("personService");
// 생성된 빈을 이용해 name 값을 출력한다
person.sayHello();
}
}
id가 personService1인 빈에 접근하여 sayHello()메서드를 호출하면 age는 0으로 출력된다. 반면에 id가 personService2인 빈에 접근한 후 sayHello()메서드를 호출하면 두 속성 값이 모두 출력된다.
3. 회원 기능 이용해 DI 실습하기
회원 기능 관련 Service 클래스와 DAO클래스의 계층 구조다.
Service 클래스는 DB와 연동을 위해 DAO클래스에 의존하는 관계임을 알 수 있다
member.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="memberService" class="com.spring.ex03.MemberServiceImpl">
<property name="memberDAO" ref="memberDAO" />
</bean>
<bean id="memberDAO" class="com.spring.ex03.MemberDAOImpl" />
</beans>
id가 memberService인 빈이 id가 memberDAO인 빈을 자신의 속성 memberDAO에 바로 주입한다
기본형 데이터가 아닌 참조형 데이터일 경우 ref속성을 주입해야 한다.
MemberServiceImpl.java
public class MemberServiceImpl implements MemberService {
private MemberDAO memberDAO;
public void setMemberDAO(MemberDAO memberDAO) {
this.memberDAO = memberDAO;
}
@Override
public void listMembers() {
memberDAO.listMembers();
}
}
setter로 주입되는 빈을 받을 MemberDAO타입의 속성과 setter를 이용해 구현한다.
MemberDAOImpl.java
public class MemberDAOImpl implements MemberDAO {
@Override
public void listMembers() {
System.out.println("listMembers 메서드 호출");
System.out.println("회원정보를 조회합니다.");
}
}
MemberTest1.java
public class MemberTest1 {
public static void main(String[] args) {
BeanFactory factory = new XmlBeanFactory(new FileSystemResource("member.xml"));
MemberService service = (MemberService) factory.getBean("memberService");
service.listMembers();
}
}
member.xml을 읽어 들인 후 빈을 생성한다. 그리고 setter주입 방식으로 주입한 후 빈 id인 memberService로 접근하여 listMembers()메서드를 호출한다.
이 예제에서는 자바코드로 어떤 클래스 객체도 생성하지 않았다. 오로지 스프링 DI기능을 이용해 빈을 생성했고, 의존 관계에 있는 빈을 속성에 주입한 후 빈의 기능을 사용했다.
이후 진행할 스프링 관련 실습에서도 빈을 생성한 후 사용한다. 자바 코드로 직접 빈을 생성해서 사용하는 방식과 DI를 이용해서 실습한 방식이 어떻게 다른지 잘 구분하자!
참고 : ⌜자바 웹을 다루는 기술⌟ 책을 공부하며 요약・정리한 내용입니다.
'💻 Web_Back end > Spring' 카테고리의 다른 글
스프링 트랜잭션 기능 (0) | 2023.05.22 |
---|---|
스프링 JDBC 기능 (0) | 2023.05.16 |
스프링 MVC 기능 (0) | 2023.05.07 |
스프링 AOP (Aspect-Oriented Programming) 관점 지향 프로그래밍 기능 (0) | 2023.05.07 |
스프링 프레임워크 시작하기 (0) | 2023.05.06 |