Dependency Injection(의존관계 주입)이란?

 

이 글은 IoC , Dependency 의 개념을 알고있다는 전제하에 작성된 글입니다.

원활한 이해를 위해서 아래글을 읽어주세요

https://masiljangajji-coding.tistory.com/51

 

IoC(Inversion Of Control)란

IoC/DI(Inversion Of Control/Dependency Injection)란 IoC(Inversion Of Control)란? IoC는 제어의 역전을 뜻합니다. 제어의 역전.. 제어가 역전된다.. 이게 어떤 의미일까요? 기존의 프로그램은 구현 객체가 프로그램

masiljangajji-coding.tistory.com

https://masiljangajji-coding.tistory.com/52

 

Dependency(의존관계)란?

Dependency(의존관계)란? 의존관계는 코드에서 두 모듈간의 연결을 의존관계라 합니다. 객체지향언어에서 두 클래스 간의 관계를 말하기도 합니다. 의존관계의 종류는 크게 4가지가 존재합니다. Dep

masiljangajji-coding.tistory.com

 

Spring Bean과 Context

Spring Bean

Spring Bean은 Spring Container에 등록된 객체를 의미합니다.

Java Beans 와는 다른것으로 Spring FrameWork에서 중요하게 관리하는 객체로 이해하면 됩니다.

 

Java Beans 의 조건

  1. public default (no argument) constructor
  2. getter/setter
  3. implement java.io.Serializable

Container에 Bean이 등록되기 위한 조건

  1. name(식별할 이름)
  2. class(타입)
  3. object(객체)

Java Beans와 다른 별개의 개념입니다.

 

Container/Context

Container는 Interface로 일종의 개념에 해당합니다.

프로그램의 구성 요소들을 담고 관리하는 환경을 나타내며 DI Container , 서블릿 컨테이너 , 웹 컨테이너 등등이 존재합니다.

 

Context는 Container라는 Interface의 구현체입니다.

(Container = DI Container = IoC Container)

 

스프링 프레임워크에서는 빈(Bean) 설정과 관련된 정보를 나타내는 Metadata에 따라

어떤 클래스의 ApplicationContext를 이용할지 정해집니다.

 

다음의 Git Repo를 참고하시길 바랍니다.

https://github.com/masiljangajji/DI_Exapmle

 

GitHub - masiljangajji/DI_Exapmle

Contribute to masiljangajji/DI_Exapmle development by creating an account on GitHub.

github.com

 

XML 을 이용한 DI

xml 파일을 이용해 의존관계를 주입하는 방식입니다.

Context에 등록을 돕는 메타데이터 형식이 xml형식이며 ClassPathXmlApplicationContext 클래스를 사용합니다.

<bean id="english" class="com.springframework.core.practice.language.domain.English"/>

id는 Bean에 등록될 이름을 , class는 클래스의 경로를 나타냅니다

 

이 코드는 다음과 같습니다.

  1. english라는 이름으로
  2. com.springframework.core.practice.language.domain.English 경로에있는
  3. 클래스파일을 reflection으로 읽어 객체를 생성하고 컨테이너에 등록해
public class XmlMain {


    public static void main(String[] args) {

        // 컨텍스트 등록을 돕는 메타데이터로 xml 선택시 ClassPathXmlApplicationContext 사용
        try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                // resources/beans.xml 파일을 읽어서 컨테이너에 객체 등록하겠다.
                "beans.xml")) {

            English english = context.getBean("english", English.class);
            Korean korean = context.getBean("korean", Korean.class);
            english.hello();
            korean.hello();
        }

    }

}

korean은 컨테이너에 등록이 안됐기 때문에 다음과 같은 에러가 발생 No bean named 'korean' available

(English와 같은 방식으로 xml에 추가하면 에러가 해결됩니다.)

 

생성자 주입

public class LanguageSpeakService {

    private final Language language;

    public LanguageSpeakService(Language language) {
        this.language = language;
    }

    public void speak() {
        System.out.println("Speak Service");
        language.hello();
    }

}

 

LanguageSpeakService 클래스는 Language에 대한 의존관계를 갖습니다.

 

Field로 갖고있는 타입이 Interface기 때문에 실질적인 동작은 Language의 SubType인

English,Korean 중 하나를 받아 동작할 것입니다.

 

따라서 기존과 같은 방법으로 DI시켜주게 된다면

LanguageSpeakService languageSpeakService =
                    context.getBean("languageSpeakService", LanguageSpeakService.class);

    <bean id="languageSpeakService" class="com.springframework.core.practice.language.service.LanguageSpeakService"/>

스프링은 Service가 필요한 Language가 English , Korean 중 무엇인지 알 수 없음으로 에러가 발생하게 됩니다.

 

이런 경우 Constructor Injection(생성자 주입) 방식을 사용합니다.

 

<bean id="languageSpeakService" class="com.springframework.core.practice.language.service.LanguageSpeakService">
        <constructor-arg ref="korean"/>
</bean>

 

이 코드는 다음과 같습니다.

  1. languageSpeakService라는 이름으로
  2. com.springframework.core.practice.language.service.LanguageSpeakService 경로에있는
  3. 클래스파일을 reflection으로 읽어 객체를 생성할때 korean 이름으로 등록된 Bean을 참고해
  4. 만들어진 languageSpeakService 객체를 컨테이너에 등록해
 public static void main(String[] args) {

        // 컨텍스트 등록을 돕는 메타데이터로 xml 선택시 ClassPathXmlApplicationContext 사용
        try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                // resources/beans.xml 파일을 읽어서 컨테이너에 객체 등록하겠다.
                "beans.xml")) {

            English english = context.getBean("english", English.class);
            Korean korean = context.getBean("korean", Korean.class);
            english.hello();
            korean.hello();


            LanguageSpeakService languageSpeakService =
                    context.getBean("languageSpeakService", LanguageSpeakService.class);

            languageSpeakService.speak();

        }

생성자 주입 사용시 코드가 문제없이 동작하게 됩니다.

 

setter 주입

public class LanguageSpeakService {

    private  Language language;

    // setter 주입을 위한 기본 생성자
    public LanguageSpeakService(){

    }
    public LanguageSpeakService(Language language) {
        this.language = language;
    }

    public void setLanguage(Language language) {
        this.language = language;
    }

    public void speak() {
        language.hello();
    }

}

<bean id="languageSpeakService" class="com.springframework.core.practice.language.service.LanguageSpeakService">
        <property name="language" ref="english"/>
</bean>

이 방식은 language 에 대한 표준 명명규칙을 따르는 set메서드를 찾아갑니다.

 

실질적으론 setLanguage 메서드를 찾아 동작하는 것이죠 따라서 name을 바꾼다면 정상동작 하지 않습니다.

<bean id="languageSpeakService" class="com.springframework.core.practice.language.service.LanguageSpeakService">
        <property name="language2" ref="english"/>
</bean>

 

표준 명명규칙을 따르지 않기 떄문에 오류가 발생 (property name 을 language로 변경하면 정상동작 합니다.)

 

 public void setLanguage2(Language language) {
        this.language = language;
    }

<bean id="languageSpeakService" class="com.springframework.core.practice.language.service.LanguageSpeakService">
        <property name="language2" ref="english"/>
</bean>

이렇게 set메서드 이름을 맞춰줄시 정상동작 합니다.
(name = setLanguage2 를 부르겠다 , 넣어줄 인자는 english 이름을 가진 빈)

 

이렇듯 setter주입은 명명규칙을 지켜줘야 하며 기본 생성자가 필요합니다.

 

Autowired Injection

autowired injection 은 다음의 방식을 사용할 수 있습니다.

  1. byType
  2. byName
