본문 바로가기
백엔드/Spring Framework

AOP(Aspect Oriented Programming) / 관점 지향 프로그래밍

by maverick11471 2024. 7. 11.

[요약]

 - LogConsole 클래스를 FreeBoardServiceImpl 클래스에 계속 반복 주입해야 하는데 LogConsole 클래스가 수정될 때 계속 수정해야 하니 이를 간단하게 하기 위해 AOP를 사용한다.

 

[과제]

 - FreeBoardServiceImpl 클래스

 - LogConsole 클래스

 - FreeBoardServiceRun 클래스

 - root-context.xml aop 설정

 


[내용 정리]

 

1. Why? (① DB 연동방식의 이해)

 - DB 연결 요청 시 커넥션의 개수가 계속 증가함으로 인한 메모리 부족을 방지

 - 스프링에서 설정파일로 공통관심을 묶어서 관리
   공통관심(로그출력, 트랜잭션, 예외처리 등) 을 축약. 내용 변경 시 클래스만 변경할 수 있도록 수정.

더보기

[DB 연동 방식]

 

① JDBC(Java DataBase Connectivity)

 - 자바에서 제공해주는 DB 연결 표준 API

 - DB 연결 요청 시 Web Application 이 커넥션을 계속 생성

 - 커넥션 개수 증가 시 DB 서버 과부하, 메모리 부족 야기

 

② DBCP(DataBase Connection Pool)

 - Web Application 내 DB Connection을 미리 만들어 놓고 DB 요청 시 DB Connection을 대여하는 방식.

 - 설정 값: 초기 Connection 개수, 최소 Connection 개수, 평소 유지되는 Connection 개수

 

③ JNDI(Java Naming and Directory Interface)

 - 방식 DBCP와 같음 / 차이점: WAS(Web Application Server)에서 관리함.

 

2. Why? (② 연계된 클래스가 변경될 시 어떻게 할까?)

  가. FreeBoardServiceImple 클래스 생성

@Service
public class FreeBoardServiceImpl implements BoardService {
    private FreeBoardDao freeBoardDao;

    @Autowired
    public FreeBoardServiceImpl(FreeBoardDao freeBoardDao) {
        this.freeBoardDao = freeBoardDao;
    }


    @Override
    public void post(BoardDto boardDto) {
        freeBoardDao.post(boardDto);
    }

    @Override
    public void modify(BoardDto boardDto) {
        freeBoardDao.modify(boardDto);
    }

    @Override
    public void delete(int id) {
        freeBoardDao.delete(id);
    }

    @Override
    public List<BoardDto> getBoardList() {
        return freeBoardDao.getBoardList();
    }

    @Override
    public BoardDto getBoard(int id) {
        return freeBoardDao.getBoard(id);
    }
}

 

 나. LogConsole 클래스 생성

public class LogConsole {
    public void consoleLog() {
        System.out.println("[로그] 로직 수행 전");
    }
}

   

 

 다. FreeBoardServiceImple 클래스에 LogConsole 클래스를 추가

@Service
public class FreeBoardServiceImpl implements BoardService {
    private FreeBoardDao freeBoardDao;
//    private LogConsole logConsole;
    private LogConsoleV logConsoleV;

    @Autowired
    public FreeBoardServiceImpl(FreeBoardDao freeBoardDao) {
        this.freeBoardDao = freeBoardDao;
        this.logConsoleV = new LogConsoleV();
    }


    @Override
    public void post(BoardDto boardDto) {
        logConsoleV.consoleLogPlus();
        freeBoardDao.post(boardDto);
    }

    @Override
    public void modify(BoardDto boardDto) {
        logConsoleV.consoleLogPlus();
        freeBoardDao.modify(boardDto);
    }

    @Override
    public void delete(int id) {
        logConsoleV.consoleLogPlus();
        freeBoardDao.delete(id);
    }

    @Override
    public List<BoardDto> getBoardList() {
        logConsoleV.consoleLogPlus();
        return freeBoardDao.getBoardList();
    }

