AOP Aspect Oriented Programming
(=관점지향 프로그램)
*횡단 관심사의 분리를 허용함으로써 모듈성을 증가시키는 것이 목적인 프로그램잉으로 코드를 수정하지 않는 대신 기존의 코드에 추가 동작(advice)을 추가함으로써, 어느 코드가 포인트컷(Pointcut) 사양을 통해 수정되는지를 따로 지정하여 비즈니스 로직에 핵심적이지 않은 동작들을 프로그램에 추가할 수 있게 한다(Logging, Security, Transaction)
MVC 웹 어플리케이션은
Web Layer - Business Layer - Data Layer 로 정의
Web : REST API 를 제공하며, Client 중심의 로직 적용
Business : 내부 정책에 따른 Logic를 개발하며, 주로 해당 부분을 개발
Data : 데이터 베이스 및 REST클라이언트를 사용하여 외부와 서버와 연동을 처리할 때
로 구성
* 횡단 관심
객체 지향 개발에서 다른 관심사에 영향을 미치는 프로그램의 애스펙트를 횡단 관심 또는 크로스 커팅 관심라고 한다.
요구사항에 대해 관심사항으로 분할, 개발, 통합하여 모듈화를 극대화하는 프로그램 기법
횡단 관심 요소(보안, 로깅, 트랜잭션, 인코딩, 파라미터)들의 처리를 모듈화를 통한 효율적 극복
출처 : https://velog.io/@geesuee/Spring-AOPAspect-Oriented-Programming%EC%99%80-%ED%94%84%EB%A1%9D%EC%8B%9C
위의 그림 처럼 공통된 로직(로깅,보안,트랜잭션등)이 매 비즈니스 로직마다 반복 작성된 그림이다.
반복된 로직을 줄이고, 유지보수 및 확장을 용이하게 하려면
AOP를 적용하여 비즈니스 로직과 공통 로직을 분리하고, 매핑!
주요 Annotation
@Target
관심사항을 적용 받게 되는 대상
@Aspect
자바에서 널리 사용하는 AOP프레임워크에 포함되며, AOP를 정의하는 Class에 할당
여러 개의 Advice와 여러 개의 PointCut의 결합체
여러 객체에 공통적으로 적용 되는 공통 관심 사항
@Pointcut
기능을 어디에 적용시킬지, 메소드,어노테이션 등 AOP를 적용 시킬 지점을 설정
Advice : 공통 관심 기능을 언제 핵심 로직에 적용할 지 정의
어노테이션 - XML 스키마 태그
@Before - <aop:before>
Target객체 메소드실행하기 이전
@After - <aop:after>
Target객체 메소드가 성공적으로 실행후, 예외가 발생되더라도 실행
@AfterReturning - <aop:after-returning>
Target객체 메소드 호출 성공 실행시
@AfterThrowing - <aop:after-throwing>
Target객체 메소드 호출 실패 예외 발생
@Around - <aop:around>
Target객체 메소드 전후Before/after 모두 제어
JoinPoint : 연결점 , Aspect를 적용할 수 있는 지점, PointCut은 JointPoint의 부분집합
클라이언트가 호출한 비즈니스 메소드 정보를 구현하기 위한 JoinPoint인터페이스
PointCut : Aspect를 적용할 타겟(로직) 메소드를 선택하는 지시자
지시자
- @Pointcut(“execution(수식)”) : execution([접근제한자]* 패키지.클래스.메서드(파라미터 타입))
- @Pointcut(“within(수식)”) : within([접근제한자]* 패키지.클래스)
- bean(빈이름)
MethodSignature : JoinPoint 객체 내 메소드 서명정보
예제
build.gradle에 aop dependency 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
DTO 클래스
public class UserDto {
private String id;
private String pw;
private String email;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPw() {
return pw;
}
public void setPw(String pw) {
this.pw = pw;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "UserDto{" +
"id='" + id + '\'' +
", pw='" + pw + '\'' +
", email='" + email + '\'' +
'}';
}
}
메인 클래스
@SpringBootApplication
public class AopApplication {
public static void main(String[] args) {
SpringApplication.run(AopApplication.class, args);
// aHVtYmFjazkxNUBnbWFpbC5jb20=
System.out.println(Base64.getEncoder().encodeToString("humback915@gmail.com".getBytes()));
}
}
Decode 어노테이션 인터페이스
/*
* 사용자 정의 어노테이션
* @Target : 해당 어노테이션을 사용가능한 대상을 지정,
* ex) 타입선언시, 메소드 선언시
* @Retention : 해당 어노테이션의 정보유지 범위를 지정
* 컴파일 이후에도 런타임 환경JVM에서 참조
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decode {
}
Timer 어노테이션 인터페이스
/*
* 사용자 정의 어노테이션
* @Target : 해당 어노테이션을 사용가능한 대상을 지정,
* ex) 타입선언시, 메소드 선언시
* @Retention : 해당 어노테이션의 정보유지 범위를 지정
* 컴파일 이후에도 런타임 환경JVM에서 참조
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}
REST API Controller
@RestController
@RequestMapping("/api")
public class RestApiController {
/*
* AOP 없이 REST API 사용
*/
/*
@GetMapping("/get/{id}")
public void get(@PathVariable Long id, @RequestParam String name){
System.out.println("get :");
System.out.println("get :"+id);
System.out.println("get :"+name);
}
@PostMapping("/post")
public void post(@RequestBody UserDto userDto){
System.out.println("post : "+userDto);
}
*/
/*
* AOP 사용으로 각 메소드마다 로그를 찍는 것을 한 곳으로 몰자.
*/
@GetMapping("/get/{id}")
public String get(@PathVariable Long id, @RequestParam String name){
return id+" : "+name;
}
@PostMapping("/post")
public UserDto post(@RequestBody UserDto userDto){
return userDto;
}
/*
* @Timer 사용자 정의 어노테이션를 사용하여 delete() 메소드 정의
*/
@Timer
@DeleteMapping("/delete")
public void delete(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/*
* @Decode 사용자 정의 어노테이션 적용하여 put() 메소드 정의
*/
@Decode
@PutMapping("/put")
public UserDto put(@RequestBody UserDto userDto){
return userDto;
}
}
AOP Config 클래스
@Aspect // AOP 동작하기 위해
@Component // 스프링에서 관리,클래스에 선언
public class AopConfig {
/*
* Pointcut 하는 부분
* 해당 수식 부분에 aCut()메소드이름으로 AOP 적용
*/
@Pointcut("execution(* com.test.aop.controller..*.*(..))")
private void aCut(){}
/*
* 수식에 Aspect를 추가할 어노테이션 제약을 추가
*/
@Pointcut("@annotation(com.test.aop.aspect.Timer)")
private void enableTimer(){}
@Pointcut("@annotation(com.test.aop.aspect.Decode)")
private void enableDecode(){}
/*
* @Before("메소드명()")
* 해당 메소드 실행 전 arg를 확인
*/
@Before("aCut()")
public void before(JoinPoint joinPoint){
// 메소드 명 출력
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
Method method = ms.getMethod();
System.out.println("@Before aCut() method name : "+method.getName());
// 메소드에 들어가는 매개변수 출력
Object[] args = joinPoint.getArgs();
for(Object obj : args){
System.out.println("@Before aCut() type : "+obj.getClass().getSimpleName());
System.out.println("@Before aCut() value : "+obj);
}
}
/*
* @AfterReturning("메소드명()",returning = 반환객체명)
* 해당 메소드 실행 후 반환 값 확인
*/
@AfterReturning(value = "aCut()", returning = "returnObj")
public void afterReturn(JoinPoint joinPoint, Object returnObj){
System.out.println("@AfterReturning aCut() returning :"+returnObj);
}
/*
* @Around
* 두가 지 조건의 Aspect를 사용
* ProceedingJoinPoint : 호출되는 대상 객체에 대한 정보, 실행되는 메소드 정보, 전달된 인자 정보에 접근할 수 있는 인터페이스
*/
@Around("aCut() && enableTimer()")
public void around(ProceedingJoinPoint jp){
StopWatch sw = new StopWatch();
sw.start();
try {
Object result = jp.proceed(); // 실질적 메소드 실행 return타입 유무에 따라 값이 들어옴.
} catch (Throwable e) {
throw new RuntimeException(e);
}finally {
sw.stop();
System.out.println("총 초 : "+sw.getTotalTimeSeconds());
}
}
/*
*
*/
@Before("aCut() && enableDecode()")
public void beforeDecode(JoinPoint jp) throws UnsupportedEncodingException {
System.out.println("@Before beforeDecode() Start : ");
Object[] args = jp.getArgs(); // 메소드 실행전 파라미터들
for(Object arg : args){ //
if(arg instanceof UserDto){ // 같은 클래스 타입일 경우
UserDto userDto = (UserDto) arg;//UserDto.class.cast(arg); // 형변환
String base64Email = userDto.getEmail();
//String email = String.valueOf(Base64.getDecoder().decode(base64Email));
String email = new String(Base64.getDecoder().decode(base64Email),"UTF-8");
System.out.println("@Before beforeDecode() email : "+email);
userDto.setEmail(email);
}
}
}
@AfterReturning(value = "aCut() && enableDecode()",returning = "returnObj")
public void afterDecodeReturn(JoinPoint jp , Object returnObj){
System.out.println("@AfterReturning afterDecodeReturn() Start : ");
if(returnObj instanceof UserDto){
UserDto userDto = (UserDto) returnObj; // 형변환
String email = userDto.getEmail();
String base64Email = Base64.getEncoder().encodeToString(email.getBytes());
System.out.println("@Before beforeDecode() base64Email : "+base64Email);
userDto.setEmail(base64Email);
}
}
}
GET 실행 결과
@Before aCut() method name : get
@Before aCut() type : Long
@Before aCut() value : 100
@Before aCut() type : String
@Before aCut() value : tom
@AfterReturning aCut() returning :100 : tom
POST 실행 결과
@Before aCut() method name : post
@Before aCut() type : UserDto
@Before aCut() value : UserDto{id='boy', pw='pw1234', email='boy1234@gmail.com'}
@AfterReturning aCut() returning :UserDto{id='boy', pw='pw1234', email='boy1234@gmail.com'}
DELETE 실행 결과
@Before aCut() method name : delete
@AfterReturning aCut() returning :null
총 초 : 2.002148721
PUT 실행 결과
@Before aCut() method name : put
@Before aCut() type : UserDto
@Before aCut() value : UserDto{id='boy', pw='pw1234', email='aHVtYmFjazkxNUBnbWFpbC5jb20='}
@Before beforeDecode() Start :
@Before beforeDecode() email : humback915@gmail.com
@AfterReturning aCut() returning :UserDto{id='boy', pw='pw1234', email='humback915@gmail.com'}
@AfterReturning afterDecodeReturn() Start :
@Before beforeDecode() base64Email : aHVtYmFjazkxNUBnbWFpbC5jb20=
REST API Tester Data File