1. Spring MVC

1) 개념

- 스프링 프레임워크의 하나로 웹 애플리케이션을 개발할 대 사용하는 프레임워크

- Model-View-Controller 아키텍처를 따르며 이를 통한 웹 애플리케이션의 각각의 역할을 분리하여 개발하고 유지보수할 수 있음

 

2) 주요 컴포넌트

① DispatcherServlet 

- 스프링 MVC의 핵심 컴포넌트

- 클라이언트의 요청을 받아들이고 적절한 핸들러에게 요청 전달

- 컨트롤러에서 반환된 결과를 HTTP 응답으로 변환하여 클라이언트에게 반환

② HandlerMapping

- DisptcherServlet이 클라이언트 요청을 받았을 때 요청을 처리할 핸들러(Controller)를 찾아줌

③ Controller

- 클라이언트의 요청을 처리하고, 요청에 대한 비즈니스 로직을 실행하여 결과 반환

④ ViewResolver

- Controller에서 반환한 결과를 보여줄 View를 찾아주는 역할

⑤ View

- Controller에서 반환한 결과를 실제로 화면에 출력하는 역할

 

3) 실행 흐름

① 클라이언트 요청이 DispatcherServlet으로 들어온다.

DispatcherServlet은 HandlerMapping을 이용하여 어떤 Controller가 요청을 처리할지 결정한다.

③ 선택된 Controller는 클라이언트의 요청에 대한 비즈니스 로직을 실행하고, 결과를 반환한다.

④ Controller가 반환한 결과는 ViewResolver를 통해 적절한 View로 변환된다.

⑤ 변환된 View는 클라이언트에게 응답으로 반환된다.

 

4) 구성 요소

① Model : Controller에서 생성된 결과를 담는 객체 → View에서 출력할 데이터를 담아서 전달

② View : Controller에서 생성된 Model을 출력하기 위한 템플릿 역할

③ Controller : 클라이언트의 요청을 받아 Model 객체를 생성하고, 생성된 Model을 View에 전달

 

'네트워크캠퍼스 > SPRING' 카테고리의 다른 글

Request Handling  (0) 2024.02.05
REST API  (0) 2024.02.01
Spring Core 모듈  (0) 2024.01.31
SOLID 원칙  (0) 2024.01.31
스프링 프레임워크  (0) 2024.01.31

1. Spring Core

→ 스프링 프레임워크의 기본적인 기능 담당

→ IoC 컨테이너와 DI 컨테이너를 제공하여 객체지향적인 설계를 촉진하고, 애플리케이션의 유연성과 확장성을 향상시킴

위 그림과 같이 애너테이션을 붙이는 방법만으로 객체 A에서 필요한 객체를 직접 생성하는 방식이 아닌 스프링 컨테이너에서 받아오는 방식으로 코드를 작성할 수 있게 된다.

 

1) IoC (Inversion of Control) 컨테이너

- 객체 생성과 관리 담당

- 빈(Bean) 객체 생성, 의존성 주입(DI) 수행

- 빈의 생명주기 관리

- XML, 애너테이션, 자바 설정파일을 사용하여 빈의 구성 정의

 

2) DI (Dependency Injection) 컨테이너

- 빈 객체간의 의존성 관리

- 런타임 시 빈들의 의존성 주입

- 인터페이스와 구현체 간의 느슨한 결합 가능하게 함

- 생성자 주입, Setter 주입 등 다양한 방법 제공  

 

3) AOP (Aspect Oriented Programming)

- Aspect Oriented Programming을 지원

- 메서드 호출 전/후 등 특정 시점에서 공통적인 로직을 수행할 수 있도록 함

- 애너테이션을 활용하여 간편하게 할 수 있음

 

4) 유틸리티 클래스 및 기능

- 스프링 프레임워크에서 자주 사용되는 유틸리티 클래스 제공

- 프로퍼티 파일을 읽어들이는 등의 기능 지원 

 

 

2. 의존성 주입 (Dependency Injection)

1) 필드 주입 (Field Injection)

→ 의존성 주입을 받을 객체의 필드에 @Autowired 어노테이션을 붙여 의존성 주입을 받는 방법

public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    // ...
}

코드 길이가 짧아 간편하지만, 테스트 코드 작성이 어렵고 DI 컨테이너에서 빈을 교체할 수 없어 유연성이 떨어짐

 

