관점지향 프로그래밍 (AOP)

woom·2023년 2월 16일
0

Framework

목록 보기
10/20
post-thumbnail

💡 OOP의 문제점

  • 절차지향프로그램의 함수는 저장해서 사용이 불가능하여 생산성 감소.
    이를 보완하기 위해 클래스를 자료형으로 만들어 객체의 메소드로 구현하는 객체 지향 프로그램이 생성됨

  • but OOP는 모듈화가 너무 강력하여 핵심관심코드와 횡단관심코드를 분리하여 프로그램 작성이 어려움

    • 코드의 중복성이 높아 프로그램의 생산성 및 유지보수의 효율성 감소
    • 핵심관심코드와 관련없이 중복을 최소화하기위해 만든 클래스(횡단관심코드)를 작성하여 포함시키는 것은 객체지향프로그램 특징인 캡슐화(표현하고 자 하는 대상을 모듈화 시키는 것)와 추상화(대상을 구체화 시키는 것)의 규칙을 위반한다.
  • 코드가 지저분해짐, 재활용성이 저하됨


🌼 관점지향 (AOP)

  • 관점지향(AOP : Aspect Oriented Programming) : 로그 처리, 보안, 트랜잭션 관리 그리고 예외사항 처리 등의 코드(횡단 관심 코드)를 단일 모듈로 각각 작성하고 필요한 시점에 핵심코드(개발자가 작성한 코드)를 삽입하여 동작하게 하는 것
    • 객체지향 프로그래밍 (OOP의 문제점을 보완)

  • 횡단관심코드 : 프로그램 실행에 보조적인 기능을 제공하는 명령

    • 로그 처리(기록), 권한 처리, 트렌젝션 처리, 예외 처리
    • 횡단관심모듈 : 횡단관심코드만 이용하여 메소드를 작성한 클래스 (Advice 클래스)
  • 핵심관심코드 : 프로그램 실행에 핵심적인 기능을 제공하는 명령

    • 데이터 처리 명령
    • 핵심관심모듈 : 핵심관심코드만 이용하여 메소드를 작성한 클래스

📌 AOP의 용어

  • Joinpoint : 횡단 관심 모듈의 기능이 삽입되어 동작할 수 있는 실행 가능한 특정위치 (핵심관심코드의 앞 or 뒤)

  • Pointcut : 어떤 클래스의 어느 Joinpoint(메소드)를 사용할 것인지를 결정하는 선택 기능

  • Advice : 각 Joinpoint에 삽입되어 동작할 수 있는 코드 (횡단관심코드)

  • Weaving : Pointcut에 의해서 결정된 조인포인트에 지정된 Advice를 삽입하는 과정

    • AOP가 기존의 핵심 관심 모듈의 코드에 전혀 영향을 주지 않으면서 필요한 횡단 관심 기능을 추가할 수 있게 해주는 핵심적인 처리 과정
  • Introduction : 기존의 클래스와 인터페이스에 필요한 메소드나 필드를 추가해서 사용할 수 있게 해주는 방법

  • Aspect : Pointcut(어디에서)과 Advice(무엇을 할 것인지)를 합쳐놓은 것 (Proxy 클래스)


📕 Proxy 클래스 (Aspect)

  • Proxy 클래스 : 핵심관심모듈의 메소드에 횡단관심모듈의 메소드를 삽입하여 동작하는 기능을 제공하는 클래스 (Aspect)

  • 인터페이스의 추상메소드(PointCut)를 오버라이드 선언하여 핵심관심코드와 횡단관심코드를 결합하여 실행되도록 작성 (Weaving)

  • 핵심관심모듈의 메소드 호출 전 또는 후에 횡단관심모듈의 모듈의 메소드를 호출하여 실행되도록 설정 (JoinPoint)



package xyz.itwill06.oop;

public class AopProxy implements Aop {
	//핵심관심모듈로 선언된 클래스의 객체를 저장하기 위한 필드
	// => 필드의 자료형을 인터페이스로 선언하여 모든 자식 클래스의 객체 저장 가능 
	private Aop target;
	
	//횡단관심모듈로 선언된 클래스의 객체를 저장하기 위한 필드
	private AopLogger logger;

	//필드에 객체를 생성하여 저장하거나 매개변수로 객체를 전달받아 저장 - 의존성 주입(DI)
	public AopProxy(Aop target) {
		super();
		this.target = target;
		logger=new AopLogger();
	}

	//인터페이스의 추상메소드(PointCut)를 오버라이드 선언하여 핵심관심코드와 횡단관심코드를
	//결합하여 실행되도록 작성 - Weaving
	// => 핵심관심모듈의 메소드 호출 전 또는 후에 횡단관심모듈의 모듈의 메소드를 호출하여
	//실행되도록 설정 - JoinPoint
	@Override
	public void display1() {
		logger.beforeLog();
		target.display1();
	}

	@Override
	public void display2() {
		logger.beforeLog();
		target.display1();
	}

	@Override
	public void display3() {
		logger.beforeLog();
		target.display1();
	}
}





🐣 앱실행

  • 매개변수에 핵심코드를 가지고 있는 객체를 전달해주면 객체를 전달받아 타겟이라는 필드에 저장

package xyz.itwill06.oop;

public class AopApp {
	public static void main(String[] args) {
		AopProxy one= new AopProxy(new AopOne());
		AopProxy two= new AopProxy(new AopTwo());
		
		one.display1();
		one.display2();
		one.display3();
		
		two.display1();
		two.display2();
		two.display3();
	}
}





🐣 VO 클래스

package xyz.itwill07.aop;

import lombok.Data;

@Data
public class Student {
	private int num;
	private String name;
}






🌸 interface (DAO)

package xyz.itwill07.aop;

import java.util.List;

public interface StudentDAO {
	int insertStudent(Student student);
	Student selectStudent(int num);
	List<Student> selectStudentList();
}




📙 DAO클래스 (interface 상속)

  • 핵심관심모듈(Core Concern Module) : 핵심관심코드만 이용하여 메소드를 작성한 클래스

    • 핵심관심코드 : 데이타 처리를 위한 필수적인 명령


package xyz.itwill07.aop;

import java.util.List;

public class StudentDAOImpl implements StudentDAO {
	@Override
	public int insertStudent(Student student) {
		System.out.println("*** StudentDAOImpl 클래스의 insertStudent(Student student) 메소드 호출 ***");
		return 0;
	}

	@Override
	public Student selectStudent(int num) {
		System.out.println("*** StudentDAOImpl 클래스의 selectStudent(int num) 메소드 호출 ***");
		return null;
	}

	@Override
	public List<Student> selectStudentList() {
		System.out.println("*** StudentDAOImpl 클래스의 selectStudentList() 메소드 호출 ***");
		return null;
	}
}





🌸 interface (Service)

package xyz.itwill07.aop;

import java.util.List;

public interface StudentService {
	void addStudent(Student student);
	Student getStudent(int num);
	List<Student> getStudentList();
}





📒 Service클래스 (interface 상속)

  • 핵심관심모듈

package xyz.itwill07.aop;

import java.util.List;

import lombok.Setter;

@Setter
public class StudentServiceImpl implements StudentService {
	private StudentDAO studentDAO;
	
	@Override
	public void addStudent(Student student) {
		System.out.println("*** StudentServiceImpl 클래스의 addStudent(Student student) 메소드 호출 ***");
		studentDAO.insertStudent(student);
	}

	@Override
	public Student getStudent(int num) {
		System.out.println("*** StudentServiceImpl 클래스의 getStudent(int num) 메소드 호출 ***");
		return studentDAO.selectStudent(num);
	}

	@Override
	public List<Student> getStudentList() {
		System.out.println("*** StudentServiceImpl 클래스의 getStudentList() 메소드 호출 ***");
		return studentDAO.selectStudentList();
	}

}





📗 Advice클래스 (횡단관심모듈)

  • 횡단관심모듈(CrossCutting Concern Module) : 횡단관심코드만 이용하여 메소드를 작성한 클래스 (Advice 클래스)

    • 횡단관심코드 : 데이타 처리 명령을 제외한 보조적인 기능을 제공하는 명령
    • 로그 처리, 보안(권한) 처리, 트렌젝션 처리, 예외 처리 등의 명령
  • JoinPoint : 핵심관심코드를 기준으로 횡단관심코드가 삽입되어 동작될 위치를 표현

  • 타겟메소드(Target Method) : 핵심관심모듈의 메소드 중 PointCut 표현식으로 지정된 메소드

    • PointCut 표현식 : 핵심관심모듈의 메소드 중 원하는 메소드만 지정하기 위한 언어
  • 횡단관심코드가 삽입될 위치(JoinPoint)에 대한 하위 엘리먼트

    • Before Advice : 타겟메소드의 명령 실행전에 삽입되어 실행될 명령을 작성한 메소드
    • After Advice : 타겟메소드의 명령 실행후에 예외와 상관없이 무조건 삽입되어 실행될 명령을 작성한 메소드
    • After Returning Advice : 타겟메소드의 명령이 정상적으로 실행된 후에 삽입되어 실행될 명령을 작성한 메소드
    • After Throwing Advice : 타겟메소드의 명령 실행시 예외가 발생된 경우에만 삽입되어 실행될 명령을 작성한 메소드
    • Around Advice : 타겟메소드의 명령이 실행 전과 후에 삽입되어 실행될 명령을 작성한 메소드
      • public void aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {}
      • 매개변수 필수로 작성, 예외는 무조건 Throwable로 넘기기