<bean id="koreanChef" class="com.springframework.core.practice.domain.chef.KoreanChef"/>
<bean id="chefCookService" class="com.springframework.core.practice.service.ChefCookService" autowire="byType"/>

public class ChefCookService {
    private Chef chef;

    public ChefCookService() {
    }

    public void setChef(Chef chef) {
        this.chef = chef;
    }

    public void makeFood() {
        chef.cook();
    }

}

autowired byType설정을 사용하려면 일치하는 빈이 하나여야 합니다.

 

지금은 일치하는 빈이 KoreanChef 하나기 때문에 정상동작 합니다.

하지만 AmericanChef가 추가된다면 일치하는 빈에 중복이 생기며 에러가 발생합니다.

 

<bean id="chefCookService" class="com.springframework.core.practice.service.ChefCookService" autowire="byName"/>

// 양식 조리 출력 
public void setAmericanChef(Chef chef) {
        this.chef = chef;
}

// 한식 조리 출력
public void setKoreanChef(Chef chef) {
        this.chef = chef;
    }

byName을 사용하게되면 set메서드의 이름을 따라갑니다.

 

XML 이용한 DI With Annotation

XML 방식으로 Bean 의존성 주입을 Annotation으로 구현할 수 있습니다.

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

(Autowired는 생성자 , 메서드 , 파라미터 , 필드 등 대부분에 사용가능)

 

<bean id="koreanChef" class="com.springframework.core.practice.domain.chef.KoreanChef"/>
<bean id="americanChef" class="com.springframework.core.practice.domain.chef.AmericanChef"/>
<bean id="chefCookService" class="com.springframework.core.practice.service.ChefCookService"/>

public class ChefCookService {

    //// Autowired 어노테이션 기반 설정
    @Autowired
    // americanChef 이름을 가진 빈을 사용하겠다
    @Qualifier("americanChef")
    private  Chef chef;

    public ChefCookService() {
    }

    public void makeFood() {
        chef.cook();
    }
}

Annotation 을 이용하지만 XML방식을 기반으로 두고있기 떄문에

등록하고자 하는 객체들은 기존과 동일하게 전부 XML 파일안에 정의되있어야 합니다.

 

이러한 방법은 설정을 아예 분리함으로써 프레임워크와의 의존성을 최소화합니다.

하지만 만약 클래스가 5천개쯤 된다면?

 

지금은 프로그램이 간단해 xml을 읽고 설정하는게 쉽지만 프로그램이 커지는 경우 XML방식은 큰 부담이 됩니다.

 

따라서 프레임워크와의 의존성이 강화되더라도 순수 자바코드로 해결하는 Java Configuration 방식이 도입됩니다.

 

Java Configuration 을 이용한 DI

 

기존에는 XML파일을 메타데이터로 사용했지만 이제는 순수자바를 사용하기 떄문에

AnnotationConfigApplicationContext를 사용합니다.

 

Java Configuration 이용한 방법은 크게 2가지가 존재합니다.

  1. 자바코드로 Bean등록
  2. Component Scan

자바코드로 Bean등록

// 이건 설정하는 파일이야
@Configuration
// JavaConfiguration 에서 XML 설정을 사용할 수 있습니다.
@ImportResource("classpath:/beans.xml")
public class JavaConfig {

    @Bean
    public AmericanChef americanChef() {
        return new AmericanChef();
    }

    @Bean
    public KoreanChef koreanChef() {
        return new KoreanChef();
    }

    // 메서드 주입방식이라 합니다.
    @Bean
    ChefCookService chefCookService() {
        return new ChefCookService(americanChef());
    }
}

여기서 Bean에 등록될 이름은 메서드의 이름과 같습니다.

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.springframework.core.practice");

이 코드는 다음과 같습니다.

  1. com.springframework.core.practice 경로 아래에있는 모든 파일에서
  2. @Configuration , @Component , @Controller , @Service , @Repository 에 해당하는 
  3. 어노테이션이 붙은 클래스들을 자동으로 빈에 등록해.

 

Component Scan

컴포넌트 스캔은 다음의 어노테이션을 포함하는 클래스를 읽어 자동으로 빈에 등록시킵니다.

  1. @Component : 컴포넌트 스캔에서 사용
  2. @Controller : 스프링 MVC 컨트롤러에서 사용
  3. @Service : 스프링 비즈니스 로직에서 사용
  4. @Repository : 스프링 데이터 접근 계층에서 사용
  5. @Configuration : 스프링 설정 정보에서 사용

(Configuration,Controller,Service,Repository 모두 내부적으로 @Component을 상속하고있습니다.)

@Component
public class English implements Language{
    @Override
    public void hello() {
        System.out.println("Hello");
    }
}

@Component
public class Korean implements Language {
    @Override
    public void hello() {
        System.out.println("안녕하세요");
    }
}

생성자 주입

@Component
public class LanguageSpeakService {

    private  final Language language;


    // 생성자 주입
    @Autowired
    public LanguageSpeakService(@Qualifier("english") Language language) {
        this.language = language;
    }

    public void speak() {
        language.hello();
    }

}

생성자를 이용하기 떄문에 Field를 final로 선언 가능해 불변성을 지킬수 있습니다.

가장 권고되는 주입방식입니다.

setter 주입

@Component
public class LanguageSpeakService {
    private Language language;

    // setter 주입
    @Autowired
    public void setLanguage(@Qualifier("english") Language language) {
        this.language = language;
    }

    public void speak() {
        language.hello();
    }

}

기본생성자 필요 , final 선언 불가 

필드주입

@Component
public class LanguageSpeakService {

    // 필드주입 
    @Autowired
    @Qualifier("english")
    private Language language;


    public void speak() {
        language.hello();
    }
}

코드가 가장 간결하지만 외부에서 변경이 불가능해 테스트가 어렵습니다.

또한 setter와 마찬가지로 final 선언이 불가합니다.

 

(이러한 단점들 때문에 거의 사용하지 않음이 권고됩니다.)

Bean Vs Component

자바코드를 이용한 Bean등록은 주로 System 전체에서 공통적으로 사용되야 하는 것들을 대상으로 합니다.

 

예를들어 전체에서 공통적으로 사용되는 Thread Pool , Connection Pool의 경우 

설정파일(JavaConfig.class)에 모아서 셋팅하게되면 "아 이것들은 공통적으로 전체 System에서 사용하는 구나" 이런 이해를 돕습니다.

 

반대로 Business Model들은 각각의 것들이 고유한 성질을 갖습니다.(ChefCookService , LanguageSpeakService...)

이런경우 @Service , @Controller 같은 Component Scan 방식을 이용합니다.

 

 

도움이 되셨으면 좋겠습니다.

'Spring > Spring Core' 카테고리의 다른 글

Dependency(의존관계)란?  (3) 2024.01.04
IoC(Inversion Of Control)란  (1) 2024.01.02

Dependency(의존관계)란?

의존관계는

  • 코드에서 두 모듈간의 연결을 의존관계라 합니다.
  • 객체지향언어에서 두 클래스 간의 관계를 말하기도 합니다.

 

의존관계의 종류는 크게 4가지가 존재합니다.

  1. Dependency(의존관계)
  2. Association(연관관계)
  3. Aggregation(집합관계)
  4. Composition(합성관계)

보통 4가지를 통틀어 Dependency라고 뭉뚱그려 부르긴 하지만 각각의 차이를 인지하는 것이 중요합니다. 

 

이제부터 하나씩 알아보겠습니다.

Dependency(의존관계)

public class UserService {

    public void saveUser(UserRepository userRepository){
        System.out.println("유저저장");
        userRepository.save();
    }

}


