본문 바로가기

서버 심화 스터디

의존성 주입(Dependency Injection) 을 구현한 예제<알림 서비스>

1. Sender.java(인터페이스)

메시지를 보내는 기능을 정의 

package depth.notification.sender;

public interface Sender {
    void send(String message);
}

 

2. EmailSender.java

  • Sender 인터페이스를 구현한 구체적인 클래스
  • send 메서드를 구현하여 이메일 메시지를 발송하는 기능을 제공
package depth.notification.sender;

public class EmailSender implements Sender {
    @Override
    public void send(String message) {
        System.out.println("[EMAIL 발송]: " + message);
    }
}

 

 3. NotificationService.java (인터페이스)

NotificationService 인터페이스는 사용자에게 알림을 보내는 기능을 정의 

package depth.notification.service;

public interface NotificationService {
    void notifyUser(String message);
}

 

4. NotificationServiceImpl.java (생성자 주입)

  • NotificationService 인터페이스를 구현하며, 실제로 사용자를 알리기 위해 Sender 객체(이 예제에서는 EmailSender)를 사용
  • 생성자 주입 방식으로 Sender를 의존성으로 받음
package depth.notification.service;

import depth.notification.sender.Sender;

public class NotificationServiceImpl implements NotificationService {
    private final Sender sender;

    public NotificationServiceImpl(Sender sender) {
        this.sender = sender;
    }

    @Override
    public void notifyUser(String message) {
        sender.send(message);
    }
}

 

5. XmlApplicationContext.java (XML 기반 DI 설정 로더)

  • 이 클래스는 Spring의 ApplicationContext를 사용하여 beans.xml 파일을 로드하고, 이를 통해 의존성 주입을 관리하는 역할을 함
  • ClassPathXmlApplicationContext를 사용하여 XML 설정 파일을 읽고, 그 안에서 정의된 빈들을 관리함
package depth.notification.config;

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

public class XmlApplicationContext {
    public static ApplicationContext loadContext() {
        return new ClassPathXmlApplicationContext("beans.xml");
    }
}

 

6. App.java (실행 클래스)

  • XmlApplicationContext를 통해 Spring의 ApplicationContext를 로드하고, NotificationService 빈을 가져와 사용자에게 알림을 보냄
  • Spring 프레임워크의 IoC 컨테이너가 beans.xml파싱하고 객체를 생성·관리
package depth.notification;

import depth.notification.config.XmlApplicationContext;
import depth.notification.service.NotificationService;
import org.springframework.context.ApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext context = XmlApplicationContext.loadContext();
        NotificationService service = context.getBean(NotificationService.class);
        service.notifyUser("생성자 주입으로 메시지 발송!");
    }
}

 

7.  beans.xml (DI 설정)

  • Spring에서 의존성 주입을 어떻게 할지 설정하는 XML 파일
  • 이 파일에서 EmailSender와 NotificationServiceImpl을 정의하고, NotificationServiceImpl의 생성자에 EmailSender를 주입
<?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
                           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- EmailSender 빈 정의 -->
    <bean id="emailSender" class="depth.notification.sender.EmailSender" />

    <!-- NotificationServiceImpl 빈 정의 및 생성자 주입 -->
    <bean id="notificationService"
          class="depth.notification.service.NotificationServiceImpl">
        <constructor-arg ref="emailSender"/>
    </bean>

    <!-- EmailService (직접 DI 컨테이너용) -->
    <bean id="emailService"
          class="depth.notification.EmailService"
          init-method="init"
          destroy-method="cleanup">
        <constructor-arg ref="emailSender"/>
    </bean>

</beans>

핵심개념

  • 의존성 주입 (Dependency Injection):
    • NotificationServiceImpl 클래스는 EmailSender 객체를 생성자 주입 방식으로 받는다. 즉, EmailSender 객체는 외부에서 주입된다.
    • SpringIoC 컨테이너beans.xml정의된 설정을 바탕으로 의존성 주입을 자동으로 처리한다.
  • XML 기반 설정:
    • beans.xml 파일은 Spring 설정 파일로, NotificationServiceImpl 클래스와 EmailSender 클래스의 관계를 설정한다.
    • SpringNotificationServiceImpl 객체를 생성할 때, EmailSender 객체를 생성자 주입 방식으로 자동으로 주입한다.
  • 실행 흐름:
    • App.java에서 ApplicationContext로드하고, NotificationService 빈을 가져와서 notifyUser 메서드를 호출하면, EmailSendersend 메서드가 실행되어 메시지가 출력된다.

 


직접만든 간단한 DI 컨테이너를 사용해서 beans.xml 설정에 따라 객체를 생성하고 의존성을 주입하며,  생명주기 메서드를 호출 하는 구조

1. SimpleBeanFactory.java

직접 구현한 간단한 DI 컨테이너

  • 하는 일:
    • beans.xml 파일을 파싱해서 <bean><constructor-arg>읽음
    • 클래스 로딩 → 객체 생성 → 생성자 주입 수행
    • 초기화 메서드(init-method) 실행
    • 종료 메서드(destroy-method) 실행
  • 특징:
    • Spring 없이도 DI 가능
    • 의존성 1개인 생성자만 처리 (간단한 구조)
public class SimpleBeanFactory {
    private final Map<String, Object> beanMap = new HashMap<>();
	
