실습 코드 참조
moonhy7/SpringFramework: Spring Framework 실습 코드 정리 (github.com)
1.1절 AOP 이해하기
1. 결합도와 응집도
- 비즈니스 컴포넌트 개발에서 중요한 두 가지 원칙 : 낮은 결합도와 높은 응집도
- 스프링의 의존성 주입을 이용하면 객체들의 결합도를 떨어뜨릴 수 있어서 의존관계를 쉽게 변경 가능
- IoC (Inversion of Control) : 낮은 결합도를 만들어주는 프로그래밍
- AoP (Aspect Oriented Programming) : 높은 응집도를 만들어주는 프로그래밍
2. 핵심 관심과 횡단 관심
1) 비즈니스 메소드 코드 특성
- 핵심 비즈니스 로직은 몇 줄 안 되고 로깅, 예외, 트랜잭션 처리 같은 부가적인 코드가 대부분임
- 비즈니스 메소드에 부가적인 코드들이 반복되어 코드 분석이나 유지보수가 어려워짐
- AOP는 이러한 부가적인 공통 코드들을 효율적으로 관리하도록 함
2) 관심 분리 (Separation of Concerns)
- 횡단 관심 (Crosscutting Concerns) : 메소드마다 공통으로 등장하는 로깅이나 예외, 트랜잭션 처리 같은 코드들
- 핵심 관심 (Core Concerns) : 사용자의 요청에 따라 실제로 수행되는 핵심 비즈니스 로직
- 이 두 관심을 완벽하게 분리한다면 실제 비즈니스 로직만으로 메소드를 구성할 수 있으므로 더욱 간결하고 응집도가 높아짐
3. 객체지향 (OOP) vs 관점지향(AOP)
- OOP 언어에서는 위에서 말한 공통 코드를 완벽하게 독립적인 모듈로 분리해내기가 어려움
- 스프링에서는 AOP를 이용하여 관심 분리 문제를 해결할 수 있음
- Java, JS 등은 객체지향프로그래밍(OOP)이고 Spring은 관점지향프로그래밍(AOP)임
4. OOP에서 완벽한 관심 분리가 어려운 이유 ( _025_BoardWeb_Business_UnderstandAOP )
1) LogAdvice 클래스 생성
- BoardService 컴포넌트의 모든 비즈니스 메소드가 실행되기 직전에 공통으로 처리할 로직을 LogAdvice 클래스에 printLog() 메소드로 구현
package com.springbook.biz.common;
public class LogAdvice {
public void printLog() {
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
2) BoardServiceImpl 클래스
- printLog() 메소드를 사용할 수 있도록 LogAdvice 멤버변수 등록
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;
import com.springbook.biz.common.LogAdvice;
@Service("boardService")
/*
* 업무로직 처리를 담당하는 클래스
* ex) 계좌이체 기능 처리할 때 DB입력은 DAO 하게 되는데
* DB입력 전 필요한 작업들(계좌암호화, 금액 컴마 추가...) 하는 로직들을 ServiceImpl클래스에서 처리해주면 됨
* */
public class BoardServiceImpl implements BoardService{
@Autowired
BoardDAO boardDAO;
private LogAdvice log;
public BoardServiceImpl() {
log = new LogAdvice();
}
public void insertBoard(BoardVO vo) {
log.printLog();
boardDAO.insertBoard(vo);
}
public void updateBoard(BoardVO vo) {
log.printLog();
boardDAO.upadateBoard(vo);
}
public void deleteBoard(BoardVO vo) {
log.printLog();
boardDAO.deleteBoard(vo);
}
public BoardVO getBoard(BoardVO vo) {
return boardDAO.getBoard(vo);
}
public List<BoardVO> getBoardList(BoardVO vo) {
log.printLog();
return boardDAO.getBoardList(vo);
}
}
3) 실행 결과
- BoardServiceImpl 객체가 생성될 때 생성자에서 LogAdvice 객체도 함께 생성됨
- 각 비즈니스 메소드에서 비즈니스 로직을 수행하기 전에 LogAdvice 클래스의 printLog() 메소드를 호출
- 공통 기능 수정 시 printLog() 메소드만 수정하면 되므로 관리가 편해짐
- 단점 : 메소드 이름 변경시 BoardServiceImpl 클래스의 모든 메소드 호출 부분을 수정해야함
4) Log4jAdvice 클래스 생성
- 변경될 로그 클래스 생성
package com.springbook.biz.common;
public class Log4jAdvice {
public void printLogging() {
System.out.println("[공통 로그-Log4j] 비즈니스 로직 수행 전 동작");
}
}
5) BoardServiceImpl 클래스
- 이번에는 printLogging() 메소드를 사용할 수 있도록 Log4jAdvice로 수정해주어야함
- 메소드 이름도 일일이 다 수정해주어야하는 단점
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;
import com.springbook.biz.common.LogAdvice;
@Service("boardService")
/*
* 업무로직 처리를 담당하는 클래스
* ex) 계좌이체 기능 처리할 때 DB입력은 DAO 하게 되는데
* DB입력 전 필요한 작업들(계좌암호화, 금액 컴마 추가...) 하는 로직들을 ServiceImpl클래스에서 처리해주면 됨
* */
public class BoardServiceImpl implements BoardService{
@Autowired
BoardDAO boardDAO;
private Log4jAdvice log;
public BoardServiceImpl() {
log = new Log4jAdvice();
}
public void insertBoard(BoardVO vo) {
log.printLogging();
boardDAO.insertBoard(vo);
}
public void updateBoard(BoardVO vo) {
log.printLogging();
boardDAO.upadateBoard(vo);
}
public void deleteBoard(BoardVO vo) {
log.printLogging();
boardDAO.deleteBoard(vo);
}
public BoardVO getBoard(BoardVO vo) {
return boardDAO.getBoard(vo);
}
public List<BoardVO> getBoardList(BoardVO vo) {
log.printLogging();
return boardDAO.getBoardList(vo);
}
}
6) 실행 결과
7) 결론
- 모듈화가 뛰어난 언어인 OOP를 사용한다해도 공통 모듈인 Advice 객체 생성코드와 공통 메소드 호출 코드가 비즈니스 메소드에 들어있으면 핵심 관심과 횡단 관심을 완벽히 분리할 수 없음
- 스프링의 AOP는 이 한계를 극복할 수 있음
2. AOP 시작하기
- 목표 : BoardServiceImpl 소스와 무관하게 LogAdvice와 Log4jAdvice 클래스 메소드 실행이 가능
1. 비즈니스 클래스 수정 ( _026_BoardWeb_Business_Start_AOP )
- BoardServiceImpl 클래스를 원 상태로 돌려 로그 클래스들과는 아무 상관 없도록 만들기
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;
import com.springbook.biz.common.LogAdvice;
@Service("boardService")
/*
* 업무로직 처리를 담당하는 클래스
* ex) 계좌이체 기능 처리할 때 DB입력은 DAO 하게 되는데
* DB입력 전 필요한 작업들(계좌암호화, 금액 컴마 추가...) 하는 로직들을 ServiceImpl클래스에서 처리해주면 됨
* */
public class BoardServiceImpl implements BoardService{
@Autowired
BoardDAO boardDAO;
public void insertBoard(BoardVO vo) {
boardDAO.insertBoard(vo);
}
public void updateBoard(BoardVO vo) {
boardDAO.upadateBoard(vo);
}
public void deleteBoard(BoardVO vo) {
boardDAO.deleteBoard(vo);
}
public BoardVO getBoard(BoardVO vo) {
return boardDAO.getBoard(vo);
}
public List<BoardVO> getBoardList(BoardVO vo) {
return boardDAO.getBoardList(vo);
}
}
2. AOP 라이브러리 추가
1) <dependency> 설정 추가
- BoardWeb 프로젝트에 있는 pom.xml 파일 수정하여 AOP 관련 라이브러리 추가
- aspectjweaver 내려받을 수 있도록 pom.xml 파일에 <dependency> 설정 추가
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
2) 라이브러리가 추가 확인
- Maven Dependency에 라이브러리가 추가되었는지 확인
3) 최신 버전 확인
- 최신 버전을 쓰고 싶으면 검색을 통해 최신 버전을 추가해주면 됨
3. 네임스페이스 추가 및 AOP 설정
1) 네임스페이스 추가
- AOP 설정을 추가하려면 AOP에서 제공하는 엘리먼트들을 사용해야함
2) AOP 설정
- LogAdvice 클래스를 등록 후 AOP 네임스페이스를 추가
<!-- AOP 설정 -->
<context:component-scan base-package="com.springbook.biz"></context:component-scan>
<bean id="log" class="com.springbook.biz.common.LogAdvice"></bean>
<!-- AOP 설정 -->
<aop:config>
<aop:pointcut expression="execution(* com.springbook.biz..*Impl.*(..))" id="allPointcut"/>
<aop:aspect ref="log">
<aop:before pointcut-ref="allPointcut" method="printLog"/>
</aop:aspect>
</aop:config>
4. 테스트 및 결과 확인
1) bean 객체에 LogAdvice로 설정한 실행 결과
- BoardServiceClient 프로그램 실행
- 두 메소드가 호출될 때 LogAdvice 클래스의 printLog() 메소드가 실행되는지 확인
2) bean 객체에 Log4jAdvice로 설정한 실행 결과
- 로그 파일 교체시에는 스프링 설정 파일의 AOP 설정에서 <bean> 객체의 class 속성과 method만 수정하면 됨
3) Maven 오류 해결법
4) 정리
- 스프링의 AOP는 클라이언트가 핵심 관심 메소드를 호출할 때 횡단 관심 메소드를 적절하게 실행해줌
- 여기서 핵심 관심 메소드와 횡단 관심 메소드 사이에서 소스상의 결합은 발생하지않음 (우리가 원하던 바)
댓글