본문 바로가기
Everyday Study

2024.08.13(화) { IoC 컨테이너 동작 과정, enum데이터베이스, CGLIB 서브클래싱 = 프록시, 추상클래스->익명클래스O, 람다X }

by xogns93 2024. 8. 13.

 

public List<User> getAll() {
    return this.jdbcTemplate.query("select * from users order by id", this.userMapper);
}

해당 쿼리문은 Users 테이블에서 모든 행(row)과 모든 열(column)을 선택(select)한 후, id 열을 기준으로 오름차순(ASC, 기본값)으로 정렬하는 SQL 쿼리    

 

 


enum은 데이터베이스에 존재하지않음 그래서 따로 int 값으로 변환해서 데이터베이스에 넣어야함

 

enum 타입은 Java에서 제공하는 열거형 타입으로, 일반적으로 특정한 상수 집합을 표현할 때 사용됩니다. 하지만 데이터베이스에는 enum 타입이 존재하지 않으므로, enum 값을 데이터베이스에 저장하려면 일반적으로 int나 String과 같은 기본 데이터 타입으로 변환하여 저장해야 합니다.

enum 타입을 int로 변환해서 저장하기

아래에 enum 값을 int로 변환해서 데이터베이스에 저장하고, 다시 불러올 때 enum 값으로 변환하는 과정을 설명하겠습니다.

1. enum 정의

먼저, Java에서 enum을 정의합니다.

public enum UserRole {
    ADMIN(1),
    USER(2),
    GUEST(3);

    private final int value;

    UserRole(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public static UserRole fromValue(int value) {
        for (UserRole role : UserRole.values()) {
            if (role.getValue() == value) {
                return role;
            }
        }
        throw new IllegalArgumentException("Unknown enum value: " + value);
    }
}

코드 설명

  • ADMIN, USER, GUEST: 각각의 enum 값에 대해 고유한 int 값을 할당합니다.
  • getValue() 메서드: enum 값에 매핑된 int 값을 반환합니다.
  • fromValue(int value) 메서드: int 값을 enum 값으로 변환합니다. 이 메서드는 데이터베이스에서 가져온 int 값을 enum 타입으로 변환할 때 사용됩니다.

2. 데이터베이스에 enum 값 저장

User 객체에 UserRole enum 타입이 있다고 가정해 보겠습니다.

public class User {
    private int id;
    private String name;
    private UserRole role;

    // getters and setters
}

 

데이터를 저장할 때는 enum의 getValue() 메서드를 사용하여 int로 변환한 후, 데이터베이스에 저장합니다.

public void saveUser(User user) {
    String sql = "INSERT INTO users (id, name, role) VALUES (?, ?, ?)";
    jdbcTemplate.update(sql, user.getId(), user.getName(), user.getRole().getValue());
}

 

3. 데이터베이스에서 enum 값 불러오기

데이터베이스에서 int 값을 불러와서 다시 enum 값으로 변환할 때는 UserRole.fromValue() 메서드를 사용합니다.

public class UserMapper implements RowMapper<User> {
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setId(rs.getInt("id"));
        user.setName(rs.getString("name"));
        // 데이터베이스에서 가져온 int 값을 enum으로 변환
        user.setRole(UserRole.fromValue(rs.getInt("role")));
        return user;
    }
}

최종 동작

  • 저장 시: UserRole enum 값을 int로 변환하여 데이터베이스에 저장합니다.
  • 불러올 때: 데이터베이스에서 int 값을 가져와 UserRole enum 값으로 변환합니다.

CGLIB 서브클래싱 = 프록시를 만든다

서브클래스객체 = 프록시객체

 

CGLIB는 Java에서 프록시 객체를 생성하기 위해 사용하는 라이브러리 중 하나입니다. CGLIB는 바이트코드 조작을 통해 서브클래싱을 사용하여 프록시 객체를 생성합니다.

 

