본문 바로가기

학원/복기

[Spring] AOP 사용 예제 (JavaMail 기능 구현 / StopWatch 객체)

예제1)

 

EmailSendBean 클래스를 이용해 메일 서버의 STMP 서비스를 사용하여 메일을 전송시킬 수 있도록 만들어 볼것이다.

  • 메일 서버(Mail Server)란 메일을 송수신하는 서비스를 제공하는 컴퓨터를 의미한다.
  • SMTP(Simple Messgae Transfer Protocol) 서비스로 메일을 보내고 POP3(Post Office Protocol 3) 서비스나 IMAP(Internet Message Access Protocol) 서비스로 메일을 받아 사용자에게 전달할 수 있다.

 

JavaMail 기능을 구현하기 위해서는 spring-context-support 라이브러리 javax.mail 라이브러리가 프로젝트에 빌드되도록 처리해주어야 한다. 

 

메이븐을 이용해 pom.xml을 수정해주자

...
<!-- 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><!-- spirng 프레임워크와 동일한 버전이어야 함 -->
</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>
...

 

 

클래스에서 사용할 객체 및 메소드 정리 

 

  • JavaMailSender 객체 : SMTP 서비스를 제공하는 서버의 정보를 저장하기 위한 객체
    • JavaMailSender.createMimeMessage() : MimeMessage 객체를 생성하여 반환하는 메소드 
    • JavaMailSender.send(MimeMessage message) : SMTP 서비스를 사용하여 메일을 전송하는 메소드
  • MimeMessage 객체 : 메일 전송 관련 정보를 저장하기 위한 객체 
    • MimeMessage.setSubject(String subjet) : MimeMessage 객체의 메일 제목을 변경하는 메소드
    • MimeMessage.setText(String content) : MimeMessage 객체의 메일 내용(텍스트 메세지)을 변경하는 메소드 
    • MimeMessage.setContent(Object o, Spring type) : MimeMessage 객체의 메일 내용(일반문서)을 변하는 메소드
    • → type 매개변수에 메일로 전달할 문서의 형식(MimeType)을 전달하여 저장 
  • message.setRecipient(RecipientType type, Address address) : MimeMessage 객체의 메일을 받는 사람의 이메일 주소 관련 정보를 변경하는 메소드 
    • → RecipientType : 메일 수신 사용자를 구분하기 위한 상수값을 전달한다.
    • → Address : 이메일 주소가 저장된 Address 객체를 전달한다.
  • message.setRecipient(RecipientType type, Address[] address) :  MimeMessage 객체의 메일을 받는 사람들의 이메일 주소 관련 정보를 변경하는 메소드 - 다수의 사람에게 메일을 전달한다. 

 

  • InternetAddress 객체 : 이메일 주소를 저장하기 위한 객체 

 

EmailSendBean

package xyz.itwill07.aop;

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

import org.springframework.mail.javamail.JavaMailSender;

import lombok.Setter;

//메일 전송 기능을 제공하기 위한 클래스 - 메일 서버의 SMTP 서비스를 사용하여 메일 전송
public class EmailSendBean {
	//JavaMailSender 객체를 저장하기 위한 필드 선언
	// - JavaMailSender 객체 : SMTP 서비스를 제공하는 서버의 정보를 저장하기 위한 객체
	@Setter
	private JavaMailSender javaMailSender;
	
	//메일을 전송하는 메소드
	// => 메일을 받는 사람의 이메일 주소, 제목, 내용을 매개변수로 전달받아 저장
	// => 메일을 받는 사람의 이메일 주소를 반환
	public String sendEmail(String email, String subject, String content) throws Exception {
		//MimeMessage 객체를 생성하여 반환 
		MimeMessage message=javaMailSender.createMimeMessage();
		//MimeMessage 객체의 메일 제목을 변경
		message.setSubject(subject);
		//MimeMessage 객체의 메일 내용을 변경
		//message.setText(content);
		
		//MimeMessage 객체의 메일 내용을 변경 - 메일로 전달할 문서의 형식을 전달하여 저장 
		message.setContent(content, "text/html; charset=utf-8");//HTML 문서로 전달(HTML 태그로 동작되어 전달)
		
		//MimeMessage 객체에서 메일을 받는 사람의 이메일 관련 정보를 변경
		message.setRecipient(RecipientType.TO, new InternetAddress(email));
		
		//SMTP 서비스를 사용하여 메일을 전송
		javaMailSender.send(message);
		
		return email;
	}
}

 

 

