2020-4-2 다시 시작하는 스프링(8) - 스프링 AOP

스프링AOP

  • 스프링AOP특정
    • 프록시 기반AOP구현체
    • 스프링 빈에만 AOP를 적용할 수 있다.
    • 모든 AOP 기능을 제공하는 것이 아니라, IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제에 대한 해결책을 제공하는 것이 목적
    • 프록시를 쓰는이유 : 기존 코드변경 없이 접근,제어, 부가기능 추가할 수 있다. 인터페이스가 있는 경우에는 인터페이스로 주입을 받는게 좋다.
@Service
public class SimpleEventService implements EvenetService {
    @Override
    public void createEvenet() {
        long begin = System.currentTimeMillis();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("create an event");
        System.out.println(System.currentTimeMillis()-begin);
    }

    @Override
    public void publishEvent() {
        long begin = System.currentTimeMillis();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("published an event");
        System.out.println(System.currentTimeMillis()-begin);
    }
}
  • 여기서 메서드 전후로 시간 측정하는 부분이 cross-cutting-concern(횡단관심사)이다.

Making proxy

@Primary
@Service
public class ProxySimpleEventService implements EvenetService {
    @Autowired
    SimpleEventService simpleEventService;

//    @Autowired
//    EvenetService simpleEventService;

    @Override
    public void createEvent() {
        long begin = System.currentTimeMillis();
        simpleEventService.createEvent();
        System.out.println(System.currentTimeMillis()-begin);
    }

    @Override
    public void publishEvent() {
        simpleEventService.publishEvent();
    }

    @Override
    public void deleteEvent() {
        long begin = System.currentTimeMillis();
        simpleEventService.deleteEvent();
        System.out.println(System.currentTimeMillis()-begin);
    }
}
  • 프록시를 만들기 위해 같은 인터페이스를 구현해야한다.
  • proxy는 subject빈을 주입받아야한다.
  • @Autowired로 주입을 받을때 타입을 SimpleEventService나 EventService로 해야한다.
  • 프록시가 RealSubject를 가지고 있고, 부가적인 기능들은 프록시에 처리. 클라이언트에서는 EventService를 주입받지만 @Primary로 주입한 Proxy를 주입받는다.
  • 문제
    • 프록시를 만들어도 위의 코드처럼 중복코드들이 생성.
    • Proxy클래스를 만드는데 비용이 발생
  • 해결책: IoC컨테이너가 제공하는 기반 시설과 Dynamic프록시를 사용
    • 동적프록시 : 동적으로 프록시 객체를 생성
      • 자바가 제공하는 방법은 인터페이스 기반으로 프록시 생성
      • CGlib은 클래스 기반 프록시도 지원
    • 스프링IoC: 기존 빈을 대체하는 동적 프록시 빈을 만들어준다. => BeanPostProcessor를 사용
      • 클라이언트 코드 변경없음
      • class AbstractAutoProxyCreater implements BeanPostProcessor : BeanPostProcessor의 구현체이다.
      • BeanPostProcessor가 새로운 빈 인스턴스를 조작할 수 있다.
  • 스프링이 SimpleEvenetService빈을 감싸는 프록시빈을 만든다. 그 빈을 대신 등록해준다.

spring-aop

  • pom.xml에 aop의존성을 추가한다. spring-boot-starter-aop를 추가하면 된다.
  • 추가가 되면 내부적으로 aspectjweaver가 내부적으로 추가된다.
  • 2가지를 정의해야한다.
    • 어디에 정의할것인가 : PointCut
    • 해야할일 : Advice
@Aspect
@Component
public class PerfAspect {

  	@Around("@annotation(PerLogging)") // case1 is more useful
    @Around("execution(* me.jimmy.springmvc.*.EvenetService.*(..))") // case2
  	@Around("bean(simpleEventService)")
    public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
        long begin = System.currentTimeMillis();
        Object retVal = pjp.proceed();
        System.out.println(System.currentTimeMillis()-begin);
        return retVal;
    }

    @Before("bean(simpleEventService)")
    public void hello() {
        System.out.println("hello Before");
    }
}

Aspect정의

  • @Aspect
  • 빈으로 등록해야되서 @Component도 추가

포인트컷정의

  • @PointCut(표현식)
  • 주요 표현식
    • execution
      • pointcut expression을 통해 어느메서드에 적용시킬지 정할 수 있다. 이 경우 적용하고 싶지 않은 메서드들도 정의되기때문에 어노테이션을 적용해서 하는것이 좋다.
      • excution은 &&,   등 조합이 안된다.
    • @annotation
      • 어노테이션을 만들때 RetentionPolicy를 Class이상으로 줘야한다. 그러면 어노테이션 정보가 바이트코드까지 남아있게된다. 하지만 source로 변경하면 컴파일하고 나서 사라진다. runtime까지 유지를 안해도 된다. 기본값은 class이다.
    • bean : 빈이 가지고 있는 모든 public메서드에 정의가 된다.
  • 어드바이스정의
    • @Before
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 결론 : execution대신 @annotaion을 사용하자.

번외

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication app = new SpringApplication(DemoApplication.class);
		app.setWebApplicationType(WebApplicationType.NONE);
		app.run(args);
	}
}
  • setWebApplicationType에서 None옵션을 통해 웹서버를 키지않고 돌릴 수 있다.
  • WebApplicationType에는 NONE,SERVLET, REACTIVE가 있다.

Reference

  • https://stackoverflow.com/questions/2650168/building-vs-compiling-java
Written on April 2, 2020