본문 바로가기
일상/일기장

TIL22.03.13 - Spring : AOP

by jmaster 2022. 3. 13.

핵심관심모듈이 선언된 클래스 생성

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));
	}
}

실행파일 작성

public class JoinPointApp {
	public static void main(String[] args) {
		ApplicationContext context=new ClassPathXmlApplicationContext("07-2_param.xml");
		System.out.println("===============================================================");
		JoinPointBean bean=context.getBean("joinPointBean", JoinPointBean.class);
		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();		
	}
}

횡단관심모듈이 선언된 클래스 생성

  • Around Advice 메소드를 제외한 나머지 Advice 메소드는 반환형을 void로 작성하며
  • 매개변수를 작성하지 않거나 JoinPoint 인터페이스를 자료형으로 하는 매개변수 선언 가능
    • 횡단관심모듈의 매개변수를 비정상적으로 선언한 경우 IllegalArgumentException 발생
  • JoinPoint : 타겟메소드(핵심관심모듈)의 정보를 저장하기 위한 객체
    • => Spring Container가 Advice 메소드를 호출할 때 타겟메소드의 정보(JoinPoint 객체)를 매개변수에 자동 저장
    • Advice 메소드(횡단관심모듈)에서 타겟메소드의 정보가 필요한 경우 JoinPoint 매개변수 작성
  • 횡단관심 반환형이나 매개변수를 비정상적으로 선언한 경우 IllegalArgument 발생
  • JoinPoint.getTarget() : 타겟메소드를 호출하는 Spring Bean 객체를 반환하는 메소드
    • => 타겟메소드가 선언된 클래스의 객체를 Object 타입으로 반환
    • Object.getClass() : 객체에 대한 클래스 정보(Clazz)를 반환하는 메소드
    • Class.getName() : Class 객체(Clazz)의 클래스명(패키지 포함)을 반환하는 메소드
System.out.println(joinPoint.getTarget().getClass().getName());
  • Class.getSimpleName() : Class 객체(Clazz)의 클래스명(패키지 미포함)을 반환하는 메소드