* Google의 앱 비밀번호를 제공받는 방법

Google 계정 관리 ?>> 보안 >> 2 단계 보안 인증 >> 앱 비밀번호 >> 비밀번호 생성

 

07-3_email.xml

<?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으로 등록 -->
	<!-- => SMTP 서비스를 제공하는 메일 서버의 정보를 JavaMailSenderImpl 객체 필드에 저장되도록 값 주입  -->
	<bean class="org.springframework.mail.javamail.JavaMailSenderImpl" id="javaMailSender">
		<!-- host 필드 : SMTP 서비스를 제공하는 메일 서버의 이름 저장 -->
		<property name="host" value="smtp.gmail.com"/>
		<!-- port 필드 : SMTP 서비스를 제공하는 메일 서버의 PORT 번호 저장 -->
		<property name="port" value="587"/>
		<!-- username 필드 : SMTP 서비스를 제공하는 메일 서버의 접속 사용자 이름(아이디)을 저장 -->
		<property name="username" value="아이디"/>
		<!-- password 필드 : SMTP 서비스를 제공하는 메일 서버의 접속 사용자 비밀번호를 저장 -->
		<!-- => 사용자 비밀번호 대신 앱 비밀번호를 제공받아 사용하면 된다. -->
		<property name="password" value="앱비밀번호"/>
		
		<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>
	
	
	<!-- 핵심관심모듈의 클래스(EmailSendBean 클래스)를 Spring Bean으로 등록 -->
	<!-- => EmailSendBean 클래스의 javaMailSender 필드에 JavaMailSender 객체(Spring Bean)가 저장되도록 의존성 주입 -->
	<bean class="xyz.itwill07.aop.EmailSendBean" id="emailSendBean">
		<property name="javaMailSender" ref="javaMailSender"/>
	</bean>
</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-3_email.xml");
		EmailSendBean bean=context.getBean("emailSendBean", EmailSendBean.class);
		System.out.println("==========================================================");
		bean.sendEmail("아이디@naver.com", "메일 전송 테스트"
			, "<h1>JavaMail 기능을 사용하여 전달된 이메일입니다.<h1>");
		System.out.println("==========================================================");
		((ClassPathXmlApplicationContext)context).close();			
	}
}

 

 

 

 

메일이 잘 전송됐는지 확인하기 위해 Advice 클래스를 이용할 것이다

 

 

EmailSendAdvice 

package xyz.itwill07.aop;

import org.aspectj.lang.JoinPoint;

import lombok.extern.slf4j.Slf4j;

//횡단관심모듈 - Advice 클래스
@Slf4j
public class EmailSendAdvice {
	//메일을 전송하기 전에 삽입되어 실행될 명령이 작성된 메소드 - Before Advice Method 
	// => 받는 사람의 이메일 주소와 제목을 제공받아 로그로 기록 
	public void acessLog(JoinPoint joinPoint) {
		//타겟메소드의 매개변수에서 받는 사람의 이메일 주소를 제공받아 저장 
		String email=(String)joinPoint.getArgs()[0];
		//타겟메소드의 매개변수에서 메일 제목을 제공받아 저장 
		String subject=(String)joinPoint.getArgs()[1];
		log.info(email+"님에게 <"+subject+"> 제목의 이메일을 전송합니다.");
	}
}

 

 

07-3_email.xml

	<!-- 횡단관심모듈의 클래스(EmailSendAdvice 클래스)를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.EmailSendAdvice" id="emailSendAdvice"/>
	
	<aop:config>
		<aop:pointcut expression="execution(* sendEmail(..))" id="sendEmailPointcut"/>
		<aop:aspect ref="emailSendAdvice">
			<aop:before method="accessLog" pointcut-ref="sendEmailPointcut"/>
			<aop:after-returning method="successLog" pointcut-ref="sendEmailPointcut" returning="email"/>
			<aop:after-throwing method="errorLog" pointcut-ref="sendEmailPointcut" throwing="exception"/>
		</aop:aspect>
	</aop:config>

 

 

