본문 바로가기
Java . Spring . Web . SQL

[AOP] AOP의 기본 설명, AOP 라이브러리 추가

by heidish 2020. 10. 13.
반응형

 

 

 

 

 

 

 

비즈니스 컴포넌트 개발에서 가장 중요한 두 가지 원칙은,

낮은 결합도 & 높은 응집도 를 유지하는 것이다.

 

DI(Dependency Injection) 즉, 의존성 주입을 이용하면

비즈니스 컴포넌트를 구성하는 객체들의 결합도를 낮출 수 있어서 의존관계를 쉽게 변경할 수 있다.

 

스프링에서의 IoC(Inversion of Control)가 결합도와 관련된 기능이라면,

AOP(Aspect Oriented Programming)는 응집도와 관련된 기능이다.

 

 

 


 

AOP 의 메소드들은 대부분 복잡한 코드들로 이루어져 있다.

 

이런 복잡한 코드들에서 핵심 비즈니스 로직은 몇 줄 안되고,

주로 로깅, 예외처리, 트랜잭션 처리 등과 같은 부가적인 코드가 대부분이다.

 

복잡한 코드들로 인해 비즈니스 메소드의 복잡도는 올라가고 개발자는 지치게 된다,,

 

 

그렇다고 이런 코드들을 맘대로 삭제하거나 소홀히해서는 안되는데,

이런 부가적인 코드들 역시 AOP 에서 비즈니스 로직 만큼이나 중요한 기능들이기 때문이다.

 

 

 

새로운 메소드를 구현하는 가장 일반적인 방법은 기존에 잘 만들어놓은 메소드를 복붙해서 구현하는건데,

이러면 결국 비즈니스 메소드에서는 부가적인 코드들이 중복 등장한다.

또한 코드 분석, 이해, 유지보수 등이 어려워진다.

 

AOP는 이런 공통 코드들을 효율적으로 관리하는데 초점을 맞춘다.

 

 


 

AOP의 핵심 개념

:  관심 분리 (Separation of Concerns)

 

 

횡단 관심 (Crosscutting Concerns)

-   메소드마다 공통으로 등장하는 로깅, 예외, 트랙잭션 처리 등의 코드들

핵심 관심 (Core Concerns)

-   사용자의 요청에 따라 실제로 수행되는 핵심 비즈니스 로직

 

 

 

만약 횡단관심, 핵심관심의 두 가지 관심을 완벽히 분리할 수 있다면,

우리가 구현하는 메소드 내부 코드는 실제 비즈니스 로직만으로 구성될 수 있기 때문에

더 간결하고, 응집도가 높은 코드를 유지하는게 가능해진다.

 

기존의 OOP (Object-Oriented Programming) 언어에서는 횡단 관심에 해당하는 공통 코드를 완벽하게 독립적인 모듈로써 분리해내는게 어렵다,,

 

 

 

 

 



 

 

AOP 라이브러리 추가

 

 

AOP를 적용하기 위해서는 pom.xml 파일에 AOP 관련 라이브러리를 추가해야 한다.

 

 

(현재 이미 Spring JDBC, Connection Pool, MySQL 관련 라이브러리가 추가되어 있는 상태임)

(저 라이브러리 추가 코드 바로 밑에 AOP 라이브러리를 추가할꺼임)

 

 

		<!-- 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>

위의 코드를 pom.xml 내부 코드에서,

 

<!-- Spring --> 주석이 달린 <dependency> 태그와,

<!-- Logging --> 주석이 달린 <dependency> 태그 사이에 넣어주고,

 

완벽히 다 코딩한 뒤 저장을 해주자.

 

 

 

저장한 뒤, "Maven Dependencies" 로 가서 아래와 같이 라이브러리가 추가되었는지 확인하자!

 

 


 

여기까지 한 뒤, AOP 설정을 추가하려면 AOP에서 제공하는 엘리먼트들을 사용해야 하는데

이를 위해서 aop 네임스페이스를 추가 해 줘야한다.

 

 

스프링설정파일 (applicationContext.xml) 로 가서,

하단의 탭 중 [Namespaces] 탭 클릭 후,

