Aspect-oriented programming, Spring AOP
Highlighting end-to-end functionality
Before

and after the

words, There is a functionality that affects several modules, but it does not have a direct relationship to the business code, and it would be good to bring it to a separate place, this is shown in the figure above.
Join point

Join point is the next concept of AOP, these are points of observation, joining to the code where the introduction of functionality is planned.
Pointcut

A pointcut is a slice, a request for attachment points, it can be one or more points. The rules for querying points are very diverse, in the figure above, the request for annotation on the method and the specific method. Rules can be combined by &&, || ,!
Advice

Advice - a set of instructions executed on the points cut (Pointcut). Instructions can be executed on the event of different types:
- Before - before the method call
- After - after calling the method
- After returning - after returning a value from a function
- After throwing - in the case of exception
- After finally - in case of execution of the finally block
- Around - you can do a pre., Post., Processing before a method call, and also bypass the method call altogether.
on one Pointcut you can “hang” several Advice of different types.
Aspect

Aspect - the module in which the Pointcut and Advice descriptions are collected.
Now I will give an example and finally everything will fall (or almost all) into place. We all know about logging code that permeates many modules, not related to business code, but nevertheless it is impossible without it. And so I separate this functionality from the business code.
Example - Code Logging
Target service
@ServicepublicclassMyService{
publicvoidmethod1(List<String> list){
list.add("method1");
System.out.println("MyService method1 list.size=" + list.size());
}
@AspectAnnotationpublicvoidmethod2(){
System.out.println("MyService method2");
}
publicbooleancheck(){
System.out.println("MyService check");
returntrue;
}
}
Aspect description with Pointcut and Advice.
@Aspect@ComponentpublicclassMyAspect{
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(public * com.example.demoAspects.MyService.*(..))")
publicvoidcallAtMyServicePublic(){ }
@Before("callAtMyServicePublic()")
publicvoidbeforeCallAtMethod1(JoinPoint jp){
String args = Arrays.stream(jp.getArgs())
.map(a -> a.toString())
.collect(Collectors.joining(","));
logger.info("before " + jp.toString() + ", args=[" + args + "]");
}
@After("callAtMyServicePublic()")
publicvoidafterCallAt(JoinPoint jp){
logger.info("after " + jp.toString());
}
}
And the calling test code
@RunWith(SpringRunner.class)
@SpringBootTestpublicclassDemoAspectsApplicationTests{
@Autowiredprivate MyService service;
@TestpublicvoidtestLoggable(){
List<String> list = new ArrayList();
list.add("test");
service.method1(list);
service.method2();
Assert.assertTrue(service.check());
}
}
Explanations. In the target service there is no mention of writing to the log, in the calling code, all the more, to all logging is concentrated in a separate module. In Pointcut
@Aspect
class MyAspect ...@Pointcut("execution(public * com.example.demoAspects.MyService.*(..))")
publicvoidcallAtMyServicePublic(){ }
I requested all public MyService methods with any return type * and the number of arguments (..)
In Advice Before and After that refer to Pointcut (callAtMyServicePublic) , I wrote instructions for writing to the log. JoinPoint is not a required parameter which provides additional information, but if it is used, it should be the first.
Everything is separated into different modules! Calling code, target, logging.
Result in the console

. Pointcut rules can be different.
Some examples of Pointcut and Advice are:
Request for annotation on the method.
@Pointcut("@annotation(AspectAnnotation)")
publicvoidcallAtMyServiceAnnotation(){ }
Advice for him
@Before("callAtMyServiceAnnotation()")
publicvoidbeforeCallAt(){ }
Request for specific method with indication of target method parameters
@Pointcut("execution(* com.example.demoAspects.MyService.method1(..)) && args(list,..))")
publicvoidcallAtMyServiceMethod1(List<String> list){ }
Advice for him
@Before("callAtMyServiceMethod1(list)")
publicvoidbeforeCallAtMethod1(List<String> list){ }
Pointcut for return result
@Pointcut("execution(* com.example.demoAspects.MyService.check())")
publicvoidcallAtMyServiceAfterReturning(){ }
Advice for him
@AfterReturning(pointcut="callAtMyServiceAfterReturning()", returning="retVal")
publicvoidafterReturningCallAt(boolean retVal){ }
Example of checking the rights to the Around type Advice, via annotation
@Retention(RUNTIME)
@Target(METHOD)
public@interface SecurityAnnotation {
}
//@Aspect@ComponentpublicclassMyAspect{
@Pointcut("@annotation(SecurityAnnotation) && args(user,..)")
publicvoidcallAtMyServiceSecurityAnnotation(User user){ }
@Around("callAtMyServiceSecurityAnnotation(user)")
public Object aroundCallAt(ProceedingJoinPoint pjp, User user){
Object retVal = null;
if (securityService.checkRight(user)) {
retVal = pjp.proceed();
}
return retVal;
}
Methods that need to be checked before the call, on the right, you can annotate “SecurityAnnotation”, then in Aspect we will get their cut, and all of them will be intercepted before the call and the rights will be checked.
Target code:
@ServicepublicclassMyService{
@SecurityAnnotationpublic Balance getAccountBalance(User user){
// ...
}
@SecurityAnnotationpublic List<Transaction> getAccountTransactions(User user, Date date){
// ...
}
}
Calling code:
balance = myService.getAccountBalance(user);
if (balance == null) {
accessDenied(user);
} else {
displayBalance(balance);
}
Those. in the calling code and the target, there is no rights check, only the business code itself.
An example of profiling the same service using the Around type Advice
@Aspect@ComponentpublicclassMyAspect{
@Pointcut("execution(public * com.example.demoAspects.MyService.*(..))")
publicvoidcallAtMyServicePublic(){
}
@Around("callAtMyServicePublic()")
public Object aroundCallAt(ProceedingJoinPoint call)throws Throwable {
StopWatch clock = new StopWatch(call.toString());
try {
clock.start(call.toShortString());
return call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
}
}
If we run the calling code with calls to the MyService methods, then we get the time to call each method. Thus, without changing the calling code and the target, I added new functionalities: logging, profiler, and security.
Example use in UI forms
There is a code that hides / shows fields on the form by setting:
publicclassEditFormextendsForm{
@Overridepublicvoidinit(Form form){
formHelper.updateVisibility(form, settingsService.isVisible(COMP_NAME));
formHelper.updateVisibility(form, settingsService.isVisible(COMP_LAST_NAME));
formHelper.updateVisibility(form, settingsService.isVisible(COMP_BIRTH_DATE));
// ...
}
You can also updateVisibility in the Around type Advice.
@AspectpublicclassMyAspect{
@Pointcut("execution(* com.example.demoAspects.EditForm.init() && args(form,..))")
publicvoidcallAtInit(Form form){ }
// ...@Around("callAtInit(form)")
public Object aroundCallAt(ProceedingJoinPoint pjp, Form form){
formHelper.updateVisibility(form, settingsService.isVisible(COMP_NAME));
formHelper.updateVisibility(form, settingsService.isVisible(COMP_LAST_NAME));
formHelper.updateVisibility(form, settingsService.isVisible(COMP_BIRTH_DATE));
Object retVal = pjp.proceed();
return retVal;
}
etc.
Project structure

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>demoAspects</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>demoAspects</name><description>Demo project for Spring Boot Aspects</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.6.RELEASE</version><relativePath/><!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>Materials
Aspect Oriented Programming with Spring