관점지향 프로그래밍을 지원하는 라이브러리 의존성을 추가한다. aspectjrt와 aspectRutime을 다운받아준다.
sample package로 aop파일을 만들어준다.
package를 만들고 context-root에 등록해준다.
namespace에서 aop를 체크하고 <aop:aspectj-autoproxy />라는 위빙을 이용해서 proxy객체로 변경해주는 작업을 선언하고
다음 bean class로 aop를 선언해주면 된다.
aop를 선언할 때 Aspect는 what, where, when을 설정해주면 된다.
loggin모든 실행전후로 실행하고 싶다면 일단 aop에 @Aspect라는 어노테이션을 부착한다.
@Aspect
공통기능 적용 패키지 이다.
공통기능 적용 패키지에는 what(공통기능) + when(언제) + where(대상) 정보를 포함한다.
what
logging()메소드의 수행문을 작성한다.
when
@Before 어노테이션을 부착에 언제 실행될 지 작성한다.
where
어디에서 실행될지 AspectJ 표현식을 이용하여 괄호 안에 작성한다.
@Before("within(com.sample.service.*Service)")//when/where (com.sample.service패키지안에서 Service로 끝나는 애들)AspectJ 표현식 public void logging() { //what System.out.println("################ AOP로 로그를 출력합니다."); //What 로그를 출력하게 할꺼야 }
주요 어노테이션
@Aspect
AutoProxyCreator 스프링 컨테이너는 등록된 빈 중에서 @Aspect어노테이션이 부착된 빈을 스캔해서 대상 객체를 검색하고, 대상객체의 조인포인트와 결합시킨 프록시 객체를 만들어서 스프링 컨테이너의 빈으로 등록한다.
메소드 실행시점 어노테이션
@Befor
공통기능이 대상 메소드 실행전에 실행된다.
try에서 맨 처음을 담당한다.
@AfterReturning
공통기능이 대상 메소드가 오류없이 종료된 후에 실행된다.
try안에서 맨마지막을 담당한다.
@AfterThrowing
공통기능이 대상 메소드 실행중 예외가 발생하면서 실행된다.
throw new로 예외를 던질 때 실행된다.
@After
공통기능이 대상 메소드가 오류없이 종료된 후에 실행된다.
finally로 사용된다.
@Around
공통기능이 대상 메소드 실행 전부에 실행된다.
선언적 트랜잭션 처리를 지원하는 TransactionManage의 구현체들이 @AroundAdvice와 유사하다
거의 @Around를 사용한다.
모든 부분에서 실행된다.
규칙 role
@Pointcut
공통 기능이 적용될 규칙을 지정할 때 사용한다.
loggin에 jointpoint를 적을 수 있다. before와 after는 매개변수를 작성하지 않아도 되지만
Around는 매개변수를 작성해야하고, 메소드가 반드시 존재해야한다.
반환값이 있으면 값이 존재해야하며 jointpoint.proceed(); 메소드를 이용해서 @AroundAdive가 적용되는 대상메소드 실행 후 반환값을 담는 변수로 값을 지정해야한다.
proceed()로 값을 담지 않을 경우, 실행전코드, 정상종료 후 , 종료 후 코드만 실행되고 아예 기능자체가 실행되지 않으며 서버가 오류가 뜬다.
서비스의 메소드를 실행하기 위해서는 값을 전달하는 것이 중요하다.
jointPoint와 ProceedingJoinPoint
공통기능이 적용되는 지점에 대한 정보를 제공하는 객체이다.
대상객체, 대상메소드, 대상메소드의 매개변수 등의 정보를 조회할 수 있다.
JointPoint는 실행싯점이 @Before, @After, @AfterReturning, @AfterThrowin인 Advice에서 사용한다.
ProceedingJointPoint는 실행시점이 @ARound인 Advice에서만 사용한다.
ProceedingJointPoint는 @AroundAdvice가 적용되는 대상 메소드를 실행시키는 기능이 포하모디어 있다.
@Around("execution(* com.sample.service.*Service.*(..))") //공통기능 정의시 특별한 제약이 없지만, Around은 제약조건이 존재한다. public Object runningTimeCheck(ProceedingJoinPoint jointPoint) throws Throwable { //생략할수없다
//@Around를 사용하면 ProceedingJointPoint를 매개변수에 작성해주어야한다.
throwable 예외를 받아야한다.
Object returnValue = null; 반환타입이 Object로 값을 담는 변수를 null로 지정하여 선언한다.
try {
//@Around Advice가 적용되는 대상메소드 실행 전에 실행할 코드 작성 -@Before 싯점과 동일하다. System.out.println("################################# @Around Advice에서 대상메소드 실행전 코드를 실행함"); returnValue = jointPoint.proceed(); //@Around Advice가 적용되는 대상메소드를 실행시키는 코드 //값이 없으면 null값이 들어간다. //@Around Advice가 적용되는 대상메소드 정상적으로 종료된 후 실행할 코드 작성 -@AfterReturning싯점과 동일하다. System.out.println("################################# @Around Advice에서 대상메소드 정상 종료 후 코드를 실행함"); return returnValue; //예외가 발생하지 않으면 return한다. }catch(Throwable e) { //@Around Advice가 적용되는 대상메소드 실행 중 오류가 발생했을 때 실행할 코드 작성 -@AfterThrowing싯점과 동일하다. System.out.println("################################# @Around Advice에서 대상메소드 오류발생후 코드를 실행함");
throw e; //예외가 발생시 예외를 던져준다. } finally { //@Around Advice가 적용되는 대상메소드 실행 후에 실행할 코드 작성 -@After싯점과 동일하다. System.out.println("################################# @Around Advice에서 대상메소드 종료후 코드를 실행함");
}
대상 메소드 실행 전후에 실행할 공통기능을 구현하는 Advice 메소드
@Around
반환타입은 반드시 Object로 정한다.
메소드 이름은 상관없다.
매개변수에는 반드시 ProceedingJoinPoint타입의 매개변수를 포함해야한다.
예외는 throwable을 던져진다.
대상 메소드 실행 전, 후에 실행할 공통기능임을 나타낸다.
서비스 내부에서 실행되는 것을 확인할 수 있다.
stopwatch라는 객체를 사용해서 시간을 확인할 수 있다.
StopWatch stopWatch = new StopWatch();객체를 생성 try앞에 stopWatch.start();를 호출한다. finally마지막에 stopWatch.stop()을 호출하여 stopWatch.getTotalTimeMillis()메소드를 호출하여 밀리초로 표현할 수 있다.
실행을 하면 첫번째는 커넥션 풀 구성을 위해서 시간이 걸리고 두번째 이후로는 똑같은 쿼리는 또 요청할 가능성이 존재하여 Oracle메모리안에 가지고 있기 때문에 몇밀리초 소모가 된다.
그럼이런 시간체크는 언제 할까?
부하테스트/통합테스트시 사용한다. step by step으로 사람의 수를 늘려서 서버의 부하테스트를 진행한다.
시나리오를 받고 그 시나리오에 해당하는지 확인한다
ex)부하테스트가 잘 안되면 연말정산 때 국세청 서버가 터지는 것과 동일하게 서버가 정지될 수 있다. 테스트는 필수 이다.
만약, AOP가 존재하지 않았다면?
Autowired로 Dao호출해서 입력하고, 전체적으로 Service에 입력해야한다.
public List<Book> getAllBookList(){ StopWatch stopWatch = new StopWatch(); stopWatch.start(); List<Book> books = bookDao.getAllBooks(); stopWatch.stop(); statDao.insert("getAllBookList", stopWatch.getTotalMilis()); SQL에 Dao로 만들어서 입력해준다. };
패키지가 몇백개가 된다.
공부를 배우는 우리마져도, 실전이면 엄청많을 것이다.
package만 해도 몇백개가 되는데, 전체 작성하려면 복잡하고, 부하테스트가 끝나면 삭제를 해줘야한다.
그러나 AOP로 설정을 한다면 공통기능을 별도의 클래스로 만들수있고, 해당 메소드를 사용할 수 있고, root에서 주석처리를 하면 되니 제도로 쉽다.
AOP로 탈부착이 쉽다. 핵심기능을 해치지 않는다.
공통기능 AOP는 제한적이다.
요청객체와 응답객체의 접근이 어렵다.
requstContextHolder를 만들었고, 객체접근이 쉬워지라고 제공했다.
springAOP는 만능 공통 기능으로 어렵고, 일반적으로는 트랜잭션으로 처리한다. AOP처럼 동작하고
인증과 인가의 경우 인터셉터와 filter로 확인 하면 된다.
AspectJ의 언어표현식
execution(* com.sample.service.*Service.*(..))
execution (반환타입/ 패키지 명을 포함한 클래스와 /메소드명)
execution(* com.sample.dao.BookDao.get*(..))
com.sample.dao패키지의 BookDao클래스에서 get으로 시작하는 모든 메소드가 실행될 때
execution(* com.sample.dao.BookDao.update*(*))
com.sample.dao패키지의 BookDao클래스애서 update로 시작하고, 매개변수 한 개 전달받는 메소드가 실행될 때
execution(* com.sample.dao.BookDao.*(..))
com.sample.dao패키지의 BookDao클래스에서 모든 메소드가 실행될 때
execution(Book com.sample.dao.BookDao.*(..))
com.sample.dao패키지의 BookDao클래스에서 반환타입이 Book객체인 모든 메소드가 실행될 떄
execution(* com.sample..*Dao.*(..))
com.sample 패키지에서 Dao로 끝나는 클래스의 모든 메소드가 실행될 때, com.sample 패키지의 모든 하위 패키지에서 Dao로 끝나는 클래스의 모든 메소드가 실행될 때
반환타입에 *은 반환타입이 any라는 의미이다.(어떤 반환타입이든 가능하다라는 의미)
패키지 경로의 ..은 현재 패키지 및 그 하위의 모든 패키지를 의미한다.
클래스 이름, 메소드 이름에 *을 붙이면 any라는 의미이다.
매개변수자리에 *은 매개변수 any라는 의미이다.(어떤 타입이든 가능하다라는 의미다.)
매개변수자리에 ..은 매개변수 상관없다라는 의미다.(매개변수가 없어도 되고, 매개변수가 여러 개여도 되고, 매개변수 타입도 상관없다.
get*(*) 과 get*(..)의 차이
get*(*)은 getBookByNo(int no), getBookByTitle(String title)등이 해당된다. 아무거나 한개는 와야한다.
get*(..은 getAllBoos(), getBookByNo(int no), getBookByTitle(String title), getBooksByPrice(int min, int max)등이 해당된다. // 매개변수가 하나도 없어도되고, 10개넘게 와도 상관없다.
within(com.sample.service.*Service)")
within(패키지를 포함한 클래스 명)
within(com.sample.service.BookService)
com.sample.service.BookService객체의 모든 메소드가 실행될 때
within(com.sample.service.*Service)
com.sample.service 패키지의 모든 Service객체의 모든 메소드가 실행될 떄
within(com.sample..*ServiceImpl)
com.sample의 모든 ServiceImpl와 com.sample의 모든 하위패키지의 ServiceImpl객체의 모든 메소드가 실행될 때
패키지 경로 ..은 현재 패키지 및 그 하위 모든 패키지를 의미한다.
클래스 이름에 *을 붙이면 any라는 의미로 *Service는 Service인 모든 클래스를 의미한다.