aop 네임스페이스를 선택해서 추가해주자.

 

이렇게 aop에 선택해주기!

( beans, context 네임스페이스는 이전에 작성하던 코드 때문에 이미 선택되어 있는 상태인거! )

 

 

 

aop 설정이 잘 되는지는 아래의 예시를 통해 테스트 해보자.

 

 

 

 



 

예제

 


aop 설정 테스트 전, 기본설정

(좀 많이 길지만 아래와 같이 생성된 프로젝트를 기본으로 aop 설정이 잘 되었나 테스트할꺼임..)

 

 

프로젝트마다 조금씩은 다르지만, 일반적으로 비즈니스 컴포넌트는 4개의 자바 파일로 구성된다.

 

BOARD_SPRING 이라는 테이블과 관련된 BoardService 컴포넌트는

BoardVO, BoardDAO, BoardService, BoardServiceImpl 총 4개의 클래스로 구현되어 있다.

 

BoardService 컴포넌트는 각 클래스파일들이 작성되는 위치도 중요하기 때문에

꼭 알맞은 패키지에 잘 넣어주자!

 

 

 

< 프로젝트 구조 >

위의 Java Resources > src/main/java 의 하위 패키지들 중,

우선은 com.heidi.biz.boardcom.heidi.biz.board.impl 두개의 패키지만 사용할 것이다.

 

 

< DB >

CREATE TABLE BOARD_SPRING (
	SEQ NUMBER(5) PRIMARY KEY,
    TITLE VARCHAR2(200),
    WRITER VARCHAR2(20),
    CONTENT VARCHAR2(2000),
    REGDATE DATE DEFAULT SYSDATE,
    CNT NUMBER(5) DEFAULT 0
);

 

 


 

1.  VO (Value Object) 클래스

 

 

가장 먼저 VO (Value Object) 클래스를 생성한다.

 

VO 클래스는 DTO(Data Transfer Object) 클래스라고도 하며

레이어와 레이어 사이에서 관련된 데이터를 한꺼번에 주고받을 목적으로 사용하는 클래스이다.

 

VO 클래스명은 보통 테이블명 뒤에 VO 또는 DTO를 붙여서 써준다.

 

또한 VO 클래스의 멤버변수명DB의 테이블에 입력한 필드명과 동일하게 쓰며,

private 접근제한자로 선언한 뒤, 이 private 멤버변수에 접근할 수 있는 Getter/Setter 메소드를 선언하면

VO 클래스는 완성이다.

 

 

( com.heidi.biz.board 패키지 내부에 BoardVO.java 클래스 생성하기 )

package com.heidi.biz.board;

import java.sql.Date;

// VO (Value Object)
public class BoardVO {

	private int seq;
	private String title;
	private String writer;
	private String content;
	private Date regDate;
	private int cnt;
	
    // 기본생성자
	public BoardVO() {
	}
	
    // toString() 재정의 메서드
	@Override
	public String toString() {
		String values = "";
		values += "BoardVO [seq=" + this.seq;
		values += ", title=" + this.title;
		values += ", writer=" + this.writer;
		values += ", content=" + this.content;
		values += ", regDate=" + this.regDate;
		values += ", cnt=" + this.cnt;
		values += "]";
		return values;
	}

	public int getSeq() {
		return seq;
	}