public class UserRepository {

    public void save(){
        // do something ..
    }

}

의존관계란 클래스가 다른 클래스를 일시적으로 참조하는 형태입니다.

이 코드에서는 Service가 User를 저장할 때 UserRepository를 param으로 불러와 사용합니다.

saveUser 연산을 동작시킬때만 일시적으로 참조하게 되며 생명주기와 같은 어떤것도 일치하지 않습니다.

가장 낮은 수준의 결합도를 가집니다.

 

Association(연관관계)

public class UserService {
    private UserRepository userRepository;

    public UserRepository getUserRepository() {
        return userRepository;
    }

    public void saveUser() {
        this.userRepository = new UserRepository();
        System.out.println("유저저장");
        userRepository.save();
    }

}


public class UserRepository {

    public void save(){
        // do something ..
    }

}

UserService 객체를 생성할 때는 UserRepository가 생성돼있지 않습니다.
이 부분은 의존관계와 동일하지만 saveUser() 메서드를 호출 해 동작이 끝났음에도 Repository 객체가 남아있게 됩니다.

 

public class Main {

    public static void main(String[] args) {

        UserService userService = new UserService();

        System.out.println(userService.getUserRepository());

        userService.saveUser();

        System.out.println(userService.getUserRepository());

    }

}


이것이 의존관계와의 차이점이며 의존관계보다 높은 결합도를 가집니다.

 

Aggregation(집합관계)

public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public UserRepository getUserRepository() {
        return userRepository;
    }

    public void saveUser() {
        System.out.println("유저저장");
        userRepository.save();
    }

}

public class UserRepository {
    public void save() {
        // do something ..
    }

}

생성자를 통해 다른 클래스의 객체를 받아오는 경우입니다.

(Field를 Final로 선언할 수 있다는 장점이 있습니다.)

UserService 생성을 위해서 필수적으로 UserRepo가 필요합니다.

하지만 이것이 UserService와 UserRepository가 동일한 생명주기를 가진다는 건 아닙니다.

 

UserRepository가 먼저 생성되고 사용된 후 Service 생성도 가능하기 때문입니다.

public class Main {

    public static void main(String[] args) {

        UserRepository userRepository = new UserRepository();

        userRepository.save();
        userRepository.save();
        userRepository.save();

        UserService userService = new UserService(userRepository);
        System.out.println(userService.getUserRepository());
        userService.saveUser();
    }

}

 


기존 관계들과의 차이점은 메서드 호출없이 객체 생성만해도 Repository가 존재합니다.

 

Composition(합성관계)

public class UserService {
    private final UserRepository userRepository;

    public UserService() {
        this.userRepository = new UserRepository();
    }

    public UserRepository getUserRepository() {
        return userRepository;
    }

    public void saveUser() {
        System.out.println("유저저장");
        userRepository.save();
    }

}

public class UserRepository {
    public void save() {
        // do something ..
    }

}

UserService와 UserRepository의 생명주기가 완전히 일치하게 됩니다.
이런 형태를 강하게 결합한다 라고도 말합니다.

 

코드의 결합이 강해지게되면 재사용성이 크게 떨어지기 때문에 보통 Aggregation 관계를 사용합니다.

 

IoC/DI와 Dependency의 관계

 

IoC는 프로그램이 흐름의 제어권을 갖음을 말합니다.

Spring에 경우 Context에 Bean을 등록하고 관리하는 등 Spring Bean의 생명주기를 스스로 관리합니다.

그런데 Aggregation , Composition 과 같이 객체 생성을 위해서 다른 클래스를 필요로하는 경우는 어떡할까요??

 

public interface Language {
    // do something
}

public class Korean implements Language{
    // do something
}

public class English implements Language{
    // do something
}

public class LanguageService {

    private final Language language;

    public LanguageService(Language language) {
        this.language = language;
    }
}

LanguageService는 Aggregation 의존관계를 갖습니다.

따라서 자체적인 객체생성이 불가능하며 다른 타입의 객체가 필요합니다.

하지만 Type이 Language로 되어있기 떄문에 필요한 객체의 타입은 English가 될 수도 Korean이 될 수도 있습니다.

따라서 프로그램이 무엇을 의존할지 자체적으로 정할 수 없게되고

어떤 타입의 객체를 사용할 것인지를 외부에서 주입해줘야 합니다.
(Korean,English 또한 설정을 추가해 주입해줘야 함)

 

이를위한 여러가지 의존관계 주입방법이 나오게됐으며 이를 통틀어 Dependency Injection 이라 부릅니다.

 

아래는 같이읽어보면 좋은 글입니다. 

https://masiljangajji-coding.tistory.com/51

 

IoC(Inversion Of Control)란

IoC/DI(Inversion Of Control/Dependency Injection)란 IoC(Inversion Of Control)란? IoC는 제어의 역전을 뜻합니다. 제어의 역전.. 제어가 역전된다.. 이게 어떤 의미일까요? 기존의 프로그램은 구현 객체가 프로그램

masiljangajji-coding.tistory.com

 

https://masiljangajji-coding.tistory.com/53

 

Dependency Injection(의존관계 주입)이란

Dependency Injection(의존관계 주입)이란? 이 글은 IoC , Dependency 의 개념을 알고있다는 전제하에 작성된 글입니다. 원활한 이해를 위해서 아래글을 읽어주세요 https://masiljangajji-coding.tistory.com/51 IoC(Inver

masiljangajji-coding.tistory.com

도움이 되셨으면 좋겠습니다.

'Spring > Spring Core' 카테고리의 다른 글

Dependency Injection(의존관계 주입)이란  (2) 2024.01.04
IoC(Inversion Of Control)란  (1) 2024.01.02

IoC(Inversion Of Control)란?

IoC는 제어의 역전을 뜻합니다.
제어의 역전.. 제어가 역전된다.. 이게 어떤 의미일까요?

 

기존의 프로그램은 구현 객체가 프로그램의 제어 흐름을 조종합니다.
즉 사용자에 의해서 흐름이 제어되며 프로그램은 사용자가 만들어놓은 흐름에 따라 실행 될 뿐입니다.

 

IoC는 이 상태를 반전시킨 것으로 사용자에 의해 흐름이 제어되는 것이 아닌 프로그램 자체가 제어권을 갖는 것입니다.

 

Non - IoC Exapmle

public class Main {


    public static void main(String[] args) {


        MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository();
        MemberService memberService = new MemberServiceImpl(memoryMemberRepository);

        /**
         * memberService 를 이용한 코드
         */

    }

}

 

일반적인 형태의 코드입니다.

 

main문 안에서 필요한 객체들을 생성하고 사용하게 됩니다

이 경우 해당 객체들의 생명주기는 main이 시작하면서 생성되고 main이 종료되면서 삭제됩니다.

 

만약 사용자가 코드를 변경한다면

public class Main {

    public static MemberService memberService = new MemberServiceImpl(new MemoryMemberRepository());

    public static void main(String[] args) {

        /**
         * memberService 를 이용한 코드
         */

    }

}

 

객체들의 생명주기는 클래스가 로드될때 생성되고 프로그램 종료시 삭제됩니다.

또한 사용자가 언제 memberService를 호출해 사용할 지 흐름을 정의할 수 있습니다.

 

사용자가 코드를 만드는 방식에 따라서 객체의 생명주기와 동작의 흐름이 바뀌게되며

이는 사용자가 프로그램의 제어 흐름을 조종하는 것과 같습니다.

 

IoC는 이러한 흐름을 역전시켜 프로그램이 자체적으로 흐름을 제어하는 것입니다.

 