package xyz.itwill07.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StudentAdvice {
	private static final Logger logger=LoggerFactory.getLogger(StudentAdvice.class);
	
	//타겟메소드의 명령 실행전에 삽입되어 실행될 명령을 작성한 메소드 - Before Advice 메소드
	// => JoinPoint : 핵심관심코드를 기준으로 횡단관심코드가 삽입되어 동작될 위치를 표현
	//타겟메소드(Target Method) : 핵심관심모듈의 메소드 중 PointCut 표현식으로 지정된 메소드
	// => PointCut 표현식 : 핵심관심모듈의 메소드 중 원하는 메소드만 지정하기 위한 언어
	public void beforeLog() {
		logger.info("[before]핵심관심코드 동작 전 삽입되어 실행될 횡단관심코드");
	}
	
	//타겟메소드의 명령 실행후에 예외와 상관없이 무조건 삽입되어 실행될 명령을 작성한 메소드
	// => After Advice 메소드
	public void afterLog() {
		logger.info("[after]핵심관심코드 동작 후 무조건 삽입되어 실행될 횡단관심코드");
	}
	
	//타겟메소드의 명령이 정상적으로 실행된 후에 삽입되어 실행될 명령을 작성한 메소드
	// => After Returning Advice 메소드
	public void afterReturningLog() {
		logger.info("[after-returning]핵심관심코드가 정삭적으로 동작된 후 삽입되어 실행될 횡단관심코드");
	}

	//타겟메소드의 명령 실행시 예외가 발생된 경우에만 삽입되어 실행될 명령을 작성한 메소드
	// => After Throwing Advice 메소드
	public void afterThrowingLog() {
		logger.info("[after-throwing]핵심관심코드 동작시 예외가 발생된 경우 삽입되어 실행될 횡단관심코드");
	}
	
	//타겟메소드의 명령이 실행 전과 후에 삽입되어 실행될 명령을 작성한 메소드
	// => Around Advice 메소드
	public void aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
		logger.info("[around]핵심관심코드 동작 전 삽입되어 실행될 횡단관심코드");
		joinPoint.proceed();//타겟메소드 호출 - 핵심관심코드 실행 
		logger.info("[around]핵심관심코드 동작 후 삽입되어 실행될 횡단관심코드");
	}
}






📘 환경설정파일 (AOP)

  • 핵심관심모듈로 작성된 클래스와 횡단관심모듈로 작성된 클래스를 Spring Bean으로 등록

  • Spring AOP(Aspect Oriented Programming - 관점 지향 프로그래밍) : AspectJ 컴파일러에 의해 프로그램 실행시 핵심관심코드와 횡단관심코드가 결합(Weaving)되어 동작하기 위한 기능 제공

    • Spring AOP 기능을 사용하기 위해서는 aspectjrt 라이브러리와 aspectjweaver 라이브러리가 프로젝트에 빌드 처리 (메이븐 사용 : pom.xml)
  • Spring Bean Configuration File에서 Spring AOP 기능을 구현하고자 할 경우 AOP 네임스페이스를 추가하여 spring-aop.xsd 파일의 엘리먼트로 설정

  • config : Spring AOP 관련 설정을 제공하기 위한 엘리먼트 <aop:config>
  • aspect : 핵심관심코드에 횡단관심코드를 원하는 위치(JoinPoint)에 삽입되어 실행되도록 설정하기 위한 엘리먼트
    • 횡단관심코드가 삽입될 위치(JoinPoint)를 하위 엘리먼트로 설정
    • 하위 엘리먼트 : before, after, after-returning, after-throwing, around
    • ref 속성 : 횡단관심모듈로 작성된 Advice 클래스에 대한 Spring Bean의 식별자(beanName)를 속성값으로 설정
    • <aop:aspect ref="studentAdvice">
  • aspect의 하위 엘리먼트에 대한 속성
    • method 속성 : Advice 클래스의 메소드명(횡단관심코드가 작성된 메소드)을 속성값으로 설정
    • pointcut 속성 : 핵심관심모듈의 메소드 중 횡단관심모듈의 메소드가 삽입된 타겟메소드를 지정하기 위한 PointCut 표현식을 속성값으로 설정
  • aspect의 하위 엘리먼트 종류
    • before : 핵심관심코드 실행 전에 횡단관심코드를 삽입하여 실행되도록 설정하기 위한 엘리먼트
      • pointcut-ref 속성 : pointcut 엘리먼트의 식별자를 속성값으로 설정
      • <aop:before method="beforeLog" pointcut-ref="studentDAOPointCut"/>
    • after : 핵심관심코드 실행 후 예외 발생과 상관없이 무조건 횡단관심코드를 삽입하여 실행되도록 설정하기 위한 엘리먼트
    • after-returning : 핵심관심코드가 정상적으로 실행된 후 횡단관심코드를 삽입하여 실행되도록 설정하기 위한 엘리먼트
    • after-throwing : 핵심관심코드 실행시 예외가 발생된 후 횡단관심코드를 삽입하여 실행되도록 설정하기 위한 엘리먼트
    • around : 핵심관심코드 실행 전과 후에 횡단관심코드를 삽입하여 실행되도록 설정하기 위한 엘리먼트

📌 PointCut 표현식

  • execution 함수 또는 within 함수에 검색패턴문자와 연산자를 사용하여 타겟메소드 지정

    • PointCut 표현식으로 사용 가능한 검색패턴문자 : ..(0개 이상), *(1개 이상), ?(0개 또는 1개)
    • PointCut 표현식으로 사용 가능한 연산자 : !(Not), &&(And), ||(Or)
  • execution 함수를 이용하여 타겟메소드를 지정하는 방법
    • execution 함수에 메소드의 머릿부를 표현하여 타겟메소드 지정
    • 형식) execution([접근지정자] 반환형 [패키지.클래스.]메소드명(자료형,자료형,...)
    • 클래스 대신 인터페이스 사용 가능 (인터페이스를 상속받은 모든 자식클래스 표현)
    • 반환형 또는 매개변수의 자료형이 클래스(인터페이스)인 경우 패키지를 포함하여 표현

🐣 예제

  • <aop:before method="beforeLog" pointcut="execution(void addStudent(xyz.itwill07.aop.Student))"/>
    : StudentServiceImpl 클래스의 addStudent(Student student)메소드 동작 전 삽입되어 실행
  • <aop:before method="beforeLog" pointcut="execution(* *(..))"/>
    : 모든 메소드 동작 전 삽입되어 실행
  • <aop:before method="beforeLog" pointcut="execution(* xyz.itwill07.aop..*(..))"/>
    : xyz.itwill07.aop 패키지에 있는 모든 메소드 동작 전 삽입되어 실행
  • <aop:before method="beforeLog" pointcut="execution(* xyz.itwill07.aop.StudentDAO.*(..))"/>
    : xyz.itwill07.aop.StudentDAO 클래스에 있는 모든 메소드 동작 전 삽입되어 실행
  • <aop:before method="beforeLog" pointcut="execution(xyz.itwill07.aop.Student *(..))"/>
    : 반환형의 자료형이 Student 클래스인 모든 메소드 동작 전 삽입되어 실행
  • <aop:before method="beforeLog" pointcut="execution(* get*(..))"/>
    : get으로 시작하는 모든 메소드 동작 전 삽입되어 실행
  • <aop:before method="beforeLog" pointcut="execution(* *(int)) or execution(int *(..))"/>
    : 매개변수의 자료형이 int 이거나 반환형이 int 인 모든 메소드 동작 전 삽입되어 실행

  • within 함수를 이용하여 타겟메소드를 지정하는 방법
    • Spring Bean으로 등록된 클래스(핵심관심모듈)의 모든 메소드를 타겟메소드로 지정
    • 형식) within(패키지.클래스명)
    • 클래스 대신 인터페이스 사용 불가능
    • <aop:before method="beforeLog" pointcut="within(xyz.itwill07.aop.StudentDAOImpl)"/>

  • pointcut : 핵심관심모듈의 메소드 중 횡단관심모듈의 메소드가 삽입된 타겟메소드를 지정하기 위한 엘리먼트
    • 자주 사용되는 PointCut 표현식을 저장하여 타겟메소드에 대한 정보 제공
    • aspect 엘리먼트 선언 전에 작성하거나 aspect 엘리먼트의 하위 엘리먼트로 작성
    • expression 속성 : 타겟메소드를 지정하기 위한 PointCut 표현식을 속성값으로 설정
    • id 속성 : PointCut 표현식을 구분하기 위한 식별자를 속성값으로 설정
    • <aop:pointcut expression="execution(* xyz.itwill07..StudentDAO.*(..))" id="studentDAOPointCut"/>