CGLIB 서브클래싱과 프록시 객체

  1. CGLIB 서브클래싱:
    • CGLIB는 특정 클래스의 서브클래스를 동적으로 생성합니다. 이 서브클래스는 원래 클래스의 기능을 상속받으며, 추가적으로 메서드를 오버라이딩하여 기능을 확장하거나 메서드 호출을 가로챌 수 있습니다.
  2. 프록시 객체:
    • CGLIB가 생성한 서브클래스 객체는 프록시 객체니다. 이 프록시 객체원래 클래스의 동작을 대리하여 수행하며, 메서드 호출을 가로채서 추가적인 로직(예: 로깅, 트랜잭션 관리, 권한 검사 등)을 실행할 수 있습니다.
    • 예를 들어, 원래 클래스의 메서드가 호출될 때, 프록시 객체는 그 호출을 가로채고, 추가 작업을 수행한 후 원래 메서드를 호출하거나, 원래 메서드를 호출하지 않고 대체 동작을 수행할 수도 있습니다.

요약

  • CGLIB 서브클래싱 = 프록시를 만든다: CGLIB는 동적으로 서브클래스를 생성하여 프록시 객체를 만든다.
  • 서브클래스 객체 = 프록시 객체: CGLIB가 생성한 서브클래스 객체는 프록시 객체로, 원래 클래스의 역할을 대리하고 메서드 호출을 가로챌 수 있다.

Spring IoC 컨테이너가 동작하는 과정

 

  1. 빈 정의(Bean Definition) 설정:
    • Spring IoC 컨테이너는 애플리케이션에서 관리할 객체들을 "빈(Bean)"이라고 부릅니다.
    • 먼저, IoC 컨테이너는 빈 정의(Bean Definition)를 읽어들입니다. 빈 정의는 XML 파일, 자바 설정 클래스(@Configuration), 또는 @ComponentScan 등을 통해서 설정될 수 있습니다.
  2. 빈 정의 읽기(Bean Definition Reader):
    • 빈 정의 리더(Bean Definition Reader)가 빈 정의를 읽어들이는 역할을 합니다.
    • XML 설정을 사용하는 경우, XmlBeanDefinitionReader가 빈 정의를 읽어들이고, 자바 설정 클래스를 사용하는 경우에는 AnnotatedBeanDefinitionReader가 역할을 수행합니다.
  3. 빈 정의 등록:
    • 빈 정의가 읽혀지면, 이 정보는 IoC 컨테이너에 등록됩니다.
    • 빈 정의에는 빈의 이름, 클래스 타입, 스코프, 의존성 주입 방법 등이 포함됩니다.
  4. @Bean 어노테이션 처리:
    • IoC 컨테이너는 자바 설정 클래스에서 @Bean 어노테이션이 달린 메서드를 찾아, 이를 빈으로 등록합니다.
    • @Bean 어노테이션이 달린 메서드는 직접적으로 빈을 생성하여 IoC 컨테이너에 등록하는 역할을 합니다.
    • 예를 들어, 자바 설정 클래스에서 @Bean 메서드를 사용하여 특정 객체를 생성하고 이를 Spring 컨테이너가 관리하도록 설정할 수 있습니다.
@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

 

위 예시에서 myService() 메서드는 MyServiceImpl 객체를 생성하고, 이 객체를 myService라는 이름의 빈으로 등록합니다.

 

 

5. @Component 스캔 및 등록:

 

  • IoC 컨테이너는 @ComponentScan 어노테이션을 사용하여 특정 패키지를 스캔하고, @Component, @Service, @Repository, @Controller와 같은 어노테이션이 달린 클래스를 빈으로 자동 등록합니다.
  • 이때, @ComponentScan이 지정된 패키지와 그 하위 패키지를 스캔하며, 해당하는 클래스들을 빈으로 등록합니다.

6. 빈 초기화 및 의존성 주입:

 

  • IoC 컨테이너는 빈을 초기화하며, 설정된 의존성 주입 방식(생성자 주입, 세터 주입, 필드 주입 등)에 따라 다른 빈들을 주입합니다.
  • 초기화 과정에서 @PostConstruct 어노테이션이 달린 메서드가 있다면, 그 메서드가 실행됩니다.