IoC Exapmle

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memoryMemberRepository());
    }

    @Bean
    public MemoryMemberRepository memoryMemberRepository() {
        return new MemoryMemberRepository();
    }

}

 

Spring FrameWork에서 자바 코드를 이용해 Bean을 등록하는 코드입니다.

 

이 코드를 통해서 MemberService , MemoryMemberRepository는 Spring Bean의 형태로 Context에 등록됩니다.

또한 Bean들의 생명주기또한 Context가 스스로 제어합니다.

 

사용자는 이 과정에 개입하지 않으며 모든 동작은 Spring이 정의해놓은 순서와 규칙을 통해 이루어집니다.

 

즉 사용자에게 제어권이있는게 아닌 프로그램 자체가 제어권을 갖고있는 것입니다.
이것을 제어권이 역전되었다 , IoC 되었다 라고 말합니다.

 

FrameWork없이 순수 자바코드로도 가능합니다.

@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    enum Method{
        POST,GET
    }
    String[] value();
    Method method() default Method.GET;

}

(사용자정의 어노테이션)

public void initialize(Set<Class<?>> c, ServletContext ctx) {

        if (Objects.isNull(c)) {
            log.info("Controller not found");
            return;
        }

        for (Class<?> controllerClass : c) {
            try {
                Constructor<?> constructor = controllerClass.getConstructor();
                Object controllerInstance = constructor.newInstance();

                RequestMapping requestMapping = controllerClass.getAnnotation(RequestMapping.class);
                if (requestMapping != null) {
                    String[] values = requestMapping.value();

                    for (String value : values) {
                        String key = getKey(requestMapping.method().name(), value);
                        beanMap.put(key, controllerInstance);
                    }
                }
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }


        ctx.setAttribute(CONTEXT_CONTROLLER_FACTORY_NAME, this);

    }

 

이 코드는 Java Servlet을 이용해 WebApplication 을 구축하는 코드의 일부입니다.

(Java Servlet은 FrameWork이 아닌 순수자바)

 

Reflection 을 통해 RequestMapping 어노테이션을 갖고있는 클래스의 정보를 읽어와 객체를 생성한 후

Map에 넣어 관리하는 코드로 프로그램이 실행될 때 동적으로 클래스들을 읽어 관리하게 됩니다.

 

 

단위 테스트를 수행하는데 사용되는 프레임워크인 JUnit또한 실행 및 관리를 위한 생명주기를 갖습니다.

@SpringBootTest
class CoreApplicationTests {

    @BeforeEach
    void beforEach() {
    }

    @Test
    void contextLoads() {
    }

    @AfterEach
    void afterEach() {
    }

}

 

사용자는 @BeforeEach , @Test , @AfterEach 어노테이션만 사용하면 프로그램이 알아서 테스트로 인식하고

실행시 JUnit이 이미 정의한 동작 순서와 생명주기에 따라 테스트하게 됩니다.

 

따라서 IoC가 됐다 = 프로그램이 자체적으로 흐름을 제어한다.

이렇게 정리할 수 있습니다.

 

프로그램의 자체적인 흐름이란??

  1. 프로그램이 컨테이너를 통해 생명주기를 관리한다.
  2. 동작의 순서가 프로그램이 정의한 흐름을 따른다.

(이런 흐름에 사용자가 억지로 개입할수는 있지만 일반적인 경우만 다루겠습니다.)

 

IoC의 이점

 

IoC/DI를 구현함으로 사용영역과 구성영역을 나눌 수 있습니다.

쉽게말하면 사용자는 객체의 실행에만 신경쓰고 , 객체들의 관리와 동작의 흐름은 프로그램이 하게 하는것입니다.

// 실행영역 
public static void main(String[] args) {

        // 사용자가 객체 사용시 new 를 이용해 찍어내지 않음 , Context에 등록된 Bean을 꺼내 사용
        // 즉 객체를 사용자가 관리하는게 아님 
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);

        // 따라서 객체자체의 실행에만 집중할 수 있음 
        Member member = new Member(1L, "memberA", Grade.VIP);
        memberService.join(member);
    }


// 여기는 구성영역 , 해당 객체들이 Bean에 등록되는 과정과 관리방법은 이미 Spring에 정의돼있음
@Configuration 
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memoryMemberRepository());
    }

    @Bean
    public MemoryMemberRepository memoryMemberRepository() {
        return new MemoryMemberRepository();
    }

}

실행하는 코드와 구성하는 코드를 분리하게 됨으로써 유지보수의 용이성과 가독성을 높여줍니다.

 

이런 IoC개념이 기본적으로 깔려있는 FrameWork는 사용자가 제어 해야 할 상당 부분들을 대신함으로써

개발 생산성을 크게 증가시켜줍니다.

 

Ex) 스프링 사용시 사용자는 객체의 

의존관계를 어떻게 주입할것 인지만 설정해주면 
Bean을 등록하고 관리하는 모든 과정은 스프링이 제어 함 (IoC/DI의 개념)

 

추가적으로 흔히 말하는 FrameWork Vs Library 의 가장 큰 차이도 IoC개념이 있냐 없냐의 차이가 가장 핵심적입니다.

 

아래는 추가로 읽으면 좋은 글입니다.

https://masiljangajji-coding.tistory.com/52

 

Dependency(의존관계)란?

Dependency(의존관계)란? 의존관계는 코드에서 두 모듈간의 연결을 의존관계라 합니다. 객체지향언어에서 두 클래스 간의 관계를 말하기도 합니다. 의존관계의 종류는 크게 4가지가 존재합니다. Dep

masiljangajji-coding.tistory.com

https://masiljangajji-coding.tistory.com/53

 

Dependency Injection(의존관계 주입)이란

Dependency Injection(의존관계 주입)이란? 이 글은 IoC , Dependency 의 개념을 알고있다는 전제하에 작성된 글입니다. 원활한 이해를 위해서 아래글을 읽어주세요 https://masiljangajji-coding.tistory.com/51 IoC(Inver

masiljangajji-coding.tistory.com

도움이 되셨으면 좋겠습니다.

'Spring > Spring Core' 카테고리의 다른 글

Dependency Injection(의존관계 주입)이란  (2) 2024.01.04
Dependency(의존관계)란?  (3) 2024.01.04

NHN Academy(조선대) 4기 회고

여러 부트 캠프 중에 NHN Academy 관련해서는 정보가 많지 않아서 회고를 위장한 정보글을 써봅니다.

아카데미에 지원에 정보를 찾는 분들을 위해 글을 작성하는 것이며 제가 겪은 4기를 기준으로 말씀드리겠습니다.

저의 경우는 체험과정(끝) - 본 과정(끝) - Spring 과정(진행 중) - 프로젝트 과정(추후 선발 필요)의 과정을 거쳤습니다

이는 어디까지나 4기를 기준으로 쓴 글이며 향후 아카데미 교육정책에 따라서 변경이 가능합니다.

 

교육방식

아카데미가 지향하는 교육 방법은 동료 학습을 통한 성장입니다.

3명~5명을 기반으로 팀을 이루어서 학습이 진행되며 아카데미에서는 공부할 것들에 대한 교재 및 자료들을 제공하고 관리합니다.

각 팀마다 TA(조교)가 배정되고 TA 또한 플레잉코치와 비슷하게 팀원들과 같이 공부하고 코드 리뷰를 진행합니다.

이 부분에 대해서는 자유도가 매우 높아 정해진 교재나 자료를 활용하지 않고 팀 자체적으로 다른 자료를 참고하는 학습도 가능합니다.

뿐만 아니라 학습 진도에 대해서도 팀 차원에서 자율적으로 이루어집니다.

 