<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- 핵심관심모듈로 작성된 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.StudentDAOImpl" id="studentDAO"/>
	<bean class="xyz.itwill07.aop.StudentServiceImpl" id="studentService">
		<property name="studentDAO" ref="studentDAO"/>
	</bean>
	
	<!-- 횡단관심모듈로 작성된 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.StudentAdvice" id="studentAdvice"/>
	
	<!-- config : Spring AOP 관련 설정을 제공하기 위한 엘리먼트 -->
	<aop:config>
		<!-- aspect : 핵심관심코드에 횡단관심코드를 원하는 위치(JoinPoint)에 삽입되어 실행
		되도록 설정하기 위한 엘리먼트 -->
		<!-- => 횡단관심코드가 삽입될 위치(JoinPoint)를 하위 엘리먼트로 설정 -->
		<!-- => 하위 엘리먼트 : before, after, after-returning, after-throwing, around -->
		<!-- ref 속성 : 횡단관심모듈로 작성된 Advice 클래스에 대한 Spring Bean의 식별자
		(beanName)를 속성값으로 설정 -->
		<aop:aspect ref="studentAdvice">
			<!-- before : 핵심관심코드 실행 전에 횡단관심코드를 삽입하여 실행되도록 설정하기 위한 엘리먼트 -->
			<!-- method 속성 : Advice 클래스의 메소드명(횡단관심코드가 작성된 메소드)을 속성값으로 설정 -->
			<!-- pointcut 속성 : 핵심관심모듈의 메소드 중 횡단관심모듈의 메소드가 삽입된 
			타겟메소드를 지정하기 위한 PointCut 표현식을 속성값으로 설정 -->
			<!-- => execution 함수 또는 within 함수에 검색패턴문자와 연산자를 사용하여 타겟메소드 지정 -->
			<!-- => PointCut 표현식으로 사용 가능한 검색패턴문자 : ..(0개 이상), *(1개 이상), ?(0개 또는 1개) -->
			<!-- => PointCut 표현식으로 사용 가능한 연산자 : !(Not), &&(And), ||(Or) -->
			
			<!-- execution 함수를 이용하여 타겟메소드를 지정하는 방법 -->
			<!-- => execution 함수에 메소드의 머릿부를 표현하여 타겟메소드 지정 -->
			<!-- 형식)execution([접근지정자] 반환형 [패키지.클래스.]메소드명(자료형,자료형,...) -->
			<!-- => 클래스 대신 인터페이스 사용 가능 - 인터페이스를 상속받은 모든 자식클래스 표현 -->
			<!-- => 반환형 또는 매개변수의 자료형이 클래스(인터페이스)인 경우 패키지를 포함하여 표현 -->		
			<!-- <aop:before method="beforeLog" pointcut="execution(* *(..))"/> -->		
			
			<!-- within 함수를 이용하여 타겟메소드를 지정하는 방법 -->
			<!-- => Spring Bean으로 등록된 클래스(핵심관심모듈)의 모든 메소드를 타겟메소드로 지정 -->
			<!-- 형식)within(패키지.클래스명) -->
			<!-- => 클래스 대신 인터페이스 사용 불가능 -->
			<!-- <aop:before method="beforeLog" pointcut="within(xyz.itwill07.aop.StudentDAOImpl)"/> -->
			<!-- <aop:before method="beforeLog" pointcut="within(xyz.itwill07.aop.StudentServiceImpl)"/> -->
			
			<!-- pointcut : 핵심관심모듈의 메소드 중 횡단관심모듈의 메소드가 삽입된 타겟
			메소드를 지정하기 위한 엘리먼트 -->
			<!-- => 자주 사용되는 PointCut 표현식을 저장하여 타겟메소드에 대한 정보 제공 -->
			<!-- => aspect 엘리먼트 선언 전에 작성하거나 aspect 엘리먼트의 하위 엘리먼트로 작성 -->
			<!-- expression 속성 : 타겟메소드를 지정하기 위한 PointCut 표현식을 속성값으로 설정 -->
			<!-- id 속성 : PointCut 표현식을 구분하기 위한 식별자를 속성값으로 설정 -->
			<aop:pointcut expression="execution(* xyz.itwill07..StudentDAO.*(..))" id="studentDAOPointCut"/>
			<aop:pointcut expression="execution(* xyz.itwill07..StudentService.*(..))" id="studentServicePointCut"/>
			
			<!-- pointcut-ref 속성 : pointcut 엘리먼트의 식별자를 속성값으로 설정 -->
			<!-- <aop:before method="beforeLog" pointcut-ref="studentDAOPointCut"/> -->
			<aop:before method="beforeLog" pointcut-ref="studentServicePointCut"/>
			
			<!-- after : 핵심관심코드 실행 후 예외 발생과 상관없이 무조건 횡단관심코드를 삽입 
			하여 실행되도록 설정하기 위한 엘리먼트 -->
			<aop:after method="afterLog" pointcut-ref="studentServicePointCut"/>

			<!-- after-returning : 핵심관심코드가 정상적으로 실행된 후 횡단관심코드를 삽입하여 
			실행되도록 설정하기 위한 엘리먼트 -->
			<aop:after-returning method="afterReturningLog" pointcut-ref="studentServicePointCut"/>
			
			<!-- after-throwing : 핵심관심코드 실행시 예외가 발생된 후 횡단관심코드를 삽입하여 
			실행되도록 설정하기 위한 엘리먼트 -->
			<aop:after-throwing method="afterThrowingLog" pointcut-ref="studentServicePointCut"/>
			
			<!-- around : 핵심관심코드 실행 전과 후에 횡단관심코드를 삽입하여 실행되도록 설정하기
			위한 엘리먼트 -->
			<aop:around method="aroundLog" pointcut-ref="studentServicePointCut"/>
		</aop:aspect>
	</aop:config>
</beans>





📌 Spring AOP lib 빌드 처리 (pom.xml)

  • Spring AOP 기능을 사용하기 위해서는 aspectjrt 라이브러리와 aspectjweaver 라이브러리가 프로젝트에 빌드 처리

    • 메이븐 사용 : pom.xml
    • aspectjrt : AspectJ로 생성된 결과물(핵심관심코드+횡단관심코드)을 실행하기 위한 라이브러리
    • aspectjweaver : AspectJ를 사용하여 핵심관심코드와 횡단관심코드가 결합된 결과물을 생성하기 위한 라이브러리 (proxy)

		<!-- AspectJ -->
		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
		<!-- => AspectJ로 생성된 결과물(핵심관심코드+횡단관심코드)를 실행하기 위한 라이브러리 -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
		<!-- => AspectJ를 사용하여 핵심관심코드와 횡단관심코드가 결합된 결과물을 생성하기 위한 라이브러리 -->
		<dependency>
		    <groupId>org.aspectj</groupId>
		    <artifactId>aspectjweaver</artifactId>
		    <version>${org.aspectj-version}</version>
		    <scope>runtime</scope>
        </dependency>  





🐣 앱 실행


package xyz.itwill07.aop;

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

public class StudentAopApp {
	public static void main(String[] args) {
		ApplicationContext context=new ClassPathXmlApplicationContext("07-1_aop.xml");
		StudentService service=context.getBean("studentService", StudentService.class);
		System.out.println("================================================================");
		service.addStudent(null);
		System.out.println("================================================================");
		service.getStudent(0);
		System.out.println("================================================================");
		service.getStudentList();
		System.out.println("================================================================");
		((ClassPathXmlApplicationContext)context).close();
	}
}





📌 log4j.xml (로그구현체)

  • root 엘리먼트의 속성값을 warn으로 설정하였으므로 warn 로그 이벤트보다 상위의 이벤트가 발생된 경우에만 로그 구현체로 기록된다.

  • 원하는 패키지에서 발생되는 info 레벨의 로그 이벤트를 기록하기 위해서는 logger 엘리먼트를 추가해야한다.


	<!-- logger : 특정 패키지의 클래스에서 발생되는 로그 이벤트를 기록하기 위한 엘리먼트 -->
	<!-- name 속성 : 패키지 경로를 속성값으로 설정 -->
	<logger name="xyz.itwill07.aop">
		<level value="info" />
	</logger>

	<!-- root : 모든 클래스에서 발생되는 로그 이벤트를 기록하기 위한 엘리먼트 -->
	<!-- => 모든 logger 엘리먼트는 root 엘리먼트의 정보를 상속받아 사용 -->
	<root>
		<!-- priority : 모든 클래스에서 발생되는 기본 로그 이벤트를 설정하기 위한 엘리먼트 -->
		<!-- value 속성 : 로그 이벤트를 속성값으로 설정 -->
		<!-- => 속성값으로 설정된 로그 이벤트보다 상위의 이벤트가 발생된 경우 로그 구현체로 기록 -->
		<priority value="warn" />
		<!-- appender-ref : 로그 구현체를 참조하기 위한 엘리먼트 -->
		<!-- ref 속성 : appender 엘리먼트의 식별자를 속성값으로 설정 -->
		<appender-ref ref="console" />
	</root>