    @Override
    public BoardDto getBoard(int id) {
        logConsoleV.consoleLogPlus();
        return freeBoardDao.getBoard(id);
    }
}

 

근데 만약 LogConsole이 업그레이드 돼서 LogConsole2로 바꿔야 한다면....... 다 바꿔줘야 한다.

버전이 v3, v4, v5..... 계속 될 수록 해야 될 일들이 많아진다.. 

 

이를 해결하기 위해 위의 DB 연동방식 중 2번 DBCP를 이용하여 연결해 주고,

클래스만 교체할 수 있도록 설정해주려 한다.

 

 

3. How?  (① AOP 설정 하기)

 가. root-context.xml - beans 설정

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       
       // xmlns:aop 추가
       xmlns:aop="http://www.springframework.org/schema/aop"
       
       // 스키마에 aop와 spring-aop 추가
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

 

 나. root-context.xml - aop 설정

    // 라이브러리(jar 파일)의 클래스에는 어노테이션을 달 수 없기때문에 
    // bean 엘리먼트를 통해서 bean 객체를 생성하고 등록한다.-->  
 
    // springboard 폴더 안에 있는 모든 폴더를 가져옴
    <context:component-scan base-package="com.bit.springboard"/>
    
    // 기존 JDBC 방식에서는 JDBCUtill을 모든 메서드를 통해 연결해줘야 했지만, 
    // DBCP 방식을 이용해 JDBC Driver를 별도로 연결하지 않도록 설정해준다.
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/studydb?serverTimezone=UTC"/>
        <property name="username" value="study"/>
        <property name="password" value="!dkdlxl1234"/>
    </bean>

    <!--AOP 설정-->
    <!--공통적으로 사용될 클래스를 bean으로 등록-->
    <bean id="logConsole" class="com.bit.springboard.common.LogConsoleV2"/>

    <!--aop:config AOP의 설정의 루트 엘리먼트-->
    <aop:config>
        <!--aop:pointcut 공통적인 기능이 실행될 클래스와 메소드를 지정한다.-->
        <aop:pointcut id="allpointcut" expression="execution(* com.bit.springboard.service..*Impl.*(..))"/>
        <!--aop:aspect 공통 기능의 메소드와 실행될 메소드를 매핑하는 작업-->
        <aop:aspect ref="logConsole">
            <aop:before method="consoleLogPlus" pointcut-ref="allpointcut"/>
        </aop:aspect>
    </aop:config>

 

 

위 AOP 설정을 상세히 살펴보면 아래 사진과 같다.

 

What(결과) : FreeBoardServiceRun 클래스 생성 후 실행

public class FreeBoardServiceRun {
    public static void main(String[] args) {
        AbstractApplicationContext factory =
                new GenericXmlApplicationContext("root-context.xml");

        BoardService boardService = factory.getBean("freeBoardServiceImpl", BoardService.class);

        BoardDto boardDto = new BoardDto();
        boardDto.setTitle("자유게시글1");
        boardDto.setContent("자유게시글 1번입니다.");
        // writer_id는 member 테이블의 id 컬럼을 foreign key로 가져오기 때문에
        // member 테이블에 존재하는 id 값만 입력할 수 있다.
        boardDto.setWRITER_ID(1);

        boardService.post(boardDto);

        // 게시글 수정
        BoardDto modifyBoardDto = new BoardDto();

        modifyBoardDto.setId(1);
        modifyBoardDto.setTitle("자유게시글1 수정");
        modifyBoardDto.setContent("자유시글 1번입니다.-수정됨");
        modifyBoardDto.setModdate(LocalDateTime.now());

        boardService.modify(modifyBoardDto);

        // 게시글 삭제
        boardService.delete(2);

        // 게시글 목록 조회
        boardService.getBoardList().forEach(board -> {
            System.out.println(board);
        });

        // 특정 id의 게시글 하나만 조회
        System.out.println(boardService.getBoard(5));

        factory.close();
    }
}

 

 

결과 : 시행시기를 aop:before 로 실행했기 때문에 모든 메소드 시행 전에  (LogConsole2 클래스 내용이) 실행되는 것으로 나온다.