2) 수정자 주입 (Setter Injection)

→ 의존성 주입을 받을 객체의 Setter 메서드에 @Autowired 어노테이션을 붙여 의존성 주입을 받는 방법

public class UserServiceImpl implements UserService {

    private UserRepository userRepository;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // ...
}

선택적인 의존성 주입이 가능하여 유연하게 사용할 수 있지만, Setter 메서드를 public으로 열어두어야 하고, 객체 생성 후 의존성 주입이 완료되어야 하므로 일부 속성이 null일 수 있음

 

3) 생성자 주입 (Constructor Injection)

→ 의존성 주입을 받을 객체의 생성자 파라미터에 @Autowired 어노테이션을 붙여 의존성 주입을 받는 방법

public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // ...
}

불변성을 보장하고, 생성자를 통해 필수적인 의존성을 주입받기 때문에 객체 생성 시점에 의존성 주입이 완료되어 NullPoiinterException 등의 문제가 발생할 가능성이 적음

 

∴ 생성자 주입을 사용하자!

→ 객체 생성 시점에 필요한 의존성을 완전히 제공하고, 불변성을 유지하는 클래스를 구현할 수 있으며 의존성이 변경되는 경우에도 유연하게 대처할 수 있기 때문에 생성자 주입을 사용하는 것이 안전하다.

 

 

3. Spring Core 모듈 주요 애너테이션

@Component 스프링의 컴포넌트 스캔 기능을 사용하여 빈으로 등록하고자 하는 클래스에 사용
@Autowired 자동 주입을 위한 애너테이션으로 스프링 컨테이너가 해당 타입에 맞는 빈을 자동으로 주입
@Qualifier 여러 개의 빈이 등록되어 있을 때 어떤 빈을 주입할지 선택
@Controller 스프링 MVC에서 컨트롤러 역할을 하는 클래스에 사용
@Service 비즈니스 로직을 처리하는 서비스 클래스에 사용
@Repository 데이터베이스와 관련된 작업을 처리하는 DAO 클래스에 사용
@Configuration 자바 기반의 스프링 설정 클래스에 사용
@Bean @Configuration 클래스에서 스프링 빈을 직접 등록하기 위한 애너테이션
@Scope 빈의 범위를 지정하기 위한 애너테이션
singleton, prototype, request, session, global session 등이 있음
@Value 프로퍼티 값을 주입하기 위한 애너테이션
스프링의 PropertyPlaceholderConfigurer를 통해 값을 가져올 수 있음

 

'네트워크캠퍼스 > SPRING' 카테고리의 다른 글

Request Handling  (0) 2024.02.05
REST API  (0) 2024.02.01
Spring MVC 모듈  (0) 2024.02.01
SOLID 원칙  (0) 2024.01.31
스프링 프레임워크  (0) 2024.01.31

1. SOLID 원칙

1) SRP (Single Reponsibility Principle) : 단일 책임 원칙

한 클래스는 단 하나의 책임을 가져야 하고, 클래스가 변경되어야 하는 이유는 단 하나의 이유여야 한다.

public class User {
    private String username;
    private String password;
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
    
    public boolean isValid() {
        // 유효성 검사
        return true;
    }
    
    public void save() {
        // 데이터 저장
    }
}

위 클래스는 사용자 정보를 나타내는 클래스이고, 유효성 검사와 데이터 저장은 사용자 정보와 직접적인 연관이 없는 작업이므로 단일 책임 원칙에 어긋난다. → 클래스를 더 작은 단위로 분리하여 단일 책임을 부여하는 것이 바람직하다.

public class User {
    private String username;
    private String password;
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
}

public class UserValidator {
    public boolean isValid(User user) {
        // 유효성 검사
        return true;
    }
}

public class UserDAO {
    public void save(User user) {
        // 데이터 저장
    }
}

위와 같이 클래스를 분리하여 User 클래스는 사용자 정보만 관리하고, 유효성 검사와 데이터 저장은 각각 다른 클래스에서 담당한다.

→ 코드의 유지보수성이 증가하고, 다른 기능을 추가하거나 변경할 때 영향을 최소화할 수 있다.

 

2) OCP (Open-Closed Principle) : 개방-폐쇄 원칙

→ 기존 코드를 변경하지 않으면서 기능을 확장할 수 있도록 설계해야 한다.