🌼 Advice클래스 매개변수

  • Advice 클래스의 메소드 내에서 실제 호출되었던 핵심 관점의 클래스 정보 혹은 메소드 정보가 필요할 때 SpringAOP에서는 이를 위해 JoinPoint 클래스가 제공됨

📒 핵심관심모듈 (JoinPoint)

package xyz.itwill07.aop;

public class JoinPointBean {
	public void add() {
		System.out.println("### JoinPointBean 클래스의 add() 메소드 호출 ###");
	}
	
	public void modify(int num, String name) {
		System.out.println("### JoinPointBean 클래스의 modify(int num, String name) 메소드 호출 ###");
	}
	
	public void remove(int num) {
		System.out.println("### JoinPointBean 클래스의 remove(int num) 메소드 호출 ###");
	}
	
	public String getName() {
		System.out.println("### JoinPointBean 클래스의 getName() 메소드 호출 ###");
		return "홍길동";
	}
	
	public void calc(int num1, int num2) {
		System.out.println("### JoinPointBean 클래스의 calc(int num1, int num2) 메소드 호출 ###");
		System.out.println("몫 = "+(num1/num2));
	}
}





📗 횡단관심모듈 (Advice클래스)

  • Around Advice 메소드를 제외한 나머지 Advice 메소드는 일반적으로 반환형을 void로 작성하고 매개변수를 작성하지 않거나 JoinPoint 인터페이스로 선언된 매개변수 작성 가능

    • Advice 메소드를 작성 규칙에 맞지 않게 작성할 경우 IllegalArgumentException 발생
  • JoinPoint 객체 : 타겟메소드 관련 정보가 저장된 객체

    • 스프링 컨테이너가 Advice 클래스의 메소드를 호출할 때 타겟메소드 관련 정보를 JoinPoint 객체에 저장하여 매개변수에 전달
    • Advice 클래스의 메소드에서 타겟메소드 관련 정보가 필요한 경우 매개변수를 선언하여 사용
  • 타겟메소드의 클래스 이름
    • JoinPoint.getTarget() : 타겟메소드를 호출한 객체(Spring Bean)를 반환하는 메소드
    • Object.getClass() : 객체를 생성한 클래스의 Class 객체(Clazz)를 반환하는 메소드
    • Class.getName() : Class 객체에 저장된 클래스의 이름(패키지 포함)을 문자열로 반환하는 메소드
    • System.out.println(joinPoint.getTarget().getClass().getName());
    • Class.getSimpleName() : Class 객체에 저장된 클래스의 이름(패키지 미포함)을 문자열로 반환하는 메소드
  • 타겟메소드 이름
    • JoinPoint.getSignature() : 타겟메소드의 정보가 저장된 Signature 객체를 반환하는 메소드
    • Signature.getName() : 타겟메소드의 이름을 문자열로 반환하는 메소드
    • System.out.println(joinPoint.getSignature().getName());
  • 타겟메소드의 매개변수에 저장된 값
    • JoinPoint.getArgs() : 타겟메소드의 매개변수에 저장된 모든 값(객체)을 제공받아 Object객체 배열로 반환하는 메소드
    • System.out.println(joinPoint.getArgs()); ( [L : 객체 배열 )
  • After Returning Advice 메소드에는 JoinPoint 인터페이스의 매개변수 외에 Object 클래스의 매개변수 선언 가능

    • 스프링 컨테이너는 Object 클래스의 매개변수에 타겟메소드의 반환값이 저장되도록 전달
    • 타겟메소드에서 반환되는 값(객체)의 자료형이 고정되어 있는 경우 Object 클래스 대신 반환되는 값(객체)의 자료형으로 매개변수 작성 가능
    • Spring Bean Configuration File의 AOP 설정에서 after-returning 엘리먼트에 반드시 returning 속성을 사용하여 반환값을 저장할 매개변수의 이름을 속성값으로 지정
    • after-returning 엘리먼트에 returning 속성이 없거나 속성값이 잘못 설정된 경우 IllegalArgumentException 발생
  • After Throwing Advice 메소드에는 JoinPoint 인터페이스의 매개변수 외에 Exception 클래스의 매개변수 선언 가능

    • 스프링 컨테이너는 Exception 클래스의 매개변수에 타겟메소드의 명령 실행시 발생된 예외(Exception 객체)가 저장되도록 전달
    • 타겟메소드에서 발생되는 예외가 고정되어 있는 경우 Exception 클래스 대신 자식클래스로 선언된 매개변수 작성 가능
    • Spring Bean Configuration File의 AOP 설정에서 after-throwing 엘리먼트에 반드시 throwing 속성을 사용하여 Exception 객체를 저장할 매개변수의 이름을 속성값으로 지정
    • after-throwing 엘리먼트에 throwing 속성이 없거나 속성값이 잘못 설정된 경우 IllegalArgumentException 발생
  • Around Advice 메소드반환형을 Object 클래스로 작성하고 매개변수의 자료형은 ProceedingJoinPoint 인터페이스로 작성

    • 타겟메소드의 반환값을 제공받아 반환하기 위해 Object 클래스를 반환형으로 작성
    • 타겟메소드 관련 정보를 ProceedingJoinPoint 인터페이스의 매개변수로 제공받아 Around Advice 메소드에서 사용
    • ProceedingJoinPoint : 타겟메소드 관련 정보를 저장하기 위한 객체
      • JoinPoint 객체와 다른점은 타겟메소드를 직접 호출하기 위한 메소드 제공
      • ProceedingJoinPoint.proceed() : 타겟메소드를 호출하는 메소드 → 핵심관심코드 실행
      • 타겟메소드를 호출하여 반환되는 결과값을 제공받아 저장
      • Throwable(Error 클래스와 Exception 클래스의 부모클래스) 객체(예외)가 발생되므로 예외를 처리하거나 예외를 전달


package xyz.itwill07.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

//횡단관심모듈 - Advice 클래스
public class JoinPointAdvice {
	//Around Advice 메소드를 제외한 나머지 Advice 메소드는 일반적으로 반환형을 void로 작성하고
	//매개변수를 작성하지 않거나 JoinPoint 인터페이스로 선언된 매개변수 작성 가능
	// => Advice 메소드를 작성 규칙에 맞지 않게 작성할 경우 IllegalArgumentException 발생
	//JoinPoint 객체 : 타겟메소드 관련 정보가 저장된 객체
	// => 스프링 컨테이너가 Advice 클래스의 메소드를 호출할 때 타겟메소드 관련 정보를 JoinPoint
	//객체에 저장하여 매개변수에 전달
	// => Advice 클래스의 메소드에서 타겟메소드 관련 정보가 필요한 경우 매개변수를 선언하여 사용
	
	//Before Advice 메소드
	public void beforeDisplay(JoinPoint joinPoint) {
		//System.out.println("[before]핵심관심코드 실행 전에 삽입되어 실행될 횡단관심코드");
		
		//JoinPoint.getTarget() : 타겟메소드를 호출한 객체(Spring Bean)를 반환하는 메소드
		//Object.getClass() : 객체를 생성한 클래스의 Class 객체(Clazz)를 반환하는 메소드
		//Class.getName() : Class 객체에 저장된 클래스의 이름(패키지 포함)을 문자열로 반환하는 메소드
		//System.out.println(joinPoint.getTarget().getClass().getName());
		
		//Class.getSimpleName() : Class 객체에 저장된 클래스의 이름(패키지 미포함)을 문자열로 반환하는 메소드
		//System.out.println(joinPoint.getTarget().getClass().getSimpleName());
		
		//JoinPoint.getSignature() : 타겟메소드의 정보가 저장된 Signature 객체를 반환하는 메소드
		//Signature.getName() : 타겟메소드의 이름을 문자열로 반환하는 메소드
		//System.out.println(joinPoint.getSignature().getName());
		
		//JoinPoint.getArgs() : 타겟메소드의 매개변수에 저장된 모든 값(객체)을 제공받아 Object
		//객체 배열로 반환하는 메소드
		//System.out.println(joinPoint.getArgs());
		
		String className=joinPoint.getTarget().getClass().getSimpleName();
		String methodName=joinPoint.getSignature().getName();
		Object[] params=joinPoint.getArgs();
		
		System.out.print("[before]"+className+" 클래스의 "+methodName+"(");
		for(int i=0;i<params.length;i++) {
			System.out.print(params[i]);
			if(i<params.length-1) {
				System.out.print(", ");
			}
		}
		System.out.println(") 메소드 호출");
	}
	
	//After Advice 메소드
	public void displayMessage(JoinPoint joinPoint) {
		//System.out.println("[after]핵심관심코드 실행 후에 무조건 삽입되어 실행될 횡단관심코드");
		
		Object[] params=joinPoint.getArgs();
		System.out.println("[after]학번이 "+params[0]+"인 학생정보를 삭제 하였습니다.");
	}
	
	//After Returning Advice 메소드에는 JoinPoint 인터페이스의 매개변수 외에 Object 클래스의
	//매개변수 선언 가능
	// => 스프링 컨테이너는 Object 클래스의 매개변수에 타겟메소드의 반환값이 저장되도록 전달
	// => 타겟메소드에서 반환되는 값(객체)의 자료형이 고정되어 있는 경우 Object 클래스 대신
	//반환되는 값(객체)의 자료형으로 매개변수 작성 가능
	// => Spring Bean Configuration File의 AOP 설정에서 after-returning 엘리먼트에 반드시
	//returning 속성을 사용하여 반환값을 저장할 매개변수의 이름을 속성값으로 지정
	// => after-returning 엘리먼트에 returning 속성이 없거나 속성값이 잘못 설정된 경우 IllegalArgumentException 발생
	
	//After Returning Advice 메소드
	public void displayName(Object object) {
		//System.out.println("[after-returning]핵심관심코드가 정상적으로 실행된 후에 삽입되어 실행될 횡단관심코드");

		//instanceof 연산자를 사용하여 매개변수에 저장된 객체의 자료형을 구분하여 처리
		if(object instanceof String) {
			String name=(String)object;//명시적 객체 형변환
			System.out.println("[after-returning]"+name+"님, 안녕하세요.");
		}
	}
	
	//After Throwing Advice 메소드에는 JoinPoint 인터페이스의 매개변수 외에 Exception 클래스의
	//매개변수 선언 가능
	// => 스프링 컨테이너는 Exception 클래스의 매개변수에 타겟메소드의 명령 실행시 발생된 
	//예외(Exception 객체)가 저장되도록 전달
	// => 타겟메소드에서 발생되는 예외가 고정되어 있는 경우 Exception 클래스 대신 자식클래스로
	//선언된 매개변수 작성 가능
	// => Spring Bean Configuration File의 AOP 설정에서 after-throwing 엘리먼트에 반드시
	//throwing 속성을 사용하여 Exception 객체를 저장할 매개변수의 이름을 속성값으로 지정
	// => after-throwing 엘리먼트에 throwing 속성이 없거나 속성값이 잘못 설정된 경우 IllegalArgumentException 발생

	//After Throwing Advice 메소드
	public void exceptionHandle(JoinPoint joinPoint, Exception exception) {
		//System.out.println("[after-throwing]핵심관심코드 실행시 예외가 발생된 경우 삽입되어 실행될 횡단관심코드");
		
		String className=joinPoint.getTarget().getClass().getSimpleName();
		String methodName=joinPoint.getSignature().getName();
		
		System.out.println("[after-throwing]"+className+" 클래스의 "+methodName
			+" 메소드에서 발생된 예외 = "+exception.getMessage());
	}
	
	//Around Advice 메소드는 반환형을 Object 클래스로 작성하고 매개변수의 자료형은 
	//ProceedingJoinPoint 인터페이스로 작성
	// => 타겟메소드의 반환값을 제공받아 반환하기 위해 Object 클래스를 반환형으로 작성
	// => 타겟메소드 관련 정보를 ProceedingJoinPoint 인터페이스의 매개변수로 제공받아 
	//Around Advice 메소드에서 사용
	//ProceedingJoinPoint : 타겟메소드 관련 정보를 저장하기 위한 객체
	// => JoinPoint 객체와 다른점은 타겟메소드를 직접 호출하기 위한 메소드 제공
	
	//Around Advice 메소드
	public Object disply(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("[around]핵심관심코드 실행 전 삽입되어 실행될 횡단관심코드");
		//ProceedingJoinPoint.proceed() : 타겟메소드를 호출하는 메소드 - 핵심관심코드 실행
		// => 타겟메소드를 호출하여 반환되는 결과값을 제공받아 저장
		// => Throwable(Error 클래스와 Exception 클래스의 부모클래스) 객체(예외)가 발생되므로
		//예외를 처리하거나 예외를 전달
		Object object=joinPoint.proceed();
		System.out.println("[around]핵심관심코드 실행 후 삽입되어 실행될 횡단관심코드");
		return object;//타겟메소드를 호출하여 반환된 결과값을 메소드를 호출한 명령으로 반환
	}
}





📘 환경설정파일 (AOP)

  • After Returning Advice 메소드에서 Object 클래스의 매개변수 선언 시 Spring Bean Configuration File의 AOP 설정에서 after-returning 엘리먼트에 반드시 returning 속성을 사용하여 반환값을 저장할 매개변수의 이름을 속성값으로 지정

    • returning 속성 : 타겟메소드의 반환값을 전달받아 저장할 매개변수의 이름을 속성값으로 설정
    • <aop:after-returning method="displayName" pointcut="execution(java.lang.String getName())" returning="object"/>
  • After Throwing Advice 메소드에서 Exception 클래스의 매개변수 선언 시 Spring Bean Configuration File의 AOP 설정에서 after-throwing 엘리먼트에 반드시 throwing 속성을 사용하여 Exception 객체를 저장할 매개변수의 이름을 속성값으로 지정


<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- 핵심관심모듈로 선언된 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.JoinPointBean" id="joinPointBean"/>

	<!--  횡단관심모듈로 선언된 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.JoinPointAdvice" id="joinPointAdvice"/>
	
	<aop:config>
		<aop:aspect ref="joinPointAdvice">
			<aop:before method="beforeDisplay" pointcut="execution(* *(..))"/>
			<aop:after method="displayMessage" pointcut="execution(void remove(int))"/>
			<!-- returning 속성 : 타겟메소드의 반환값을 전달받아 저장할 매개변수의 이름을 속성값으로 설정 -->
			<aop:after-returning method="displayName" 
				pointcut="execution(java.lang.String getName())" returning="object"/>
			<aop:after-throwing method="exceptionHandle" 
				pointcut="execution(void calc(int, int))" throwing="exception"/>
			<aop:around method="disply" pointcut="execution(* modify(..))"/>		
		</aop:aspect>
	</aop:config>
</beans>





🐣 앱 실행


package xyz.itwill07.aop;

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

public class JoinPointApp {
	public static void main(String[] args) {
		ApplicationContext context=new ClassPathXmlApplicationContext("07-2_param.xml");
		JoinPointBean bean=context.getBean("joinPointBean", JoinPointBean.class);
		System.out.println("================================================================");
		bean.add();
		System.out.println("================================================================");
		bean.modify(1000, "홍길동");
		System.out.println("================================================================");
		bean.remove(2000);
		System.out.println("================================================================");
		bean.getName();
		System.out.println("================================================================");
		//bean.calc(20, 10);
		bean.calc(20, 0);//ArithmeticException 발생
		System.out.println("================================================================");
		((ClassPathXmlApplicationContext)context).close();
	}
}





🎀 연습문제1 (aop:around)


📒 핵심관심모듈 (실행시간)

  • System.currentTimeMillis() : 시스템의 현재 날짜와 시간에 대한 타입스템프를 반환하는 메소드

    • 타입스템프(TimeStamp) : 날짜와 시간을 정수값으로 변환한 값 (날짜와 시간에 대한 연산)
  • 메소드가 실행되는데 걸리는 시간을 출력하는 클래스



package xyz.itwill07.aop;

public class ExecutionTimeBean {
	public void one() {
		//long startTime=System.currentTimeMillis();
		
		long count=0;
		for(long i=1;i<=10000000000L;i++) {
			count++;
		}
		System.out.println("count = "+count);

		//long endTime=System.currentTimeMillis();
		//System.out.println("ExecutionTimeBean 클래스의 one 메소드 실행 시간 = "+(endTime-startTime)+"ms");
	}
	
	public void two() {
		//long startTime=System.currentTimeMillis();

		long count=0;
		for(long i=1;i<=20000000000L;i++) {
			count++;
		}
		System.out.println("count = "+count);
		
		//long endTime=System.currentTimeMillis();
		//System.out.println("ExecutionTimeBean 클래스의 two 메소드 실행 시간 = "+(endTime-startTime)+"ms");
	}
}





📗 Advice클래스 (횡단관심모듈)

  • 타겟메소드의 명령이 실행되는 처리시간을 계산하여 출력하기 위한 메소드 (Around Advice Method)

  • StopWatch 객체 : 시간을 측정하기 위한 기능을 제공하기 위한 객체

    • stopWatch.start() : 시간 측정을 시작하는 메소드
    • StopWatch.stop() : 시간 측정을 종료하는 메소드
    • StopWatch.getTotalTimeMillis() : 측정된 시간을 ms 단위로 반환하는 메소드

package xyz.itwill07.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class ExecutionTimeAdvice {
	public Object timeWatchLog(ProceedingJoinPoint joinPoint) throws Throwable {
		//타겟메소드의 명령 실행전에 동작될 명령 작성
		//long startTime=System.currentTimeMillis();
		
		//StopWatch 객체 : 시간을 측정하기 위한 기능을 제공하기 위한 객체
		StopWatch stopWatch=new StopWatch();
		
		//stopWatch.start() : 시간 측정을 시작하는 메소드
		stopWatch.start();

		//타겟메소드 호출하여 명령 실행
		Object object=joinPoint.proceed();
		
		//타겟메소드의 명령 실행후에 동작될 명령 작성
		//long endTime=System.currentTimeMillis();

		//StopWatch.stop() : 시간 측정을 종료하는 메소드
		stopWatch.stop();
		
		String className=joinPoint.getTarget().getClass().getSimpleName();
		String methodName=joinPoint.getSignature().getName();
		
		//System.out.println(className+" 클래스의 "+methodName+" 메소드 실행 시간 = "+(endTime-startTime)+"ms");
		
		//StopWatch.getTotalTimeMillis() : 측정된 시간을 ms 단위로 반환하는 메소드 
		System.out.println(className+" 클래스의 "+methodName+" 메소드 실행 시간 = "
				+stopWatch.getTotalTimeMillis()+"ms");
		
		return object;
	}
}





📘 환경설정파일 (AOP)


<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<bean class="xyz.itwill07.aop.ExecutionTimeBean" id="executionTimeBean"/>
	
	<bean class="xyz.itwill07.aop.ExecutionTimeAdvice" id="executionTimeAdvice"/>
	
	<aop:config>
		<aop:aspect ref="executionTimeAdvice">
			<aop:around method="timeWatchLog" pointcut="within(xyz.itwill07.aop.ExecutionTimeBean)"/>
		</aop:aspect>
	</aop:config>
</beans>





🐣 앱 실행


package xyz.itwill07.aop;

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

public class ExecutionTimeApp {
	public static void main(String[] args) {
		ApplicationContext context=new ClassPathXmlApplicationContext("07-3_timer.xml");
		ExecutionTimeBean bean=context.getBean("executionTimeBean", ExecutionTimeBean.class);
		System.out.println("================================================================");
		bean.one();	
		System.out.println("================================================================");
		bean.two();	
		System.out.println("================================================================");
		((ClassPathXmlApplicationContext)context).close();	
	}
}





🎀 연습문제2 (메일 전송)

  • Java Mail 기능을 구현하기 위해서는 spring-context-support 라이브러리와 javax.mail 라이브러리가 프로젝트에 빌드되도록 처리

    • 메이블 사용 : pom.xml

📌 mail lib 빌드 처리

  • spring-context-support : Spring Context 기능을 지원하는 라이브러리 (springframework와 동일한 version 사용)

  • javax.mail : Java Mail 기능을 제공하는 라이브러리



		<!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
		<!-- => Spring Context 기능을 지원하는 라이브러리 -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-context-support</artifactId>
		    <version>${org.springframework-version}</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/com.sun.mail/javax.mail -->
		<!-- => Java Mail 기능을 제공하는 라이브러리 -->
		<dependency>
		    <groupId>com.sun.mail</groupId>
		    <artifactId>javax.mail</artifactId>
		    <version>1.6.2</version>
		</dependency>




📒 핵심관심모듈 (sendemail)

  • 메일 전송 기능을 제공하는 클래스 (메일 서버의 SMTP 서비스를 사용하여 메일 전송)

    • 메일 서버(Mail Server) : 메일을 송수신하는 서비스를 제공하는 컴퓨터
    • SMTP(Simple Message Transfer Protocol) 서비스로 메일을 보내고 POP3(Post Office Protocol 3 : 재전송 불가) 서비스나 IMAP(Internet Message Access Protocol : 재전송 가능) 서비스로 메일을 전달 받아 사용자에게 전달
    • 메일을 전송하는 서비스(SMTP)를 제공하는 메일서버의 정보가 저장된 JavaMailSender 객체를 저장하기 위한 필드 선언
      • private JavaMailSender javaMailSender;
  • 메일을 전송하는 메소드

    • 메일을 받는 사람의 이메일 주소, 제목, 내용을 매개변수로 전달받아 저장
    • 메일을 받는 사람의 이메일 주소를 반환
    • public String sendEmail(String email, String subject, String content) {}
  • JavaMailSender.createMimeMessage() : MimeMessage 객체를 생성하여 반환하는 메소드
    • MimeMessage 객체 : 메일 전송 관련 정보를 저장하기 위한 객체
    • MimeMessage.setSubject(subject) : 전송할 메일의 제목을 변경하는 메소드
    • MimeMessage.setText(content) : 전송할 메일의 내용(텍스트 메소드)을 변경하는 메소드
    • MimeMessage.setContent(Object o, String type) : 전송할 메일의 내용을 변경하는 메소드 (type 매개변수에 문서의 형식(MimeType)을 전달하여 저장 )
      • message.setContent(content, "text/html; charset=utf-8");
    • MimeMessage.setRecipient(RecipientType type, Address address) : 받는 사람의 이메일 주소 관련 정보를 변경하는 메소드
      • RecipientType : 메일 수신 사용자를 구분하기 위한 상수값 전달
      • Address : 이메일 주소가 저장된 Address 객체를 전달
      • InternetAddress : 이메일 주소를 저장하기 위한 클래스 (Address 추상 클래스를 상속받은 자식클래스)
      • InternetAddress.parse(String email) : 문자열로 전달된 이메일 주소를 Address 객체 배열로 반환
      • message.setRecipient(MimeMessage.RecipientType.TO, InternetAddress.parse(email)[0]);
  • JavaMailSender.send(MimeMessage message) : 메일을 보내는 서비스를 이용하여 메일을 전송하는 메소드

  • 메일을 보내는 사람의 정보도 설정 가능

    • MimeMessage.setFrom(new InternetAddress("이메일주소", "이름","내용"));


package xyz.itwill07.aop;

import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.springframework.mail.javamail.JavaMailSender;

import lombok.Setter;

//Java Mail 기능을 구현하기 위해서는 spring-context-support 라이브러리와 javax.mail 라이브러리가
//프로젝트에 빌드되도록 처리 - 메이븐사용 : pom.xml

//메일 전송 기능을 제공하는 클래스 - 메일 서버의 SMTP 서비스를 사용하여 메일 전송
// => 메일 서버(Mail Server) : 메일을 송수신하는 서비스를 제공하는 컴퓨터
// => SMTP(Simple Message Transfer Protocol) 서비스로 메일을 보내고 POP3(Post Office Protocol 3) 
//서비스나 IMAP(Internet Message Access Protocol) 서비스로 메일을 받아 사용자에게 전달
@Setter
public class EmailSendBean {
	//메일을 전송하는 서비스(SMTP)를 제공하는 서버의 정보가 저장된 JavaMailSender 객체를 저장하기 위한 필드 선언
	private JavaMailSender javaMailSender;
	
	//메일을 전송하는 메소드
	// => 메일을 받는 사람의 이메일 주소, 제목, 내용을 매개변수로 전달받아 저장
	// => 메일을 받는 사람의 이메일 주소를 반환
	public String sendEmail(String email, String subject, String content) throws Exception {
		//JavaMailSender.createMimeMessage() : MimeMessage 객체를 생성하여 반환하는 메소드
		//MimeMessage 객체 : 메일 전송 관련 정보를 저장하기 위한 객체
		MimeMessage message=javaMailSender.createMimeMessage();
		 
		//MimeMessage.setSubject(subject) : 전송할 메일의 제목을 변경하는 메소드
		message.setSubject(subject);
			
		//MimeMessage.setText(content) : 전송할 메일의 내용(텍스트 메세지)을 변경하는 메소드
		//message.setText(content);
		
		//MimeMessage.setContent(Object o, String type) : 전송할 메일의 내용을 변경하는 메소드
		// => type 매개변수에 문서의 형식(MimeType)을 전달하여 저장 
		message.setContent(content, "text/html; charset=utf-8");
			
		//MimeMessage.setRecipient(RecipientType type, Address address) : 받는 사람의
		//이메일 주소 관련 정보를 변경하는 메소드
		// => RecipientType : 메일 수신 사용자를 구분하기 위한 상수값 전달
		// => Address : 이메일 주소가 저장된 Address 객체를 전달
		//InternetAddress : 이메일 주소를 저장하기 위한 클래스 - Address 클래스를 상속받은 자식클래스
		message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(email));

		//MimeMessage.setRecipients(RecipientType type, Address[] addresses) : 받는 사람의
		//이메일 주소 관련 정보를 변경하는 메소드 - 다수의 메일을 전송하는 메소드
		//InternetAddress.parse(String emailList) : 문자열로 전달된 이메일 주소 목록을 Address 객체 배열로 반환
		//message.setRecipients(MimeMessage.RecipientType.TO, InternetAddress.parse(email));
			
		//JavaMailSender.send(MimeMessage message) : 메일을 보내는 서비스를 이용하여 메일을 전송하는 메소드
		javaMailSender.send(message);
			
		System.out.println("[메세지]메일을 성공적으로 전송 하였습니다.");
		
		return email;
	}
}




