3. Bean

스프링 IoC 컨테이너가 관리하는 객체

IoC컨테이너를 생성하고 @어노테이션 으로 Bean을 등록할 때 사용하는 다양한 인터페이스들 -> 라이프사이클 콜백

다양한 라이프사이클 콜백중 @Component 어노테이션이 등록된 클래스를 찾아 해당 인스턴스를 Bean으로 등록하는 annotation process 처리기가 설정되어 있습니다.

@ComponentScan 이 Bean객체를 스캔할 루트를 지정해주고 하위 패키지 범위에서 모든 클래스의 Bean 등록 여부를 확인합니다.

  • @Component

    • @Controller
    • @Repository
    • @Service
    • @Configuration

이를 통해 Controller 클래스를 구현시 사용했던 @Controller 어노테이션은 우리가 직접 해당 클래스를 Bean으로 등록하지 않아도 스프링 IoC컨테이너에서 직접 라이프사이클 콜백@ComponentScan 의 기능을 통해 Bean으로 등록하여 사용할 수 있도록 도와주는 것입니다.

SampleController

1
2
3
4
@Controller
public class SampleController {

}

SampleControllerTest

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleControllerTest {

    @Autowired
    ApplicationContext applicationContext;

    @Test
    public void testDI() {
      //SampleController의 @Controller 어노테이션의 Bean 객체 등록이 되어있는지 확인
        SampleController bean = applicationContext.getBean(SampleController.class);
        assertThat(bean).isNotNull();
    }
}

Bean의 직접 등록

1
2
3
4
5
6
7
8
@Configuration
public class SampleConfig {

    @Bean
    public SampleController sampleController() {
            return new SampleController();
    }
}

위와 같은 코드로 Bean을 직접 등록하면 Controller에 등록한 @Controller 어노테이션을 제거해도 Bean으로 등록된 것을 확인할 수 있습니다.

4. 의존성 주입

  • 생성자
  • 필드
  • Setter

OwnerControllerPetRepository 주입하기

생성자 사용

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Controller
class OwnerController {

    private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
    private final OwnerRepository owners;


    private PetRepository pets;


    public OwnerController(OwnerRepository clinicService, PetRepository petRepository) {
        this.owners = clinicService;
      	this.pets = petRepository;
    }
  
}

필드

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Controller
class OwnerController {

    private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
    private final OwnerRepository owners;

		@Autowired
    private PetRepository pets;
  
}

Setter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class OwnerController {

    private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
    private final OwnerRepository owners;
//    private final ApplicationContext applicationContext;

    private PetRepository petRepository;

    @Autowired
    public void setPetRepository(PetRepository petRepository) {
        this.petRepository = petRepository;
    }

5. AOP

관점 지향 프로그래밍(Aspect Oriented Programming)은 공통 처리 메소드나 기능을 별도의 클래스에 구현하여 관리하는 것을 말합니다.

쉽게 말해 구현하고자 하는 기능의 코드를 해당 메소드에 작성하지 않았지만 기능이 별도로 외부에서 관리하며 처리되는 것으로 방법은 크게 3가지로 나눌 수 있습니다.

  • 컴파일

    • A.java —(AOP)—> A.class

      • 컴파일 도중 AOP 추가
  • 바이트코드 조작

    • A.java -> A.class —(AOP)—> 메모리(AspectJ)

      • 클래스 로더가 클래스파일을 읽으면서 메모리에 적재시 AOP 추가
  • 프록시 패턴(스프링 AOP)

    • 프록시는 어떤 일을 대신 명령하는 것을 말하며 기존 코드를 건드리지 않고 새로운 기능을 추가

5-1 프록시 패턴

Payment.java

1
2
3
public interface Payment {
    void pay(int amount);
}

Cash.java

1
2
3
4
5
6
7
8
public class Cash implements Payment  {

    @Override
    public void pay(int amount) {
        System.out.println("현금 : " + amount + "원");
    }
    
}

CashPerf.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class CashPerf implements Payment{
		//프록시 클래스
    Payment cash;

    @Override
    public void pay(int amount) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();

            cash = new Cash();
            cash.pay(amount);

            stopWatch.stop();
            System.out.println(stopWatch.prettyPrint());
        }

    }

Store.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Store {

    private Payment payment;

    public Store (Payment payment) {
        this.payment=payment;
    }

    public void buySomething(int amount) {
        payment.pay(amount);
    }
}

SampleTest.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class StoreTest {

    @Test
    public void testPay() {
      	//Payment Cash = new Cash();
        Payment cashPerf = new CashPerf();
        Store store = new Store(cashPerf);
        store.buySomething(100);
    }
}

위에 코드를 보면 인터페이스로 Payment 클래스를 작성하였고 기존 코드의 영향을 받지 않도록 프록시 패턴을 작성한 예제입니다.

실제로 Client 코드인 Store를 호출하게 되면 이러한 처리는 프록시 역할을 하는 클래스 CashPerf를 주입받게 됩니다. 직접 접근하지 않고 CashPerf(프록시) 객체에 Cash 클래스의 인스턴스를 작성함과 동시에 StopWatch로 성능측정을 구현하였습니다. 이를 통해 Cash클래스에 직접 접근하는 방법이 아닌 기존 코드는 건드리지 않는 프록시 패턴을 확인할 수 있습니다.

5-2. AOP 적용 예제

  • 사용할 @어노테이션 표기(메소드, 필드 등등)
  • IDE IntelliJ (option + Enter) -> @어노테이션 생성
  • @Target(어느 영역에 적용할지)
  • @Retention(어노테이션의 정보 유지 기준)

Aspect.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Component
@Aspect
public class LogAspect {

    Logger 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();
        logger.info(stopWatch.prettyPrint());

        stopWatch.stop();

        return proceed;
    }

}

OwnerController.java

1
2
3
4
5
6
7
		
@GetMapping("/owners/find")
    @LogExecutionTime
    public String initFindForm(Map<String, Object> model) {
        model.put("owner", new Owner());
        return "owners/findOwners";
    }

LogExecutionTime

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}

결과

6. PSA

Portable Service Abstraction 는 쉽게 말하여 환경에 제약없이 유연하고 확장성 있는 방식의 추상화 구조를 말합니다.

조금 더 유연한 코드를 작성할 수 있다는 의미로 볼 수 있습니다.

ex) 스프링에서 어노테이션을 통해 @GetMapping, @PostMapping등 추상화 계층을 사용하여 코딩을 하지만 내부적으로는 HttpServlet을 상속받아 서블릿 기반으로 동작하게 됩니다.

  • Spring 웹 MVC(@Controller, @GetMapping)

  • Spring 트랜잭션(@Transactinal)

    • setAutoCommit(false);commit(); 등의 자동처리
    • JPA, mybatis, hibernate 등 구현체가 변해도 코드는 동일하게 @Transactional을 사용
  • Spring 캐시(@Cacheable)

    • 사용하는 구현체는 변해도 사용하는 어노테이션은 변화 x

Reference