public interface Shape {
    double calculateArea();
}

public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class Rectangle implements Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double calculateArea() {
        return width * height;
    }
}

public class AreaCalculator {
    public double calculateArea(Shape[] shapes) {
        double totalArea = 0;
        for (Shape shape : shapes) {
            totalArea += shape.calculateArea();
        }
        return totalArea;
    }
}

shape 인터페이스는 다양한 도형의 면적을 계산하기 위한 공통 기능을 정의하고, Circle과 Rectangle 클래스는 각각 원과 사각형의 면적을 계산하는 구체적인 기능을 구현한다.

AreaCalculator 클래스는 입력받은 여러 도형의 면적을 모두 더해 총 면적을 계산하는 역할을 하고, 이때 shape 인터페이스를 구현한 어떤 도형 클래스도 입력받을 수 있다. 이로 인해 새로운 도형 클래스가 추가되더라도 AreaCalculator 클래스는 수정할 필요 없이 기존의 동작을 그대로 유지할 수 있다.

 

3) LSP (Liskov Substitution Principle) : 리스코프 치환 원칙

→ 서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다. 자식의 일은 부모의 일보다 작거나 같아야 한다.

    즉, 자식클래스가 부모클래스의 인스턴스 대신 사용될 때 언제나 정상적으로 작동해야 한다.

public class PrintPositiveNum {
    private int num;

    public PrintPositiveNum(int num){
        this.num = num;
    }
    public getNum(){
        if(this.num <= 0){
                throw new RuntimeException("0 이하는 출력 불가능!!!");
        }
        return this.num;
    }
}

public class PrintNum extends PrintPositiveNum {
    @Override
    public getNum(){
        return this.num;
    }
}

public class Client{
    public static void main(String[] args){
        PrintPositiveNum obj = new PrintPositiveNum(1);
        //PrintPositiveNum obj = new PrintNum(-1); // 이 경우 위반
        obj.getNum();
    }
}

위 코드에서 PrintPositiveNum은 부모클래스로 양수만 화면에 출력 가능, 자식클래스인 PrintNum은 모든 범위의 숫자 출력 가능

Client 클래스에서 양수를 주는 경우 어떤 타입이 들어와도 실행가능하지만, 음수나 0의 경우 클라이언트측 코드를 수정해야만 함

부모가 수행가능한 범위 내에서만 오버라이딩을 해야 클라이언트측 코드를 고칠 필요가 없어진다는 것이 리스코프 치환 원칙의 핵심

 

4) ISP (Interface Segregation Principle) : 인터페이스 분리 원칙

→ 인터페이스는 클라이언트에 특화되어야 하고, 클라이언트가 사용하지 않는 메서드는 포함하지 않아야 한다.

public interface Shape {
    double calculateArea();
    double calculateVolume();
}

public class Rectangle implements Shape {
    private double width;
    private double height;

    public double calculateArea() {
        return width * height;
    }

    public double calculateVolume() {
        throw new UnsupportedOperationException();
    }
}

public class Cube implements Shape {
    private double width;
    private double height;
    private double depth;

    public double calculateArea() {
        return 2 * (width * height + width * depth + height * depth);
    }

    public double calculateVolume() {
        return width * height * depth;
    }
}

Shape 인터페이스는 도형의 면적과 부피를 계산하는 두 가지 메서드를 정의하는데, Rectangle 클래스는 면적만 가능하고 부피를 계산할 수 없기 때문에 ISP를 위반한다. 즉, 클라이언트는 Shape 인터페이스를 구현한 모든 클래스에서 부피 계산 메서드를 사용해야 하기 때문에 불필요한 의존성이 발생된다.

public interface Area {
    double calculateArea();
}

public interface Volume {
    double calculateVolume();
}

public class Rectangle implements Area {
    private double width;
    private double height;

    public double calculateArea() {
        return width * height;
    }
}

public class Cube implements Area, Volume {
    private double width;
    private double height;
    private double depth;

    public double calculateArea() {
        return 2 * (width * height + width * depth + height * depth);
    }

    public double calculateVolume() {
        return width * height * depth;
    }
}

위와 같이 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 작게 분리해야 한다.

 

5) DIP (Dependency Inversion Principle) : 의존 역전 원칙