중간중간에 교육 전반에 것들을 책임 지시는 학장님, DB를 담당하시는 부학장님이 단체 교육을 하실 때도 있고 특강의 형태로

진행될 때도 있으며 과제 및 시험을 통한 검증 시스템이 존재합니다.

장점

보장된 교육

가장 중요한 부분은 학습에 도움을 주시는 모든 분들이 NHN 소속이십니다.

오시는 강사분들이 CTO부터 시작해 최소 팀장급의 인사가 오기 때문에 제공하는 교육과 자료의 질 또한 매우 뛰어난 편입니다.

 

개인적으로 저는 이 부분이 특장점이라 생각합니다.

 

보통의 부트 캠프에서는 강사진에 대한 정확한 레퍼런스를 알 수 없을 때가 많습니다.

 

뛰어난 강사진, 네카라 출신 강사진, 현직 xx 개발자 ... 이런 식의 홍보를 하지만 그것이 사실인지

또 사실이라면 얼마나 경력을 쌓은 건지 , 이전 근무지가 어디인지 공개하지 않는 경우가 많습니다.

 

보통의 회사에서는 겸업금지 조항이 있기 때문에 실제 현직 개발자여도 그 레퍼런스를 공개할 순 없으며
개인적으로 이는 교육과정 자체에 대한 신뢰를 매우 떨어트린다 생각합니다.
(이런 이유로 강사진의 수준을 보장받는 네이버 부스트 캠프, 우아한 테크 코스 등등이 유명한 것이라 생각합니다.)

 

특히 아카데미의 빛과 소금이자 코드의 메시아이신 학장님은 정말 대단합니다.

자유로운 분위기

학습에 대한 자유도가 매우 높기 때문에 중간중간 공부하면서 자체적인 팀 과제를 설정하거나

진도를 재설정하는 것이 가능해 유연하게 계획을 세울 수 있습니다.

 

동료 학습을 기반으로 하기 때문에 개인으로는 할 수 없는 경험이 가능합니다.

Git을 통해 PR 날리고... Merge 하고.... 코드 리뷰하고...

동료들과 같이 학습하고 리뷰하는 시간을 통해 다양한 의견을 주고받는 것은 협업에 대한 경험치를 확실히 축적시켜줍니다.

 

충분한 토의 후에도 결론이 나지 않는 문제들에 대해서는 TA를 이용하거나 학장님께 가서 의견을 물을 수 있기 때문에

개인적으로는 이 방식을 매우 선호합니다.

자유로운 장소

기본적으로 조선대에서 제공하는 독자적인 공간을 사용합니다.

이 공간은 NHN Academy 연수생들만 사용 가능하며, 시설 또한 괜찮은 편입니다.

배정된 공간 이외에도 학교 시설물을 이용 가능하기 때문에 공간의 제약이 적은 편입니다.

연계채용

본 과정을 수료한 뒤 선발을 거쳐 프로젝트 과정을 들어가게 되면

프로젝트 결과물과 추가적인 심사 후 NHN에 연계 채용이 가능합니다.

단점

자유로운 분위기

앞서 말했듯이 팀을 이루어 학습하는 동료학습의 방식입니다.

따라서 정해진 시간에 교육장소만 온다면 무엇을 공부하든 , 얼마나 공부하든 그것은 팀의 책임입니다.

 

이런 높은 자유도는 성향에 따라 장점이 될 수도 단점이 될 수도있습니다.

 

개개인의 실력이 다르기 떄문에 팀의 학습 진도를 따라가는게 벅찬 경우도 있으며
팀원 모두가 열성적으로 참여하지 않으면 팀 전체의 학습에 지장이 생길 수 있습니다.

 

"팀의 학습이 느리고 정체되는 건 우리가 직접 개입할건 아니고 팀의 문제야 , 네가 원하면 팀을 변경해 줄게"
이것이 아카데미의 기조기 때문에 빡센 관리를 원하시는 분 혹은 집체교육으로 빠르게 학습하는 방식을 선호하시는 분들에게는 적합하지 않습니다.

거주지

교육이 진행되는 장소는 조선대학교(전라도 광주)입니다.

만약 거주지가 가깝다면 문제가 없지만 타 지역에서 교육을 수강하기에는 무리가 있을 수 있습니다.

저 같은 경우에도 본가는 경기도, 학교는 서울이었기 때문에 자취를 하고 있습니다.

마무리

가장 중요한 건 개인의 성향이라 생각합니다.

 

자유로운 분위기에서 유동적으로 행동하시는 걸 좋아하시는 분들께는 정말 좋은 경험이겠지만
그렇지 않으신 분들은 만족하기 힘들지 않을까 생각합니다.

 

개인적으로는 교육에 너무 만족했고 더 많은 사람들 참여해 더욱 뛰어난 분들이 배출되면 좋겠습니다.

 

아래는 실제 저의 팀 페이지입니다.

 

 

도움이 되셨으면 좋겠습니다.

 

 

신입 개발자 취업 여정기 (feat: 가비아 인턴 합격)

들어가는 글안녕하세요 최근 정말 좋은 일이 있었는데요 백엔드 포지션으로 가비아에 합류하게 됐습니다.이번글은 합류의 과정과 제 개인적인 생각을 작성하려 합니다.서비스 회사에 가고싶다

masiljangajji-coding.tistory.com

 

 

2025년 상반기 회고 (feat: 카카오게임즈 최종탈락)

들어가는 글안녕하세요 오랜만에 글을 작성합니다.블로그를 꾸준히 이어가겠다고 다짐했는데,미디엄 기준 마지막 글 작성일이 5월, 티스토리 기준 2월인 것을 보면 반성하게도 되고돌아보면 글

masiljangajji-coding.tistory.com

 

프로세스란

 

프로세스는 메모리에 적재되어 실행중인 프로그램을 말합니다.

 

프로세스의 메모리 구조는 다음과 같습니다.

 

Stack: 여기에는 메서드 호출 시 생성되는 지역 변수, 매개변수 및 자바의 기본 데이터 유형(primitive type) 값들이 저장됩니다.

재귀 함수 호출과 같이 런타임에 크기가 변경될 수 있습니다.

 

Heap: 참조 타입(Reference Type, Non-Primitive Type)의 객체들이 여기에 할당됩니다.
런타임에 크기가 동적으로 결정되며 Garbage Collector에 의해 관리됩니다.

 

Data: BSS 영역과 Data 영역으로 나뉩니다. 정적(static) 변수, 상수 등이 이 영역에 저장됩니다.

 

Code: 프로그램의 소스 코드가 컴파일되어 생성된 기계어 코드가 저장되는 영역입니다. 이 영역은 읽기 전용이며

프로그램이 실행될 때 수정되지 않습니다.

 

Stack , Heap 은 Dynamic한 특징을 Data , Code 는 Static한 특징을 갖습니다.

 

프로세스는 고유한 메모리를 갖습니다. 따라서 N개의 프로세스가 실행중이라면 실제로 N개의 Stack,Heap,Data,Code 영역을 갖습니다.

또한 프로세스가 실행되면 OS는 프로세스 관리를 위해 PCB(Process Control Block)을 생성합니다.

(이것 역시 N개의 PCB가 생성됩니다.)

 

PCB(Process Control Block)

 

아래 그림은 PCB의 구조입니다.

PCB는 운영체제가 프로세스를 관리하고 제어하기 위해 사용되는 자료구조이며 

Process를 설명하는 일종의 메타데이터입니다.

 

