[필기정리] Day109-2 - AOP(관점지향 프로그래밍) 및 예제문제

Web/Spring

2020. 11. 26. 15:01

# 관점지향 프로그래밍(AOP, Aspect-Oriented Programming)

   여러 클래스로 분산된 책임을 관점(Aspect)이라고 하는 별도의 클래스로 캡슐화하는 프로그래밍 기법

   문제를 해결하기 위한 핵심 관심 사항과 전체에 적용되는 공통 모듈 사항을 기준으로 프로그래밍함으로써

   공통 모듈을 여러 코드에 쉽게 적용할 수 있도록 도와주는 역할 

 

   공통 기능을 모든 모듈에 적용하기 위해 상속을 활용하는 방법도 있으나,

   다중 상속 불가 및 기능 구현부분에 핵심 기능과 공통 기능 코드가 섞여 효율성이 떨어진다는 단점이 있어

   이러한 한계를 극복하고자 AOP가 등장하게 되었다.

 

   개발자가 작성한 코드와 분리된 관점을 구현한 코드를 컴파일 혹은 실행시점에 결합시키기 때문에

   전체 코드 기반에 흩어져 있는 관심 사항이 하나의 장소로 응집된다는 점과 

   여타 서비스 모듈이 자신의 주요 관심 사항에 대한 코드만 포함하고

   그 외 관심 사항은 모두 관점으로 옮겨지므로

   코드가 깔끔해져 유지보수 및 관리가 편하다는 두 가지의 장점이 있다.

    

 - AOP 용어

명칭 설명
Joinpoint Advice의 적용 지점, 메소드 호출, 필드값 변경 등이 해당됨
애플리케이션 실행 흐름 내 모든 지점 의미
Pointcut Aspect가 '어디서' 실행할 지 정의하는 것
Advice의 대상이 될 Joinpoint 정의한 것(실제로 Advice가 적용되는 Joinpoint)
정규 표현식이나 AspectJ의 문법을 이용해 Pointcut을 재정의 할 수 있음
Advice Aspect가 '무엇'을 '언제' 실행할 지 정의하는 것
애플리케이션의 여러 객체에 적용해야 할 공통 동작 정의한 것
Weaving Advice(공통코드)를 핵심로직코드에 적용하는 것
Aspect Pointcut과 Advice의 결합
두 가지 정보가 합쳐지면 Aspect에 필요한 모든 정보가 정의됨(언제, 어디서, 무엇을)
ex) 트랜잭션, 보안 등

+ Weaving 방식

방식 설명
컴파일 시
(compile time)
타깃 클래스가 컴파일 될 때 Aspect가 Weaving되며,
AspectJ의 Weaving compiler 필요
클래스 로드 시
(classload time)
클래스가 JVM에 로드될 때 Aspect가 Weaving됨
AspectJ5의 LoadTime Weaving 기능 사용 시 클래스로드 시간에 Weaving됨
실행시간
(runtime)
애플리케이션 실행 중 Weaving
소스코드나 클래스 정보 자체를 변경하지 않고 Proxy 객체를 생성하여 AOP 적용됨
이 때 Proxy는 핵심 로직을 실행하기 전 또는 후에 공통 모듈 기능을 적용하는 방식으로 AOP 구현

// Tip : AspectJ - AOP 프레임워크

 

+ Advice 종류

종류 설명
이전
(before)
타겟 메소드가 호출되기 전에 어디바이스 기능 수행
이후
(after)
타겟 메소드의 결과에 상관없이 타겟 메소드가 완료된 후 어드바이스 기능 수행
반환 이후
(after-returning)
타겟 메소드가 성공적으로 완료된 후에 어드바이스 기능 수행
예외 발생 이후
(after-throwing)
타겟 메소드가 예외를 던진 후에 어드바이스 기능 수행
메소드 실행 전후
(around)
어드바이스가 타겟 메소드를 감싸서
타겟 메소드 호출 전과 후에 어드바이스 기능 수행

 

// Tip - 메소드 시그니처(Method Signature)

          메소드의 이름과 파라미터 의미

          파라미터의 갯수와 타입이 다른 경우 컴파일러가 서로 다른 메소드로 구분함  

          단, return타입은 메소드 시그니처에 포함되지 않기 때문에

          서로 다른 리턴 타입을 가져도 동일 시그니처의 2개 메소드는 선언할 수 없음

 

Q1. 다음의 요소를 가지는 Student 클래스를 만든다.

private String name; 
private int age; 
private int gradeNum; 
private int classNum;


위에 멤버변수에 대한 각각의 getter, setter을 만든다.
이름, 나이, 학년, 반을 출력하는 getStudentInfo 메소드를 만든다.

다음의 요소를 가지는 Worker 클래스를 만든다.

private String name; 
private int age; 
private String job;


