본문 바로가기

서버/Spring
[스프링(Spring)] AOP

// AOP,  Aspect Oriented Programming
    - 관점 지향 프로그래밍
    - 관점, 관심사 > 코드를 작성하는 데 필요한 주업무가 아닌, 나머지 신경써야할 보조 업무 
    - 핵심 업무는 아니지만, 코드를 완성하기 위해서 필요한 나머지 업무들
    - Concern > 관심사, 관점
    - 공통 기능을 핵심 기능과 분리해 두고, 적용하고자 하는 부분에 적용하는 것

    - 스프링에서 AOP 구현 방법: proxy 이용


ex ) [게시판 글쓰기]
  1. 기존 방식
    - Add.do
       a. 글쓰기 : 주업무
       b. 권한 체크 : 보조업무
    - 주업무와 보조업무를 같은 곳에서 구현(한 파일에서 구현)

  2. AOP 방식
    - Add.do
    - 주업무와 보조업무를 분리시켜서 따로 구현하자 > AOP 목적
    - 스프링을 통해서 주업무와 보조업무를 다시 결합 후 동작

    - 용어
        1. Core Concern
            - 비즈니스 코드, 주업무

        2. Cross-cutting Concern
            - 보조업무

        3. Target
            - 순수한 비즈니스 로직을 소유한 객체
            - 객체

        4. Proxy
            - Target을 감싸고 있는 객체

        5. JoinPoint
            - Target이 가지고 있는 메서드(주업무)
            - Advice를 적용해야 하는 부분
              ~ 필드, 메서드(스프링에서는 메서드만!)

        6. Pointcut
            - 보조업무와 어떤 JoinPoint에 연결하는 작업
            - Joinpoint의 부분으로 실제로 Advice가 적용된 부분

        7. Aspect
            - 보조업무를 구현하는 객체
            - 공통 기능

        8. Advice★
            - 주업무가 실행되는 어느 시점에서 보조 업무를 실행할지?
            - Aspect의 기능 자체

            ◼ Before Advice
                - 주업무가 실행되기 전에 보조업무를 실행

            After Advice
                - 주업무가 종료된 후에 보조업무를 실행

            Around Advice
                - 주업무 실행 전후에 보조업무를 실행

            After-returning Advice
                - After Advice와 유사
                - 주업무가 성공했을 때만 실행

            After-throwing Advice
                - 예외가 발생할 때만 실행

        9. Weaving
            - Advice를 핵심 기능에 적용하는 행위


1. XML 방식
    - "com.test.aop1" 패키지 생성
                         > Main.java  //메인

                         > Memo.java(I) //메인 업무 객체(인터페이스)
                         > MemoImpl.java(C) //메인 업무 객체(클래스)

                         > Logger.java(C) //보조 업무 객체(클래스)

                         > memo.xml //스프링 설정 파일(<beans>)


package com.test.aop1;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

	public static void main(String[] args) {

		//주업무 객체
		//Memo memo = new MemoImpl();
		
		ApplicationContext context = new ClassPathXmlApplicationContext("com/test/aop1/memo.xml");
		
		Memo memo = (Memo)context.getBean("memo");
		
		memo.add("스프링 AOP");
		
		try {
			String txt = memo.read(15);
			System.out.println(txt);
			
		} catch (Exception e) {
			e.printStackTrace();
			//DB insert..
			//담당자 메일 or 메시지
		}
		
		memo.edit(5, "수정한 내용");
		
		memo.del(5);

	} //main
	
}

 

package com.test.aop1;

public interface Memo {
	
	//메모 쓰기
	void add(String memo);
	
	//메모 읽기
	String read(int seq) throws Exception;
	
	//메모 수정
	boolean edit(int seq, String memo);
	
	//메모 삭제
	boolean del(int seq);

}

 

package com.test.aop1;

public class MemoImpl implements Memo {

	@Override
	public void add(String memo) {
		
		System.out.println("메모 쓰기: " + memo);
		
		//로그 기록
//		Calendar now = Calendar.getInstance();
//		System.out.printf("[LOG][%tF %tT] 로그를 기록합니다.\n", now, now) ;
		
//		Logger log  = new Logger();
//		log.log();
	}