PCB의 주요 역할은 다음과 같습니다.

  1. 프로세스 상태(대기,실행)
  2. 프로세스 번호(프로세스의 고유 식별 번호 , PID)
  3. 프로그램 카운터(PC, 다음 실행될 명령의 주소를 나타냄)
  4. 레지스터

 

Context Switching

 

프로세스의 컨텍스트 스위칭(Context Switching)은 하나의 프로세스가 실행 중인 상태에서 다른 프로세스로 전환하는 과정을 말합니다.

 

이는 운영체제가 다중 프로세스를 실행하기 위해 필요한 기술로 컨텍스트는 프로세스가 현재 실행 중인

상태를 나타내는 정보의 집합(환경)을 나타냅니다

 

컨텍스트 스위칭은 PCB를 기반으로 동작합니다.(스레드 또한 가능)

 

Process1 실행중 -> 인터럽트 발생 -> PCB1저장 -> PCB2리로드 -> Process2실행 의 순서를 나타내며

 

이때 프로세스의 실행환경이 전환됩니다.

Context Switching 하기 위해서는 Interrupt가 필요하며 이는 CPU사용률을 낮춥니다.

(스레드의 경우 Stack영역을 제외한 모든 부분을 공유하기 떄문에 Switching에 대한 자원소모가 훨씬 적습니다.)

 

IPC(Inter Process Communication)

 

앞서 말했듯이 프로세스는 각각 고유한 메모리와 실행환경을 가집니다.
따라서 프로세스끼리 통신하기 위해서는 특정한 메커니즘이 필요합니다.

공유메모리(Shared Memory)

IPC 중에서 가장 빠른 통신 방법으로 여러 프로세스가 통신할 수 있도록 메모리를 공유하는 것을 말합니다.
메모리 자체를 매핑하여 공유하기 떄문에 불필요한데이터 복사의 오버헤드가 발생하지 않습니다.

전체 데이터를 메모리에 로드하지않고 필요한 부분을 선택적으로 로드할 수 있다는 장점또한 존재합니다.

 

 

파일

디스크에 저장된 데이터를 기반으로 통신하는 것을 의미합니다.

프로세스의 메모리에 데이터를 로드시키지 않기 떄문에 I/O관련 오버헤드가 발생합니다.

Socket

네트워크 인터페이스 (TCP , UDP , HTTP 등)를 기반으로 통신하는 것을 의미합니다.

예를들어 크롬브라우저에서(프로세스) www.naver.com 검색하면 네이버 서버에서 프로세스 띄워서 응답을 주게됩니다.

 

채팅프로그램의 경우에도 클라이언트 프로세스 , 서버 프로세스끼리의 통신이 필요합니다.

 

 

파이프

파이프 기법은 익명파이프(Anonymous Pipe) , 명명파이프(Named Pipe)로 나뉩니다.

익명파이프(Anonymous Pipe)

프로세스 사이에 FIFO기반 통신채널을 만들어 통신하는 것입니다.

파이프 하나당 단방향 통신이기 때문에 양방향 통신을 하려면 2개의 익명파이프를 만들어야 합니다.

부모-자식 프로세스 간에만 사용 가능하며 다른 네트워크상에서는 사용이 불가능합니다.

 

명명파이프(Named Pipe)

익명파이프의 확장된 개념으로 부모-자식 뿐 아니라 다른 네트워크 상에서도 통신할 수 있는 파이프입니다.

 

익명파이프의 경우 부를 이름이 없어 다른 네트워크에서 해당 파이프를 불러 통신하는게 불가능했지만    

명명파이프에는 이름이있어 다른 네트워크와 통신이 가능합니다.

 

보통 서버,클라이언트용 파이프를 구분해서 동작합니다.

 

메시지 큐

프로세스나 스레드 간에 비동기적으로 데이터를 주고받을 수 있도록 하는 통신 방식 중 하나로

메시지를 큐(Queue) 자료구조 형태로 관리하는 버퍼를 만들어 통신하는 것을 말합니다.

  1. 프로세스가 메시지를 보내거나 받기 전에 큐를 초기화합니다.
  2. 보내는 프로세스(sender)의 메시지는 큐에 복사되어 받는 프로세스(receiver)에 전달됩니다.

 

 

IPC기법을 자바로 나타낸 간단한 예제입니다.
https://github.com/masiljangajji/IPC_Example

 

GitHub - masiljangajji/IPC_Example

Contribute to masiljangajji/IPC_Example development by creating an account on GitHub.

github.com

도움이 되셨으면 좋겠습니다.

'프로그래밍 기초 > 운영체제' 카테고리의 다른 글

가상메모리(Virtual Memory)란  (1) 2023.12.28
운영체제(Operating System)란  (1) 2023.12.28

가상메모리(Virtual Memory)란

OS에서 사용하는 메모리 관리 기법중 하나 입니다.
컴퓨터가 실제 이용가능한 메모리 자원을 추상화하여 이를 사용하는 사용자들에게 더 큰 메모리로 보이게 만드는 것으로
HDD의 일부 영역을 RAM처럼 사용함으로써 구현됩니다.

 

포토샵과 유투브를 실행해 RAM이 꽉 찬 상황을 가정하겠습니다.
기존의 방법으로는 RAM이 꽉 찬 상태에서 Word를 실행시키는 것은 불가능합니다.

 

하지만 RAM에 적재돼 있는 포토샵,유투브 중 사용하지 않는 프로세스를 가상메모리(HDD)로 보내 공간을 만들고

그 공간에 Word를 할당시킨다면 더 많은 프로세스를 실행 가능하게 만들고 더 큰 메모리를 사용하는것과 같은 효과를 나타내게 됩니다.

 

이는 프로세스들끼리 메모리 침범이 일어날 여지를 크게 줄이며
가상메모리라는 통일된 주소 공간을 배정할 수 있음으로 메모리 관리의 단순화를 돕습니다.

 

이것이 가상메모리를 사용하는 이유이며 동작방법에 대해 알아보겠습니다.

Page Table

Page Table은 RAM에 존재하는 실제 주소(Physical Address)와 HDD에 존재하는 가상 주소(Virtual Address)를

매핑한 테이블 입니다.

 

Page Table은 다음을 포함합니다.

  1. 페이지를 식별하는 번호인 Virtual Page Number
  2. 가상페이지가 현재 RAM에 어느 프레임에 매핑되어 있는지를 나타내는 Physical Frame Number
  3. 해당 페이지가 현재 RAM에 존재하는지 여부를 나타내는 Valid Bit

 

페이지와 프레임의 상태 관리를 위해 사용하며

이 가상주소는 사실 RAM에서 이런 주소야~ 라는 것을 알려줍니다.

 

Page는 가상 메모리의 최소 단위를 의미하며
Frame은 물리 메모리의 최소 단위를 의미합니다.
(Page Table은 MMU(Memory Management Unit)에 의해 관리됩니다.)


또 자주 사용되는 주소는 TLB(Translation lookaside buffer)라는 캐싱계층을 둡니다.

 

Page Fault , Swapping

Page Falut란 Page에 접근했으나 해당 페이지가 RAM에 존재하지 않는 경우를 의미합니다.

모든 페이지를 RAM에 적재해놓지 않고 필요한 페이지만 RAM에 적재하는 방식을 사용하기 떄문에 발생합니다.

실제 동작 순서는 다음과 같습니다.

TLB 확인 -> Page Table 확인 -> RAM에 Page 존재안함 -> Page Fault 발생

 

이를 해결하기위해 Page Replacement Algorithm에 따라 RAM에 로드된 페이지를 HDD로 보내게되고(Swap Out)
필요한 Page를 RAM에 로드하게 됩니다(Swap In).

 

이러한 과정을 Swapping이라 부릅니다.

 

