Starting Spring: Spring AOP usage advancement

Keywords: Java Spring Junit JSON

In the previous blog, we learned what AOP is and how to use AOP in Spring. This blog goes on to explain the advanced usage of AOP.

1. Declare tangent points with parameters

Suppose we have an interface CompactDisc and its implementation class BlankDisc:

package chapter04.soundsystem;

/**
 * Compact disc
 */
public interface CompactDisc {
    void play();

    void play(int songNumber);
}
package chapter04.soundsystem;

import java.util.List;

/**
 * Blank CD
 */
public class BlankDisc implements CompactDisc {
    /**
     * Record Name
     */
    private String title;

    /**
     * Artist
     */
    private String artist;

    /**
     * A collection of songs included in a record
     */
    private List<String> songs;

    public BlankDisc(String title, String artist, List<String> songs) {
        this.title = title;
        this.artist = artist;
        this.songs = songs;
    }

    @Override
    public void play() {
        System.out.println("Playing " + title + " by " + artist);
        for (String song : songs) {
            System.out.println("-Song:" + song);
        }
    }

    /**
     * Play a song
     *
     * @param songNumber
     */
    @Override
    public void play(int songNumber) {
        System.out.println("Play Song:" + songs.get(songNumber - 1));
    }
}

Now our requirement is to record the number of times each song is played. According to the past practice, we may modify the logic of BlankDisc class and increase the logic of recording the number of times at the code of playing each song. But now we use facets to achieve the same function without modifying the BlankDisc class.

First, the new section SongCounter is as follows:

package chapter04.soundsystem;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import java.util.HashMap;
import java.util.Map;

@Aspect
public class SongCounter {
    private Map<Integer, Integer> songCounts = new HashMap<>();

    /**
     * Reusable Tangent Points
     *
     * @param songNumber
     */
    @Pointcut("execution(* chapter04.soundsystem.CompactDisc.play(int)) && args(songNumber)")
    public void songPlayed(int songNumber) {
    }

    @Before("songPlayed(songNumber)")
    public void countSong(int songNumber) {
        System.out.println("Play song count:" + songNumber);
        int currentCount = getPlayCount(songNumber);
        songCounts.put(songNumber, currentCount + 1);
    }

    /**
     * Get the number of songs played
     *
     * @param songNumber
     * @return
     */
    public int getPlayCount(int songNumber) {
        return songCounts.getOrDefault(songNumber, 0);
    }
}

Focus on the lower tangent expression execution (* chapter04. soundsystem. CompactDisc. play (int) & args (songNumber), where int represents the parameter type and songNumber represents the parameter name.

New configuration class SongCounterConfig:

package chapter04.soundsystem;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableAspectJAutoProxy
public class SongCounterConfig {
    @Bean
    public CompactDisc yehuimei() {
        List<String> songs = new ArrayList<>();
        songs.add("Eastern Wind Breaks");
        songs.add("In the Name of the Father");
        songs.add("A sunny day");
        songs.add("Class Two, Three Years");
        songs.add("You can hear it.");

        BlankDisc blankDisc = new BlankDisc("Ye Huimei", "Jay Chou", songs);
        return blankDisc;
    }

    @Bean
    public SongCounter songCounter() {
        return new SongCounter();
    }
}

Matters needing attention:

1) The configuration class should add the @EnableAspectJAutoProxy annotation to enable AspectJ automatic proxy.

2) The section SongCounter should be declared bean, otherwise the section will not take effect.

Finally, the new test class SongCounterTest is as follows:

package chapter04.soundsystem;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.assertEquals;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SongCounterConfig.class)
public class SongCounterTest {
    @Autowired
    private CompactDisc compactDisc;

    @Autowired
    private SongCounter songCounter;

    @Test
    public void testSongCounter() {
        compactDisc.play(1);

        compactDisc.play(2);

        compactDisc.play(3);
        compactDisc.play(3);
        compactDisc.play(3);
        compactDisc.play(3);

        compactDisc.play(5);
        compactDisc.play(5);

        assertEquals(1, songCounter.getPlayCount(1));
        assertEquals(1, songCounter.getPlayCount(2));

        assertEquals(4, songCounter.getPlayCount(3));

        assertEquals(0, songCounter.getPlayCount(4));

        assertEquals(2, songCounter.getPlayCount(5));
    }
}

Running test method testSongCounter(), the test passes, and the output results are as follows:

Play song count: 1

Play Song: Eastern Wind Breaks

Play song count: 2

Play Song: In the Name of Father

Play song count: 3

Play Song: Sunny Day

Play song count: 3

Play Song: Sunny Day

Play song count: 3

Play Song: Sunny Day

Play song count: 3

Play Song: Sunny Day

Play song count: 5

Play Song: You can hear it

Play song count: 5

Play Song: You can hear it

2. Limit matching join points with specified annotations

In the previous tangent points we declared, all tangent expressions are matched to a specific method using fully qualified class names and method names, but sometimes we need to match all methods that use a certain annotation. At this time, we can use @annotation to implement the tangent expression. Note and use exe in the tangent expression before. The difference between cutions.

In order to better understand, we will also explain it through a specific example.

First, define an annotated Action:

package chapter04;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Action {
    String name();
}

Then define two ways to use the @Action annotation:

package chapter04;

import org.springframework.stereotype.Service;

@Service
public class DemoAnnotationService {
    @Action(name = "Annotated interceptor add operation")
    public void add() {
        System.out.println("DemoAnnotationService.add()");
    }

