Get familiar with the Spring Boot Internals by using the @Conditional annotations.

Time: 35 minutes.

Directory Web Internals App

This application will use a custom @Audit annotation over any method and log the execution.

  1. Open a browser and hit the url: http://start.spring.io

  2. Click the Switch to the full version link.

  3. Fill out the Directory Web App Project metadata with (See Figure 1):

    Table 1. Directory Web App - metadata
    Property Value

    Group:

    io.pivotal.workshop

    Artifact:

    directory-web-internals

    Name:

    directory-web-internals

    Package Name:

    io.pivotal.workshop.directory

    Dependencies:

    Web

    Spring Boot:

    2.0.0.M7

    Figure 1: Spring Initializr - http://start.spring.io

    SpringInitializr

    Tip
    You can choose either Maven or Gradle project types.
  4. Type Web in the Dependencies field and press Enter.

  5. Click the Generate Project button.

  6. Unzip the file in any directory you want.

  7. Import your project in any IDE you want.

  8. You can copy all the code from the previous lab (we are going to use it).

  9. Add the following dependency to your pom.xml or build.gradle:

    maven - pom.xml
    <dependency>
        <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    gradle - build.gradle
    compile('org.springframework.boot:spring-boot-starter-aop')

    we are going to use AOP, Aspect Oriented Programming.

    Tip
    Adding dependencies like this is commonly used. You don’t need to re-gerate the project if you forgot to add a dependency. There is a naming convention for Spring Boot: spring-boot-starter-[TECHNOLOGY].
  10. Create the custom @Audit annotation by adding the io.pivotal.workshop.directory.annotation.Audit interface to the project.

    package io.pivotal.workshop.directory.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 Audit {
            Auditor value() default Auditor.NOTHING;
    }
  11. Create the io.pivotal.workshop.directory.annotation.Auditor enum, that is part of the @Audit annotation possible values.

    package io.pivotal.workshop.directory.annotation;
    
    public enum Auditor {
            BEFORE, AFTER, BEFORE_AND_AFTER, NOTHING
    }

    the values in the enum above will be used later in the challenges section.

  12. Create the io.pivotal.workshop.directory.aop.SimpleAudit class that will be used as cross-cutting concern for logging the execution of any method that has the @Audit annotation.

    package io.pivotal.workshop.directory.aop;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import io.pivotal.workshop.directory.annotation.Audit;
    
    @Aspect
    public class SimpleAudit {
    
            private static Logger log = LoggerFactory.getLogger("[AUDIT]");
    
            @Before("@annotation(audit)")
            public void audit(JoinPoint jp, Audit audit){
                    log.info("[EXECUTING] " + jp.getSignature());
            }
    }
  13. In the io.pivotal.workshop.directory.repository.DirectoryRepository class add the findByEmail method (a challenge from the previous lab).

        @Audit
            public Optional<Person> findByEmail(String email){
                    return findFirstBy( p -> p.getEmail().equals(email));
            }

    as you can see, this method will have the @Audit annotation that will be log the execution of this method. If you missed the challenge from the previous lab, here it is, the findFirstBy code:

    private Optional<Person> findFirstBy(Predicate<Person> findBy){
            return directory.stream()
                    .filter(findBy)
                    .findFirst();
    }
  14. Create a io.pivotal.workshop.directory.config.DirectoryConfig class that will create the aspect as Spring bean.

    package io.pivotal.workshop.directory.config;
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import io.pivotal.workshop.directory.aop.SimpleAudit;
    
    @Configuration
    public class DirectoryConfig {
    
            @ConditionalOnClass(name={"io.pivotal.workshop.directory.repository.PersonRepository"})
            @Bean
            public SimpleAudit simpleAudit(){
                    return new SimpleAudit();
            }
    
    }

    is important to notice the usage of the @ConditionalOnClass annotation. Here the simpleAudit bean will be created only if the PersonRepository is on your classpath.

  15. In the io.pivotal.workshop.directory.controller.DirectoryController class add the searchByEmail method ( achallenge from the previous lab).

    @RequestMapping("/directory/search")
    public ResponseEntity<?> searchByEmail(@RequestParam String email) {
            Optional<Person> result = this.repo.findByEmail(email);
    
            if (result.isPresent()) {
                    return ResponseEntity.ok(result.get());
            }
    
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("{}");
    }
  16. Run your application and test the /directory/search endpoint by open a browser and hit: http://localhost:8080/directory/search?email=john@email.com.

    Tip
    If you are not using any IDE, then you can run your application with maven: ./mvnw spring-boot:run or if you are using gradle: ./gradlew bootRun
  17. You should see the result.

    Figure 2: Directory Web Internals App - http://localhost:8080/directory/search?email=john@email.com

    03 spring boot internals 02

Questions

Q: Did you see the logs in the console window (or terminal)?

A: NO, because the @ConditionalOnClass annotation is looking for a PersonRepository class.

Modify the DirectoryConfig class and change the right class in the @ConditionalOnClass annotation and run the application again:

@ConditionalOnClass(name={"io.pivotal.workshop.directory.repository.DirectoryRepository"})

now you should see:

[AUDIT] : [EXECUTING] Optional io.pivotal.workshop.directory.repository.DirectoryRepository.findByEmail(String)

Challenges

This is a trivial example using the @ConditionalOnClass annotation, so let’s add more features to our project and have a real use of the Auditor enum:

  • Create a new @Around advice for using the Auditor enum, so it can log also:

    • the parameters values if @Audit(Auditor.BEFORE).

    • the return value if @Audit(Auditor.AFTER).

    • the parameters and return values if @Audit(Auditor.BEFORE_AND_AFTER).

  • Use the @ConditionalOnProperty to evaluate if the directory.audit is on or off. If is on then log, but if is off or not present then don’t do anything.

  • Add the logic to the Aspect to review the directory.info if is with value short to log just the name of the method being executed, or with value long to show the full name of the method being executed.

[HOMEWORK]

This lab is just simple enough to get started, but everything is in the same code. The challenge is now to create a new audit project and use it in the directory-web-internals by creating @EnableAudit annotation.