[System.out.println](notion://system.out.println/)(joinPoint.getTarget().getClass().getSimpleName());
  • JoinPoint.getSignature() : Signature 객체(타겟메소드 정보)를 반환하는 메소
  • Signature.getName() : 타겟메소드의 이름을 반환하는 메소드
[System.out.println](notion://system.out.println/)(joinPoint.getSignature().getName());
  • JoinPoint.getArgs() : 타겟메소드의 매개변수에 저장된 모든 값(객체)을 Object 배열로 반환하는 메소드
Object[] objects=joinPoint.getArgs();
  • After Returning Advice 메소드에는 JoinPoint 매개변수외에 Object 매개변수 작성 가능
    • Object 매개변수에는 타겟메소드의 반환값을 전달받아 저장
    • 타겟메소드 반환값의 자료형이 고정되어 있는 경우 Object 타입 대신 반환값의 자료형으로 매개변수 작성
  • Bean Configuration File의 AOP 설정에서 after-returning 엘리먼트의 returning 속성에 매개변수명을속성값으로 반드시 설정해야만 타겟메소드의 반환값을 매개변수로 전달받아 저장 가능
public void displayName(Object object) {//After Returning Advice		
		if(object instanceof String) {//매개변수가 String 클래스로 형변환될 수 있는 경우
			String name=(String)object;
			System.out.println("[after-returning]"+name+"님, 환영합니다.");
		}
	}
  • After Throwing Advice 메소드에는 JoinPoint 매개변수외에 Exception 매개변수 작성 가능
    • Exception 매개변수에는 타겟메소드에서 발생된 예외(Exception 객체)를 전달받아 저장
  • Bean Configuration File의 AOP 설정에서 after-throwing 엘리먼트의 throwing 속성에 매개변수명
  • 속성값으로 반드시 설정해야만 타겟메소드의 예외를 매개변수로 전달받아 저장 가능
	public void displayException(Exception exception) {//After Throwing Advice
		
		System.out.println("[after-throwing]타겟메소드에서 발생된 예외 메세지 = "+exception.getMessage());
	}
  • Around Advice 메소드는 반환형을 void 또는 Object 타입으로 작성하고 ProceedingJoinPoint 인터페이스를 자료형으로 작성된 매개변수를 반드시 선언
    • Around Advice 메소드는 타겟메소드의 반환값을 제공받아 반환하기 위해 Object 타입으로 선언
  • ProceedingJoinPoint : 타겟메소드(핵심관심모듈)의 정보를 저장하기 위한 객체
    • JoinPoint 객체와 다른점은 타겟메소드를 호출할 수 있는 기능 제공
  • ProceedingJoinPoint.proceed() : 타겟메소드를 호출하는 메소드 - 핵심관심모듈 동작
    • 타겟메소드 호출시 반환되는 결과값을 제공받아 저장 가능
    • 프로그램 실행시 발생되는 모든 오류 정보를 저장한 객체(Throwable) 발생 가능 - 예외 처리
	public Object display(ProceedingJoinPoint joinPoint) throws Throwable {//Around Advice
		System.out.println("[around]핵심관심모듈 동작 전에 삽입되어 실행될 횡단관심모듈");

		Object object=joinPoint.proceed();
		
		System.out.println("[around]핵심관심모듈 동작 후에 삽입되어 실행될 횡단관심모듈");
		return object;
	}

핵심관심모듈이 선언된 클래스를 Spring Bean으로 등록

<?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.JoinPointBean" id="joinPointBean"/>
	
	<bean class="xyz.itwill07.aop.JoinPointAdvice" id="joinPointAdvice"/>
	
	<aop:config>
		<aop:aspect ref="joinPointAdvice">
			<aop:before method="displayTarget" pointcut="execution(* *(..))"/>
		</aop:aspect>
	</aop:config>
</beans>

시간초 예제

public class ExecutionTimeAdvice {
	private static final Logger logger=LoggerFactory.getLogger(ExecutionTimeAdvice.class);
	
	public Object timeWatchLog(ProceedingJoinPoint joinPoint) throws Throwable {
		long beginTime=System.currentTimeMillis();
		
		Object object=joinPoint.proceed();		
		long endTime=System.currentTimeMillis();

		String className=joinPoint.getTarget().getClass().getSimpleName();
		String methodName=joinPoint.getSignature().getName();
		logger.info(className+" 클래스의 "+methodName+" 메소드 실행 시간 = "
				+(endTime-beginTime)+"ms");
		
		return object;
	}
}

메일 예제

  • Spring Context 확장 기능을 제공하는 라이브러리
    org.springframework
    spring-context-support
    ${org.springframework-version}


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

메일 전송 기능을 제공하는 클래스

- 라이브러리 다운!

 

  • 메일을 전송하는 JavaMailSender 객체를 저장하는 필드, getter,setter
private JavaMailSender mailSender;

public JavaMailSender getMailSender() {
	return mailSender;
}

public void setMailSender(JavaMailSender mailSender) {
	this.mailSender = mailSender;
}
  • 메일을 전송하는 메소드 - 핵심관심모듈
    • 받는 사람 이메일 주소, 제목, 내용을 매개변수로 전달받아 저장
    • 받는 사람 이메일 주소 반환 - 메세지 출력
  • MimeMessage.setSubject(String subject) : MimeMessage 객체의 메일 제목을 변경하는 메소드-
  • MimeMessage.setText(String content) : MimeMessage 객체의 메일 내용(텍스트)을 변경하는 메소드
  • MimeMessage.setRecipients(RecipientType type, InternetAddress email) : MimeMessage 객체의 메일을 수신하는 사용자에 대한 정보를 변경하는 메소드
    • RecipientType : 메일 수신 사용자 대상을 표현하기 위한 상수
    • InternetAddress : 수신 사용자의 이메일 주소를 저장한 객체 - InternetAddress 대신 String 사용 가능
  • InternetAddress.parse(String mail) : 문자열을 전달받아 InternetAddress 객체로 변환하여 반환하는 메소드
  • JavaMailSender.send(MimeMessage message) : SMTP 서비스를 사용하여 메일을 전송하는 메소드
	public String sendEmail(String email, String subject, String content) {

		MimeMessage message=mailSender.createMimeMessage();
		
		try {
			message.setSubject(subject);
			message.setText(content);
			
			message.setRecipients(MimeMessage.RecipientType.TO, InternetAddress.parse(email));
			
			mailSender.send(message);
		} catch (MessagingException e) {
			e.printStackTrace();
		}
		return email;
	}
}
  • 메일 전송기능을 제공하는 JavaMailSenderImpl 클래스를 Spring Bean으로 등록
    • SMTP 서비스를 제공하는 메일 서버의 정보(값)를 JavaMailSenderImpl 객체의 필드에 전달하여 저장(Value Injection)
	<bean class="org.springframework.mail.javamail.JavaMailSenderImpl" id="javaMailSender">
		<property name="host" value="smtp.gmail.com"/>
		<property name="port" value="587"/>
		<property name="username" value="abc123"/>
		<property name="password" value="123456"/>
		<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>
			</props>
		</property>
	</bean>
  • 핵심관심모듈이 선언된 클래스를 Spring Bean으로 등록
    • mailSender 필드에 JavaMailSenderImpl 클래스의 Spring Bean으로 의존관계 설정 - DI(Dependency Injection)
<bean class="xyz.itwill07.aop.EmailSendBean" id="emailSendBean">
		<property name="mailSender" ref="javaMailSender"/>
	</bean>
	
	<bean class="xyz.itwill07.aop.EmailSendAdvice" id="emailSendAdvice"/>
	
	<aop:config>
		<aop:pointcut expression="execution(* sendEmail(..))" id="emailSendPointcut"/>
	
		<aop:aspect ref="emailSendAdvice">
			<aop:before method="beforeMessage" pointcut-ref="emailSendPointcut"/>
		</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) {
		ApplicationContext context=new ClassPathXmlApplicationContext("07-4_email.xml");
		System.out.println("===============================================================");
		EmailSendBean bean =context.getBean("emailSendBean", EmailSendBean.class);
		bean.sendEmail("jmaster1020@gmail.com", "눈누랄랄ㅎㅎ"
				, "JavaMail 기능을 이용한 이메일 전송 테스트입니다.");
		System.out.println("===============================================================");
		((ClassPathXmlApplicationContext)context).close();				
	}
}