📗 Advice클래스 (횡단관심모듈)

  • 메일을 전송하기 전에 삽입되어 실행될 명령이 작성된 메소드 (Before Advice 메소드)

    • 받는 사람의 이메일 주소와 제목을 얻어와 저장하기 위해 JointPoint 매개변수 필요
  • 메일 전송이 성공한 경우 삽입되어 실행될 명령이 작성된 메소드 (After Returning Advice 메소드)

    • 타겟메소드(sendEmail)의 반환값(받는 사람의 이메일 주소)을 얻어와 저장하기 위해 매개변수 필요
  • 예외가 발생되어 메일 전송이 실패한 경우 삽입되어 실행될 명령이 작성된 메소드 (After Throwing 메소드)

    • 타겟메소드(sendEmail)의 명령 실행시 발생된 예외(Exception 객체)를 얻어와 저장하기 위한 매개변수 필요

package xyz.itwill07.aop;

import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EmailSendAdvice {
	private static final Logger logger=LoggerFactory.getLogger(EmailSendAdvice.class);
	
	//메일을 전송하기 전에 삽입되어 실행될 명령이 작성된 메소드 - Before Advice 메소드
	// => 받는 사람의 이메일 주소와 제목을 얻어와 저장하기 위해 JointPoint 매개변수 필요
	public void accessLog(JoinPoint joinPoint) {
		//타겟메소드(sendEmail)의 매개변수에 저장된 값(객체)를 반환받아 저장
		String email=(String)joinPoint.getArgs()[0];//받는 사람의 이메일 주소
		String subject=(String)joinPoint.getArgs()[1];//메일 제목
		logger.info(email+"님에게 <"+subject+"> 제목을 이메일을 전송합니다.");
	}
	
	//메일 전송이 성공한 경우 삽입되어 실행될 명령이 작성된 메소드 - After Returning Advice 메소드
	// => 타겟메소드(sendEmail)의 반환값(받는 사람의 이메일 주소)을 얻어와 저장하기 위해 매개변수 필요
	public void successLog(String email) {
		logger.info(email+"님에게 이메일을 성공적으로 전송 하였습니다.");
	}
	
	//예외가 발생되어 메일 전송이 실패한 경우 삽입되어 실행될 명령이 작성된 메소드 - After Throwing 메소드
	// => 타겟메소드(sendEmail)의 명령 실행시 발생된 예외(Exception 객체)를 얻어와 저장하기 위한 매개변수 필요
	public void errorLog(Exception exception) {
		logger.info("이메일 전송 실패 = "+exception.getMessage());
	}
}





