Spring Framework Fully Mastered

Posted by touchingvirus on Mon, 26 Aug 2019 05:58:05 +0200

Next to the content of the previous article Spring Framework Fully Mastered (Part I) We continue to gain insight into the Spring framework.

Spring_AOP

Considering that AOP is very important in Spring, it is necessary to take it out and say it alone. So this article basically talks about AOP programming for Spring.

brief introduction

Let's start with an example:

package com.itcast.spring.bean.calc;

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @Override
    public int add(int num1, int num2) {
        int result = num1 + num2;
        return result;
    }

    @Override
    public int sub(int num1, int num2) {
        int result = num1 - num2;
        return result;
    }

    @Override
    public int mul(int num1, int num2) {
        int result = num1 * num2;
        return result;
    }

    @Override
    public int div(int num1, int num2) {
        int result = num1 / num2;
        return result;
    }
}

This is an implementation class of four arithmetic interfaces, which can add, subtract, multiply and divide between two numbers. At this time, we have a requirement that log information must be output before and after each method is executed, so we have to add log information to each method:

...
@Override
    public int add(int num1, int num2) {
        System.out.println("add method start with[" + num1 + "," + num2 + "]");
        int result = num1 + num2;
        System.out.println("add method start with[" + num1 + "," + num2 + "]");
        return result;
}
...

What is the problem?

  1. Code confusion: With more and more non-business requirements (such as logging, parameter validation, etc.) added, the original business methods have expanded dramatically, and each method must take into account multiple other concerns while dealing with the core logic.
  2. Code Decentralization: For example, in order to meet this single requirement, we have to repeat the same log code many times in multiple modules. If the log requirement changes, we must modify the log code in all modules.

Now that the problem has arisen, how can we solve it? (using dynamic proxy)

public class ArithmeticCalculatorLoggingProxy {

    private ArithmeticCalculator target;

    public ArithmeticCalculator getLoggingProxy() {
        ArithmeticCalculator proxy = null;

        ClassLoader loader = target.getClass().getClassLoader();
        Class[] interfaces = new Class[] { ArithmeticCalculator.class };
        InvocationHandler h = new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method.getName() + "method start with[ " + Arrays.asList(args) + "]");
                Object result = method.invoke(target, args);
                System.out.println(method.getName() + "method end with[ " + result + "]");
                return result;
            }
        };
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
        return proxy;
    }
}

In this way, we can get the proxy object to realize the log business without changing the basic business code.
In fact, this implementation is a little troublesome, but don't worry, the Spring framework provides us with an implementation way - AOP.
AOP(Aspect-Oriented Programming): This is a new methodology and a complement to the traditional OOP(Object-Oriented Programming), the main programming object of AOP is Aspect.
When programming with AOP, you still need to define common functions, but you can clearly define where and how this function is applied without modifying the affected classes, so that cross-cutting concerns are modularized into specific objects.
Benefits:

  1. Everything logic is in one place, the code is not scattered, easy to maintain and upgrade.
  2. Business modules are simpler and contain only core business code

In this way, AOP can solve our problems very accurately.

Before advice

In Spring, AOP based on AspectJ annotations or XML configuration can be used. AspectJ is the most complete and popular AOP framework in the Java community, so let's take AspectJ annotation as an example.
First import the jar package of the AOP framework:

Then we modify it in the above case:

@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @Override
    public int add(int num1, int num2) {
        int result = num1 + num2;
        return result;
    }

    @Override
    public int sub(int num1, int num2) {
        int result = num1 - num2;
        return result;

    }

    @Override
    public int mul(int num1, int num2) {
        int result = num1 * num2;
        return result;
    }

    @Override
    public int div(int num1, int num2) {
        int result = num1 / num2;
        return result;
    }
}

A comment is added at the beginning of the implementation class to leave the class under Spring container management without any changes in other code.

//Declare this class as a facet
@Aspect
@Component
public class LoggingAspect {

    // Declare that this method is a pre-notification: execute before the target method starts
    @Before("execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.add(int,int))")
    public void beforeMethd(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName + " method start with" + args);
    }
}

Then we look at the business of exporting logs as a facet, create a class, and arbitrarily define a method that adds a comment: Before. Used to declare that this method is a pre-notification, which is executed before the target method starts. So we also need to declare the target method in Before. This method can add a parameter of JoinPoint type, and the method name and parameters of the execution method are encapsulated in the object. Second, the class must also be managed by the Spring container, so add the annotation @Component, and the class is a facet, add the annotation @Aspect.
Then configure in the configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <!-- Configuring Packages for Automatic Scanning -->
    <context:component-scan
        base-package="com.itcast.aop.impl"></context:component-scan>
    
    <!-- send AspjectJ Annotations work:Automatic generation of proxy objects for matching classes -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

