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메서드에 정의가 된다.
- execution
- 어드바이스정의
- @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