Spring

leedong617·2024년 9월 13일
post-thumbnail

Spring Framework는 자바 기반의 오픈소스 프레임워크로, 특히 기업용 애플리케이션 개발을 위한 다양한 기능을 제공함. Spring은 개발자의 생산성을 높이기 위해 설계되었으며, 의존성 주입(Dependency Injection, DI)관점 지향 프로그래밍(Aspect-Oriented Programming, AOP) 등의 개념을 활용하여 애플리케이션의 모듈화와 유지보수를 쉽게 함. 특히, Spring은 애플리케이션의 구성 요소 간 결합도를 낮추고, 확장성을 높여주는 경량 프레임워크로 자주 언급됨.

1. Spring의 핵심 개념

IoC (Inversion of Control, 제어의 역전)

제어의 역전(IoC)은 객체 생성과 관리를 개발자가 아닌 Spring 컨테이너가 담당하는 개념임. IoC의존성 주입(DI) 패턴을 기반으로 동작하며, 객체 간의 의존성을 코드에서 직접 설정하지 않고, Spring이 필요한 객체를 주입해줌.

의존성 주입(DI)

객체가 다른 객체와 협력할 때 필요한 의존 객체를 외부에서 주입받는 방식임. DI는 생성자 주입(Constructor Injection), 수정자 주입(Setter Injection), 필드 주입(Field Injection)의 세 가지 방식으로 제공됨.

1) 생성자 주입(Constructor Injection)

@service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService (UserRepository userRepository) { // 의존성 주입
        this.userRepository = userRepository;
    }
}
  • 클래스의 생성자가 하나이고, 그 생성자로 주입받을 객체가 빈으로 등록되어 있다면  @Autowired를 생략 할 수 있음.

2) 수정자 주입(Setter Injection)

@service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public setUserService (UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
  • Setter 메소드에 @Autowired 어노테이션을 붙이는 방법.

3) 필드 주입(Field Injection)

@service
public class UserService {

	@Autowired 
    private final UserRepository userRepository;

}
  • 필드에 @Autowired 어노테이션만 붙여주면 자동으로 의존성 주입.

Spring Framework에서 권장하는 방법은 생성자를 통한 주입임 아래는 그 이유임

1. 불변성 보장

  • 불변성(Immutability)은 객체가 생성된 후 그 상태가 변경되지 않는 속성을 말함. 생성자 주입을 사용하면, 객체가 생성될 때 필요한 모든 의존성을 최초로 한 번만 설정할 수 있음. 한 번 생성된 객체는 의존성이 변경되지 않으므로 불변 객체가 됨.
  • 이는 객체 상태의 일관성을 보장하며, 객체가 예측 가능한 방식으로 동작하게 함. 불변 객체는 스레드 안전성이 뛰어나며, 동시성 문제가 발생할 가능성이 줄어듦.
public class UserService {

    private final UserRepository userRepository;

    // 생성자 주입을 통해 의존성 주입
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 의존성은 final로 선언되어 객체 생성 후 변경되지 않음
}
  • 생성자 주입을 통해 의존성을 final로 선언하면, 불변 객체가 되어 외부에서 임의로 의존성을 변경할 수 없음

2. 필수 의존성 주입 보장

  • 생성자 주입은 객체를 생성할 때 반드시 의존성을 주입하도록 강제함. 즉, 필수 의존성을 설정하지 않고 객체를 생성하는 것을 방지할 수 있음. 이는 런타임 오류를 줄이고, 잘못된 상태의 객체가 생성되는 것을 방지함.
  • 반면, 세터 주입이나 필드 주입은 객체 생성 후에 의존성을 주입할 수 있어, 주입이 누락될 가능성이 있음. 이 경우 주입되지 않은 의존성을 사용하면 NullPointerException과 같은 오류가 발생할 수 있음.
// 생성자 주입을 통해 의존성을 강제
public class OrderService {

    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        if (paymentService == null) {
            throw new IllegalArgumentException("PaymentService cannot be null");
        }
        this.paymentService = paymentService;
    }

    // OrderService는 PaymentService가 반드시 필요함
}
  • 위 예시에서는 필수 의존성인 PaymentService가 반드시 생성자에 주입되어야만 객체가 생성됨. 주입이 누락되면 컴파일 시점에서 경고를 받을 수 있음.

3. 테스트 용이성

  • 세터 주입이나 필드 주입의 경우, 테스트 중에 별도의 주입 방식이 필요하거나, 리플렉션(reflection)을 사용해야 할 수도 있지만, 생성자 주입은 간단히 생성자 호출을 통해 의존성을 주입할 수 있음.

  • 생성자 주입 덕분에 테스트 환경에서 의존성 주입이 매우 단순해짐. 이를 통해 테스트 코드를 더 직관적이고 읽기 쉽게 작성할 수 있음.

4. 순환 의존성 방지

  • 순환 의존성(Circular Dependency)이란 두 개 이상의 클래스가 서로를 필요로 할 때 발생하는 문제임. 예를 들어, 클래스 A가 클래스 B를 의존하고, 클래스 B가 다시 클래스 A를 의존하는 경우 순환 의존성이 발생함.
  • 생성자 주입을 사용하면 순환 의존성을 컴파일 시점에서 감지할 수 있음. 두 의존성이 서로 순환하고 있으면 객체 생성 시점에서 오류가 발생하여, 이를 빠르게 해결할 수 있음.
  • 반면, 세터 주입과 필드 주입은 객체가 이미 생성된 상태에서 의존성을 주입하기 때문에 순환 의존성 문제를 감지하기 어려울 수 있음. 이로 인해 런타임 오류로 이어질 수 있음.
public class ClassA {
    private final ClassB classB;

    public ClassA(ClassB classB) {
        this.classB = classB;
    }
}

public class ClassB {
    private final ClassA classA;

    public ClassB(ClassA classA) {
        this.classA = classA;
    }
}
  • 위 코드처럼 순환 의존성이 있을 경우, Spring은 컴파일 시점에서 오류를 발생시켜 개발자가 즉시 문제를 인식할 수 있음.

5. 의존성 변경의 안전성

  • 세터 주입이나 필드 주입의 경우, 객체가 생성된 후에 의존성을 변경할 수 있으므로, 의도하지 않은 상태에서 의존성이 수정될 위험이 있음. 이는 객체의 상태 불안정성을 야기할 수 있음.
  • 생성자 주입은 객체 생성 후 의존성을 변경할 수 없으므로, 의존성 변경으로 인한 위험이 사라짐. 이는 애플리케이션의 안정성과 일관성을 보장하는 데 중요한 역할을 함.

6. 필드 주입의 문제점 (권장되지 않는 이유)

  • 필드 주입은 의존성을 직접 필드에 주입하는 방식으로, 코드가 간단해 보일 수 있지만 여러 문제를 야기할 수 있음.

의존성 숨김

  • 필드 주입은 의존성을 외부에서 알 수 없게 만듦. 즉, 클래스가 어떤 의존성을 사용하는지 명확히 알기 어렵고, 객체의 책임이 명확하지 않음.

테스트 어려움

  • 필드 주입은 객체 생성 시 주입되지 않기 때문에, 테스트에서 의존성을 설정하기 어려움. Mock 객체를 주입하려면 리플렉션을 사용해야 할 수 있음.

순환 의존성 감지 불가

  • 필드 주입은 객체 생성 후 의존성을 주입하기 때문에 순환 의존성 문제를 즉시 감지하지 못하고, 런타임 시점에만 문제가 발생할 수 있음.

필드 주입은 구조가 불명확하고, 코드의 명확성을 떨어뜨리기 때문에 생성자 주입이 더 권장됨.

