Aspect-oriendted Programming (AOP)은 OOP를 보완하는 수단으로, 흩어진 Aspect를 모듈화 할 수 있는 프로그래밍 기법이다.
흩어진 관심사(Crosscutting Concerns)
AOP를 적용하면?
ex) 트랜잭션, 로깅 ...
Advice는 해아할 일, Pointcut은 어디에 적용해야 하는지 Aspect는 Advice + Pointcut
Target은 위의 그림에서는 Class A, Class B, Class C 이다. JoinPoint는 끼어들 수 있는 모든 지점이다(생성자 호출, 메서드 호출 전, 필드 접근 전 등).
기존 코드
public interface EventService {
void createEvent();
void publishEvent();
void deleteEvent();
}
@Service
public class SimpleEventService implements EventService{
@Override
public void createEvent() {
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Created an event");
}
@Override
public void publishEvent() {
try {
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Published an event");
}
public void deleteEvent(){
System.out.println("Delete an event");
}
}
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
EventService eventService;
@Override
public void run(ApplicationArguments args) throws Exception {
eventService.createEvent();
eventService.publishEvent();
eventService.deleteEvent();
}
}
@Service
public class SimpleEventService implements EventService{
@Override
public void createEvent() {
long begin = System.currentTimeMillis();
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Created 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);
}
public void deleteEvent(){ // 이 메소드에서는 성능 측정 기능을 추가하고 싶지 않다.
System.out.println("Delete an event");
}
}
문제는 기존 코드를 건드리지 않고 싶다는 것이다. 따라서 프록시 패턴을 사용한다.
@Primary
@Service
public class ProxySimpleEventService implements EventService{
@Autowired
EventService simpleEventService; // 빈의 이름에 기반해 SimpleEventService를 주입받는다.
//@Autowired
//SimpleEventService simpleEventService;
@Override
public void createEvent() {
long begin = System.currentTimeMillis();
simpleEventService.createEvent();
System.out.println(System.currentTimeMillis() - begin);
}
@Override
public void publishEvent() {
long begin = System.currentTimeMillis();
simpleEventService.publishEvent();
System.out.println(System.currentTimeMillis() - begin);
}
@Override
public void deleteEvent() {
simpleEventService.deleteEvent();
}
}
프록시 클래스가 하는 일은 의존성을 주입받은 simpleEventService 인스턴스에게 위임되었으나, 성능은 프록시 클래스에서 측정한다.
참고 : 웹 서버 모드로 실행하지 않는 방법
@SpringBootApplication
public class Springtest11Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Springtest11Application.class);
app.setWebApplicationType(WebApplicationType.NONE);
app.run(args);
//SpringApplication.run(Springtest11Application.class, args);
}
}
애노테이션 기반의 스프링 @AOP
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Component
@Aspect
public class PerfAspect {
//@Around("execution(* com.example..*.EventService.*(..))")
//com.example 패키지에 속한 EventService에 있는 모든 메서드에 적용한다.
//@Around("@annotation(PerfLogging)")
//PerfLogging 애노테이션이 달려있는 메소드에만 적용한다.
@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");
}
}
/**
* 이 애노테이션을 사용하면 성능을 로깅해 줍니다.
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PerfLogging {
}
@Service
public class SimpleEventService implements EventService{
@PerfLogging
@Override
public void createEvent() {
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Created an event");
}
@PerfLogging
@Override
public void publishEvent() {
try {
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Published an event");
}
public void deleteEvent(){
System.out.println("Delete an event");
}
}
주의할 점은 애노테이션을 만들 때 Retention 정보를 CLASS(default) 이상으로 줘야 한다. SOURCE가 되면 컴파일 이후에 애노테이션이 사라진다.
참고