	@Override
	public String read(int seq) throws Exception {
		
		if (seq < 10) {
			System.out.println("메모 읽기");
		} else {
			throw new Exception("존재하지 않는 메모");
		}
		
		return "홍길동";
	}

	@Override
	public boolean edit(int seq, String memo) {
		
		System.out.println("메모 수정: " + memo);

		return true;
	}

	@Override
	public boolean del(int seq) {
		
		System.out.println("메모 삭제: " + seq);
		
		return true;
	}

}

 

package com.test.aop1;

import java.util.Calendar;

import org.aspectj.lang.ProceedingJoinPoint;

//보조 업무 객체
public class Logger {
	
	//보조 업무 구현
	public void log() {
		
		Calendar now = Calendar.getInstance();
		System.out.printf("[LOG][%tF %tT] 로그를 기록합니다.\n", now, now) ;
		
	}
	
	public void time(ProceedingJoinPoint jp) {
		
		//주 업무를 실행하는 소요시간
		
		long begin = System.nanoTime();
		System.out.println("[LOG] 기록을 시작합니다.");
		
		//주 업무 실행
		//- 글쓰기 > 주 업무 객체의 가상 객체 참조
		try {
			
			jp.proceed(); //현재 실행되는 주 업무 메서드
			
		} catch (Throwable e) {
			e.printStackTrace();
		}
		
		long end = System.nanoTime();
		System.out.println("[LOG] 기록을 종료합니다.");
		
		System.out.printf("[LOG] 소요 시간 %,dns\n", end - begin);
		
	}
	
	//매개변수 memo를 memo.xml의 <aop:after-returning method="history" pointcut-ref="p2" returning="memo"/> 에서 참조
	public void history(Object memo) {
		System.out.println("[LOG] 읽기 기록: " + memo);
	}
	
	public void check(Exception e) {
		System.out.println("[LOG] 예외 발생: " + e.getMessage());
		//DB insert..
		//담당자 메일 or 메시지
	}

}

 

<?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:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
        				
    <!-- Spring AOP -->
    
    <!-- 주업무 객체 -->
    <bean id="memo" class="com.test.aop1.MemoImpl"></bean>
    
    <!-- 보조업무 객체 -->        				
    <bean id="logger" class="com.test.aop1.Logger"></bean>
    
    <!-- 주업무 객체 + 보조업무 객체 -->
    <aop:config>
    	
    	<!-- 보조 업무를 담당할 객체를 지정: 역할 -->
    	<aop:aspect id="loggerAdvice" ref="logger">
    	
	    	<!-- 
	    		주 업무 객체를 지정 > 메서드 찾기 > PointCut 지정하기
	    		
	    		PointCut 	> 주업무 객체의 특정 메서드
	    					> Memo 객체의 메서드 중 하나(이상)   
	
				AspectJ 표현식 > PointCut 지정
				- execution() 지시자
				- execution([접근지정자] 반환형 [클래스].메서드(인자))				
					~ execution(public void com.test.aop1.MemoImpl.add(String))
				
				- '*' : 와일드카드(접근지정자, 반환형, 패키지, 클래스. 메서드)
				- '..' : 와일드카드(인자) > 0개 이상의 인자값
				
				- within() 지시자
	    	-->
	    	
	    	<aop:pointcut expression="execution(public void com.test.aop1.MemoImpl.add(String))" id="p1"/>
    	
    		<aop:pointcut expression="execution(public String com.test.aop1.MemoImpl.read(int))" id="p2"/>
    		
    		<aop:pointcut expression="execution(public void com.test.aop1.MemoImpl.add(String)) || execution(public String com.test.aop1.MemoImpl.read(int))" id="p3"/>
    		
    		<aop:pointcut expression="execution(void add(String))" id="p4"/>
    		
    		<aop:pointcut expression="execution(* com.test.aop1.MemoImpl.*(..))" id="p6"/>
    		
    		<!-- 이름의 일부에도 *를 사용할 수 있다. ex) a* > a로 시작하는 메서드 찾기  -->
    		<aop:pointcut expression="execution(* com.test.aop1.Memo*.a*(..))" id="p7"/>
    		
    		<aop:pointcut expression="within(com.test.aop1.MemoImpl)" id="p8"/>
    		
    		<aop:pointcut expression="execution(void add(String))" id="p9"/>
    	
    		<!--    			
    			주업무와 보조업무 = 결합 > 위빙(Weaving) > Advice 5가지 중 하나    		
    		-->
    		
    		<!-- 
    		<aop:after method="log" pointcut-ref="p1"/>
    		<aop:after method="log" pointcut-ref="p2"/>
    		<aop:after method="log" pointcut-ref="p3"/>
    		<aop:after method="log" pointcut-ref="p4"/>
    		<aop:after method="log" pointcut-ref="p6"/>
    		<aop:after method="log" pointcut-ref="p7"/>
    		<aop:after method="log" pointcut-ref="p8"/>
    		-->
    		
    		<!-- 
	    	<aop:before method="log" pointcut-ref="p9"/>
	    	 -->
	    	 
	    	<!-- <aop:around method="time" pointcut-ref="p9"/> -->
	    	
	    	<!-- <aop:after-returning method="history" pointcut-ref="p2" returning="memo"/> -->
	    	
	    	<aop:after-throwing method="check" pointcut-ref="p2" throwing="e"/>
    	
    	</aop:aspect>
    	
    </aop:config>
        				
