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