In this way, the framework automatically finds matching classes and generates proxy objects.
Finally, write the test code:

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator ac = ctx.getBean(ArithmeticCalculator.class);
        int result = ac.add(1, 1);
        System.out.println("result:" + result);
    }

Operation results:

add method start with[1, 1]
result:2

But when you call other methods, you find that the log information can't be printed again, because when you configure the target method, you configure only the add() method, so you can configure all the methods in the class by wildcards.

@Before("execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.*(int,int))")

Exution here means execution, that is to say, the target method is filled in parentheses of the attribute. For the target method, it can be expressed more abstractly, such as permission modifiers, return values, etc. can be replaced by wildcards.
At this point, Spring AOP easily realizes the problems we are starting to encounter.

after returning advise

Since there is a pre-notification, there must be a post-notification. The implementation of post-notification is similar to that of pre-notification.

@After("execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.*(int,int))")
public void afterMetohd(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());
    System.out.println(methodName + " method ends with" + args);
}

Running the test code, the results are as follows:

add method start with[1, 1]
add method ends with[1, 1]
result:2

Post-notification is executed after the target method is executed, but it should be noted that, regardless of whether the target method is successfully executed or not, even if an exception occurs during the execution of the target method, the post-notification will still be executed, and the execution result of the target method can not be accessed in the post-notification.

Notification of return

The return notification is similar to the post-notification, but the return notification is only executed after the correct execution of the target method. If there is an error in the execution of the target method, the return notification will not work. So the return notification can obtain the execution result of the target method:

    // Declare that the method is a return notification: executed after the normal execution of the method
    // Return notifications are accessible to the return value of the target method
    @AfterReturning(value = "execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.*(int,int))", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName + " method ends with" + result);
    }

Operation results:

add method start with[1, 1]
add method ends with[1, 1]
add method ends with2
result:2

Exception Notification

Exception notification is executed after an exception occurs during the execution of the target method. Exception notification can obtain the exception information generated by the target method:

    // Declare that the method is an exception notification: executed when the method execution produces an exception
    // Exception notifications can capture the generated exception information
    @AfterThrowing(value = "execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.*(int,int))", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName + " method's exception is " + ex);
    }

Let's test by artificially generating an anomaly:

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator ac = ctx.getBean(ArithmeticCalculator.class);
        
        result = ac.div(10, 0);
        System.out.println("result:" + result);
    }

Operation results:

div method start with[10, 0]
div method ends with[10, 0]
div method's exception is java.lang.ArithmeticException: / by zero

Around Advice

For surround notifications, this is the most powerful notification of all notifications, which is not commonly used, but we still need to understand its usage:

    // Declare that this method is a surround notification, which needs to carry parameters of ProceedingJoinPoint type
    // The whole process of surround notification is similar to dynamic proxy
    // The parameters of the ProceedingJoinPoint type can determine whether the target method is executed or not.
    // And the circular notification must have a return value, which returns the return value of the target method.
    @Around(value = "execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.*(int,int))")
    public Object aroundMethod(ProceedingJoinPoint point) {
        Object result = null;
        String methodName = point.getSignature().getName();
        // Target implementation approach
        try {
            // Before advice
            System.out.println(methodName + " method' start with" + Arrays.asList(point.getArgs()));
            result = point.proceed();
            // Notification of return
            System.out.println(methodName + " method' end with " + result);
        } catch (Throwable e) {
            // Exception Notification
            System.out.println(methodName + " method's exception is " + e);
        }
        // after returning advise
        System.out.println(methodName + " method' end with");
        return result;
    }

Circumferential notification can perform all other notification functions, but it has many limitations.

  • You must carry parameters of type ProceedingJoinPoint
  • The circular notification must have a return value, which is the return value of the target method.

Test code:

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator ac = ctx.getBean(ArithmeticCalculator.class);
        int result = ac.add(1, 1);
        System.out.println("result:" + result);
}

Operation results:

add method' start with[1, 1]
add method' end with 2
add method' end with
result:2

Priority of section

In projects with multiple facets, we can specify the priority of facets and determine the execution order of facets. Use the @Order() annotation to configure the priority (annotation at the beginning of the class), and fill an integer in parentheses. The smaller the value, the higher the priority.
For example:

@Order(1)
public class LoggingAspect {
......
......
}

This is what Spring AOP is all about. If there are any mistakes, please correct them.

Topics: Java Spring Programming xml