날아라쩡글이의 블로그입니다.

AOP만들기 본문

중앙 HTA (2106기) story/spring java framwork story

AOP만들기

날아라쩡글이 2022. 1. 12. 08:19
반응형

우리는 @Aspect을 만들면 된다.

  • pom.xml에서 라이브러리를 추가한다. 
  • <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
  • <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.9.7</version>
    </dependency>
  • 관점지향 프로그래밍을 지원하는 라이브러리 의존성을 추가한다. 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인 모든 클래스를 의미한다.
반응형
Comments