→ 상위 수준 모듈은 하위 수준 모듈에 의존하지 않아야 하고, 추상화는 구체적인 사항에 의존하지 않아야 한다.

    ① 고차원 모듈은 저차원 모듈에 의존해서는 안 된다.

    ② 추상화는 세부사항에 의존해서는 안된다.

public class RedLight {
    public void turnOn() {
        System.out.println("Red Light turned on");
    }
}

public class Switch {
    private RedLight light;
    
    public Switch() {
        this.light = new RedLight();
    }
    
    public void flip() {
        if (light != null) {
            light.turnOn();
        }
    }
}

위 코드에서 Switch 클래스는 RedLight 클래스를 직접 생성하고 사용하므로 Switch 클래스가 Redlight 클래스에 의존하게 된다.

만약 RedLight 클래스를 BlueLight 클래스로 변경한다면 Switch 클래스도 변경해야 하는 문제가 발생한다.

public interface Light {
    void turnOn();
}

public class RedLight implements Light {
    @Override
    public void turnOn() {
        System.out.println("Red Light turned on");
    }
}

public class Switch {
    private Light light;
    
    public Switch(Light light) {
        this.light= light;
    }
    
    public void flip() {
        if (light!= null) {
            light.turnOn();
        }
    }
}

위 코드에서 Switch 클래스는 Light 인터페이스를 통해 RedLight 클래스와 의존 관계를 맺는다. 이렇게 함으로써 RedLight 클래스에 변경이 생긴다고 해도 Switch 클래스는 영향을 받지 않는다.

'네트워크캠퍼스 > SPRING' 카테고리의 다른 글

Request Handling  (0) 2024.02.05
REST API  (0) 2024.02.01
Spring MVC 모듈  (0) 2024.02.01
Spring Core 모듈  (0) 2024.01.31
스프링 프레임워크  (0) 2024.01.31

1. 스프링 프레임워크

1) 스프링 프레임워크란?

- 자바 언어를 위한 오픈소스 경량급 애플리케이션 프레임워크

- 도메인 객체를 다루기 위한 포괄적인 프로그래밍 및 테스트를 위한 가벼운 솔루션 제공

- IoC(Inversion of Control)와 AOP(Aspect Oriented Programming)를 적용한 경량 컨테이너로 구성

  → 객체의 생명주기와 의존성 관리 해결 및 프로그래머가 비즈니스 로직에 집중할 수 있도록 도움

- 여러 모듈로 구성되어 있으며 필요한 모듈만 선택하여 사용 가능

- 대표적인 모듈 : 스프링 코어, 스프링 MVC, 스프링 데이터 등

- 다양한 개발 환경과 통합이 가능하다는 장점

- 대규모 애플리케이션에서도 유지보수가 용이하고, 테스트 코드 작성이 쉬움

 

2) 스프링 프레임워크를 구성하는 대표적인 모듈

① Spring Core : 스프링 프레임워크의 핵심 모듈, IoC와 DI 기능 제공

② Spring MVC : 웹 개발 시 사용, MVC 패턴을 기반으로 웹애플리케이션 구성

③ Spring JDBC : JDBC를 사용하기 쉽도록 간단한 인터페이스 제공

④ Spring ORM : ORM 프레임워크를 사용하기 쉽도록 지원

⑤ Spring Security : 보안을 쉽게 구현할 수 있도록 지원 → 인증, 권한, 인가 등의 보안 기능

⑥ Spring Test : 테스트를 쉽게 작성할 수 있도록 지원, JUnit과 연동하여 작성 가능

⑦ Spring Data : 데이터 액세스를 지원하는 모듈

 

☆ 스프링 프레임워크는 강력한 객체지향 프로그램을 만들기 위한 도구이다.

- SOLID 원칙을 엄격하게 준수

- 다형성, 캡슐화, 추상화 등을 활용하여 결합도를 낮추고 유연하고 확장성 있는 코드 작성

- 코드의 가독성과 유지보수성, 확장성, 재사용성 높여 개발 생산성 향상

'네트워크캠퍼스 > SPRING' 카테고리의 다른 글

Request Handling  (0) 2024.02.05
REST API  (0) 2024.02.01
Spring MVC 모듈  (0) 2024.02.01
Spring Core 모듈  (0) 2024.01.31
SOLID 원칙  (0) 2024.01.31

+ Recent posts