📌 구글 SMTP 서비스 이용 방법

  1. Google 계정 로그인 후 Google 계정 관리 접속

  1. 보안 → 2단계 인증(핸드폰) 클릭하여 사용 처리

  1. 2단계 인증 사용 확인 후 앱비밀번호 설정

  1. 앱 비밀번호를 생성할 앱 및 기기 선택 후 생성

  1. 생성된 앱 비밀번호 복사하여 작성 후 확인 클릭


📘 환경설정파일

  • SMPT 서비스를 제공하는 메일 서버의 정보를 JavaMailSenderImpl 객체 필드에 저장되도록 값 주입

    • <bean class="org.springframework.mail.javamail.JavaMailSenderImpl" id="javaMailSender">
    • host 필드 : SMTP 서비스를 제공하는 메일 서버의 이름을 전달하여 저장
    • port 필드 : SMTP 서비스를 제공하는 메일 서버의 PORT 번호를 전달하여 저장
    • username 필드 : SMTP 서비스를 제공하는 메일 서버의 접속 사용자 이름(아이디)을 전달하여 저장
    • password 필드 : SMTP 서비스를 제공하는 메일 서버의 접속 사용자 비밀번호를 전달하여 저장 (사용자 비밀번호 대신 앱 비밀번호를 필드에 저장)
      • 구글의 SMTP 서비스를 제공받기 위해서는 계정의 2단계 보안 인증 후 앱 비밀번호를 발급받아 사용 (구글 계정 관리에서 보안 메뉴에 앱 비밀번호를 생성하여 사용)
    • javaMailProperties 필드 : SMTP 서비스를 제공하는 메일 서버의 메일 전송 관련 부가적인 정보를 Properties 객체로 전달하여 저장