위에 멤버변수에 대한 각각의 getter, setter을 만든다.
이름, 나이, 직업을 출력하는 getWorkerInfo 메소드를 만든다.

LogAop클래스를 만든다.
loggerAop메소드를 만들고 메소드가 시작할때 메소드의 시그니처를 출력한다.
그리고 시작한 시간을 저장한다.
끝나는 시간을 저장한다.
그리고 메소드의 시그니처를 출력한다
그리고 시작시간과 끝시간의 경과시간을 출력한다.

Spring bean Configuration 파일에서 Student와 Work 빈을 생성한다.
student는 세터 인젝션으로 이름 홍길동, 나이 10, 학년 3, 번호 5로 초기화 한다.

worker는 세터 인젝션으로 이름 홍길순, 나이 35, 직업 개발자로 초기화 한다.

그리고 위에 Student, Worker 클래스가 속한 모든 메소드에 around 속성으로 loggerAop 메소드가 실행되게 한다.

- pom.xml 삽입 필요 내용

<!-- AOP -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.7.4</version>
		</dependency>

A.

- Student.java

package com.tistory.coderbear;

public class Student {
	private String name;
	private int age;
	private int gradeNum;
	private int classNum;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public int getGradeNum() {
		return gradeNum;
	}
	public void setGradeNum(int gradeNum) {
		this.gradeNum = gradeNum;
	}
	public int getClassNum() {
		return classNum;
	}
	public void setClassNum(int classNum) {
		this.classNum = classNum;
	}
	
	public void getStudentInfo() {
		System.out.println("이름 : " + getName());
		System.out.println("나이 : " + getAge());
		System.out.println("학년 : " + getGradeNum());
		System.out.println("반 : " + getClassNum());
	}
}

- Worker.java

package com.tistory.coderbear;

public class Worker {
	private String name;
	private int age;
	private String job;
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getJob() {
		return job;
	}

	public void setJob(String job) {
		this.job = job;
	}

	public void getWorkerInfo() {
		System.out.println("이름 : " + getName());
		System.out.println("나이 : " + getAge());
		System.out.println("직업 : " + getJob());
	}
}

- LogAop.java

package com.tistory.coderbear;

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

public class LogAop {
	public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable {
//		String signatureStr = joinpoint.getSignature().toShortString();
		String signatureStr = joinpoint.getSignature().toString();
		System.out.println( signatureStr + " 시작");
		long st = System.currentTimeMillis();
		
		try {
			Object obj = joinpoint.proceed(); // 해당하는 메소드
			return obj; // 반환값 obj 형태로 받음
		} finally {
			long et = System.currentTimeMillis();
			System.out.println( signatureStr + " 종료");
			System.out.println( signatureStr + " 경과시간 : " + (et - st));
		}
	}
	public void beforeAdvice(JoinPoint joinPoint) {
		System.out.println("beforeAdvice()");
	}
	
	public void afterReturningAdvice() {
		System.out.println("afterReturningAdvice()");
	}
	
	public void afterThrowingAdvice() {
		System.out.println("afterThrowingAdvice()");
	}
	
	public void afterAdvice() {
		System.out.println("afterAdvice()");
	}
}

- MainClass.java

package com.tistory.coderbear;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class MainClass {

	public static void main(String[] args) {
		AbstractApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
		
		Student student = (Student)ctx.getBean("student");
		student.getStudentInfo();
		
		Worker worker = ctx.getBean("worker", Worker.class);
		worker.getWorkerInfo();
		
		ctx.close();
	}
}

- applicationContext.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-3.2.xsd">

	<bean id="logAop" class="com.tistory.coderbear.LogAop" />
	
	<aop:config>
	
		<aop:aspect id="logger" ref="logAop">
			<aop:pointcut id="publicAround" expression="within(com.tistory.coderbear.*)"  />
			<aop:around pointcut-ref="publicAround" method="loggerAop" />
		</aop:aspect>
		
		<aop:aspect id="logger2" ref="logAop">
			<aop:pointcut id="publicBefore" expression="within(com.tistory.coderbear.*)"  />
			<aop:before pointcut-ref="publicBefore" method="beforeAdvice" />
		</aop:aspect>
		
		<aop:aspect id="logger3" ref="logAop">
			<aop:pointcut id="publicAfterReturning" expression="within(com.tistory.coderbear.*)"  />
			<aop:after-returning pointcut-ref="publicAfterReturning" method="afterReturningAdvice" />
		</aop:aspect>
		
		<aop:aspect id="logger4" ref="logAop">
			<aop:pointcut id="publicAfterThrowing" expression="within(com.tistory.coderbear.*)"  />
			<aop:after-throwing pointcut-ref="publicAfterThrowing" method="afterThrowingAdvice" />
		</aop:aspect>
		
		<aop:aspect id="logger5" ref="logAop">
			<aop:pointcut id="publicAfter" expression="within(com.tistory.coderbear.*)"  />
			<aop:after pointcut-ref="publicAfter" method="afterAdvice" />
		</aop:aspect>
		
	</aop:config>
	
	<bean id="student" class="com.tistory.coderbear.Student" >
		<property name="name" value="홍길동" />
		<property name="age" value="10" />
		<property name="gradeNum" value="3" />
		<property name="classNum" value="5" />
	</bean>
	
	<bean id="worker" class="com.tistory.coderbear.Worker" >
		<property name="name" value="홍길순" />
		<property name="age" value="35" />
		<property name="job" value="개발자" />
	</bean>