    @Action(name = "Annotated interceptor plus operation")
    public void plus() {
        System.out.println("DemoAnnotationService.plus()");
    }
}

Next, define the section LogAspect:

package chapter04;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class LogAspect {
    @Pointcut("@annotation(chapter04.Action)")
    public void annotationPointCut() {
    }

    @After("annotationPointCut()")
    public void after(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Action action = method.getAnnotation(Action.class);
        System.out.println("Annotated Interception " + action.name());
    }
}

Matters needing attention:

1) Aspects use the @Component annotation so that Spring can automatically scan to and create beans. If this annotation is not added here, you can also declare the aspect as a bean through Java configuration or xml configuration, otherwise the aspect will not take effect.

2)@Pointcut("@annotation(chapter04.Action)"), where we use @annotation to specify a comment when defining a pointcut, rather than execution to specify some or some method before.

The tangent expression we used before is execution(* chapter04.concert.Performance.perform(.)) which matches a specific method. If you want to match certain methods, you can modify it to the following format:

execution(* chapter04.concert.Performance.*(..))

Then, define the configuration class AopConfig:

package chapter04;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AopConfig {
}

Note: The configuration class needs to add the @EnableAspectJAutoProxy annotation to enable AspectJ automatic proxy, otherwise the aspect will not take effect.

Finally, create a new Main class and add the following test code in its main() method:

package chapter04;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);

        DemoAnnotationService demoAnnotationService = context.getBean(DemoAnnotationService.class);

        demoAnnotationService.add();
        demoAnnotationService.plus();

        context.close();
    }
}

The output is as follows:

DemoAnnotationService.add()

Annotation Interception Added Operation of Annotation Interception

DemoAnnotationService.plus()

plus Operation of Annotation Interception

You can see that after execution of the add() and plus() methods using the @Action annotation, the after() method defined in the aspect is executed.

If you add a subtract() method with the @Action annotation, after execution, the after() method defined in the aspect will also be executed.

3. Actual use in projects

In practical use, aspect is very suitable for logging, which not only meets the needs of logging, but also isolates the log code from the actual business logic.

Let's look at the specific implementation.

First, declare an access log annotation AccessLog:

package chapter04.log;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Access Log Annotations
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLog {
    boolean recordLog() default true;
}

Then define the aspect AccessLogAspectJ of the access log:

package chapter04.log;

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AccessLogAspectJ {
    @Pointcut("@annotation(AccessLog)")
    public void accessLog() {

    }

    @Around("accessLog()")
    public void recordLog(ProceedingJoinPoint proceedingJoinPoint) {
        try {
            Object object = proceedingJoinPoint.proceed();

            AccessLog accessLog = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod().getAnnotation(AccessLog.class);

            if (accessLog != null && accessLog.recordLog() && object != null) {
                // It's just printed out here. It's usually recorded in the company's log center when it's actually used.
                System.out.println("Method name:" + proceedingJoinPoint.getSignature().getName());
                System.out.println("Participation:" + JSON.toJSONString(proceedingJoinPoint.getArgs()));
                System.out.println("Ginseng production:" + JSON.toJSONString(object));
            }
        } catch (Throwable throwable) {
            // Here you can log exceptions to the company's log Center
            throwable.printStackTrace();
        }
    }
}

The above code needs to add the following dependencies in pom.xml:

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.59</version>
</dependency>

Then define the configuration class LogConfig:

package chapter04.log;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class LogConfig {
}

Note: Don't forget to add the @EnableAspectJAutoProxy annotation, otherwise the section will not take effect.

Then, suppose your external interface is as follows:

package chapter04.log;

import org.springframework.stereotype.Service;

@Service
public class MockService {
    @AccessLog
    public String mockMethodOne(int index) {
        return index + "MockService.mockMethodOne";
    }

    @AccessLog
    public String mockMethodTwo(int index) {
        return index + "MockService.mockMethodTwo";
    }
}

Because you want to log, each method adds the @AccessLog annotation.

Finally, create a new Main class and add the following test code in its main() method:

package chapter04.log;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LogConfig.class);

        MockService mockService = context.getBean(MockService.class);

        mockService.mockMethodOne(1);
        mockService.mockMethodTwo(2);

        context.close();
    }
}

The output log is as follows:

Method name: mockMethodOne

Participation: [1]

Reference: "1 MockService. mockMethodOne"

Method Name: mockMethod Two

Participation: [2]

Reference: "2MockService. mockMethod Two"

If a method does not need to log, the @AccessLog annotation can be omitted:

public String mockMethodTwo(int index) {
    return index + "MockService.mockMethodTwo";
}

You can also specify recordLog as false:

@AccessLog(recordLog = false)
public String mockMethodTwo(int index) {
    return index + "MockService.mockMethodTwo";
}

Here is just a simple example of logging, you can also apply facets to more scenarios such as record interface time-consuming.

4. Source Code and Reference

Source address: https://github.com/zwwhnly/spring-action.git Welcome to download.

Craig Walls, Spring Actual Warfare (4th Edition)

Wang Yunfei's The Subverter of Java EE Development: Spring Boot Actual Warfare

AOP (Face-Oriented Programming)_Baidu Encyclopedia

5. Finally

Play a small advertisement, welcome to pay close attention to the Wechat public number: "Shencheng strangers", regularly share the Java technology dry goods, let us progress together.

Posted by blawson7 on Tue, 27 Aug 2019 20:57:47 -0700