Uvod u Spring AOP
Uvod u Spring AOP
AOP je jedan od dva veoma važna koncepta u sistemu Spring (drugi je IoC). Današnji članak će vas kroz praksu provesti kroz korišćenje AOP tehnike u SpringBoot projektu Codingmore, kako bismo kontrolnom (controller) sloju dodali aspekt i time ostvarili jedinstveno logovanje pristupa interfejsima.
1. O AOP-u
AOP, odnosno Aspect-oriented Programming, prevedeno kao aspektno-orijentisano programiranje, design ideja je u računarskim naukama koja kroz tehnologiju aspekata poslovnom subjektu dodaje dodatne savete (Advice), čime se jedinstveno upravlja i ukrašava blok koda proglašen za „pointcut" (tačka preseca).
Ova ideja je veoma pogodna za dodavanje u program onih funkcija koje nisu usko povezane sa jezgrom poslovanja — baš kao što je naša današnja tema, funkcija logovanja, tipičan primer.

AOP je dopuna objektno-orijentisanom programiranju (Object-oriented Programming, poznatom kao OOP). Jezgro OOP-a jeste klasa (class), dok je jezgro AOP-a aspekt (Aspect). Korišćenjem AOP-a mogu se izolovati pojedini delovi poslovne logike, čime se smanjuje spregnutost, povećava ponovna iskoristivost programa, a istovremeno raste i efikasnost razvoja.
AOP možemo jednostavno razumeti kao ono što prolazi kroz metod: radnje koje treba izvršiti pre metoda, tokom metoda, nakon metoda, nakon povratne vrednosti i nakon izuzetka.
2. Relevantna terminologija AOP-a
Pogledajmo sledeću sliku; to je model-diagram AOP-a: pre i posle izvršavanja nekih metoda izvršavaju se određene opšte radnje, pri čemu te radnje ne utiču na sam tok rada programa.
Upoznajmo se sa 5 ključnih termina uz koje je AOP vezan:
1) Poprečna briga (cross-cutting concern), ista vrsta ne-jezgrovnog posla izvučena iz svakog metoda.
2) Aspekt (Aspect), klasa koja učahuruje poprečnu brigu; svaka briga se iskazuje kao metod saveta; aspekt se obično definiše anotacijom @Aspect.
3) Savet (Advice), različiti konkretni poslovi koje aspekt mora da završi; na primer, naš aspekt za logovanje treba da zapiše trajanje pre i posle poziva interfejsa, pa mora da zabeleži vreme pre i posle poziva interfejsa, a zatim izračuna razliku. Postoji pet načina saveta:
- @Before: metod saveta izvršava se pre poziva ciljnog metoda
- @After: metod saveta izvršava se nakon poziva ciljnog metoda
- @AfterReturning: metod saveta izvršava se nakon što ciljni metod vrati vrednost
- @AfterThrowing: metod saveta izvršava se nakon što ciljni metod baci izuzetak
- @Around: umotava čitav ciljni metod, te se metod saveta izvršava pre poziva i nakon poziva
4) Tačka spajanja (JoinPoint), trenutak primene saveta; na primer, kada se metod interfejsa pozove, to je tačka spajanja za aspekt logovanja.
5) Pointcut (tačka preseca), opseg u kojem se funkcija saveta primenjuje; na primer, opseg primene aspekta logovanja u ovom članku jeste svi interfejsi kontrolera. Izraz pointcut-a obično se definiše anotacijom @Pointcut.
Sintaksna specifikacija formata izraza pointcut-a je sledeća:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern)
throws-pattern?)modifiers-pattern?označava modifikator prava pristuparet-type-patternoznačava tip povratne vrednosti, obično se koristi*za bilo koji tip povratne vrednostideclaring-type-pattern?označava ime paketaname-patternoznačava ime metoda; može se koristiti*za sve, iliset*za sva imena klasa koja počinju na setparam-pattern)označava tip parametara; više parametara može se razdvojiti zapetom,, svaki parametar može koristiti*za sve tipove parametara, a može se koristiti i(..)za nijedan ili bilo koji broj parametarathrows-pattern?označava tip izuzetka?označava da je prethodni deo opcion
Na primer:
@Pointcut("execution(public * com.codingmore.controller.*.*(..))")Znači da se savet aspekta primenjuje na sve public metode u paketu com.codingmore.controller.
3. Praktična primena AOP-a za logovanje pristupa interfejsima
Prvi korak, u fajl pom.xml Spring Boot projekta dodajte zavisnost spring-boot-starter-aop.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>Drugi korak, dodajte klasu za učahurivanje informacija logova WebLog, koja služi za zapisivanje kakva je operacija, ko je izvršilac, vreme početka, utrošeno vreme, putanja operacije, ime metoda operacije, IP adresa hosta operacije, parametri zahteva, rezultat povratne vrednosti itd.
/**
* Klasa za učahurivanje logova kontrolnog (Controller) sloja
* Created by macro on 2018/4/26.
*/
public class WebLog {
private String description;
private String username;
private Long startTime;
private Integer spendTime;
private String basePath;
private String uri;
private String url;
private String method;
private String ip;
private Object parameter;
private Object result;
//getter, setter metodi su izostavljeni
}Treći korak, dodajte aspekt za jedinstvenu obradu logova WebLogAspect.
/**
* Aspekt za jedinstvenu obradu logova
* Created by Shi Lei
*/
@Aspect
@Component
@Order(1)
public class WebLogAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("execution(public * com.codingmore.controller.*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
}
@AfterReturning(value = "webLog()", returning = "ret")
public void doAfterReturning(Object ret) throws Throwable {
}
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// Dobavi trenutni objekat zahteva
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// Zapisivanje informacija o zahtevu (prosleđuje se u Elasticsearch preko Logstash-a)
WebLog webLog = new WebLog();
Object result = joinPoint.proceed();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation log = method.getAnnotation(ApiOperation.class);
webLog.setDescription(log.value());
}
long endTime = System.currentTimeMillis();
String urlStr = request.getRequestURL().toString();
webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
webLog.setIp(request.getRemoteUser());
Map<String,Object> logMap = new HashMap<>();
logMap.put("spendTime",webLog.getSpendTime());
logMap.put("description",webLog.getDescription());
LOGGER.info("{}", JSONUtil.parse(webLog));
return result;
}
}Četvrti korak, pokrenite projekat i testirajte jedan od kontrolera pod controller.
Adresa za pristup Swagger knife4j: http://localhost:9022/doc.html
Izvršite operaciju upita za prijavljenog korisnika:

U konzoli možete videti sledeće informacije logova:
Adresa izvornog koda:
Referentni linkovi:
Autor cxuan: https://www.cnblogs.com/cxuanBlog/p/13060510.html
Hui Xiaoyuan: https://bbs.huaweicloud.com/blogs/289045
Shan Gao Wo Wei Feng: https://www.cnblogs.com/liaojie970/p/7883687.html
macrozheng: https://github.com/macrozheng/mall
