Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

The last article introduced dynamic proxies in Java: How Java implements dynamic proxies. This article will take a look at how dynamic proxies are used in the Spring framework.

The use of SpringAOP

Again, here’s an example of sending emails:

public interface SendEmailService {

    void sendEmail(String emailContext);
}
Copy the code
@Service
public class SendEmailServiceImpl implements SendEmailService {

    @Override
    public void sendEmail(String emailContext) {
        System.out.println("Sent an email saying:"+ emailContext); }}Copy the code

For mail sending services, how can we record the current time before and after sending emails? After learning about dynamic proxies in Java, you can easily do this:

public class SendEmailInvocationHandler<T> implements InvocationHandler {

    private final T t;

    public SendEmailInvocationHandler(T t) {
        this.t = t;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("The time before sending the mail is:" + LocalDateTime.now());
        Object result = method.invoke(t, args);
        System.out.println("The time after sending the email is:" + LocalDateTime.now());
        returnresult; }}Copy the code

The custom class inherits from InvocationHandler and is then enhanced with Proxy:

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    SendEmailService sendEmailService = context.getBean(SendEmailService.class);
    SendEmailService sendEmailProxy = (SendEmailService) Proxy.newProxyInstance(sendEmailService.getClass().getClassLoader(), sendEmailService.getClass().getInterfaces(), new SendEmailInvocationHandler<>(sendEmailService));
    sendEmailProxy.sendEmail("hello");
}
Copy the code

In Spring, enhancing a method isn’t that complicated at all, and can be done with some simple code:

@Aspect
@Component
public class SendEmailAspect {

    @Before("execution(public com.wwj.springaop.test.SendEmailService.sendEmail(..) )"
    public void printTimeBefore(a) {
        System.out.println(LocalDateTime.now());
    }

    @After("execution(public com.wwj.springaop.test.SendEmailService.sendEmail(..) )"
    public void printTimeAfter(a) { System.out.println(LocalDateTime.now()); }}Copy the code

The @component annotation is not unfamiliar. It is used to mark a class as a Bean in the Spring container, i.e. to register the class with the Spring container. While the @aspect annotation marks a Bean as an Aspect, SpringAOP’s idea is to weave this extra code logic in front of or behind the original code without changing the original code, which is called an Aspect.

The section class has 5 section annotations:

  • @before: Pre-notification
  • @after: Post notification
  • AfterReturning: Return notification
  • @afterThrowing: Exception notification
  • @around: Circular notification

Their meanings are fairly straightforward, such as pre-notification, which is executed before the target method executes, post-notification, which is executed after the target method executes, and so on. The details of each notification are described below.

Tangent expression

Before we get into the various notifications, we need to know about the concept of pointcut expressions, which allow us to specify which interfaces, in which classes, in which packages, should be enhanced, as in this example:

execution(publiccom.wwj.springaop.test.SendEmailService.sendEmail(..) )Copy the code

It represents an enhancement to the sendEmail method in the SendEmailService interface under the public-decorated com.wwj.springaop.test package. If we need to enhance methods in more than one class at the same time, we can use the * wildcard to match, for example:

execution(* com.wwj.springaop.test.*.*(..) )Copy the code

It represents any method of any class in the com.wwj.springaop.test package that needs to be enhanced with any permission modifier, (..) Represents any parameter.

Pre notice

As the name implies, pre-notification is executed before the target method is executed, which is relatively simple and requires no special attention.

The rear notice

The post-notification is executed after the target method executes, and it is important to note that the notification will be executed anyway, even if the target method fails.

Return to inform

Return notifications are executed after successful execution of the target method and not if an exception occurs in the target method.

Abnormal notice

An exception notification is executed after an exception occurs in the target method, or not if the target method does not.

Surrounding the notification

Wrap notifications are special in that they do all the work of the previous four notifications, which we’ll discuss later.

The test of four notifications

Next, we will test the four notifications except for the wrap notification and write the section class:

@Aspect
@Component
public class SendEmailAspect {

    @Before("execution(* com.wwj.springaop.test.SendEmailService.sendEmail(..) )"
    public void before(a) {
        System.out.println("Pre-notification executed......");
    }

    @After("execution(* com.wwj.springaop.test.SendEmailService.sendEmail(..) )"
    public void after(a) {
        System.out.println("Post notification executed......");
    }

    @AfterReturning("execution(* com.wwj.springaop.test.SendEmailService.sendEmail(..) )"
    public void afterReturning(a) {
        System.out.println("Return notification executed......");
    }

    @AfterThrowing("execution(* com.wwj.springaop.test.SendEmailService.sendEmail(..) )"
    public void afterThrowing(a) {
        System.out.println("Exception notification executed......"); }}Copy the code

Test code:

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    SendEmailService sendEmailService = context.getBean(SendEmailService.class);
    sendEmailService.sendEmail("hello");
}
Copy the code

The execution result is as follows:

Pre-notification is executed at...... An email was sent saying: hello return notification executed...... The post notification is implemented at......Copy the code

When the target method executes normally, the order of notification is pre-notification, return notification, and post-notification. What if the target method fails? Modify code:

@Service
public class SendEmailServiceImpl implements SendEmailService {

    @Override
    public void sendEmail(String emailContext) {
        System.out.println("Sent an email saying:" + emailContext);
        int i = 1 / 0; }}Copy the code

Re-execute the code with the result:

Pre-notification is executed at...... An email was sent saying: Hello exception notification performed...... The post notification is implemented at......Copy the code

The order of notification becomes pre-notification, exception notification, and post-notification.

Passable parameters in a notification

Because of the nature of notifications, some notifications can retrieve execution information about the target method, such as:

@Before("execution(* com.wwj.springaop.test.SendEmailService.sendEmail(..) )"
public void before(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());
    System.out.println("Pre-notification executed...... The target method is:" + methodName + ", the parameter is:" + args);
}
Copy the code

In the pre-notification, you can get information about the target method, such as method name, parameters, and so on, by passing in the JointPoint object directly.

Postnotification can also be passed this parameter:

@After("execution(* com.wwj.springaop.test.SendEmailService.sendEmail(..) )"
public void after(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());
    System.out.println("Post notification is executed...... The target method is:" + methodName + ", the parameter is:" + args);
}
Copy the code

The return notification will not only get the target method name and method parameters, but also the return value of the target method, because the return notification is executed after the target method has successfully executed:

@AfterReturning(value = "execution(* com.wwj.springaop.test.SendEmailService.sendEmail(..) )",returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());
    System.out.println("Return notification executed...... The target method is:" + methodName + ", the parameter is:" + args + ", return value:" + result);
}
Copy the code

Exception notification is similar in that it gets exception information about the target method:

@AfterThrowing(value = "execution(* com.wwj.springaop.test.SendEmailService.sendEmail(..) )", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());
    System.out.println("Exception notification performed...... The target method is:" + methodName + ", the parameter is:" + args + ", the exception information is:" + e);
}
Copy the code

The normal execution and abnormal execution of the target method are tested respectively, and the results are as follows:

Pre-notification is executed at...... The target method is sendEmail and the parameters are: [hello] An email is sent with the content: hello return notification is executed...... The target method is sendEmail. The parameter is: [hello]. The return value is:nullThe post notification is implemented at...... The target method is sendEmail and the parameter is [hello]Copy the code
Pre-notification is executed at...... The target method is sendEmail. The parameter is: [hello] An email is sent with the content: hello Exception notification...... is executed Target method is: the sendEmail, parameters: [hello], exception information as follows: Java. Lang. ArithmeticException: / rear by zero notice carried out... The target method is sendEmail and the parameter is [hello]Copy the code

The last

Finally, let’s talk about circular notification. As mentioned earlier, it can achieve the effect of the other four types of notification. The code is as follows:

@Around(value = "execution(* com.wwj.springaop.test.SendEmailService.sendEmail(..) )"
public Object around(ProceedingJoinPoint joinPoint) {
    Object result = null;
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());
    try {
        System.out.println("Pre-notification executed...... The target method is:" + methodName + ", the parameter is:" + args);
        result = joinPoint.proceed();// Execute the target method
        System.out.println("Return notification executed...... The target method is:" + methodName + ", the parameter is:" + args + ", return value:" + result);
    } catch (Throwable e) {
        System.out.println("Exception notification performed...... The target method is:" + methodName + ", the parameter is:" + args + ", the exception information is:" + e);
    } finally {
        System.out.println("Post notification is executed...... The target method is:" + methodName + ", the parameter is:" + args);
    }
    return result;
}
Copy the code

Does this look similar to dynamic proxies in the JDK? SpringAOP is essentially a dynamic proxy. If the class to be enhanced implements an interface, it will be enhanced in the manner provided by the JDK. If no interface is implemented, CGLib is used for enhancement.