본문 바로가기
  • 분조장의 개발 블로그
Spring Boot

인프런 - 예제로 배우는 스프링 입문 (개정판) 정리(3)

by 무아니 2021. 6. 6.

9. 스프링 AOP<프록시 패턴>

스프링 AOP: 기존 코드 건들이지 않고 새 기능 추가하기

 

예제1)

interface Payment.java 구현체: Cash.java, CreditCard.java

    ㄴ 각 구현체에선 인터ㅔ이스에 정의된 pay()메서드를 조금씩 다르게 @Override함.
    ㄴ Payment.java 구현체를 사용하는 Store.java 

public class Store {

    Payment payment;

    public Store(Payment payment){ //생성자 주입
	    this.payment = payment;
    }

    public void buySomething(){
	    payment.pay(100);
    }
}

    ㄴ Store 입장에선 Payment라는 인터페이스만 써도 만약 Cash 대신 CreditCard를 주면 CreditCard가 pay메서드에 대해 알아서 판단함.

 

예제2)

Payment.java 구현체 CashPerf.java 추가 

public class CashPerf implements Payment{
	
    Payment cash = new Cash(); // 기존 Cash의 pay()를 그대로 수행하기 위한 클래스이므로
    
    @Override
    public void pay(int amount) {
    	StopWatch sw = new StopWatch(); // 기존 로직 앞뒤로 성능 측정 로직만 추가합니다.
    	sw.start();
  		
        cash.pay(amount); //기존 cash.pay를 그대로 수행. 
		
        sw.stop();
        System.out.println(sw.prettyPrint());
    }
}

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestPayment{

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

    ㄴ Store 내부 코드 전혀 바뀌지 않음

    ㄴ 클라이언트(테스트코드)도 로직은 수정할 필요없고, Store 내부 payment필드에 Cash가 들어갈 땐 물건구입만 할 수도 있고, CashPerf가 들어갈 땐 추가로 성능 측정까지 할 수도 있다. 

**정리: 새로운 코드를 추가하긴 했지만 기존 코드를 건들이지 않았다.
  ㄴ 스프링 AOP에선 이런 과정이 대부분 자동으로 이뤄진다. (내부 복잡한 메커니즘이 있긴 함)
  ㄴ 위 예시에서
CashPerf.java와 같은 객체를 프록시라고 부르고 이는 자동으로 빈이 등록될 때 만들어진다.
  ㄴ 즉, 우리는 Cash를 빈으로 등록하도록 설정했지만 자동으로 프록시가 생겨서 Cash 대신 프록시인 CashPerf가 빈으로 등록되고, 빈 주입으로 사용할 때도 CashPerf를 사용하게되는 흐름이다.

 

다시 @Transactional (Spring AOP가 적용돼있는 어노테이션)

  ㄴ @Transactional이 붙은 클래스는 프록시가 새로 만들어진다.

  ㄴ 원래 jdbc 설정엔 내가 보내려는 sql 앞뒤에 코드가 붙게 됨. connection 설정, 롤백 등.

  ㄴ @Transactional은 그 코드를 생략할 수 있게 해주는 어노테이션이고 프록시내부를 살펴보면 설정에 필요한 추가 코드들이 붙어있을 것이다!


10. 스프링 AOP<스프링 @AOP 실습>

예제) 메서드 처리 시간 로깅하기

  ㄴ 애노테이션 @LogExecutionTime 붙이기

  ㄴ 없는 어노테이션이므로 직접 생성해준다. -> java의 어노테이션 강좌로 공부하길

@Retention(RetentionPolicy.RUNTIME) //어디까지 유지해줄 것인가. 
@Target(ElementType.METHOD) //어디에 쓰이는지
public @interface LogExecutionTime {
	//아무것도 안 쓰면 어노테이션 썼을 때 오류만 안 나고 로직 수행은 없다.
}

@Component
@Aspect
public class LogAspect {
	Logger logger = LoggerFactory.getLogger(LogAspect.class); //slf4j 로거
    
	/** 
    @Arround 붙인 메서드에선 ProceedingJoinPoint 파라미터를 받을 수 있음
    joinPoint -> @LogExecutionTime 어노테이션이 붙은 타깃
    */
	@Arround(“@annotation(LogExecutionTime)”)
	public Object LogExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable{
	StopWatch sw = new StopWatch();

	sw.start();
	Object proceed = joinPoint.preceed(); // 타깃 실행한다. 단지 앞뒤에 로거 출력
	sw.stop();
	logger.info(sw.prettyPrint());

	return proceed;
	}
}

ㄴ 이 예제가 스프링이 제공해주는 애노테이션 기반의 AOP이다.(프록시 패턴으로 동작)

 