메일 전송 관련 메세지 기능을 제공하는 클래스 - Advice

  • 메일 전송 전 실행될 명령을 작성한 메소드 - Before Advice
    • 타겟메소드(EmailSendBean 클래스의 sendEmail 메소드)의 매개변수에 저장된 값을 메세지 출력 기능으로 제공받아 사용하기 위해 JoinPoint 매개변수선언
	public void beforeMessage(JoinPoint joinPoint) {
		String email=(String)joinPoint.getArgs()[0];	
		String subject=(String)joinPoint.getArgs()[1];
		System.out.println("[메세지]<"+email+">님에게 <"+subject+"> 제목의 이메일을 전송합니다.");
	}
  • 메일 전송이 성공한 경우 실행될 명령을 작성한 메소드 - After Returning Advice
    • 타겟메소드(EmailSendBean 클래스의 sendEmail 메소드)의 반환값을 제공받아 메세지 출력 기능으로 사용하기 위해 매개변수 선언
	public void successMessage(String email) {
		System.out.println("[메세지]<"+email+">님에게 이메일을 성공적으로 전송 하였습니다.");
	}

  • 메일 전송이 실패한 경우 실행될 명령을 작성한 메소드 - After Throwing Advice
    • 타겟메소드(EmailSendBean 클래스의 sendEmail 메소드)에서 발생된 예외정보를 제공받아 메세지 출력 기능으로 사용하기 위해 매개변수 선언
public void failMessage(Exception exception) {
	System.out.println("[에러]메일 전송 실패 = "+exception.getMessage());
}

	<bean class="org.springframework.mail.javamail.JavaMailSenderImpl" id="javaMailSender">
		<property name="host" value="smtp.gmail.com"/>
		<property name="port" value="587"/>
		<property name="username" value="abc123"/>
		<property name="password" value="123456"/>
		<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>
			</props>
		</property>
	</bean>
	
	<bean class="xyz.itwill07.aop.EmailSendBean" id="emailSendBean">
		<property name="mailSender" ref="javaMailSender"/>
	</bean>
	
	<bean class="xyz.itwill07.aop.EmailSendAdvice" id="emailSendAdvice"/>
	
	<aop:config>
		<aop:pointcut expression="execution(* sendEmail(..))" id="emailSendPointcut"/>
	
		<aop:aspect ref="emailSendAdvice">
			<aop:before method="beforeMessage" pointcut-ref="emailSendPointcut"/>
			<aop:after-returning method="successMessage" pointcut-ref="emailSendPointcut" returning="email"/>
			<aop:after-throwing method="failMessage" pointcut-ref="emailSendPointcut" throwing="exception"/>
		</aop:aspect>
	</aop:config>
</beans>

댓글