    // beans.xml경로를 받아 파싱 시작
    public SimpleBeanFactory(String configLocation) throws Exception {
        loadBeans(configLocation);
    }

    private void loadBeans(String configLocation) throws Exception {
        File file = new File(configLocation);
        Document document = DocumentBuilderFactory.newInstance()
                .newDocumentBuilder().parse(file);
        Element root = document.getDocumentElement();
        NodeList beans = root.getElementsByTagName("bean");

        // 1차 생성 (빈 생성만)
        for (int i = 0; i < beans.getLength(); i++) {
            Element bean = (Element) beans.item(i);
            String id = bean.getAttribute("id");
            String className = bean.getAttribute("class");
            Class<?> clazz = Class.forName(className);
            beanMap.put(id, clazz); // 임시 저장
        }

        // 2차 주입 (실제 객체 생성 + 의존성 주입)
        for (int i = 0; i < beans.getLength(); i++) {
            Element bean = (Element) beans.item(i);
            String id = bean.getAttribute("id");
            Class<?> clazz = (Class<?>) beanMap.get(id);

            Object instance;
            NodeList constructorArgs = bean.getElementsByTagName("constructor-arg");

            if (constructorArgs.getLength() == 0) {
                instance = clazz.getDeclaredConstructor().newInstance();
            } else {
                // 단일 생성자 인자 가정
                Element arg = (Element) constructorArgs.item(0);
                String ref = arg.getAttribute("ref");
                Object dependency = beanMap.get(ref);
                if (dependency instanceof Class<?>) {
                    dependency = ((Class<?>) dependency).getDeclaredConstructor().newInstance();
                    beanMap.put(ref, dependency);
                }
                Constructor<?> constructor = clazz.getDeclaredConstructor(dependency.getClass());
                instance = constructor.newInstance(dependency);
            }

            // init-method 실행
            if (bean.hasAttribute("init-method")) {
                String initMethod = bean.getAttribute("init-method");
                Method method = instance.getClass().getMethod(initMethod);
                method.invoke(instance);
            }

			// 생성된 객체를 beanMap에 저장
            beanMap.put(id, instance);
        }
    }

    public Object getBean(String id) {
        return beanMap.get(id);
    }

	// XML에 등록된 destroy-method 호출
    public void destroyBeans(String configLocation) throws Exception {
        File file = new File(configLocation);
        Document document = DocumentBuilderFactory.newInstance()
                .newDocumentBuilder().parse(file);
        Element root = document.getDocumentElement();
        NodeList beans = root.getElementsByTagName("bean");

        for (int i = 0; i < beans.getLength(); i++) {
            Element bean = (Element) beans.item(i);
            String id = bean.getAttribute("id");
            Object instance = beanMap.get(id);
            if (instance != null && bean.hasAttribute("destroy-method")) {
                String destroyMethod = bean.getAttribute("destroy-method");
                Method method = instance.getClass().getMethod(destroyMethod);
                method.invoke(instance);
            }
        }
    }
}

2. EmailService.java

++생명주기 관리 포함

  • 하는 일:
    • 생성자로 EmailSender주입받음
    • sendWelcomeEmail()이메일 전송 시뮬레이션
    • init() / cleanup() 메서드를 통해 초기화 종료 처리 로그 출력
  • 특징:
    • XML에서 init-method, destroy-method 속성으로 해당 메서드 호출
public class EmailService {
    private final EmailSender sender;

    public EmailService(EmailSender sender) {
        this.sender = sender;
    }

    public void sendWelcomeEmail(String to) {
        sender.send("[TO: " + to + "] Welcome! Glad to have you.");
    }

	// SimpleBeanFactory가 초기화 시 자동 호출
    public void init() {
        System.out.println("EmailService 초기화 완료");
    }
	// 종료 시 호출
    public void cleanup() {
        System.out.println("EmailService 종료 처리");
    }
}

 

 

3. App.java

public class App {
    public static void main(String[] args) throws Exception {
        // Spring 방식
        ApplicationContext context = XmlApplicationContext.loadContext();
        NotificationService service = context.getBean(NotificationService.class);
        service.notifyUser("생성자 주입으로 메시지 발송!");

        // 또는 직접 구현 방식
        SimpleBeanFactory factory = new SimpleBeanFactory("resources/beans.xml");
        EmailService emailService = (EmailService) factory.getBean("emailService");
        emailService.sendWelcomeEmail("user@example.com");
        factory.destroyBeans("resources/beans.xml");
    }
}

 

4. beans.xml

  • init-method="init"객체 생성 호출됨
  • destroy-method="cleanup"명시적으로 종료 처리할 호출됨
<beans>
    <bean id="emailSender" class="depth.notification.sender.EmailSender"/>

    <bean id="emailService"
          class="depth.notification.EmailService"
          init-method="init"
          destroy-method="cleanup">
        <constructor-arg ref="emailSender"/>
    </bean>
</beans>

 

 

'서버 심화 스터디' 카테고리의 다른 글

AOP(Aspect-Oriented Programming)  (0) 2025.05.16
트랜잭션  (0) 2025.05.16
의존성 주입(Dependency Injection)  (0) 2025.05.09
서블릿 필터와 리스너  (0) 2025.05.02
서블릿 컨테이너 구현  (0) 2025.05.02