Study Plus는 스터디를 진행 한 후에 스스로 부족하다고 느낀 부분과 더 공부가 필요한 부분, 스스로가 생각하지 못한 부분에 대해서 추가적인 공부를 진행한 것입니다. 개인적으로 추가하는 Study입니다. 빠샤🔥
의존성 주입
Spring에서 지원하는 핵심 프로그래밍 모델 중에 하나인 의존성 주입은 외부에서 두 객체 간의 관계를 정해주는 디자인 패턴입니다. 인터페이스를 두어서 클래스 사이에서는 의존관계가 고정되지 않도록하고 런타임시에 관계를 동적으로 주입하여서 유연성을 가지게 합니다. 그리고 두 객체 간의 의존성을 맺어주는 것을 의존성 주입이라고 합니다.
public class Animal {
private Dog dog;
public Animal(){
this.dog = new Dog();
}
}
해당 코드는 두개의 클래스가 강하게 연결되어 있습니다. 객체들 간의 관계가 아니라 클래스들 간의 관계가 이루어져 있습니다. 두 클래스가 강하게 연결되어 있어서 만약 Dog가 아닌 Cat을 사용한다고 한다면 Animal 생성자의 변경이 필요합니다. 즉, 유연성이 떨어지게 됩니다. 이에 대한 해결책으로 상속을 떠올린다면, 상속은 제약이 많고 확장성이 떨어진다는 단점이 존재합니다.
또한 객체들간의 관계가 아니라 클래스 간의 관계가 만들어졌다는 것 또한 문제입니다. 객체지향적 설계라면 Dog이건 아니건, 해당 클래스가 인터페이스를 구현했다면, 인터페이스의 product로 사용가능합니다.
결국 Animal에서 어떤 동물인지 관심을 분리하지 않았기에 해당 문제가 나타났습니다. 스프링에서는 의존성 주입을 통해서 해당 문제를 해결합니다.
interface Animal {
void makeSound();
}
@Component
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Bark");
}
}
@Component
public class AnimalService {
private final Animal animal;
@Autowired
public AnimalService(Animal animal) {
this.animal = animal;
}
public void callMakeSound() {
animal.makeSound();
}
}
먼저 동물의 행동을 정의하는 인터페이스를 만듭니다.
Dog클래스는 Animal의 인터페이스를 구현합니다. makeSound 메서드를 구현합니다. @Component 애너테이션을 통해서 Spring에게 해당 클래스가 빈으로 등록되어야 함을 알려줍니다. 이는 Spring IoC 컨테이너가 해당 클래스의 인스턴스를 생성하고 관리하게 합니다.
AnimalService는 Animal 인터페이스에 의존합니다. 생성자를 통해서 Animal 타입의 빈을 찾아서 주입하도록 합니다. @Autowired 애너테이션을 이용합니다. callMakeSound 메서드에서는 주입된 Animal 객체의 makeSound 메서드를 호출합니다. 위의 코드에서는 Bark가 출력됩니다.
위와 같이 스프링 프레임워크의 도움을 받아서 객체를 사용합니다. 하지만 객체를 계속해서 생성하고 소멸한다면 많은 부담이 됩니다. 그래서 Spring에서는 Bean을 싱글톤으로 관리합니다.
싱글톤
매번 클라이언트에서 요청이 올 때마다 로직을 만들어서 빈을 새로 만들어서 사용한다면 부하가 걸리고 감당하기 힘들것입니다. 따라서 스프링에서는 빈을 싱글톤 스코프로 관리하여서 1개의 요청이 왔을때 여러 쓰레드가 빈을 공유하여 처리합니다. 즉, 스프링에 여러번 빈을 요청하더라도 매번 동일한 객체를 돌려준다는 것입니다.
따라서 스프링에서는 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공하는데, 그것은 싱글톤 레지스트리입니다. 싱글톤 레지스트리에서는 static 메소드나 private 생성자를 사용하지 않아서 객체지향적 개발이 가능하고 테스트를 하기에 편리합니다.
싱글톤이 멀티 쓰레드 환경에서 사용되기 위해서는 기본적으로 무상태여야 합니다. 여러 쓰레드들이 동시에 정보를 수정하는 것은 위험하기 때문입니다. 직접 구현을 하면 많은 어려움이 있겠지만 스프링에서 직접 관리해주므로 개발자들은 더욱 편하게 객체지향적 개발을 할수 있습니다.
의존성 주입에는 다양한 방법들이 존재합니다. 아래는 각각의 방법에 대한 설명입니다.
생성자 주입
@Component
public class AnimalService {
private final Animal animal;
@Autowired
public AnimalService(Animal animal) {
this.animal = animal;
}
public void callMakeSound() {
animal.makeSound();
}
}
생성자 주입은 생성자의 호츌 시점에 무조건 1회가 보장됩니다. 따라서 주입받은 객체가 변하지 않거나, 반드시 객체의 주입이 필요한 경우에 사용합니다. 또한 Spring 프레임워크에서는 생성자 주입을 적극 권장합니다. 따라서 생성자가 1개만 있는 경우에는 @Autowired 애너테이션이 없더라도 주입되게 하는 편의성을 제공합니다. 위의 코드에서도 해당 어노테이션을 없애도 같은 동작을 수행합니다.
수정자 주입
@Component
public class AnimalService {
private final Animal animal;
@Autowired
public void setAnimal(Animal animal) {
this.animal = animal;
}
public void callMakeSound() {
animal.makeSound();
}
}
Setter를 통해서 의존 관계를 주입하는 방식입니다. Setter 주입은 생성자 주입과 다르게 주입받는 객체가 변경될 가능성이 있는 경우에 사용합니다. 주입될 대상이 없는 경우에는 오류가 발생합니다. 자바 초기에는 많이 사용되었지만 시간이 지나면서 다른 방식이 더욱 주목을 받고 있습니다.
필드 주입
@Component
public class AnimalService {
@Autowired
private final Animal animal;
public void callMakeSound() {
animal.makeSound();
}
}
필드에 바로 의존성을 주입하는 경우 외부에서 접근이 불가합니다. 따라서 테스트 코드가 중요해지는 지금 필드의 객체를 수정할 수 없는 필드 주입은 거희 사용되지 않게 되었습니다. 또한 IntelliJ에서의 사용이 불가하고 DI 프레임워크가 반드시 필요하다는 단점들로 인하여 사용되지 않고 있습니다.
만약 생성자 주입이을 사용한다면 여러 이점이 있습니다.
먼저, 생성자 주입을 통해서 변경의 가능성을 배제하고 불변성을 보장합니다. 의존 관계의 변경이 필요한 경우는 거의없기 때문입니다. 또한 생성자 주입으로 작성된 코드는 자바만으로 테스트 코드를 만들 수 있습니다. 따라서 다른 프레임워크의 도움이 필요하지 않습니다.
생성자가 하나인 경우 애너테이션을 생략할 수 있습니다. 이때 Lombok을 사용가능하게 합니다. 따라서 코드 다이어어트가 가능하게 됩니다. 또한 순환 참조가 불가합니다. 다른 주입 방법의 경우 순환 참조 에러를 발생시킬 수 있지만 생성자 주입은 그렇지 않습니다. (스프링부트 2.6부터는 순화참조가 기본적으로 불가능하도록 변경되긴 했습니다.)
HTTP 상태 코드
Http 상태 코드란, 클라이언트가 보낸 Http 요청에 대한 응답 코드입니다. 상태 코드에 따라서 요청의 성공/실패 여부를 세자리 코드를 통해서 구분합니다.
- 1xx = 조건부 응답, 클라이언트가 서버에 정보를 요청했지만 해당 요청이 여전히 처리중임을 나타냅니다.
- 2xx = 성공, 해당 코드는 서버가 브라우저의 요청을 수신하고 성공적으로 처리했음을 의미합니다.
- 3xx = 리디렉션, 요청된 페이지가 일시적으로 또는 영구적으로 이동되었음을 알립니다. 원래 요청한 리소스를 사용할 수 없습니다.
- 4xx = 요청 오류, 클라이언트의 잘못된 요청으로 서버가 요청을 수행 할 수 없음을 알립니다.
- 5xx = 서버 오류, 요청을 성공했지만 서버 오류로 인하여 요청을 정상 처리하지 못함을 나타냅니다.
해당 상태 코드들을 모른다면 어떤 상태를 나타내는지, 어떻게 해당 문제들을 해결하는지 파악할 수 없습니다.
흔히 볼 수 있는 상태 코드
- 200 = OK, 요청이 성공적으로 수행되었음을 알리고 요청한 내용을 응답합니다. SEO 측면에서 추가적인 작업이 필요하지 않습니다.
- 301 = Move Permanently, 해당 URL이 영구적으로 다른 위치로 이동했음을 알립니다. 해당 요청 이후 다른 요청은 다른 URL로 리디렉션됩니다.
- 302 = Move Temporarily, 일시적으로 URL이 다른 위치로 이동했음을 알립니다. 시간이 많이 지나면 301로 처리하기도 합니다. 301과 다르게 다른 URL로 리디렉션되지 않습니다.
- 404 = Not Found, 요청한 리소스를 찾을 수 없다는 뜻입니다. 서버와의 통신에는 문제가 없지만 서버에서 클라이언트가 요청한 값을 찾을 수 없음을 의미합니다. 삭제된 콘텐츠에 대한 URL을 제거하지 않았다면 404 에러가 나올수 있습니다.
- 500 = Server Error, 서버가 사용자가 요청한 리소스를 처리하지 못하는 경우에 해당 코드를 볼수 있습니다. 서버 구성 에러 중 일반적인 에러에 속합니다.
- 503 = Service Unavailable, 서버를 현재 사용할 수 없음을 알립니다. 404에러가 한개의 페이지에서 나타나는 것이라면 503에러는 웹사이트 전체에서 나타납니다. 주로 서버의 과부화 혹은 서버 점검이 이유가 됩니다.
Getter and Setter
private에 대해서 접근하기 위해서는 메서드를 통해서 접근해야합니다. 이때 해당 메서드 들을 Getter, Setter라고 부릅니다.
Getter: 내부의 멤버 변수에 저장된 값으로 return을 해줍니다. 매개변수는 없으며, 리턴값만 있는 메서드로 정의됩니다.
Setter: 외부로부터 정보를 전달 받아서 멤버 변수에 저장합니다. return값은 없지만, 매개변수는 존재합니다.
해당 메서드들의 이름을 특정함으로써 private를 관리하기 위해 사용하기 훨씬 편리해집니다.
하지만 Getter와 Setter를 사용하는 것에 대해서 부정적인 시선이 존재하기도 합니다.
Private로 정보를 은닉하고 public 메서드로 해당 값에 접근가능하게 한다면 객체 지향 프로그래밍에 반하는 결과가 나오게 됩니다.
Setter의 경우 객체의 속성을 바꿀 수 있습니다. 하지만 어떤 이유로 해당 속성이 바뀌게 되는지 알수 없습니다. 또한 다른 객체로 책임이 분산 될 수 있습니다. 예를 들어서 Account라는 객체가 존재하고 해당 객체에서는 자신의 잔고를 책임지는 기능을 합니다. 이때 해당 객체는 Getter와 Setter로 이루어져 있습니다. 반면 AccountService에서는 출금을 하는 메서드가 존재합니다. 이때 잔액이 충분한지 안한지 확인하는 역할까지 동시에 수행하게 됩니다. 이런 구조에서 만약 Account 객체의 구조가 변경된다면 끔찍한 결과가 나올겁니다. 🤮
Getter의 경우 위의 Account를 사용하면 Getter에서 정보를 가져와서 해당 값이 조건에 맞는지 확인하고 비즈니스 로직이 수행되게 됩니다. 만약 Account에서 더이상 잔액을 제공하지 않고 다른 방식으로 제공한다면 Getter를 사용하고 있던 모든 코드를 수정해야 합니다. 🤮
따라서, 해당 Getter and Setter를 사용하지 않고 명확한 의도를 가지는 메서드를 만들어서 사용한다면 위와 같은 문제는 사라질 겁니다.
출처
https://mangkyu.tistory.com/150
https://mangkyu.tistory.com/125
https://seo.tbwakorea.com/blog/what-is-http-status-code/#part2