스프링 AOP는 공부할 게 정말 많다.

    ㄴ @Around 안에 들어가는 표현식.. 때에 따라 어노테이션 생략도 가능함

    ㄴ inteliij에선 aspect가 추가되는 것도 아이콘 추가해서 보여줌.


11.스프링 PSA<스프링 PSA>

PSA = Portable Service Abstraction

    ㄴ Portable : 손쉽게 다른 기술 스택으로 교체할 수 있다는 의미.

    ㄴ Service Abraction: 서비스 추상화. 기술을 더 편하게 사용하기 위해 추상화한다.

 

서비스 추상화 예시(Servlet)

    ㄴ 우리는 서블릿 애플리케이션을 만들고있음에도 코드를 작성할 때 서블릿을 전혀 쓰고있지 않다.

    ㄴ 서블릿 사용하기....

public class FooServlet extends Httpservlet {

    @Override
    public void doGet(HttpServletRequest re, HttpServletRepsonse resp) throws Exception{
        super.doGet(req, resp);
    }

    @Override
    public void doPost(HttpServletRequest re, HttpServletRepsonse resp) throws Exception{
        super.doPost(req, resp);
    }
}

        ㄴ 각 요청 url에 대해 servlet을 만들고 내부 메서드에서 get, post 요청을 처리한다.

        ㄴ 위 예제엔 어떤 url에 매핑되는지 안 써있는데? -> web.xml 파일 등에 서블릿 매핑

    ㄴ 스프링 펫클리닉 예제는....

        ㄴ 각 메서드에서 바로 url + method(get, post 등)의 요청을 구분한다.

        ㄴ 이 아래 기반에는 서블릿이 작동한다. 우리가 사용하는 건 추상화객체..

 

스프링이 제공하는 PSA 살펴보기(스프링 웹 MVC, 스프링 트랜잭션)

1) 스프링 웹 MVC

    ㄴ @Controller : 요청을 매핑. 매핑된 메서드의 input으로 들어온 Map에 넘겨줄 값을 추가해줌  

    ㄴ 이게 왜 추상화 계층임?

        ㄴ 서블릿을 직접 사용하지 않고도 편하게 매핑 및 요청 처리할 수 있음 (Service Abstraction)

        

 스프링 웹 MVC에서 다른 기술 스택(webflux)으로 바꿔보기 (Portable)

    ㄴ webflux는 기존의 spring web mvc 와 흡사하지만..

        ㄴ spring web mvc: 하나의 요청 -> 하나의 thread

        ㄴ webflux: cpu개수만큼 thread를 유지하면서 앞단에서 요청받음. 최소한의 thread로 가용성을 높인다.

   ㄴ 적용:  pom.xml

  1. spring-boot-starter-web 삭제: web-mvc 사용하는 의존성이므로 더이상 modelAndView 사용 못함.
  2. spring-boot-starter-webflux 추가

    ㄴ Netty 기반으로 실행됨!

** 원래 Netty기반으로 요청을 처리하려면 아래 처럼 코딩해야되지만... 우리는 스프링이 제공해주는 추상화 인터페이스를 쓰기때문에 web-mvc 에서 썼던 코드를 수정하지 않고도 Netty를 쓸 수 있는 것이다.

Netty 맛보기



2) 스프링 트랜잭션 (@Transactional)

    ㄴ 트랜잭션: a->b->c 까지 해야 완료하는 단위. 다 같이 되거나 다 같이 취소되거나.

    ㄴ @Transactional이 붙어있지 않으면 아래처럼 dbConnection을 만들어주고 commit 또는 rollback하는 과정을 다 적어줘야함.

    ㄴ but, 스프링에서 제공하는 @Transactional은 저 과정을 추상화해서 어노테이션으로 제공(Service Abstraction)하므로 다른 기술로 바꿔서 쓸 수 있다(Portable).

    ㄴ 어떤 기술을 바꾼다는 것임? -> jdbc를 사용하는 경우 DatasourceTransactionManager 사용. hibernate, jpa사용하는 경우 JpaTransactionmanger 를 사용할 수 있다. (객체 타입만 달라지고 커넥션 생성, 커밋,롤백하는 과정은 비슷해서 개발자가 직접 설정하지 않아도 스프링 트랜잭션이 해당 로직을 대신 처리해주며 개발자는 비즈니스 로직에 집중할 수 있다는 뜻.)

 

dbConnection 설정 및 쿼리 수행 후 commit 또는 rollback하는 코드. @Transactional을 쓰면 위 코드를 작성하지 않는다.

 

 

그 외에도..

스프링 캐시 (@Cacheable, @CashEvict)

    ㄴ 캐시도 구현체가 여러개지만 어노테이션만 쓰면 구현체가 바뀌어도 우리 코드는 바뀌지 않는다.

 

댓글