ตัวอย่างการใช้ Spring boot AOP กับ annotation แบบเบบี๋

Wanichnun Sinpitak
Jul 28, 2017 · 2 min read

What is Aspect Oriented Programming (AOP) ?

โดยปกติแล้วผมไม่ค่อยจะใช้เวลาทำความเข้าใจกับทฤษฎีซักเท่าไหร่ เวลาอยากรู้เรื่องอะไร อ่านนิยามมันแค่นิดหน่อย จากนั้นก็ลงมือทำเลย ดังนั้นผมอาจจะอธิบายความหมายของมันไม่ดีเท่าไหร่นะ

แต่เอาตามที่ผมอ่านแล้วเข้าใจแล้วกัน มันคือการเขียนโปรแกรมย่อย ที่แยกออกมาจากโปรแกรมหลัก เพื่อเอาไปทำอะไรบางอย่างกับโปรแกรมหลักนะแหละ

ยกตัวอย่างเช่น เรามี method หนึ่งที่ทำงานตาม feature ของเราได้อย่างถูกต้องอยู่แล้ว แต่บังเอิญ เอิ่มม ไม่บังเอิญสิ มันจำเป็นที่เราจะต้องเก็บ log ระยะเวลาที่ method นี้ execute

ซึ่งตรงนี้ เราคงไม่อยากเข้าไปทำอะไรกับโค้ดเดิมของเราเป็นแน่ ดังนั้นเราสามารถเขียนโค้ดส่วนที่แยกออกมา เพื่อทำงานตรงนี้ให้เราได้

ซึ่งวันนี้ผมก็จะทำตามตัวอย่างข้างบนให้ดูนะ มันอาจจะง่ายๆ แต่ลองไปประยุกต์ดูแล้วกันนะครับ เมื่อเราทำได้แล้ว เราจะเห็นประโยชน์ของมันเองหละ

Let’s get started

สร้าง spring boot project ขึ้นมาโดย build.gradle ของเราหน้าตาเป็นแบบนี้

group 'com.wanichnun.lab.aop'
version '1.0-SNAPSHOT'

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.3.RELEASE")
classpath 'se.transmode.gradle:gradle-docker:1.2'
}
}

apply plugin: 'application'
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-aop")
compileOnly("org.projectlombok:lombok")

testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile group: 'junit', name: 'junit', version: '4.11'
}

ผมมี package ตามนี้นะ controller, service, annotation, aspect

เราเริ่มจากการสร้าง class HelloController ใน package กันก่อนนะ

package com.wanichnun.lab.aop.controller;

import com.wanichnun.lab.aop.annotation.LogExecutionTime;
import com.wanichnun.lab.aop.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
@Autowired
private HelloService helloService;

@GetMapping(path = "/hello")
public String getHello() {
return helloService.hello("world!");
}
}

ง่ายๆ ธรรมดาแบบนี้แหละ method getHello() ก็จะเรียกไปยัง method อีกตัวหนึ่ง ซึ่งอยู่ใน class HelloService

package com.wanichnun.lab.aop.service;

import com.wanichnun.lab.aop.annotation.LogActivity;
import org.springframework.stereotype.Service;

@Service
public class HelloService {
public String hello(String text) {
return "Hello " + text;
}
}

ตอนนี้ feature ของเราเสร็จแล้ว ทำงานได้ตามที่เราคิดไว้แล้วแหละ

ทีนี้เราอยากจะเก็บ log ว่า API /hello ของเราใช้เวลา execute นานเท่าไหร่กันนะ

เริ่มจากสร้าง annotation class LogExecutionTime ใน package annotation กันก่อน

package com.wanichnun.lab.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}

ต่อไป มาสร้างพระเอกของเรากัน class LogAspect เอาไว้ใน package aspect

package com.wanichnun.lab.aop.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
public class LogAspect {

@Around("@annotation(com.wanichnun.lab.aop.annotation.LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
final long start = System.currentTimeMillis();
final Object proceed = joinPoint.proceed();
final long executionTime = System.currentTimeMillis() - start;
log.info(joinPoint.getSignature() + " executed in " + executionTime + "ms");

return proceed;
}
}

อธิบายเพิ่มนิดหน่อย

method logExecutionTime จะถูกควบคุมการทำงานด้วย annotation @Around(“@annotation(com.wanichnun.lab.aop.annotation.LogExecutionTime)”)

ซึ่งหมายความว่า ณ method ใดๆ ก็ตาม ที่มี annotation @LogExecutionTime แปะอยู่ เมื่อมันถูกเรียกทำงาน ก็จะเข้ามาทำงานที่ method นี้ก่อน ซึ่งบรรทัดที่ว่า

final Object proceed = joinPoint.proceed();

จะเป็นการทำงานจริงๆ ของ method นั้นๆ ดังนั้นเราก็แค่เขียนโค้ดจับเวลาก่อนและหลังบรรทัดนี้ ก็จะได้เวลาในการทำงานของ method แล้ว


ทำตามนี้แล้ว เราจะได้ Annotation หนึ่งตัว ชื่อว่า “@LogExecutionTime” ซึ่งเราสามารถเอาไปใส่ไว้กับทุกๆ method ที่เราต้องการจะ log เวลาการทำงานเลย

ดังนั้นเราจะต้องเอา @LogExecutionTime ไปใส่ไว้ที่ method getHello() ที่อยู่ใน controller เลย เพื่อ log เวลาการทำงานของ API /hello

ผลลัพธ์เมื่อ call http://localhost:8080/hello ก็จะได้แบบนี้

2017–07–28 21:13:55.068 INFO 17959 — — [nio-8080-exec-1] com.wanichnun.lab.aop.aspect.LogAspect : String com.wanichnun.lab.aop.controller.HelloController.getHello() executed in 11ms

สรุปว่า API นี้ใช้เวลาทำงานทั้งหมด 11 millisecond นั่นเอง

เป็นอันเสร็จสิ้น ตัวอย่างการใช้ Spring boot AOP ด้วย annotation เล็กๆ น้อยๆ

บัยยยยย


Sobb

Wanichnun Sinpitak

Written by

Computer science, KMITL | DevOps at LINE Thailand

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade