schedulev2 lv0, Spring 숙련 강의
객체 지향 설계의 5가지 기본 원칙, 소프트웨어 설계에서 유지보수성, 확장성, 유연성을 높이기 위한 지침을 제공
객체(Bean)를 생성 및 관리하고 의존성을 주입하는 역할을 담당
객체를 직접 생성하는 경우, 객체 간의 의존성 및 결합도 ⬆
Spring Container를 사용하면 인터페이스에만 의존하는 설계가 가능
XML, Java Annotation, Java 설정파일 등을 통해 Bean으로 등록할 수 있음
1. XML
<beans>
<!-- myBean이라는 이름의 Bean 정의 -->
<bean id="myBean" class="com.example.MyBean" />
</beans>
public class MyApp {
public static void main(String[] args) {
// Spring 컨테이너에서 Bean을 가져옴
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService myService = context.getBean("myBean", MyBean.class);
myService.doSomething();
}
}
2. Annotation
- @ComponentScan
@ComponentScan은 Spring이 특정 패키지를 스캔하여 @Component가 붙은 클래스들을 자동으로 빈(Bean)으로 등록
@ComponentScan은 여기 경로에 있는 것들을 등록해줄꺼야라는 느낌이고 실제 등록은
ApplicationContext context = new AnnotationConfigApplicationContext(~~.class);
를 통해서 컨테이너를 시작해주고 @ComponentScan을 통해 그때 등록해주는 것
~~.class는 @Configuration이 선언된 클래스를 의미 AnnotationConfigApplicationContext의 생성자로 전달하여 **해당 클래스에서 직접 정의한 빈(Bean)과 @ComponentScan으로 검색된 빈을 컨테이너에 등록하는 부분임3. Java 설정파일(수동 등록)
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService();
}
}
public class MyApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = context.getBean(MyService.class);
myService.doSomething();
}
}
클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 디자인 패턴
Web Application은 불특정 다수의 고객이 동시에 많은 요청을 보내게 되면 요청을 할 때 마다 객체를 새로 생성되고 처리가 완료되면 소멸되어 메모리 낭비가 아주 심해지게 됨
객체가 한번만 생성되어 리소스를 절약 가능
싱글톤 패턴을 구현하기 위한 코드의 양이 많음
구현 클래스에 의존해야 함(DIP, OCP 위반)
ex)
public class MainApp {
public static void main(String[] args) {
// 첫 번째 싱글톤 인스턴스 요청, 구현클래스.getInstance();
Singleton instance1 = SingletonImpl.getInstance();
instance1.showMessage(); // 인스턴스 주소값 출력
// 두 번째 싱글톤 인스턴스 요청, 구현클래스.getInstance();
Singleton instance2 = SingletonImpl.getInstance();
instance2.showMessage(); // 인스턴스 주소값 출력
// 다른 구현체로 바꾸려면 DIP, OCP 위반
Singleton instance3 = SingletonImplV2.getInstance();
instance3.showMessage();
}
}
객체의 인스턴스를 하나만 생성하여 공유하는 싱글톤 패턴의 객체는 상태를 유지(stateful)하면 안됨
@ComponentScan(basePackages = {"com.example", "com.another"})@ComponentScan(basePackageClasses = MyApp.class)@ComponentScan(excludeFilters = @ComponentScan.Filter(SomeClass.class))@ComponentScan(includeFilters = @ComponentScan.Filter(Service.class))@Component("이름")
public class v1 ~~
@Component("이름")
public class v2 ~~
설정 변경(application.properties)
// true면 수동, 자동 Bean을 동시에 등록할 때 이름이 같으면 수동 Bean이 오버라이딩, false는 default값
spring.main.allow-bean-definition-overriding=true
@Autowired 는 의존성을 자동으로 주입할 때 사용하는 Annotationrequired = true)ex)
// 생성자 주입 방식
@Component
public class MyApp {
// 필드에 final 키워드 필수! 무조건 값이 있도록 만들어준다.
private final MyService myService;
// 생성자를 통해 의존성 주입, 생성자가 하나인 경우 생략 가능
@Autowired
public MyApp(MyService myService) {
this.myService = myService;
}
}
ex)
@Component
public class MyApp {
private MyService myService;
// Setter 주입, MyService가 Spring Bean으로 등록되지 않은 경우에도 주입이 가능
@Autowired
public void setMyService(MyService myService) {
this.myService = myService;
}
}
@Component
public class MyApp {
@Autowired
private MyService myService; // 필드에 직접 주입
}
⚠ 의존관계를 자동으로 주입할 객체가 Spring Bean으로 등록되어 있어야 @Autowired 로 주입이 가능
객체를 생성할 때 최초 한번만 호출(불변 보장)
실수 방지
같은 타입의 Bean이 중복된 경우 해결하기 위해 사용하는 Annotation
@Autowired 는 타입으로 먼저 주입을 시도하고 같은 타입의 Bean이 여러개라면 필드 이름 혹은 파라미터 이름으로 매칭public interface MyService { ... }
@Component
public class MyServiceImplV1 implements MyService { ... }
@Component
public class MyServiceImplV2 implements MyService { ... }
@Component
public class ConflictApp {
// 필드명을 Bean 이름으로 설정
@Autowired
private MyService myServiceImplV2;
...
}
@Component
@Qualifier("firstService")
public class MyServiceImplV1 implements MyService { ... }
@Component
@Qualifier("secondService")
public class MyServiceImplV2 implements MyService { ... }
@Component
public class ConflictApp {
private MyService myService;
// 생성자 주입에 구분자 추가
@Autowired
public ConflictApp(@Qualifier("firstService") MyService myService) {
this.myService = myService;
}
// setter 주입에 구분자 추가
@Autowired
public void setMyService(@Qualifier("firstService") MyService myService) {
this.myService = myService;
}
...
}
@Primary를 붙여서 지정된 Bean이 우선 순위를 가짐@Primary, @Qualifier를 동시 사용하면 @Qualifier가 우선순위 더 높음
특정 데이터(주로 클라이언트의 요청 데이터)의 값이 유효한지 확인하는 단계를 의미
addError() 와 같은 추가적인 기능을 제공BeanPropertyBindingResult🚫 주의사항 : BindingResult 파라미터는 검증대상 파라미터 뒤에 위치
@ModelAttribute 필드 or 객체에 파라미터 바인딩 오류가 발생✔ @ModelAttribute는 파라미터를 필드 하나하나에 바인딩하기 때문에 파라미터에 Binding Result가 함께 있는 경우 만약 그중 하나의 필드에 오류가 발생하면 해당 필드를 제외하고 나머지 필드들만 바인딩 된 후 Controller가 호출
특정 필드 검증의 경우 빈값, 길이, 크기, 형식 과 같은 간단한 로직이라 이 로직을 모든 프로젝트에 적용할 수 있도록 표준화 한 것
@RequestBody에도 사용할 수 있음의존성 추가(build.gradle)
implementation 'org.springframework.boot:spring-boot-starter-validation'
✔ import가 org.hibernate.validator로 시작하면 하이버네이트를 사용할 때만 제공되는 검증 기능으로 다른 구현체로 validator를 교체하였을 경우 동작X. 하지만 org.hibernate.validator를 대부분 사용
@Valid 는 JAVA 표준이고 @Validated 는 Spring 에서 제공하는 Annotation@Validated 를 통해 Group Validation 혹은 Controller 이외 계층에서 Validation이 가능@Valid 는 MethodArgumentNotValidException 예외를 발생@Validated 는 ConstraintViolationException 예외를 발생✔ Bean Validator는 바인딩에 실패한 필드는 Bean Validation을 적용하지 않음
Spring의 Bean Validation은 Default로 제공하는 Message들이 존재하고 임의로 수정할 수 있음
BindingResult에 등록된 검증 오류를 확인해보면 오류가 Annotation 이름으로 등록되어 있음ex) 총 가격이 10000원 이상이여야 함.
if (result < 10000) {
// Object Error
bindingResult.reject("totalMin", new Object[]{10000, result}, "총 합이 10000 이상이어야 합니다.");
}
// Error가 있으면 출력
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return bindingResult.getAllErrors().get(0).getDefaultMessage();
}
// 저장용 group
public interface SaveCheck {
}
// 수정용 group
public interface UpdateCheck {
}
@Data
public class ProductRequestDtoV2 {
// 저장, 수정 @NotBlank Validation 적용
@NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
private String name;
// 사용하는 모든곳에서 @NotNull Validation 적용
@NotNull
// 저장만 @Range 반영
@Range(min = 10, max = 10000, groups = SaveCheck.class)
private Integer price;
}
@Slf4j
@RestController
public class ProductController {
@PostMapping("/v2/product")
public String save(
// 저장 속성값 설정
@Validated(SaveCheck.class) @ModelAttribute ProductRequestDtoV2 requestDtoV2
) {
~~~
}
@PutMapping("/v2/product/{id}")
public String update(
@PathVariable Long id,
// 수정 속성값 설정
@Validated(UpdateCheck.class) @ModelAttribute ProductRequestDto test
) {
~~~
}
}
@Validated
@Valid
@Validated를 사용해야 함오늘은 숙련 강의도 다 듣고 과제를 시작하게 되었다. 그 전 과제와 약간씩 다른 부분들이 존재해서 조금 더 걸린 것 같다. 이렇게 밀린 TIL을 쓰면서 복습하고 과제로 실습을 하면 실력이 확실히 늘 것 같다. 앞으로 더 힘내서 해봐야 겠다. (☆▽☆)