스프링입문정리-by-백기선님
2021년08월22일인프런의 백기선님의 강의를 보고 정리하는 글입니다.
intelliJ에서 command + fn + f9를 하면 변경사항 적용
스프링 IoC
IoC는 Inversion of Control로 제어의 역전을 의미한다. 본래라면 개발자가 직접 인스턴스를 만들어주거나 의존을 주입을 해주어야 하는데, 이런 점을 스프링에서 대신 만들어 주기 때문에 개발자는 비즈니스 로직에만 집중할 수 있게 된다. 본래라면 아래와 같이 내가 의존을 넣어줘야 한다. 의존이란 한 클래스에서 다른 클래스의 인스턴스를 생성해 메서드 등을 사용함을 의미한다. 어쨌든 스프링에서는 IoC 컨테이너라가 의존성을 자동으로 해주는데, 이를 DI(Dependecy Injection), 의존성 주입이라고 한다.
class OwnerController {
private OwnerRepository repo;
public OwnerController(OwnerRepository repo) {
this.repop = repo;
}
}
class OwnerControllerTest {
@Test
public void create() {
OwnerRepository repo = new OwnerRepository();
OwnerController controller = new OwnerController(repo);
}
}
그런데 스프링에서는 특정 어노테이션을 달아줌으로써 스프링에서 관리해주는 Bean 객체가 되고 스프링이 알아서 자료형을 보고 의존을 주입해준다.
스프링 IoC 컨테이너
IoC 컨테이너는 ApplicationContext(BeanFactory)를 사용해서 만들어진다. 이 컨테이너의 역할은 빈을 만들어주고 빈들 사이의 의존성을 엮어주고 제공해준다. 모든 클래스가 빈으로 등록되는 것은 아니고 특정 어노테이션등을 달아준 클래스들이 빈 객체로 관리된다.
intelliJ에서 클래스 옆에 녹색 콩(?)모양의 표시가 있으면 빈 객체이다. (마우수를 올려보면 빈이라 적혀있다 ㅎㅎ)
스프링에서 의존성 주입은 빈 끼리만 가능하다. 즉, 스프링 IoC 컨테이너에 등록된 빈 끼리만 의존성 주입이 가능하다.
public OwnerController(OwnerRepository clinicService, VisitRepository visits) {
this.owners = clinicService;
this.visits = visits;
}
이런 코드가 있는데, 만약 이 메서드의 클래스가 빈 객체로 등록되어있다면 컨테이너는 매개 변수의 자료형을 탐색하여 알맞는 빈 객체를 주입해준다. 테스트를 통해서 ApplicationContext에 등록된 빈을 한 번 조회해볼 수 있다.
@Autowired
private ApplicationContext applicationContext;
@Test
public void getBean() {
OwnerController bean = applicationContext.getBean(OwnerController.class);
assertThat(bean).isNotNull();
}
빈 객체는 싱글톤으로 제공되는데, 하나의 인스턴스로만 사용하는 것이다. 이렇게 하면 관리 용이성에 있어서 좋다.
option + command + v를 입력하면 변수명을 추천해준다.
스프링 빈(Bean)
빈 객체는 ApplicationContext가 만든 객체를 의미한다. 다른 경우 (우리가 new 해서 만든 객체)는 빈 객체가 아니다. 그렇다면 빈은 어떻게 등록을 해야할까
- Component 어노테이션으로 등록하기
- @Component로 등록을 하거나 그 외
- @Repository
- @Service
- @Controller
- @Component로 등록을 하거나 그 외
- 직접 일일히 XML이나 자바 설정 파일에 등록을 한다.
첫 번째 방법은 Spring IoC 컨테이너를 만들고 빈을 등록할 때 사용하는 인터페이스인데 이를 라이플 사이클 콜백이라고 한다. @CompnenetScan을 통해서 어디서부터 어디까지 스캔을 해서 등록할지를 정하여 하위 패키지들을 스캔을 하게 된다. 그리고 위에 적은 어노테이션들을 찾아 생성하여 빈으로 등록하는 것이다.
두 번째 방법에서는 요새는 xml파일보다는 자바 설정 파일을 등록을 하는 추세라 한다.
// SampleConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SampleConfig {
@Bean
public SampleController sampleController() {
return new SampleController();
}
}
위처럼 등록하면 @Controller 어노테이션을 안 달아도 된다.
의존성 주입 (Dependency Injection)
@Autowired 어노테이션을 통해 의존성을 주입할 수 있다. 이 어노테이션은 필드나 세터, 생성자에 달 수 있다. 생성자의 경우에는 클래스에 생성자가 하나있고, 매개 변수의 타입이 빈 객체이면 어노테이션을 안 달수도 있다. 스프링 공식 문서에서 추천하는 방법은 생성자에다가 어노테이션을 다는 것이라고 한다. 생성자를 통해 필요한 의존을 강제할 수 있기 때문이다.
Spring AOP (Aspect Oriented Programming)
스프링은 크게 IoC, AoP, PSA를 제공해준다. AOP는 관점 지향적인 프로그래밍이라고 해석된다. 예를 들어 보면
class A {
method a() {
AAAA
어쩌구 저쩌구
BBBB
}
}
class B {
method b() {
AAAA
이것저것
BBBB
}
}
class C {
method c() {
AAAA
c method 입니다.
BBBB
}
}
위와 같은 코드처럼 AAA, BBB가 클래스의 메서드마다 내용이 반복되는 것을 볼 수 있다. 이런 식이면 AAAA의 내용을 변경해주어야 하면 다른 메서드에서도 다 바꿔주어야 하는 번거로움이 발생할 수 있다. 그렇기에 공통된 내용은 따로 모아두어 관리하자는게 AOP이다.
AOP의 구현 방법은 여러가지가 있다.
- 컴파일: A.java —–(AOP)—–> A.class (AspectJ가 처리해줌)
- 바이트코드 조작: A.java -> A.class –> 메모리에 올릴 때 조작 (AspectJ)
- 프록시 패턴 (스프링 AOP)
스프링 @APO 실습
package org.springframework.samples.petclinic.owner;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
package org.springframework.samples.petclinic.owner;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Component
@Aspect
public class LogAspect {
logger = LoggerFactory.getLogger(LogAspect.class);
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object proceed = joinPoint.proceed();
stopWatch.stop();
logger.info(stopWatch.prettyPrint());
return proceed;
}
}
// 컨트롤러에 @LogExecution 어노테이션을 달아주면 된다.