<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- JavaMailSender 인터페이스를 상속받은 JavaMailSenderImpl 클래스를 Spring Bean으로 등록 -->
	<!-- => SMPT 서비스를 제공하는 메일 서버의 정보를 JavaMailSenderImpl 객체 필드에 저장되도록 값 주입 -->
	<bean class="org.springframework.mail.javamail.JavaMailSenderImpl" id="javaMailSender">
		<!-- host 필드 : SMTP 서비스를 제공하는 메일 서버의 이름을 전달하여 저장 -->
		<property name="host" value="smtp.gmail.com"/>
		<!-- port 필드 : SMTP 서비스를 제공하는 메일 서버의 POST 번호를 전달하여 저장 -->
		<property name="port" value="587"/>
		<!-- username 필드 : SMTP 서비스를 제공하는 메일 서버의 접속 사용자 이름(아이디)을 전달하여 저장 -->
		<property name="username" value="userid"/>
		<!-- password 필드 : SMTP 서비스를 제공하는 메일 서버의 접속 사용자 비밀번호를 전달하여 저장 -->
		<!-- => 사용자 비밀번호 대신 앱 비밀번호를 필드에 저장 -->
		<!-- 구글의 SMTP 서비스를 제공받기 위해서는 계정의 2단계 보안 인증 후 앱 비밀번호를 발급받아 사용 -->
		<!-- => 구글 계정 관리에서 보안 메뉴에 앱 비밀번호를 생성하여 사용 -->
		<property name="password" value="bzrpsglztzzylvrh"/>
		<!-- javaMailProperties 필드 : SMTP 서비스를 제공하는 메일 서버의 메일 전송 관련 부가적인 
		정보를 Properties 객체로 전달하여 저장 -->
		<property name="javaMailProperties">
			<props>
				<prop key="mail.smtp.ssl.trust">smtp.gmail.com</prop><!-- -->
				<prop key="mail.smtp.starttls.enable">true</prop>
				<prop key="mail.smtp.auth">true</prop>
              	<!--인증서(ssl)를 통해 발급받아 실뢰할 수 있도록 암호화(starttls) 처리 -->
			</props>
		</property>
	</bean>

	<!-- 핵심관심모듈로 작성된 클래스를 Spring Bean으로 등록 -->
	<!-- => javaMailSender 필드에 JavaMailSender 객체가 저장되도록 의존성 주입 -->
	<bean class="xyz.itwill07.aop.EmailSendBean" id="emailSendBean">
		<property name="javaMailSender" ref="javaMailSender"/>
	</bean>
	
	<!-- 횡단관심모듈로 작성된 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.EmailSendAdvice" id="emailSendAdvice"/>
	
	<aop:config>
		<aop:aspect ref="emailSendAdvice">
			<aop:before method="accessLog" pointcut="execution(* sendEmail(..))"/>
			<aop:after-returning method="successLog" pointcut="execution(* sendEmail(..))" returning="email"/>
			<aop:after-throwing method="errorLog" pointcut="execution(* sendEmail(..))" throwing="exception"/>
		</aop:aspect>
	</aop:config>	
</beans>





🐣 앱 실행


package xyz.itwill07.aop;

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

public class EmailSendApp  {
	public static void main(String[] args) throws Exception {
		ApplicationContext context=new ClassPathXmlApplicationContext("07-4_email.xml");
		EmailSendBean bean=context.getBean("emailSendBean", EmailSendBean.class);
		System.out.println("================================================================");
		bean.sendEmail("kjk2875@google.com", "메일 전송 테스트", "javaMail 기능을 사용하여 전달된 이메일입니다.");
		System.out.println("================================================================");
		((ClassPathXmlApplicationContext)context).close();	
	}
}