7. 리팩토링과 유지보수의 용이성

  • 생성자 주입을 사용하면 객체의 의존성 관계가 명확해지므로, 코드 리팩토링과 유지보수가 쉬워짐. 의존성이 명시적으로 표현되기 때문에, 어떤 클래스가 어떤 의존성을 필요로 하는지 바로 확인할 수 있음.
  • 이로 인해 애플리케이션이 커지더라도 각 클래스의 의존성 관리가 쉬워지고, 새로운 의존성을 추가하거나 기존 의존성을 변경할 때도 코드의 일관성을 유지할 수 있음.

AOP (Aspect-Oriented Programming, 관점 지향 프로그래밍)

AOP는 프로그램의 비즈니스 로직과 이를 지원하는 횡단 관심사(Cross-Cutting Concerns)를 분리하는 기법임. 횡단 관심사는 여러 모듈에 걸쳐 공통으로 필요한 로직(예: 로깅, 보안, 트랜잭션 관리)인데, AOP는 이런 공통된 로직을 핵심 비즈니스 로직과 분리하여 코드 중복을 줄이고 유지보수를 쉽게 함.

POJO (Plain Old Java Object)

POJO는 자바 객체가 특정 프레임워크나 기술에 종속되지 않고, 순수한 자바 객체로 개발할 수 있음을 의미함. Spring은 개발자가 특정 기술이나 프레임워크에 종속되지 않도록 POJO를 기반으로 애플리케이션을 구성할 수 있게 지원함. 즉, 애플리케이션 코드를 간결하고 유지보수 가능하게 만듦.

Spring의 주요 구성 요소 및 모듈

Spring은 모듈화된 구조를 가지고 있어 필요한 기능을 선택적으로 사용할 수 있음. 이 모듈들은 각각 특정 기능을 담당하며, Spring Core, Spring AOP, Spring MVC, Spring Data, Spring Security 등이 있음.

Spring Core

Spring의 핵심 모듈로, IoC(제어의 역전)DI(의존성 주입) 기능을 제공함. BeanFactoryApplicationContext 인터페이스를 통해 애플리케이션에서 사용하는 객체(빈)를 관리하고, 이 객체들 간의 의존성을 설정하고 주입함.

ApplicationContextSpring의 컨테이너로, 빈의 생성, 라이프사이클 관리, 의존성 주입 등을 담당함. 이를 통해 객체 간의 결합도를 낮추고 재사용성을 높임.

@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService(userRepository());
    }

    @Bean
    public UserRepository userRepository() {
        return new UserRepository();
    }
}

Spring AOP

AOP 모듈은 애플리케이션의 횡단 관심사(예: 로깅, 보안, 트랜잭션 관리)를 비즈니스 로직과 분리하여 모듈화함. 이를 통해 코드 중복을 줄이고 비즈니스 로직을 깔끔하게 유지할 수 있음.

Aspect: AOP에서 공통 기능을 분리한 모듈.

Advice: 특정 시점(메소드 실행 전/후 등)에 실행될 코드.

@Aspect
@Component
public class TransactionAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void startTransaction() {
        System.out.println("Start transaction...");
    }
}

Spring MVC

Spring MVC는 웹 애플리케이션 개발을 위한 Model-View-Controller 패턴을 구현한 모듈로, 클라이언트 요청을 처리하고, 응답을 제공하는 역할을 함. Spring MVCREST API 개발에도 자주 사용됨.

DispatcherServlet이 모든 요청을 받아 컨트롤러로 전달하며, 컨트롤러는 비즈니스 로직을 처리한 후, 결과를 뷰(View)에 전달하여 최종 HTML을 생성함.

@Controller
public class UserController {

    @GetMapping("/users")
    public String getUsers(Model model) {
        List<User> users = userService.getUsers();
        model.addAttribute("users", users);
        return "userList";
    }
}

Model: 애플리케이션의 데이터(비즈니스 로직에서 처리된 결과).

View: 사용자에게 보여줄 출력(HTML, JSON 등).

Controller: 클라이언트 요청을 처리하고, 적절한 응답을 반환하는 역할을 함.

Spring Data

Spring Data는 데이터베이스와의 연동을 단순화하는 모듈임. JPA, JDBC, MongoDB 등 다양한 데이터 소스와 연동을 지원하며, 데이터베이스와 상호작용하는 복잡한 코드를 자동으로 처리함.