</beans>

2. 어노테이션 방식

-  파일 생성
    ~ "com.test.controller" > AOPController.java

    ~  "com.test.persistence"  > AOPDAO.java(I)
        > AOPDAOImpl.java(C)

    ~ views > list.jsp
                 > add.jsp
                 > view.jsp

    ~ com.test.aspect> Log.java


package com.test.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import com.test.persistence.AOPDAO;

@Controller
public class AOPController {
	
	@Autowired
	private AOPDAO dao;
	
	//주업무 구현
	@GetMapping("/list.do")
	public String list() {
		
		dao.list();
		System.out.println("Controller.list");
		
		return "list";
	}
	
	@GetMapping("/add.do")
	public String add() {
		
		dao.add();
		System.out.println("Controller.add");
		
		return "add";
	}
	
	@GetMapping("/view.do")
	public String view() {
		
		dao.view();
		System.out.println("Controller.view");
		
		return "view";
	}

}

 

package com.test.persistence;

public interface AOPDAO {

	void list();

	void add();

	void view();

}

 

package com.test.persistence;

import org.springframework.stereotype.Repository;

@Repository
public class AOPDAOImpl implements AOPDAO {

	@Override
	public void list() {
		
		System.out.println("DAO.list");
		
	}
	
	@Override
	public void add() {
		
		System.out.println("DAO.add");
		
	}
	
	@Override
	public void view() {
		
		System.out.println("DAO.view");
		
	}
	
}

 

package com.test.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

//보조 업무 객체
@Component
@Aspect
public class Log {
	
	/*
	@After("execution(* add(..))")
	public void m1() {
		System.out.println("[보조업무] 기록을 남깁니다.");
	}
	*/
	
	@Pointcut("execution(* add(..))")
	public void pc1() {
		//구현부 없음
	}
	
	@After("pc1()")
	public void m1() {
		System.out.println("[보조업무] 기록을 남깁니다.");
	}
	
}

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>add.jsp</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>

</style>
</head>
<body>

	<h1>게시판 <small>add</small></h1>
	
	<div>
		<input type="button" value="돌아가기" onclick="location.href='/aop/list.do';">
	</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>

</script>
</body>
</html>

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>list.jsp</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>

</style>
</head>
<body>

	<h1>게시판 <small>list</small></h1>
	
	<div>
		<input type="button" value="글쓰기" onclick="location.href='/aop/add.do';">
		<input type="button" value="글보기" onclick="location.href='/aop/view.do';">
	</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>

</script>
</body>
</html>

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>view.jsp</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>

</style>
</head>
<body>

	<h1>게시판 <small>view</small></h1>
	
	<div>
		<input type="button" value="돌아가기" onclick="location.href='/aop/list.do';">
	</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>

</script>
</body>
</html>

'서버 > Spring' 카테고리의 다른 글

[스프링(Spring)] MVC 에러 처리  (0) 2023.06.16
[스프링(Spring)] MVC 데이터 수신 및 전송  (1) 2023.06.15
[스프링(Spring)] JUnit  (0) 2023.06.14
[스프링(Spring)] DI, IoC  (0) 2023.06.14
[스프링(Spring)] 환경설정  (0) 2023.06.13