7. 애플리케이션 시작:

  • 모든 빈이 초기화되고 의존성이 주입되면, 애플리케이션이 시작되고, 필요한 곳에서 IoC 컨테이너가 빈을 주입해줍니다.

 

추상클래스 -> 익명 클래스 가능

추상클래스 -> 람다 불가능

 

추상 클래스는 람다 표현식으로 변환할 수 없습니다. 추상 클래스는 여러 개의 메서드를 가질 수 있기 때문에, 일반적으로 람다 표현식으로 변환할 수 없습니다.

함수형 인터페이스(단 하나의 추상 메서드를 가지고 있는 인터페이스) 의 경우에만 람다 표현식을 사용할 수 있습니다.

 


Spring에서 빈(bean) 설정 및 관리와 관련된 베스트 프랙티스

 

1. 파라미터 기반 주입 사용: @Autowired를 사용한 필드 주입 대신, 파라미터 기반 주입을 권장합니다.

이유:

  • 필드 주입(@Autowired): 필드에 직접 의존성을 주입하는 방식입니다. 하지만 이는 테스트하기 어렵고, 순환 의존성 문제를 일으킬 수 있습니다.
  • 파라미터 기반 주입(생성자 주입): 생성자를 통해 의존성을 주입하는 방식입니다. 이 방식은 다음과 같은 장점이 있습니다:
    • 불변성: 객체가 생성될 때 모든 의존성이 주입되므로, 이후에 변경될 가능성이 없습니다.
    • 테스트 용이성: 의존성을 명시적으로 주입할 수 있어, 단위 테스트가 쉬워집니다.
    • 순환 의존성 방지: 컴파일 시점에 순환 의존성을 감지할 수 있습니다.
// 필드 주입 (권장되지 않음)
@Component
public class MyService {
    @Autowired
    private MyRepository repository;
}

// 파라미터 기반 주입 (권장)
@Component
public class MyService {
    private final MyRepository repository;

    @Autowired
    public MyService(MyRepository repository) {
        this.repository = repository;
    }
}

 

 

2. @PostConstruct 주의: @PostConstruct 메서드 내에서 동일한 구성 클래스의 non-static @Bean 메서드에 접근하지 않도록 합니다.

이유:

  • @PostConstruct는 빈이 초기화된 후에 실행되는 메서드를 정의할 때 사용됩니다. 이 메서드 내에서 같은 클래스의 non-static @Bean 메서드를 호출하면, 아직 초기화되지 않은 빈을 참조할 가능성이 있습니다. 이는 초기화 순서에 의존하는 문제를 발생시킬 수 있습니다.

해결 방법:

  • @PostConstruct 메서드에서 빈을 참조하지 않거나, 필요한 경우 @Bean 메서드를 static으로 정의하여 초기화 순서를 명확히 할 수 있습니다.
@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }

    @PostConstruct
    public void init() {
        // this.myService()를 호출하지 않도록 주의해야 함
        System.out.println("Initialization logic");
    }
}

 

 

 

 

3. BeanPostProcessor와 BeanFactoryPostProcessor 정의: 이러한 클래스들은 일반적으로 static @Bean 메서드로 정의해야 하며, 그렇지 않으면 예상치 못한 초기화 문제가 발생할 수 있습니다.

이유:

  • BeanPostProcessor와 BeanFactoryPostProcessor는 스프링 컨테이너의 빈 초기화 및 빈 팩토리 초기화 과정에 영향을 미치는 특별한 빈입니다. 이들은 컨테이너 초기화 단계에서 매우 중요한 역할을 하므로, @Bean 메서드로 정의할 때 static 키워드를 붙여야 초기화 순서와 관련된 문제를 방지할 수 있습니다.

해결 방법:

  • @Bean 메서드에 static 키워드를 사용하여 이 클래스들이 컨테이너 초기화 과정에서 올바르게 동작하도록 합니다.
@Configuration
public class AppConfig {

    @Bean
    public static BeanPostProcessor customBeanPostProcessor() {
        return new CustomBeanPostProcessor();
    }

    @Bean
    public static BeanFactoryPostProcessor customBeanFactoryPostProcessor() {
        return new CustomBeanFactoryPostProcessor();
    }
}