AOP (Aspect Oriented Programming)

MisCaminos·2021년 4월 1일
0

Server & Web

목록 보기
18/23

Aspect Oriented Programming이란?

Application 또는 program내 여러 파트들이 공통으로 필요로하는 관심/기능을 하나의 클래스 단위(aspect)로 분리해서 encapsulate한다. 이런 방식은 프로그램의 modularity를 높이는 것으로, 코드의 재사용성과 유지보수성을 높일 수 있다. AOP는 modularity를 지향한는 OOP의 extension이라고 생각하면된다.

Business의 핵심 모듈(기능중심의)들은 보통 공통적인 관심/기능을 가지고있다. 예를들어 아래 그림을 보면, 어느 여행사의 business module로 구성된 Ticket booking, Hotel booking, Travel Planning은 Security, Transaction, Logging 이라는 3가지 공통 기능을 공유하고있다.

AOP는 이런 공통기능을 따로 생성 및 관리해서, 각 business module에서 필요할때 호출해서 사용하도록 한다. 만약 Security 기능에 변동사항이 생긴다면, ticket booking, hotel booking, travel planning 각각의 module에서 일일히 Security 기능을 손보지 않아도 된다. 공통기능인 Security만 수정해서 AOP를 통해 각 business module에 호출되어 사용되도록 한다.

AOP 구성 요소

AOP를 구현하려면 무엇이 필요할까?

1. Aspect (or Advisor)
Aspect는 AOP의 핵심이다. Aspect가 각각의 business module들이 공통적으로 필요한 cross-cutting concern을 encapsulate하는 module역할을 하기때문이다.

Aspect가 어떻게 설정되냐에 따라서 AOP의 동작 방식이 결정된다. Aspect는 pointcut과 advice의 결합이라고 볼수도 있다. 어떤 pointcut method에 대해서 어떤 advice method를 실행할지 결정한다.

2. Joinpoint
The point in the program that has to be subjected to an aspect. Client가 호출하는 business method로서 실습 project에서는 BbsService, MemberService 클래스의 모든 method가 해당된다. Join point는 method level, constructor level, 또는 field level일 수 있다. Join points중에서 pointcut이 선택된다.

3. Pointcuts
Pointcuts는 advice가 적용될 join points를 제공한다. Pointcut은 필터링된 joinpoints라고 생각하면된다.

수많은 business method중에 원하는 특정 method에서만 cross-cutting concern에 해당하는 공통기능을 동작시키기위해서 pointcut이 필요하다. Pointcut을 이용하면 method가 포함된 class의 package, method signature까지 정확하게 지정할 수 있다.

spring project의 servlet-context.xml에서 aop 요소를 설정한다. (Dispatcher Servlet이 요청을 처리하는 과정에서 business method를 실행할때에 method 전/후/또는 둘다 언제 무엇을 실행 할지 정하는것이기때문에 servlet-context.xml에서 설정하는것같다)

4. Advice
하나의 aspect는 advice 그룹을 가지고있다. Advice는 join points에 어떤 action들이 언제 실행될지를 명시하는 것이다. Method 들어가기 전일지, method로 부터 반환된 후일지, method 전/후 둘다 일지, 아니면 method의 exception이 반환된 후 일지, 등을 정한다. spring 설정파일을 통해서 지정한다.

5. Weaving
Pointcut으로 지정한 business method가 호출될때, advice에 해당하는 cross-cutting concern method가 삽입되는 과정을 의미한다. Weaving을 통해서 business method를 수정하지 않도고 공통되는 관심에 해당하는 기능을 변경하거나 추가할 수 있다.

Weaving을 처리하는 방식은 compiletime weaving, loadingtime weaving, runtime weaving이 있다. spring에선 runtime weaving방식을 지원한다.

예시)
servlet-context.xml:

<!-- AOP를 이용해서 transaction을 사용 -->
<aop:config>
	<!-- Pointcut: 여러 Joinpoints중 하나를 선택한것  -->
	<aop:pointcut id="transactionPointcut" 
		expression="execution(* spring.model.bbs.*Service.*(..))" />
	<!-- Service로 끝나는 클래스안에 모든 method를 뜻함 -->
	<!-- advisor가 weaving될 수 있도록 여기에 설정하는 것임. -->
	<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut" />
</aop:config>

Spring에서의 AOP

Proxy
Proxy는 weaving될때 생성되는 객체이고 Spring AOP의 동작을 전반적으로 제어하는 객체이다.