Spring Data JPA를 사용하면 CRUD 작업을 자동으로 처리하는 리포지토리 인터페이스를 쉽게 정의할 수 있음.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

Spring Data는 개발자가 SQL 쿼리나 데이터베이스 상호작용에 신경 쓰지 않고 비즈니스 로직에 집중할 수 있도록 도와줌.

Spring Security

Spring Security는 애플리케이션의 보안을 관리하는 모듈로, 인증(Authentication)인가(Authorization)를 지원함. 사용자의 인증 상태를 확인하고, 특정 권한이 있는 사용자만 접근할 수 있도록 설정할 수 있음.

Spring Security세션 관리, 암호화, OAuth, JWT(Json Web Token) 기반 인증 등을 지원함.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    @Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		/* requestMathcers 해당 url로 요청될시
		 * permitAll 모두 허용
		 * hasRole 1개의 사용자만 허용
		 * hasAnyRole 명시된 사용자 모두 허용
		 * anyRequest requestMatchers에 해당되지 않는 모든 uri 가장 마지막에 설정 되어야함
		 * */
		http.authorizeHttpRequests((auth) -> auth
                .requestMatchers("/login","/","/join").permitAll()
                .requestMatchers("/manager").hasAnyRole("MANAGER")
                .requestMatchers("/admin").hasAnyRole("ADMIN")
                .anyRequest().authenticated()
        );
        return http.build();
	}
}

Spring Boot

Spring BootSpring Framework의 설정과 배포를 간소화한 확장 프레임워크임. Spring Boot는 자동 구성을 지원하여 복잡한 XML 설정 파일 없이 애플리케이션을 빠르게 개발하고 실행할 수 있게 해줌.

내장 웹 서버(예: Tomcat)를 제공하여 별도의 외부 서버 없이 애플리케이션을 독립적으로 실행할 수 있음.

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

주요 특징 )

자동 구성

  • 애플리케이션의 많은 설정을 자동으로 처리하여, 개발자는 비즈니스 로직에만 집중할 수 있음.

독립 실행 가능

  • 내장된 웹 서버로 애플리케이션을 실행할 수 있어, 외부 서버 설정이 필요 없음.

Spring Cloud

Spring Cloud는 마이크로서비스 아키텍처를 지원하는 모듈로 서비스 등록/발견, 분산 트랜잭션, API Gateway 등의 기능을 제공함.

마이크로서비스 간의 상호작용을 관리하고, 서비스의 확장성을 높이는 데 도움이 됨.

Spring의 장점

유연성

  • 다양한 모듈을 제공하여 필요한 부분만 선택해서 사용할 수 있음.

경량 프레임워크

  • POJO 기반의 경량 프레임워크로, 필요한 기능만 선택적으로 사용할 수 있음.

테스트 용이성

  • DI와 AOP 덕분에 모듈 간 결합도가 낮아, 단위 테스트와 통합 테스트를 쉽게 작성할 수 있음.

대규모 애플리케이션에 적합

  • Spring은 대규모 애플리케이션에서 확장성과 유지보수성 면에서 뛰어남.

풍부한 커뮤니티와 문서

  • Spring은 자바 생태계에서 가장 널리 사용되는 프레임워크 중 하나로, 방대한 문서와 커뮤니티 지원을 받을 수 있음.

결론

Spring Framework는 자바 기반의 유연한 아키텍처와 다양한 모듈을 통해 엔터프라이즈 애플리케이션 개발에 최적화된 프레임워크임. SpringIoC, AOP, 의존성 주입과 같은 핵심 개념은 애플리케이션의 복잡성을 낮추고 확장성과 유지보수성을 높여줌. Spring Boot와 결합하여 애플리케이션 개발을 빠르고 효율적으로 할 수 있으며 마이크로서비스, 데이터베이스 연동, 보안 등 다양한 기능을 지원함.

profile
웹개발자 취업 준비생

0개의 댓글