하지만 이 방법도 메모리 부족을 완벽하게 해결할 순 없습니다.

너무 많은 프로세스를 실행시키게 되면 Swapping이 빈번하게 발생하게 되고 CPU 사용률이 줄어들게 됩니다.

OS는 CPU사용률이 줄어 더 많은 일을 주게되고 이는 더 많은 Page Fault , 더 적은 CPU 사용률을 만듭니다.

이런현상을 Thrashing이라 부릅니다.

 

따라서 가장 명확한 해결법은 메모리를 늘리는 것입니다.

 

Page Replacement Algorithm

LFD (Longest Forward Distance)

가장 좋은 알고리즘은 오프라인 알고리즘의 일종인 LFD(Longest Forward Distance)입니다.

LFD는 가장 먼 미래에 참조되는 페이지와 현재의 페이지를 바꾸는 알고리즘이며 가장 적은 스와핑이 일어납니다.

이는 매우 효율적이지만 현실에서는 미래에 사용될 것을 알 수 없기 떄문에 구현이 불가능합니다.

따라서 이 알고리즘은 성능에 대한 Upper Bound를 제공합니다.

ex) 내 알고리즘은 LFD에 비하면 이정도야~

 

FIFO (First In First Out)

가장 먼저 메모리에 적재된 페이지부터 교체하는 알고리즘입니다.

 

LRU (Least Recently Used)

최근에 사용되지 않은 페이지를 교체하는 알고리즘입니다.

최근에 사용되지 않음 = 가장 오랫동안 사용되지 않은 페이지 교체

 

 

도움이 되셨으면 좋겠습니다.

'프로그래밍 기초 > 운영체제' 카테고리의 다른 글

프로세스(Process)와 통신기법  (3) 2023.12.29
운영체제(Operating System)란  (1) 2023.12.28

운영체제란 (Operating System)

컴퓨터 시스템을 관리하고 제어하는 소프트웨어를 말합니다.

OS 구성요소

OS의 구성 요소는 다음과 같습니다.

  1. Interface
  2. System Call
  3. Kernel (I/O 드라이버, 파일시스템)

 

Interface

Interface의 경우 크게 2가지 로 나뉩니다

 

GUI (Graphical User Interface)
그래픽으로 구성된 화면을 말하며 , 키보드와 마우스를 조합하여 상호작용 합니다. ex(Window , Mac)

 

CUI (Chracter User Interface)
텍스트로 구성된 화면을 말하며 , 키보드를 중심으로 상호작용 합니다. ex(MS-DOS, chatGPT)

 

초기에는 CUI를 사용했지만 현재는 대부분의 OS가 GUI(Graphical User Interface) 방식을 채택하고 있습니다.


System Call

System Call이란 OS가 Kernel에 접근하기 위한 인터페이스이며

유저 프로그램이 커널 함수를 호출할 때 System Call을 거쳐서 호출하도록 설계되어있습니다.

프로세스 관리(생성,삭제), 파일관리 , 디바이스 관리 , 프로세스간 통신 등에 동작에서 커널함수를 호출합니다.

public class Main {

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; ; i++) {
            Thread.sleep(1000);
            System.out.println(i);
        }

    }

}

이 코드를 실행시키면 , 하나의 매 초마다 숫자를 출력하는 자바 프로세스가 생성 됩니다.

 

이 프로세스를 kill 명령어를 통해 삭제할 수 있습니다.



이 경우 System Call을 거쳐 Kernel Function 을 호출하게 됩니다.

 

조금 더 절차를 자세히 하면 다음과 같습니다.

  1. 유저 프로그램이 trap을 발생
  2. 올바른 요청인지 확인
  3. 유저모드가 시스템콜을 통해서 커널모드로 변환
  4. 커널함수 실행

System Call 동작 사진

유저모드 , 커널모드

유저모드는 운영체제 서비스를 제공받을 수 없는 실행 모드입니다. 즉 권한이 없는 상태로 커널에 접근이 불가능 합니다.
커널모드는 운영체제 서비스를 제공받을 수 있는 실행 모드입니다. 즉 권한이 있는 상태로 커널에 접근이 가능 합니다.

 

일반적인 응용프로그램은 유저모드에서 , 특정 자원에 대한 접근은 커널모드에서 이루어집니다.
(권한 차이를 부여하는 이유는 아래에서 다루겠습니다.)

 

Kernel

OS의 뇌를 담당하며 , OS가 하는 일은 Kernel이 관리합니다.

  1. CPU 스케쥴링과 프로세스 상태관리
  2. 메모리관리
  3. 디스크파일 관리
  4. I/O 디바이스 관리

 

 

키보드 드라이버가 깔려있지 않아 키보드를 사용할 수 없는 경우를 종종 볼 수 있습니다.

이러한 드라이버 또한 커널이 관리하며 추상화를 통해 하드웨어 장치를 제어하고 동작을 관리 합니다.

 

 

우리는 앞서 배운 개념으로 System Call을 사용해 유저 모드를 커널 모드로 변경 후 커널에 접근함을 알고 있습니다.

유저 모드, 커널 모드 구분 없이 커널에 접근할 수 있게 만들면 더 빠르고 효율적이겠지만
이는 안전성과 보안성에 큰 문제가 야기할 수 있습니다.

만약 프로그램이 CPU, RAM, HDD 등에 마음대로 접근하고 조작할 수 있다면 자원이 무질서하게 관리될 것이고
프로그램이 조금만 실수해도 컴퓨터 전체에 문제가 생길 수 있습니다.

이러한 문제점들을 유저 모드와 커널 모드를 분리함으로써, 또 System Call이라는 Interface를 둠으로써

사용자와 OS 간의 경계를 설정하고 OS의 핵심 부분을 안전하게 보호하고 있습니다.

 

Kernel은 다양한 운영체제에서 Linux 커널, Windows NT 커널, macOS의 XNU 커널 등이 존재합니다.

 

도움이 되셨으면 좋겠습니다.

'프로그래밍 기초 > 운영체제' 카테고리의 다른 글

프로세스(Process)와 통신기법  (3) 2023.12.29
가상메모리(Virtual Memory)란  (1) 2023.12.28

[Immutable Vs Mutable]

프로그램의 오류는 예기치 못한 동작으로 인해 발생하게 됩니다.
이 예기치못한 동작을 줄이고 예측 가능하게 동작시키려면 어떻게 설계해야 할까요??
 
자바는 생성된 이후 상태를 변경할 수 있는 Mutable Type과 그렇지 않은 Immutable Type이 존재합니다.
 
지금부터 각 타입의 특징과 장단점에 대해 알아보겠습니다.

Immutable Type (불변 타입)

Immutable Type은 생성된 후 상태를 유지하는 타입을 의미합니다.
대표적인 예시로 String 이 존재합니다.

String Class

 
 
value , coder 등등의 field가 final로 선언된 것을 볼 수 있습니다.

이를 통해 String Type을 불변객체로 만드는 것이죠
 
하지만 우리는 한가지 의문에 빠집니다.
값이 안바껴?? += 연산으로 값 바뀌던데??

String str="1234";
str+="5678";

불변객체 임에도 다음의 연산이 가능한 이유는 "1234" 값을 갖고있는 str의 값이 변하는게 아닌
"12345678" 값을 갖고있는 새로운 str 객체를 만들어 내는 것이기 떄문입니다.
 
사실상 아래와 같은 연산을 하는 것이죠

String str = new String("12345678");

(이러한 연산또한 막고싶다면 final String 으로 선언하면 됩니다)
 

Constant Value

static final int MAX_SIZE = 10;