Spring에서는 AOP를 runtime에 적용하므로 source code나 class 정보를 변경하지 않는다. Spring에서 AOP는 proxy를 생성하고 proxy를 통해서 핵심 로직을 구현한 객체에 접근한다.Proxy기반에서는 method 호출때에만 advice를 적용할 수 있다. field값 변경과 같은 joinpoint에 대해서는 적용할 수 없다.


실습: AOP transaction 구현

댓글(reply)가 존재하는 게시글은 게시글을 바로 삭제할 수 없다. (부모를 참조하는 자식 레코드가 있기때문에 자식 레코드 삭제 후, 부모 레코드를 삭제할 수 있다.)

Transaction을 적용해서 게시판의 글 (부모 레코드)를 삭제하기전에 자식 레코드를 먼저 삭제하는 단계를 거치도록 구현해보았다.

일반 Controller 에서 작업 과정:
Controller -> DAO -> MyBatis -> Oracle

이번에 AOP Transaction을 적용한 작업 과정:
Controller -> Service -> DAO -> MyBatis -> Oracle

먼저 servlet-context.xml에서 AOP 설정이 추가되야한다.

servlet-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
....
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
....
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
....
<!-- Transaction Manager -->
  <beans:bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <beans:property name="dataSource" ref="dataSource" />
  </beans:bean>
 
  <tx:advice id="txAdvice" transaction-manager="transactionManager">
     <tx:attributes>
         <tx:method name="delete" rollback-for="Exception"/>
     </tx:attributes>
  </tx:advice>
  
  <aop:config>
     <aop:pointcut id="transactionPointcut" expression="execution(* spring.model.bbs.*Service.*(..))"/>
     <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut" />
  </aop:config>
  
</beans:beans>

pom.xml:

<!-- 인터페이스없이 transaction사용시 에러예방 -->
<dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
        <type>jar</type>
        <scope>compile</scope>
</dependency>
<!-- aspectjweaver -->
<dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${org.aspectj-version}</version>
</dependency>

그리고 Controller와 DAO사이에서 Controller로 들어온 요청(게시글 삭제)을 DAO & MyBatis를 통해 DB에 처리하기 전에 필요한 기능(게시글의 댓글 삭제)을 처리하는 Service 클래스를 생성했다.

BbsService.java:

package spring.model.bbs;

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

import spring.model.reply.ReplyMapper;

@Service
public class BbsService {
	@Autowired
	private BbsMapper mapper;
	
	@Autowired
	private ReplyMapper rmapper;
	
	public void delete(int bbsno) throws Exception{
		//1. 댓글들을 지운다
		rmapper.bdelete(bbsno);
		//2. 부모글을 지운다
		mapper.delete(bbsno);
	}
}

각각의 MyBatis에 대한 mapping 설정을 ReplyMapper와 reply.xml 그리고 BbsMapper와 bbs.xml에 생성해주고 BbsController에서 Service에 구현한 delete(bbsno) 메소드를 호출했다.

BbsController.java:

	@GetMapping("/bbs/delete")
	public String delete(int bbsno, Model model) {
		//dao의 checkRefnum: refnum column에 bbsno가 있으면 부모글이라고 인지한다.
		boolean flag = false;
		int cnt = mapper.checkRefnum(bbsno);
		if(cnt>0) flag=true;
		model.addAttribute("flag",flag);
		//tiles view resolver 안에서 delete를 찾는다.
		return "/bbs/delete";	
	}
	
	@PostMapping("/bbs/delete")
	public String delete(int bbsno, String passwd, String oldfile, 
    			HttpServletRequest request) {		
		String upDir = request.getRealPath("/storage");
		Map map = new HashMap();
		map.put("bbsno", bbsno);
		map.put("passwd", passwd);
		int cnt = mapper.passCheck(map);
		String url = "/bbs/passwdError";
		
		//AOP transaction적용 case: Controller에서 service를 통해 
        	//bbsno글 삭제 전에 댓글을 먼저 삭제하도록함.
		if(cnt>0) {
			try {
				service.delete(bbsno);
				url = "redirect:/bbs/list";
				if(oldfile!=null) {
					Utility.deleteFile(upDir, oldfile);
				}
			}catch(Exception e) {
				e.printStackTrace();
				url = "/bbs/error";
			}
		}
		return url;
    	 }


References:
  1. AOP의 구현&JPA 개념 및 JPA프로젝트 from lectureblue

  2. What is AOP? Why should we use it? by Neeraj Chimbili from Medium

  3. Aspect-Oriented Programming from Wikipedia

  4. AOP vs Functions by Roman Elizarov from Medium

profile
Learning to code and analyze data

0개의 댓글