📜 ⬆️ ⬇️

Aspect-oriented programming, Spring AOP

Aspect-oriented programming (AOP) is a programming paradigm which is a further development of procedural and object-oriented programming (OOP). The idea of ​​AOP is to highlight the so-called cross-cutting functionality. And so everything is in order, here I will show how to do it in Java - Spring @AspectJ annotation style (there is also a schema-based xml style, the functionality is similar).

Highlighting end-to-end functionality


Before

image
')
and after

image

Those. 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



image

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


image

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


image

Advice - a set of instructions executed at the point cut (Pointcut). Instructions can be executed on the event of different types:


on one Pointcut you can “hang” several Advice of different types.

Aspect


image

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

@Service public class MyService { public void method1(List<String> list) { list.add("method1"); System.out.println("MyService method1 list.size=" + list.size()); } @AspectAnnotation public void method2() { System.out.println("MyService method2"); } public boolean check() { System.out.println("MyService check"); return true; } } 

Aspect description with Pointcut and Advice.

 @Aspect @Component public class MyAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } @Before("callAtMyServicePublic()") public void beforeCallAtMethod1(JoinPoint jp) { String args = Arrays.stream(jp.getArgs()) .map(a -> a.toString()) .collect(Collectors.joining(",")); logger.info("before " + jp.toString() + ", args=[" + args + "]"); } @After("callAtMyServicePublic()") public void afterCallAt(JoinPoint jp) { logger.info("after " + jp.toString()); } } 

And the calling test code

 @RunWith(SpringRunner.class) @SpringBootTest public class DemoAspectsApplicationTests { @Autowired private MyService service; @Test public void testLoggable() { 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, in all the logging is concentrated in a separate module
@Aspect
class MyAspect ...


In pointcut

  @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } 

I requested all public MyService methods with any return type * and the number of arguments (..)

In Advice Before and After which 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 must be the first.
Everything is separated into different modules! Calling code, target, logging.

Result in the console

image

Pointcut rules may be different
Some examples of Pointcut and Advice are:

Request for annotation on the method.

 @Pointcut("@annotation(AspectAnnotation)") public void callAtMyServiceAnnotation() { } 

Advice for him

  @Before("callAtMyServiceAnnotation()") public void beforeCallAt() { } 

Request for specific method with indication of target method parameters

 @Pointcut("execution(* com.example.demoAspects.MyService.method1(..)) && args(list,..))") public void callAtMyServiceMethod1(List<String> list) { } 

Advice for him

  @Before("callAtMyServiceMethod1(list)") public void beforeCallAtMethod1(List<String> list) { } 

Pointcut for return result

  @Pointcut("execution(* com.example.demoAspects.MyService.check())") public void callAtMyServiceAfterReturning() { } 

Advice for him

  @AfterReturning(pointcut="callAtMyServiceAfterReturning()", returning="retVal") public void afterReturningCallAt(boolean retVal) { } 

Example of checking the rights to the Around type Advice, via annotation

 @Retention(RUNTIME) @Target(METHOD) public @interface SecurityAnnotation { } // @Aspect @Component public class MyAspect { @Pointcut("@annotation(SecurityAnnotation) && args(user,..)") public void callAtMyServiceSecurityAnnotation(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:

 @Service public class MyService { @SecurityAnnotation public Balance getAccountBalance(User user) { // ... } @SecurityAnnotation public 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, the rights check is missing, only the business code itself.
An example of profiling the same service using the Around type Advice

 @Aspect @Component public class MyAspect { @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } @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 the method calls MyService, 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:

 public class EditForm extends Form { @Override public void init(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.

 @Aspect public class MyAspect { @Pointcut("execution(* com.example.demoAspects.EditForm.init() && args(form,..))") public void callAtInit(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

image

pom file
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="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

Source: https://habr.com/ru/post/428548/


All Articles