🌼 Annotation 기반 AOP 설정

  • Annotation 기반의 AOP 설정을 위해서 Spring이 제공하는 Annotation

📒 핵심관심모듈 (Annotation)


package xyz.itwill07.aop;

import org.springframework.stereotype.Component;

@Component
public class AopAnnotationBean {
	public void display1() {
		System.out.println("### AopAnnotationBean 클래스의 display1() 메소드 호출 ###");
	}
	
	public void display2() {
		System.out.println("### AopAnnotationBean 클래스의 display2() 메소드 호출 ###");
	}
	
	public void display3() {
		System.out.println("### AopAnnotationBean 클래스의 display3() 메소드 호출 ###");
		throw new RuntimeException();//인위적 예외 발생
	}
}






📗 Advice클래스 (annotation)

  • @Aspect: 핵심관심코드에 횡단관심코드를 삽입하여 실행하기 위한 기능을 제공하는 어노테이션
    • Spring Bean Configuration File의 aspect 엘리먼트와 유사한 기능 제공
  • @Pointcut : 타겟메소드를 지정하기 위한 어노테이션
    • 메소드 호출을 이용하여 타겟메소드를 지정한 PointCut 표현식을 제공하기 위해 사용
    • Spring Bean Configuration File의 pointcut 엘리먼트와 유사한 기능 제공
    • value 속성 : 타겟메소드를 지정하기 위한 PointCut 표현식을 속성값으로 설정 (다른 속성이 없는 경우 속성값만 설정 가능)
  • @Before : 핵심관심코드 실행 전에 횡단관심코드를 실행하는 기능을 제공하는 어노테이션
    • Spring Bean Configuration File의 before 엘리먼트와 유사한 기능 제공 (다른 속성이 없는 경우 속성값만 설정 가능)
    • @Before(value = "within(xyz.itwill07.aop.AopAnnotationBean)")
    • value 속성값으로 @Pointcut 어노테이션을 사용한 메소드를 호출하면 등록된 PointCut 표현식을 제공받아 사용 가능
    • @Before("aopPointCut()")
  • @After : 핵심관심코드 실행 후 무조건 횡단관심코드를 실행하는 기능을 제공하는 어노테이션
  • @AfterReturning : 핵심관심코드가 정상적으로 실행된 후 횡단관심코드를 실행하는 기능을 제공하는 어노테이션
    • returning 속성 : 타겟메소드의 반환값을 저장하기 위한 매개변수의 이름을 속성값으로 설정
    • @AfterReturning(value="aopPointCut()",returning = "object")
  • @AfterThrowing : 핵심관심코드 실행시 예외가 발생된 경우 횡단관심코드를 실행하는 기능을 제공하는 어노테이션
    • throwing 속성 : 타겟메소드에서 발생된 예외(Exception 객체)를 저장하기 위한 매개변수의 이름을 속성값으로 설정
    • throwing 속성 : 타겟메소드에서 발생된 예외(Exception 객체)를 저장하기 위한 매개변수의 이름을 속성값으로 설정
  • @Around : 핵심관심코드 실행 전과 후에 횡단관심코드를 실행하는 기능을 제공하는 어노테이션


package xyz.itwill07.aop;

@Component
//@Aspect: 핵심관심코드에 횡단관심코드를 삽입하여 실행하기 위한 기능을 제공하는 어노테이션
// => Spring Bean Configuration File의 aspect 엘리먼트와 유사한 기능 제공
@Aspect
public class AopAnnotationAdvice {
	private static final Logger logger=LoggerFactory.getLogger(AopAnnotationAdvice.class);
	
	//@Pointcut : 타겟메소드를 지정하기 위한 어노테이션
	// => 메소드 호출을 이용하여 타겟메소드를 지정한 PointCut 표현식을 제공하기 위해 사용
	// => Spring Bean Configuration File의 pointcut 엘리먼트와 유사한 기능 제공
	//value 속성 : 타겟메소드를 지정하기 위한 PointCut 표현식을 속성값으로 설정
	// => 다른 속성이 없는 경우 속성값만 설정 가능
	@Pointcut("within(xyz.itwill07.aop.AopAnnotationBean)")
	public void aopPointCut() {}
	
	//Before Advice 메소드
	//@Before : 핵심관심코드 실행 전에 횡단관심코드를 실행하는 기능을 제공하는 어노테이션
	// => Spring Bean Configuration File의 before 엘리먼트와 유사한 기능 제공
	// => 다른 속성이 없는 경우 속성값만 설정 가능
	//@Before(value = "within(xyz.itwill07.aop.AopAnnotationBean)")
	//value 속성값으로 @Pointcut 어노테이션을 사용한 메소드를 호출하면 등록된 PointCut 표현식을 제공받아 사용 가능
	@Before("aopPointCut()")
	public void beforeLog() {
		logger.info("[before]핵심관심코드 실행 전 삽입되어 실행될 횡단관심코드");
	}
	
	//After Advice 메소드
	//@After : 핵심관심코드 실행 후 무조건 횡단관심코드를 실행하는 기능을 제공하는 어노테이션
	// => Spring Bean Configuration File의 after 엘리먼트와 유사한 기능 제공
	//@After("within(xyz.itwill07.aop.AopAnnotationBean)")
	@After("aopPointCut()")
	public void afterLog() {
		logger.info("[after]핵심관심코드 실행 후 무조건 삽입되어 실행될 횡단관심코드");
	}
	
	//After Returning Advice 메소드
	//@AfterReturning : 핵심관심코드가 정상적으로 실행된 후 횡단관심코드를 실행하는 기능을 제공하는 어노테이션
	// => Spring Bean Configuration File의 after-returning 엘리먼트와 유사한 기능 제공
	//returning 속성 : 타겟메소드의 반환값을 저장하기 위한 매개변수의 이름을 속성값으로 설정
	@AfterReturning(value="aopPointCut()",returning = "object")
	public void afterReturningLog(Object object) {
		logger.info("[after-returning]핵심관심코드가 정상적으로 실행된 후 삽입되어 실행될 횡단관심코드");
	}

	//After Throwing Advice 메소드
	//@AfterThrowing : 핵심관심코드 실행시 예외가 발생된 경우 횡단관심코드를 실행하는 기능을 제공하는 어노테이션
	// => Spring Bean Configuration File의 after-throwing 엘리먼트와 유사한 기능 제공
	//throwing 속성 : 타겟메소드에서 발생된 예외(Exception 객체)를 저장하기 위한 매개변수의 이름을 속성값으로 설정
	@AfterThrowing(value="aopPointCut()", throwing = "exception")
	public void afterThrowingLog(Exception exception) {
		logger.info("[after-throwing]핵심관심코드 실행시 예외가 발생되면 삽입되어 실행될 횡단관심코드");
	}
	
	//Around Advice 메소드
	//@Around : 핵심관심코드 실행 전과 후에 횡단관심코드를 실행하는 기능을 제공하는 어노테이션
	// => Spring Bean Configuration File의 around 엘리먼트와 유사한 기능 제공
	@Around("aopPointCut()")
	public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
		logger.info("[around]핵심관심코드 실행 전에 삽입되어 실행될 횡단관심코드");
		Object object=joinPoint.proceed();
		logger.info("[around]핵심관심코드 실행 후에 삽입되어 실행될 횡단관심코드");
		return object;
	}
}





📘 환경설정파일 (annotation)

  • aspectj-autoproxy : Spring Annotation를 이용하여 AOP 기능을 제공하기 위한 엘리먼트

    • AOP 관련 Annotation을 사용하여 핵심관심코드에 횡단관심코드를 삽입하여 실행되도록 설정
    • <aop:aspectj-autoproxy/>

<?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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="xyz.itwill07.aop"/>
	
	<!-- aspectj-autoproxy : Spring Annotation를 이용하여 AOP 기능을 제공하기 위한 엘리먼트 -->
	<!-- => AOP 관련 Annotation을 사용하여 핵심관심코드에 횡단관심코드를 삽입하여 실행되도록 설정 -->
	<aop:aspectj-autoproxy/>
</beans>





🐣 앱 실행


package xyz.itwill07.aop;

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

public class AopAnnotationApp {
	public static void main(String[] args) {
		ApplicationContext context=new ClassPathXmlApplicationContext("07-5_aopAnnotaion.xml");
		AopAnnotationBean bean=context.getBean("aopAnnotationBean", AopAnnotationBean.class);
		System.out.println("================================================================");
		bean.display1();
		System.out.println("================================================================");
		bean.display2();
		System.out.println("================================================================");
		bean.display3();
		System.out.println("================================================================");
		((ClassPathXmlApplicationContext)context).close();	

	}
}





profile
Study Log 📂

0개의 댓글