상수또한 불변타입에 속합니다.
불변타입이기 떄문에 값을 변경하려고 시도하면 에러가 발생하는 것을 볼 수 있습니다.

MAX_SIZE=20; // 에러발생

불변타입을 사용한다면 상태변화를 막을수 있기 때문에 객체가 초기에 설정한 상태를 유지하게 되고
상태변화로 인한 버그를 막을 수 있게 됩니다.
 
또한 다른 메서드의 파라미터로 전달될 떄에도 다른 코드에 의해 수정될 염려가 없기 떄문에 안전하게 전달가능합니다.
뿐만 아니라 불변값임을 명시함으로서 더욱 이해하기 쉬운 코드를 작성할 수 있습니다.

public static int rangeSum(final int start, final int end) {

        int result = 0;

        for (int i = start; i <= end; i++) {
            result += i;
        }
        return result;
    }

시작지점부터 끝나는 지점까지의 정수합을 반환하는 메서드입니다.

시작지점과 끝나는 지점을 불변하게 만듦으로서 이해를 돕고 버그를 방지합니다.
 
불변타입은 변경할 수 없기 떄문에 호환성이 떨어지고 코드의 작성을 어렵게 만들지만 그로인한 버그또한 줄여줍니다.
따라서 견고한 프로그램을 작성하고 싶다면 불변타입을 사용하는 것이 좋습니다.
 
 

Mutable Type(가변 타입)

StringBuilder는 String과 비슷한 기능을 하지만 가변객체 입니다.

StringBuilder sb = new StringBuilder();
sb.append("1234");
sb.append("5678");
System.out.println(sb);

이 코드는 실제로 sb가 가지고 있는 값이 변경되는 것이죠
 

String str = sb.toString();
System.out.println(str);

StringBuilder는 toString을 통해서 String Type으로도 사용가능하고
객체를 새로 만들어 내는 것이 아니기 떄문에 성능측면에서도 String보다 뛰어납니다.
 
하지만 이러한 호환성이 견고한 프로그램 작성을 어렵게 만듭니다.

Risks Of Mutaion (가변위험성)

Mutable Type은 위에서 명시한 것 처럼 장점이 많습니다.
상태 변경이 가능하기 떄문에 호환성이 뛰어나며 이로인해 쉬운 코드를 작성할 수 있는것이죠
 
하지만 똑같은 이유로 단점이됩니다.
상태 변경이 가능하기 떄문에 프로그램 시작에서의 상태와 프로그램의 특정 시점에서의 상태가 다를 수 있기 떄문이죠
 
따라서 객체가 어떤 상태인지 이해하기 어려워지고 Contract를 강제하는데 어려움이 생깁니다.
예시를 통해 알아보겠습니다.
 

Passing Mutable values

 public static int sum(List<Integer> list) {

        int sum = 0;
        for (int num : list) {
            sum += num;
        }

        return sum;
    }

List요소의 합을 반환하는 메서드

  public static int sumAbsolute(List<Integer> list) {
        for (int i = 0; i < list.size(); i++) {
            list.set(i, Math.abs(list.get(i)));
        }
        return sum(list);
    }

List 요소의 절대값 합을 반환하는 메서드
 
Mutable Type을 인자로 받았기 떄문에 상태변경이 가능합니다.
또한 sumAbsolute 메서드는 기존에 작성한 sum메서드를 재사용하기 떄문에 효율적으로 보이기까지 합니다.
 
하지만 이 코드는 치명적인 버그를 발생시킬 것입니다.
sumAbsolute() 메서드를 호출한 후에는 List요소 값이 변하기 떄문입니다.

List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,-1,-2,-3));

System.out.println(sum(list));
System.out.println(sumAbsolute(list));
System.out.println(sum(list));

이 코드의 결과 값은 다음과 같습니다.
 

list가 가변객체이기 때문에 처음 정의한 list와 sumAbsolute를 호출한 뒤의 list의 정의가 달라집니다.
이런식으로 재사용이나 성능에서 장점을 가질 수 있지만 추적하기 어려운 버그를 일으킬 수 있습니다.

 

Aliasing Risk

Alias(동일한 참조를 갖는 변수)가 존재할때 문제가 생길 수 있습니다.

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, -1, -2, -3));
List<Integer> list2 = list;

list2.add(5);
list2.add(6);
list2.add(7);

System.out.println(sum(list) + " " + sum(list2));

list2는 list와 동일한 참조를 갖기 떄문에 list2의 상태변화가 list에게도 영향을 주어 버그를 일으킬 수 있습니다.
 
 

Mutation undermines an iterator

 public static void removeUnderZero(List<Integer> list) {
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i) < 0) {
                list.remove(i);
            }
        }
    }

0보다 작은 요소를 삭제하는 메서드입니다.

문제될 것이 없어보이지만 list가 가변적으로 변함으로서 요소가 삭제될시 size() 또한 가변적으로 변하게 됩니다.
따라서 결과값이 예상과 다르게 반환됩니다.

 
 

Mutable objects reduce changeability

가변객체의 사용은 코드를 변경하기 어렵게 만듭니다.
list의 sum에 대해서 불필요한 연산을 막고자 cache를 도입하는 상황을 가정하겠습니다.
 

public static Map<List<Integer>, Integer> cache = new HashMap<>();

    public static void main(String[] args) {

        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));

        addToCache(list);

        System.out.println(cache.get(list));
    }

public static void addToCache(List<Integer> list) {
        if (cache.get(list) == null) {
            cache.put(list, sum(list));
        }
    }

문제없이 동작하는 것을 볼 수 있습니다.
 
하지만 코드가 추가된다면 어떨까요??

list.add(4);
list.add(5);

System.out.println(cache.get(list));

list의 상태가 변경되면서 cache가 정상적으로 동작하지 않게됩니다.
 
이렇게 가변객체는 논리적인 버그를 발생시킬 수 있습니다. 따라서 보다 안전한 방법으로 사용되야 합니다.
 

Use Producer

Producer는 기존객체에서 새로운 객체를 생성하는 것을 의미합니다.

  public static void sortList(List<Integer> list) {

        for (int i = 0; i < list.size(); i++) {
            for (int j = 0; j < list.size(); j++) {
                if (list.get(i) < list.get(j)) {
                    int temp = list.get(i);
                    list.set(i, list.get(j));
                    list.set(j, temp);
                }
            }
        }
    }

다음은 list를 오름차순으로 정렬하는 메서드입니다.
 
이 연산은 문제없이 동작하지만 list의 요소를 변화시킵니다. 이로인해 생기는 버그를 막기 위해 메서드를 변경하겠습니다.

 public static List<Integer> sortList(List<Integer> list) {

        List<Integer> result = new ArrayList<>(list);

        for (int i = 0; i < result.size(); i++) {
            for (int j = 0; j < result.size(); j++) {
                if (result.get(i) < result.get(j)) {
                    int temp = result.get(i);
                    result.set(i, result.get(j));
                    result.set(j, temp);
                }
            }
        }

        return result;
    }

새로운 List 객체를 반환하도록 만듦으로서 기존 list를 변화시키지 않아 버그발생을 줄일 수 있습니다.
(이런 방식을Defensive Copy라 합니다.)
 

Localize Value

변수가 사용되는 범위를 줄여야 합니다. 가변객체가 넓은 범위에서 사용될 수 있다면 (Static 이거나 메서드 자체가 거대하다면)
상태변화로 생기는 버그를 잡기 어려울 것입니다.
 
 
이 글에서는 Immutable Type 과 Mutable Type의 이해와 장단점에 대해 다뤘습니다.
도움이 되셨다면 좋겠습니다.

+ Recent posts