Spring은 AOP이다. (java : OOP(객체지향))
AOP란 관점 지향 프로그램으로 가장 핵심으로 관심 분리가 있다.
관심 분리란? 핵심관심과 횡단관심을 분리하는 것이다. 이렇게 말로만 하면 모호 하므로 아래 그림을 참고하자
위 그림을 보면 일반적으로 드는 예로 은행구조이다. 우린 은행에서 업무를 본다 주로 계좌이체나 대출 이런것들이 있을것이다. 이런 기능들은 프로그래밍 입장에서 핵심 기능들이다. 그리고 이러한 기능들을 하는데 무조건 실행하는 공통적인 기능들이 있다 바로 기록을 남기는 로깅이나 트랜젝션 등 이 있다 이런 것들을 횡단관심 (부가 기능)이라 한다. 이것을 왜 분리해야하는지 의문이들것이다.
객체 지향 관점에서 먼저 작성 해 보자. 코드 양이 좀 많다.
먼저 모든 메서드가 실행되기 전에 실행 될 코드를 작성해보자
package com.springbook.biz.common;
public class LogAdvice {
public void printLog() {
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
해당 코드는 모든 메서드가 실행 되기 전에 실행 될 것이다. 그러려면 service에서 객체를 생성해서 매번 호출 해야한다.
package com.springbook.biz.board.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.springbook.biz.board.BoardService;
import com.springbook.biz.board.BoardVO;
import com.springbook.biz.common.LogAdvice;
@Service("boardService")
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardDAO boardDAO;
private LogAdvice log;
public BoardServiceImpl() {
}
@Override
public void insertBoard(BoardVO vo) {
log.printLog();
boardDAO.insertBoard(vo);
}
@Override
public void updateBoard(BoardVO vo) {
log.printLog();
boardDAO.updateBoard(vo);
}
@Override
public void deleteBoard(BoardVO vo) {
log.printLog();
boardDAO.deleteBoard(vo);
}
@Override
public BoardVO getBoard(BoardVO vo) {
log.printLog();
return boardDAO.getBoard(vo);
}
@Override
public List<BoardVO> getBoardList(BoardVO vo) {
log.printLog();
return boardDAO.getBoardList(vo);
}
}
이렇게 말이다. 이건 문제가 크게 되지 않는다. 다만 호출해야 하는 메서드랑 클래스가 달라진다면?? 코드를 수정해야한다.
package com.springbook.biz.common;
public class Log4jAdvice {
public void printLogging() {
System.out.println("[공통 로그-Log4j] 비즈니스 로직 수행 전 동작");
}
}
package com.springbook.biz.board.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.springbook.biz.board.BoardService;
import com.springbook.biz.board.BoardVO;
import com.springbook.biz.common.Log4jAdvice;
@Service("boardService")
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardDAO boardDAO;
private Log4jAdvice log;
public BoardServiceImpl() {
}
@Override
public void insertBoard(BoardVO vo) {
log.printLogging();
boardDAO.insertBoard(vo);
}
@Override
public void updateBoard(BoardVO vo) {
log.printLogging();
boardDAO.updateBoard(vo);
}
@Override
public void deleteBoard(BoardVO vo) {
log.printLogging();
boardDAO.deleteBoard(vo);
}
@Override
public BoardVO getBoard(BoardVO vo) {
log.printLogging();
return boardDAO.getBoard(vo);
}
@Override
public List<BoardVO> getBoardList(BoardVO vo) {
log.printLogging();
return boardDAO.getBoardList(vo);
}
}
이처럼 말이다.
이제 AOP기능을 추가해보자 먼저 boardServiceImpl 클래스를 원상태로 돌리고 applicationContext.xml에 가서 추가해준다.
<bean id="log" class="com.springbook.biz.common.LogAdvice" /> <!-- 먼저 횡단관심에 해당하는 객체 생성 id는 aop:aspect ref와 매칭된다.-->
<aop:config> <!-- AOP 설정에 해당하는 요소 -->
<aop:pointcut expression="execution(* com.springbook.biz..*Impl.*(..))" id="allPointcut"/> <!-- id는 pointcut-ref와 매칭된다. -->
<aop:aspect ref="log">
<aop:before method="printLog" pointcut-ref="allPointcut"/>
</aop:aspect>
</aop:config>
그리고 실행시켜주면 아래 결과처럼 모든 메서드 앞에 실행 된것을 볼 수 있다.
package com.springbook.biz.board;
import java.util.List;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class BoardServiceClient {
public static void main(String[] args) {
AbstractApplicationContext container = new GenericXmlApplicationContext("applicationContext.xml");
BoardService boardService = (BoardService)container.getBean("boardService");
BoardVO vo = new BoardVO();
vo.setTitle("임시제목");
vo.setWriter("홍길동");
vo.setContent("일빠...");
boardService.insertBoard(vo);
List<BoardVO> boardList = boardService.getBoardList(vo);
for(BoardVO board : boardList) {
System.out.println("---> "+board.toString());
}
container.close();
}
}
위 xml 설정에서 알 수 없는 단어들이 많았을 것이다. AOP를 하면서 반드시 알아햐 하는 단어들이다.
- joinPoint : 조인 포인트는 직역하면 만나는 지점이다. 따라서 횡단 관심에 해당하는 메서드가 핵심기능을 만나는 메서드를 만나는 지점을 의미한다. 여기선 serviceImpl의 모든 메서드가 해당이 된다.
- pointCut : 포인트 컷은 직역하면 포인트를 자르다 라고 해석할 수 있는데이러한 조인포인트들 중에서 횡단관심이 실행 될 메서드 만을 잘라서 실행하는것을 의미한다.
- Advice : 횡단 관심으로 여기선 LogAdvice와 Log4jAdvice가 해당이 된다.
- Aspect : Aspect는 pointcut과 Advice의 결합으로 어떤 포인트 컷에 어떤 어드바이스가 결합하는지를 설정하는 것이다.
- Weaving : 위빙은 핵심관심이 호출될 때 횡단관심이 삽입되는것을 의미하며 Aspect가 설정한 곳으로 위빙된다.
종합하면 위 설정중 aop:aspect라는 요소를 봤을 것이다.
com.springbook.biz..*Impl.*(..) -> 모든 return형을 가진 com.springbook.biz 패키지에 끝자리가 Impl을 가진 모든 클래스의 모든 메서드의 0개 이상의 모든 파라미터를 가진 joinpoint 중 log라는 아이디를 가진 클래스의 printLog메서드 Advice 가 allPointcut이라는 아이디를 가진 포인트 컷에 위빙된다고 할 수 있다.
이렇게 설정한것이라고 할 수 있다. 이렇게 AOP를 이용하면 유지보수도 훨씬 쉬워질 것이다.
이렇게 xml 설정 뿐만 아니라 어노테이션으로도 할 수 있다.
xml에 방금 aop설정을 주석처리하고 아래 문장을 추가한다.
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
package com.springbook.biz.common;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;
@Service
@Aspect
public class LogAdvice {
@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
public void allPointcut() {}
@Before("allPointcut()")
public void printLog() {
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
이후 LogAdvice를 이렇게 수정하는데 여기서 어노테이션이 좀 많이 보일 것이다.
1. @Pointcut : 포인트 컷은 횡단 관심이 실행될 메서드의 범위를 괄호 안에 지정하고 메서드로 만들었다. 이때 메서드 명은 xml에서 id가 된다.
2. @Before : 횡단 관심이 위빙 될 위치를 지정하는 어노테이션으로 괄호 안에 Pointcut의 메서드를 지정하여 메서드의 Pointcut 어노테이션의 포인트컷이 된 메서드가 실행 될때 실행된다. (말이 좀 어렵다.....)
* 이것말고 @After(무조건 실행), @AfterTrowing(예외 발생시 실행), @AfterReturning(결과가 리턴 될 경우 실행), @Around(전후로 실행) 이 있다. xml 설정처럼 사용하면 된다.
3. @Aspect : Advice와 Pointcut의 합친 용어로 해당 클래스에 Pointcut과 Advice가 있다는 것을 알려주는 어노테이션
4. @Service : 해당 메서드가 실행되려면 클래스의 객체가 있어야 하므로 사용된다.
'BACK > Spring' 카테고리의 다른 글
[Back] Spring JdbcTemplate (0) | 2020.07.09 |
---|---|
[Back] Spring Annotation ( 스프링 어노테이션 ) (0) | 2020.06.26 |
[Back] Spring 기초 (0) | 2020.06.25 |