log4j.xml

<logger name="xyz.itwill07.aop">
	<level value="info"/>
</logger>

 

 


예제2)

 

메소드를 호출해 원하는 결과가 나올 때 까지 걸리는 시간을 계산할 수 있도록 만들어보자

 

 

 

메소드 및 용어 정리

 

  • System.currentTimeMillis() : 시스템의 현재 날짜와 시간에 대한 타임스탬프를 반환하는 메소드
    • 타임스탬프(TimeStamp) : 날짜와 시간에 대한 연산을 목적으로 날짜와 시간을 정수값으로 반환한 값 

 

ExceptionTimeBean

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 클래스의 one() 메소드 실행 시간 = "+(endTime-startTime)+"ms");
	}
	
}

 

07-4_timer.xml

<?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"/>
</beans>

 

ExcutionTimeApp

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-4_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();		
	}	
}

 

 

 

이번엔 핵심관심코드와 횡단관심코드를 분리해서 작성해보자

 

 

ExecutionTimeBean

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 클래스의 one() 메소드 실행 시간 = "+(endTime-startTime)+"ms");
	}
	
}

 

 

ExecutionTimeAdvice

package xyz.itwill07.aop;

import org.aspectj.lang.ProceedingJoinPoint;

public class ExecutionTimeAdvice {
	//타겟메소드의 명령이 실행되는 처리시간을 계산하여 기록하기 위한 메소드 - Around Advice Method
	public Object timeWatchLog(ProceedingJoinPoint joinPoint) throws Throwable {
		//타겟메소드의 명령 실행 전에 동작될 명령을 작성
		long startTime=System.currentTimeMillis();
		
		//타겟메소드의 명령 실행 - 타겟메소드 호출
		Object returnValue=joinPoint.proceed();
		
		//타겟메소드의 명령 실행 후에 동작될 명령 작성
		long endTime=System.currentTimeMillis();
		
		String className=joinPoint.getTarget().getClass().getSimpleName();
		String methodName=joinPoint.getSignature().getName();
		
		System.out.println(className+"클래스의 "+ methodName +"() 메소드 실행 시간 = "
				+(endTime-startTime)+"ms");
		
		return returnValue;
	}
}

 

07-4_timer.xml

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

 

 

main 메소드 실행

 


 

 

Spring 프레임워크에서 제공해주는 StopWatch 객체를 이용해  Advice 클래스를 작성해보자

StopWatch 객체는 시간을 측정하기 위한 기능을 제공한다.

 

ExecutionTimeAdvice

package xyz.itwill07.aop;

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

public class ExecutionTimeAdvice {
	public Object timeWatchLog(ProceedingJoinPoint joinPoint) throws Throwable {
		//타겟메소드의 명령 실행 전에 동작될 명령을 작성
		StopWatch stopWatch=new StopWatch();//StopWatch 객체 : 시간을 측정하기 위한 기능 제공 
		
		//시간 측정 시작 
		stopWatch.start();
		
		//타겟메소드의 명령 실행 - 타겟메소드 호출
		Object returnValue=joinPoint.proceed();
				
		//시간 측정 종료
		stopWatch.stop();
		
		String className=joinPoint.getTarget().getClass().getSimpleName();
		String methodName=joinPoint.getSignature().getName();
		
		//getTotalTimeMillis() : 시간 측정 결과를 ms 단위로 반환하는 메소드
		System.out.println(className+"클래스의 "+ methodName +"() 메소드 실행 시간 = "
				+stopWatch.getTotalTimeMillis()+"ms");
		
		return returnValue;
	}
}

 

 

메소드마다 중복되는 횡단관심코드를 Advice 클래스로 만들어 필요할 때마다 환경설정을 통해 실행될 수 있도록 만들어주면 중복성을 최소화할 수 있다는 점이 aop를 사용하는 가장 큰 이유이다.