	public void setSeq(int seq) {
		this.seq = seq;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getWriter() {
		return writer;
	}

	public void setWriter(String writer) {
		this.writer = writer;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public Date getRegDate() {
		return regDate;
	}

	public void setRegDate(Date regDate) {
		this.regDate = regDate;
	}

	public int getCnt() {
		return cnt;
	}

	public void setCnt(int cnt) {
		this.cnt = cnt;
	}
}

( @Component 또는 추가 어노테이션을 사용하지 않는다! )

( toString() 메소드의 경우, 나중에 VO 객체의 값을 출력할 때 사용하면 좋다! )

( 이클립스를 사용하고 있다면, Getter/Setter 메소드를 생성할 때 단축키를 이용하자.

   단축키  :  BoardVO 클래스에 멤버변수 선언 후,  Alt + Shift + S )

 

 


 

2.  DAO 클래스

 

DAO(Data Access Object) 클래스는 데이터베이스 연동을 담당하는 클래스이다.

(데이터에 실질적으로 접근하는 클래스)

 

따라서 DAO 클래스에는 CRUD 기능의 메소드가 구현되어야 한다.

CRUD  =  Create, Read, Update, Delete

 

 

DAO 클래스 작성 전, 오라클 드라이버를 설정해줘야 한다.

 

 


 

오라클 드라이버를 pom.xml 에 심어주기 위해 아래 과정이 필요하다!

 

 

프로젝트 선택 후 > 마우스 우클릭 > Build Path > Configure Build Path..

> Libraries 탭에서 Add External JARs... 클릭!

> [c: sqldeveloper : jdbc : lib : ojdbc8.jar ] 선택 후 "열기" 버튼 클릭!

> 오라클 파일이 라이브러리에 추가된거 확인 후 "OK" 버튼 클릭!

> 프로젝트 구조에 "Referenced Libraries" 가 생성되었는지 & 내부에 "ojdbc8.jar" 생겼는지 확인!

 

 

오라클 드라이버 설정은 위의 방법이 제일 안정적이다!

 

 

 

jar 파일이 추가된걸 확인했으면, ...

 

 

pom.xml  파일을 열고,

Spring 이라고 주석이 달려있는 <dependency> 태그 두 개를 찾자.

이 <dependency> 태그 두개 밑에 드라이버 관련 태그를 추가해줘야 한다.

 

 

( pom.xml 파일의 코드에 아래 코드 추가하기 )

		<!-- Spring JDBC -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
		
		<!-- Connection Pool -->
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.4</version>
		</dependency>
		
		<!-- MySQL -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.9</version>
		</dependency>

 

이 코드를 한번에!!! 추가하고 마지막에 다 추가한 뒤 저장을 해주자.

코드 치다말고 중간에 저장하면 안됨...!

 

 

 


 

3.  JDBC Utility 클래스

 

당분간 Mybatis 같은 프레임워크를 사용하기 전까지는 DB 연동 처리를 JDBC로 할것이다.

 

따라서 모든 DAO 클래스에서 공통적으로 사용할 JDBCUtil 클래스를 작성해서

Connection 연결&해제 작업을 공통적으로 처리할 수 있게 해주자.

 

 

 

( com.heidi.biz.board.impl 패키지 내부에 JDBCUtil.java 클래스 생성하기 )

package com.heidi.biz.board.impl;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JDBCUtil {

	// 기본생성자
	public JDBCUtil() {
	}

	public static Connection getConnection() {
		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");
			// 연습용 코드
			String url = "jdbc:oracle:thin:@127.0.0.1:1521:XE";
			return DriverManager.getConnection(url, "heidi", "1234");
		} catch (Exception e) {
			System.out.println("getConnection() ERR : " + e.getMessage());
		}
		return null;
	} // getConnection() END
	
    
	public static void close(Statement stmt, Connection conn) {
		if (stmt != null) {
			try {
				if (!stmt.isClosed()) {	// isClosed() : 닫혀있으면 true 반환
				stmt.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			} finally {
				stmt = null;
			}
		}
		
		if (conn != null) {
			try {
				if (!conn.isClosed()) {	// isClosed() : 닫혀있으면 true 반환
					conn.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			} finally {
				conn = null;
			}
		}
	} // close() END
	
    
	public static void close(ResultSet rs, Statement stmt, Connection conn) {
		if (rs != null) {
			try {
				if (!rs.isClosed()) {	// isClosed() : 닫혀있으면 true 반환
					rs.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			} finally {
				rs = null;
			}
		}
		
		if (stmt != null) {
			try {
				if (!stmt.isClosed()) {	// isClosed() : 닫혀있으면 true 반환
				stmt.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			} finally {
				stmt = null;
			}
		}
		
		if (conn != null) {
			try {
				if (!conn.isClosed()) {	// isClosed() : 닫혀있으면 true 반환
					conn.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			} finally {
				conn = null;
			}
		}
	} // close() END
}

 

 


 

4.  DAO 클래스

 

앞서 작성한 BoardVO 객체를 매개변수와 리턴타입으로 사용하면서,

BOARD_SPRING 테이블과 CRUD 기능을 처리할 BoardDAO 클래스를 작성해보자.

 

DAO 클래스명은 테이블명 뒤에 DAO만 추가해서 사용한다.

 

또한 DAO 클래스의 객체를 스프링 컨테이너가 자동생성할 수 있도록 어노테이션을 붙여줄껀데,

DB 연동을 처리하는 DAO 클래스에 붙이는 @Component 어노테이션을 상속받은

@Repository 어노테이션을 붙여주자.

물론 객체명도 지정해줘야한다!

 

( 이때 @Component 어노테이션을 써도 되긴 하지만, DAO 기능을 해주는 클래스에는 그냥 @Repository 어노테이션을 붙여주는게 좋다..! )

 

 

 

 

 

( com.heidi.biz.board.impl 패키지 내부에 BoardDAO.java 클래스 생성하기 )

( 좀 길다 )

package com.heidi.biz.board.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Repository;

import com.heidi.biz.board.BoardVO;

// DAO (Data Access Object)
@Repository("boardDAO")
public class BoardDAO {

	// JDBC 관련 변수
	private Connection conn = null;
	private PreparedStatement stmt = null;
	private ResultSet rs = null;
	
	// SQL 명령어들
	private final String BOARD_SPRING_INSERT = "insert into board_spring (seq, title, writer, content) "
			+ "values ((select nvl(max(seq), 0)+1 from board_spring), ?, ?, ?)";
	private final String BOARD_SPRING_UPDATE = "update board_spring set title=?, content=? where seq=?";
	private final String BOARD_SPRING_DELETE = "delete board_spring where seq=?";
	private final String BOARD_SPRING_GET = "select * from board_spring where seq=?";
	private final String BOARD_SPRING_LIST = "select * from board_spring order by seq desc";


	// CRUD 기능의 메소드 구현
	// 글 등록
	public void insertBoard(BoardVO vo) {
		System.out.println("==> JDBC로 insertBoard() 기능 처리");
		try {
			conn = JDBCUtil.getConnection();
			stmt = conn.prepareStatement(BOARD_SPRING_INSERT);
			stmt.setString(1, vo.getTitle());
			stmt.setString(2, vo.getWriter());
			stmt.setString(3, vo.getContent());
			stmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(stmt, conn);
		}
		
	}
	
	// 글 수정
	public void updateBoard(BoardVO vo) {
		System.out.println("==> JDBC로 updateBoard() 기능 처리");
		try {
			conn = JDBCUtil.getConnection();
			stmt = conn.prepareStatement(BOARD_SPRING_UPDATE);
			stmt.setString(1, vo.getTitle());
			stmt.setString(2, vo.getWriter());
			stmt.setInt(3, vo.getSeq());
			stmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(stmt, conn);
		}
	}
	
	// 글 삭제
	public void deleteBoard(BoardVO vo) {
		System.out.println("==> JDBC로 deleteBoard() 기능 처리");
		try {
			conn = JDBCUtil.getConnection();
			stmt = conn.prepareStatement(BOARD_SPRING_DELETE);
			stmt.setInt(1, vo.getSeq());
			stmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(stmt, conn);
		}
	}
	
	// 글 상세 조회
	public BoardVO getBoard(BoardVO vo) {
		System.out.println("==> JDBC로 getBoard() 기능 처리");
		BoardVO board = null;
		try {
			conn = JDBCUtil.getConnection();
			stmt = conn.prepareStatement(BOARD_SPRING_GET);
			stmt.setInt(1, vo.getSeq());
			rs = stmt.executeQuery();
			if (rs.next()) {
				board = new BoardVO();
				board.setSeq(rs.getInt("SEQ"));
				board.setTitle(rs.getString("TITLE"));
				board.setWriter(rs.getString("WRITER"));
				board.setContent(rs.getString("CONTENT"));
				board.setRegDate(rs.getDate("REGDATE"));
				board.setCnt(rs.getInt("CNT"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(rs, stmt, conn);
		}
		return board;
	}

	// 글 목록 조회
	public List<BoardVO> getBoardList(BoardVO vo) {
		System.out.println("==> JDBC로 getBoardList() 기능 처리");
		List<BoardVO> boardList = new ArrayList<BoardVO>();
		try {
			conn = JDBCUtil.getConnection();
			stmt = conn.prepareStatement(BOARD_SPRING_LIST);
			rs = stmt.executeQuery();
			while (rs.next()) {
				BoardVO board = new BoardVO();
				board = new BoardVO();
				board.setSeq(rs.getInt("SEQ"));
				board.setTitle(rs.getString("TITLE"));
				board.setWriter(rs.getString("WRITER"));
				board.setContent(rs.getString("CONTENT"));
				board.setRegDate(rs.getDate("REGDATE"));
				board.setCnt(rs.getInt("CNT"));
				boardList.add(board);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(rs, stmt, conn);
		}
		return boardList;
	}
}

 

또한 DAO 클래스에 들어가는 CRUD 기능의 메소드 이름은 일관성있게 아래와 같은 규칙을 적용해서 만들어주자.

 

등록            insert테이블명

수정            update테이블명

삭제            delete테이블명

상세조회      get테이블명 (또는 select테이블명)

목록검색      get테이블명List (또는 select테이블명List)

 

 


 

5.  Service 인터페이스

 

DAO 클래스 작성이 끝나면,

DAO 클래스가 열려있는 상태에서  Alt + Shift + T  단축키를 이용해서 BoardService 인터페이스를 작성하자.

 

이때 인터페이스가 단축키를 통해 생성되면서 자동으로 implements 코드가 설정되는데, 이 코드는 삭제해주자.

 

( BoardService 인터페이스는 BoardServiceImpl 클래스가 구현해야하며, BoardDAO는 독립된 클래스이기 때문 )

 

이렇게 생성하면 BoardDAO 클래스의 주석까지 포함되어 코드가 생성된다.

 

 

 

( com.heidi.biz.board 패키지 내부에 BoardService.java 클래스 생성하기 )

package com.heidi.biz.board;

import java.util.List;

public interface BoardService {
	
	// CRUD 기능의 메소드 구현
	// 글 등록
	void insertBoard(BoardVO vo);
	
	// 글 수정
	void updateBoard(BoardVO vo);
	
	// 글 삭제
	void deleteBoard(BoardVO vo);
	
	// 글 상세 조회
	BoardVO getBoard(BoardVO vo);
	
	// 글 목록 조회
	List<BoardVO> getBoardList(BoardVO vo);

}

 

 


 

6.  Service 구현 클래스 작성 (~Impl 클래스)

 

마지막으로 BoardService 인터페이스를 구현(implements)한 BoardServiceImpl 클래스를 구현하면

비즈니스 컴포넌트는 마무리된다.

 

이 클래스의 비즈니스 메소드를 구현할 때, 멤버변수로 선언된 BoardDAO 를 이용하자.

 

 

 

( com.heidi.biz.board.impl 패키지 내부에 BoardServiceImpl.java 클래스 생성하기 )

package com.heidi.biz.board.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.heidi.biz.board.BoardService;
import com.heidi.biz.board.BoardVO;
import com.heidi.biz.board.common.Log4jAdvice;
import com.heidi.biz.board.common.LogAdvice;

@Service("boardService")
public class BoardServiceImpl implements BoardService {

	@Autowired
	private BoardDAO boardDAO;

	@Override
	public void insertBoard(BoardVO vo) {
//		if (vo.getSeq() == 0) {
//			throw new IllegalArgumentException("0번 글은 등록하실 수 없습니다.");
//		}
		this.boardDAO.insertBoard(vo);
	}

	@Override
	public void updateBoard(BoardVO vo) {
		this.boardDAO.updateBoard(vo);
	}

	@Override
	public void deleteBoard(BoardVO vo) {
		this.boardDAO.deleteBoard(vo);		
	}

	@Override
	public BoardVO getBoard(BoardVO vo) {
		return boardDAO.getBoard(vo);
	}

	@Override
	public List<BoardVO> getBoardList(BoardVO vo) {
		return boardDAO.getBoardList(vo);
	}

}

클래스 선언부 바로 위에는 해당 클래스의 객체 생성을 위해서 @Service 어노테이션이 붙어있다.

( 비즈니스 로직을 처리하는 Service 클래스에 붙이는 @Component 어노테이션을 상속받은 @Service 어노테이션! )

 

멤버변수에는 BoardDAO 타입의 객체인 boardDAO 가 있는데,

데이터베이스 연동이 포함된 비즈니스 로직 처리를 위해 이를 멤버변수로 갖고있는 것이다.

또 이 boardDAO 라는 변수에 BoardDAO 타입의 객체를 의존성 주입하기 위해서 @Autowired 어노테이션을 붙여줬다.

 

BoardServiceImpl 클래스는 BoardService 인터페이스를 구현한 클래스이기 때문에,

BoardService 인터페이스가 갖는 모든 추상 메서드를 재정의(@Override) 해서 구현해야 한다.

 

 

 


 

7.  스프링 설정 파일 (applicationContext.xml)

 

 

( applicationContext.xml 파일의 내부 코드 )

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/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-4.2.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">

	<context:component-scan base-package="com.heidi.biz"></context:component-scan>
	
</beans>

컴포넌트의 스캔 범위를 com.heidi.biz 로 지정했기 때문에,

BoardServiceImpl 클래스, BoardDAO 클래스가 스캔 범위에 포함되어 객체가 자동으로 생성된다.

또한 의존성 주입도 적절하게 처리된다.

 

 


 

8.  클라이언트 작성 및 실행

 

마지막으로 스프링 컨테이너를 구동하고, BoardService 컴포넌트를 사용하는 클라이언트 프로그램을 만들어서,

글 등록 기능과 글 목록 검색 기능을 테스트하자.

 

 

( src/text/java 의 com.heidi.biz.board 패키지 내부에 BoardServiceClient.java 클래스 생성하기 )

package com.hedidi.biz.board;

import java.util.List;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import com.heidi.biz.board.BoardService;
import com.heidi.biz.board.BoardVO;

public class BoardServiceClient {

	public static void main(String[] args) {
		// 1. Spring 컨테이너를 구동한다.
		AbstractApplicationContext container = new GenericXmlApplicationContext("applicationContext.xml");
		
		// 2. Spring 컨테이너로부터 BoardServiceImpl 객체를 Lookup 한다.
		BoardService boardService = (BoardService) container.getBean("boardService");
		
		// 3. 글 등록 기능 테스트
		BoardVO vo = new BoardVO();
		vo.setTitle("임시 제목");
		vo.setWriter("전소현");
		vo.setContent("임시내용");
		boardService.insertBoard(vo);
	
		// 4. 글 목록 검색 기능 테스트
		List<BoardVO> boardList = boardService.getBoardList(vo);
		for (BoardVO board : boardList) {
			System.out.println("==> " + board.toString());
		}
		
		// 5. Spring 컨테이너 종료
		container.close();
		
	}
}

 

 

 

 


 

드디어 aop 설정 테스트!

 

 

 

 

가장 먼저 AOP 관련 라이브러리를 추가해야 한다.

pom.xml 문서를 열고, 아래와 같은 코드를 추가해주자.

		<!-- 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>

코드 추가 후 저장을 하면 Maven Dependencies 에 아래와 같이 라이브러리 두개가 추가된다.

aspectjrt-1.6.10.jar aspectjweaver-1.8.8.jar 두개!

 

 

 

이제 네임스페이스를 추가하자.

applicationContext.xml 을 열고, Namespaces 탭을 열어서 aop 네임스페이스를 추가해야한다.

이렇게 체크해주기!

 

 

 

이제 applicationContext.xml 에 코드를 추가하자.

 

( applicationContext.xml )

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/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-4.2.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">

	<context:component-scan base-package="com.heidi.biz"></context:component-scan>
	
	<bean id="log" class="com.heidi.biz.board.common.LogAdvice"></bean>
	
	<aop:config>
		<aop:pointcut expression="execution(* com.heidi.biz..*Impl.*(..))" id="allPointcut"/>
		<aop:aspect ref="log">
			<aop:before method="printLog" pointcut-ref="allPointcut" />
		</aop:aspect>
	</aop:config>

</beans>

 

 

그리고 BoardServiceClient.java 프로그램을 실행시켜서

insertBoard(), getBoardList() 메소드가 호출될때 LogAdvice 클래스의 printLog() 메소드가 실행되는지 확인하자.

 

 

(실행 결과)

INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [applicationContext.xml]
INFO : org.springframework.context.support.GenericXmlApplicationContext - Refreshing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Wed Oct 14 11:51:43 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
[공통로그] 비즈니스 로직 수행 전 동작
==> JDBC로 insertBoard() 기능 처리
[공통로그] 비즈니스 로직 수행 전 동작
==> JDBC로 getBoardList() 기능 처리
==> BoardVO [seq=3, title=임시 제목, writer=전소현, content=임시내용, regDate=2020-10-14, cnt=0]
==> BoardVO [seq=2, title=임시 제목, writer=전소현, content=임시내용, regDate=2020-10-13, cnt=0]
==> BoardVO [seq=1, title=test, writer=test, content=test, regDate=2020-10-13, cnt=0]
INFO : org.springframework.context.support.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Wed Oct 14 11:51:43 KST 2020]; root of context hierarchy

 

위와 같은 결과가 나온다,,,

 

 

만약에 LogAdvice 가 아니라 Log4jAdvice 를 사용하고 싶다면?

자바 소스를 수정할 필요 없이, xml 설정만 조금 수정하면 된다.

 

 

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/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-4.2.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">

	<context:component-scan base-package="com.heidi.biz"></context:component-scan>
	
	<bean id="log" class="com.heidi.biz.board.common.Log4jAdvice"></bean>
	
	<aop:config>
		<aop:pointcut expression="execution(* com.heidi.biz..*Impl.*(..))" id="allPointcut"/>
		<aop:aspect ref="log">
			<aop:before method="printLogging" pointcut-ref="allPointcut" />
		</aop:aspect>
	</aop:config>

</beans>

기존의 코드에서

1.  <bean> 태그의 class 속성값에서 마지막에 있던 .LogAdvice=>.Log4jAdvice 이렇게 변경하고

2.  <aop:config> 태그 내의 <aop:aspect> 태그 안에있는 <aop:before> 태그에서,

method 속성값을 printLog=>printLogging 으로 변경하기만 하면 된다.

 

 

바꾼 뒤 실행시켜보면 아래와 같이 잘 되는걸 확인할 수 있다.

INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [applicationContext.xml]
INFO : org.springframework.context.support.GenericXmlApplicationContext - Refreshing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Wed Oct 14 12:00:10 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
[공통로그-Log4j] 비즈니스 로직 수행 전 동작
==> JDBC로 insertBoard() 기능 처리
[공통로그-Log4j] 비즈니스 로직 수행 전 동작
==> JDBC로 getBoardList() 기능 처리
==> BoardVO [seq=4, title=임시 제목, writer=전소현, content=임시내용, regDate=2020-10-14, cnt=0]
==> BoardVO [seq=3, title=임시 제목, writer=전소현, content=임시내용, regDate=2020-10-14, cnt=0]
==> BoardVO [seq=2, title=임시 제목, writer=전소현, content=임시내용, regDate=2020-10-13, cnt=0]
==> BoardVO [seq=1, title=test, writer=test, content=test, regDate=2020-10-13, cnt=0]
INFO : org.springframework.context.support.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Wed Oct 14 12:00:10 KST 2020]; root of context hierarchy

 

 

 

 


 

 

이렇게 스프링의 AOP는 클라이언트가 핵심 관심에 해당하는 비즈니스 메소드를 호출할 때,

횡단 관심에 해당하는 메소드를 적절히 실행시켜 준다.

 

이때, 핵심 관심 메소드와 횡단 관심 메소드 사이에서 소스상의 결함은 발생하지 않으며,

이게 우리가 AOP를 사용하는 주된 목적이다,,

 

 

 

 

 

 

 

반응형

댓글