</beans>

 

Q2. 문제1에 더불어 aop 나머지 기능들도 구현해 본다.

A.

- Student.java

package com.tistory.coderbear;

public class Student {
	private String name;
	private int age;
	private int gradeNum;
	private int classNum;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public int getGradeNum() {
		return gradeNum;
	}
	public void setGradeNum(int gradeNum) {
		this.gradeNum = gradeNum;
	}
	public int getClassNum() {
		return classNum;
	}
	public void setClassNum(int classNum) {
		this.classNum = classNum;
	}
	
	public void getStudentInfo() {
		System.out.println("이름 : " + getName());
		System.out.println("나이 : " + getAge());
		System.out.println("학년 : " + getGradeNum());
		System.out.println("반 : " + getClassNum());
	}
}

- Worker.java

package com.tistory.coderbear;

public class Worker {
	private String name;
	private int age;
	private String job;
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getJob() {
		return job;
	}

	public void setJob(String job) {
		this.job = job;
	}

	public void getWorkerInfo() {
		System.out.println("이름 : " + getName());
		System.out.println("나이 : " + getAge());
		System.out.println("직업 : " + getJob());
	}
}

- LogAop.java

package com.tistory.coderbear;

import org.aspectj.lang.ProceedingJoinPoint;

public class LogAop {
	
	public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable {
		String signatureStr = joinpoint.getSignature().toShortString(); // toLongString 형태도 있음(패키지 이름까지 모두 출력)
		System.out.println(signatureStr + " is start."); // 메소드의 이름과 파라미터
		long st = System.currentTimeMillis(); // 현재 시간 잼
		
		try {
			Object obj = joinpoint.proceed(); // = student.getStudentInfo(); or worker.getWorkerInfo();
			return obj;
		} finally {
			long et = System.currentTimeMillis(); // 끝난 시간 잼
			System.out.println(signatureStr + " is finished.");
			System.out.println(signatureStr + " 경과시간 : " + (et - st));
		}
	}
}

- MainClass.java

package com.tistory.coderbear;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class MainClass {

	public static void main(String[] args) {
		
		AbstractApplicationContext factory = new GenericXmlApplicationContext("configLocation.xml");
		Student student = (Student)factory.getBean("student");
		student.getStudentInfo();
		
		Worker worker = (Worker)factory.getBean("worker");
		worker.getWorkerInfo();
		
		factory.close();		
	}
}

- log4j.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<!-- Appenders -->
	<appender name="console" class="org.apache.log4j.ConsoleAppender">
		<param name="Target" value="System.out" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p: %c - %m%n" />
		</layout>
	</appender>
	
	<!-- Application Loggers -->
	<logger name="com.tistory.coderbear">
		<level value="info" />
	</logger>
	
	<!-- 3rdparty Loggers -->
	<logger name="org.springframework.core">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.beans">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.context">
		<level value="info" />
	</logger>

	<logger name="org.springframework.web">
		<level value="info" />
	</logger>

	<!-- Root Logger -->
	<root>
		<priority value="warn" />
		<appender-ref ref="console" />
	</root>
	
</log4j:configuration>

- configLocation.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-3.1.xsd">

	<bean id="logAop" class="com.tistory.coderbear.LogAop" /> // 공통적용 메소드
	<aop:config>
		<aop:aspect id="logger" ref="logAop">
			<aop:pointcut expression="within(com.tistory.coderbear.*)" // 적용 대상
				id="publicM" /> 
			<aop:around method="loggerAop" pointcut-ref="publicM" /> // 공통 기능 실행시킬 대상을 참조
		</aop:aspect>
	</aop:config>

	<bean id="student" class="com.tistory.coderbear.Student">
		<property name="name" value="홍길동" />
		<property name="age" value="10" />
		<property name="gradeNum" value="3" />
		<property name="classNum" value="5" />
	</bean>
	<bean id="worker" class="com.tistory.coderbear.Worker">
		<property name="name" value="홍길순" />
		<property name="age" value="35" />
		<property name="job" value="개발자" />
	</bean>

</beans>

 

 

728x90