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 the

    image

    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



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


    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

    @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

    image

    . 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

    image

    pom file
    <?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

    Also popular now: