Spring框架五、Spring AOP简介与使用

AOP:Aspect Oriented Programming 面向切面编程。

OOP:Object Oriented Programming 面向对象编程。

面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。AOP是Spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC做补充。

通俗点说的话就是在程序运行期间,将某段代码动态切入指定方法指定位置进行运行的这种编程方式。

一、AOP的概念

1、为什么要引入AOP?

1、下面看一个例子,实现加减乘除:

Calculator.java

package com.bobo;

public interface Calculator {
    public int add(int i,int j);
    public int sub(int i,int j);
    public int mul(int i,int j);
    public int div(int i,int j);
}

MyCalculator.java

package com.bobo;

public class MyCalculator implements Calculator {
    public int add(int i, int j) {
        return i + j;
    }

    public int sub(int i, int j) {
        return i - j;
    }

    public int mul(int i, int j) {
        return i * j;
    }

    public int div(int i, int j) {
        return i / j;
    }
}

测试类

public class MyTest {
    @Test
    public void test01() {
        Calculator calculator = new MyCalculator();
        System.out.println(calculator.add(1, 2));
    }
}

2、此代码非常简单,就是基础的javase的代码实现,此时如果需要添加日志功能应该怎么做呢,只能在每个方法中添加日志输出,同时如果需要修改的话会变得非常麻烦。

MyCalculator.java

package com.bobo;

public class MyCalculator implements Calculator {
    public int add(int i, int j) {
        System.out.println("add方法开始执行i:" + i + " j:" + j);
        int result = i + j;
        System.out.println("add方法执行结束result:" + result);
        return result;
    }

    public int sub(int i, int j) {
        System.out.println("sub方法开始执行i:" + i + " j:" + j);
        int result = i - j;
        System.out.println("sub方法执行结束result:" + result);
        return result;
    }

    public int mul(int i, int j) {
        System.out.println("mul方法开始执行i:" + i + " j:" + j);
        int result = i * j;
        System.out.println("mul方法执行结束result:" + result);
        return result;
    }

    public int div(int i, int j) {
        System.out.println("div方法开始执行i:" + i + " j:" + j);
        int result = i / j;
        System.out.println("div方法执行结束result:" + result);
        return result;
    }
}

3、可以考虑将日志的处理抽象出来,变成工具类来进行实现:

LogUtil.java

package com.bobo;

import java.util.Arrays;

public class LogUtil {
    public static void start(Object...objects){
        System.out.println("XXX方法开始执行,参数为:"+ Arrays.toString(objects));
    }
    public static void stop(Object...objects){
        System.out.println("XXX方法执行结束,结果为:"+ Arrays.toString(objects));
    }
}

MyCalculator.java

package com.bobo;

public class MyCalculator implements Calculator {
    public int add(int i, int j) {
        LogUtil.start(i,j);
        int result = i + j;
        LogUtil.stop(i,j);
        return result;
    }

    public int sub(int i, int j) {
        LogUtil.start(i,j);
        int result = i - j;
        LogUtil.stop(i,j);
        return result;
    }

    public int mul(int i, int j) {
        LogUtil.start(i,j);
        int result = i * j;
        LogUtil.stop(i,j);
        return result;
    }

    public int div(int i, int j) {
        LogUtil.start(i,j);
        int result = i / j;
        LogUtil.stop(i,j);
        return result;
    }
}

4、按照上述方式抽象之后,代码确实简单很多,但是大家应该已经发现在输出的信息中并不包含具体的方法名称,我们更多的是想要在程序运行过程中动态的获取方法的名称及参数、结果等相关信息,此时可以通过使用动态代理的方式来进行实现。

CalculatorProxy.java

package com.bobo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class CalculatorProxy {
    public static Calculator getCalculatorProxy(final Calculator calculator) {
        //被代理对象的类加载器
        ClassLoader classLoader = calculator.getClass().getClassLoader();
        //被代理对象实现的接口
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        //方法执行器,执行被代理对象的目标方法
        InvocationHandler handler = new InvocationHandler() {
            /**
             * 执行目标方法
             * @param proxy 代理对象,给jdk使用,任何时候都不要操作此对象
             * @param method 当前将要执行的目标对象的方法
             * @param args 这个方法调用时外界传入的参数值
             * @return
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    LogUtil.start(method, args);
                    //利用反射执行目标方法,目标方法执行后的返回值
                    result = method.invoke(calculator, args);
                    LogUtil.stop(method, args);
                } catch (Exception e) {
                    LogUtil.logException(method, e);
                } finally {
                    LogUtil.end(method);
                }
                return result;
            }
        };
        return (Calculator) Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}

LogUtil.java

package com.bobo;

import java.lang.reflect.Method;
import java.util.Arrays;

public class LogUtil {
    public static void start(Method method, Object ... objects){
        System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(objects));
    }

    public static void stop(Method method,Object ... objects){
        System.out.println(method.getName()+"方法执行完成,参数是:"+ Arrays.asList(objects));

    }

    public static void logException(Method method,Exception e){
        System.out.println(method.getName()+"方法出现异常:"+ e.getMessage());
    }

    public static void end(Method method){
        System.out.println(method.getName()+"方法执行结束了......");
    }
}

上述代码感觉已经非常完美了,而在Spring中我们可以不编写上述如此复杂的代码,只需要利用AOP,就能够轻轻松松实现上述功能,当然,Spring AOP的底层实现也依赖的是动态代理。

2、AOP的核心概念及术语

  • 切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以@Aspect注解(@AspectJ 注解方式)来实现。
  • 连接点(Join point): 在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
  • 通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。通知的类型将在后面的章节进行讨论。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
  • 切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。
  • 引入(Introduction): 声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现 IsModified接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。
  • 目标对象(Target object): 被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。
  • AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
  • 织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。

3、AOP的通知类型

  • 前置通知(Before advice): 在连接点之前运行但无法阻止执行流程进入连接点的通知(除非它引发异常)。
  • 后置返回通知(After returning advice):在连接点正常完成后执行的通知(例如,当方法没有抛出任何异常并正常返回时)。
  • 后置异常通知(After throwing advice): 在方法抛出异常退出时执行的通知。
  • 后置通知(总会执行)(After (finally) advice): 当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
  • 环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型,。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。

4、AOP的应用场景

  • 日志管理
  • 权限认证
  • 安全检查
  • 事务控制

二、Spring AOP的简单配置

在上述代码中我们是通过动态代理的方式实现日志功能的,但是比较麻烦,现在我们将要使用spring aop的功能实现此需求,其实通俗点说的话,就是把LogUtil的工具类换成另外一种实现方式。

1、添加pom依赖

添加pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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.bobo</groupId>
    <artifactId>MyAopProject</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <!--https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
    </dependencies>

</project>

2、编写配置

1、将目标类和切面类加入到IOC容器中,在对应的类上添加组件注解

  • 给LogUtil添加@Component注解
  • 给MyCalculator添加@Service注解
  • 添加自动扫描的配置
<context:component-scan base-package="com.bobo"></context:component-scan>

2、设置程序中的切面类

在LogUtil.java中添加@Aspect注解

3、设置切面类中的方法是什么时候在哪里执行

package com.bobo;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
@Component
@Aspect
public class LogUtil {
    @Before("execution(public int com.bobo.MyCalculator.*(int,int))")
    public static void start(){
        System.out.println("方法开始执行,参数是:");
    }
    @AfterReturning("execution(public int com.bobo.MyCalculator.*(int,int))")
    public static void stop(){
        System.out.println("方法执行完成,结果是:");

    }
    @AfterThrowing("execution(public int com.bobo.MyCalculator.*(int,int))")
    public static void logException(){
        System.out.println("方法出现异常:");
    }
    @After("execution(public int com.bobo.MyCalculator.*(int,int))")
    public static void end(){
        System.out.println("方法执行结束了......");
    }
}

4、开启基于注解的aop的功能

<?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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.bobo"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

5、测试

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public void test01() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculator calculator=context.getBean( "myCalculator",Calculator.class);
        calculator.add(1, 2);
    }
}

6、运行输出

方法开始执行,参数是:
方法执行结束了......
方法执行完成,结果是:

3、cglib方式来创建代理对象

上述例子我们可以把代理对象打印出来:

import com.bobo.Calculator;
import com.bobo.LogUtil;
import com.bobo.MyCalculator;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public void test01() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculator calculator=context.getBean( Calculator.class);
        calculator.add(1, 2);
        System.out.println(calculator.getClass());
    }
}

输出结果:

方法开始执行,参数是:
方法执行结束了......
方法执行完成,结果是:
class com.sun.proxy.$Proxy23

可以看到:class com.sun.proxy.$Proxy23是JDK自带的代理类。

接下来我们通过cglib来创建代理对象:只需要把我们的MyCalculator.java中实现的Calculator.java的接口去掉就行了

MyCalculator.java

package com.bobo;

import org.springframework.stereotype.Service;

@Service
public class MyCalculator{
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}

测试类

public class MyTest {
    @Test
    public void test01() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyCalculator myCalculator=context.getBean( MyCalculator.class);
        myCalculator.add(1, 2);
        System.out.println(myCalculator.getClass());
    }
}

输出结果:

方法开始执行,参数是:
方法执行结束了......
方法执行完成,结果是:
class com.bobo.MyCalculator$$EnhancerBySpringCGLIB$$8951f0ea

可以看到,打印的是class com.bobo.MyCalculator E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB8951f0ea,是cglib的代理类。

综上所述:在spring容器中,如果有接口,那么会使用jdk自带的动态代理,如果没有接口,那么会使用cglib的动态代理。

三、Spring AOP的基本使用

1、切入点表达式

在使用表达式的时候,除了之前的写法之外,还可以使用通配符的方式:

*:星号通配符

  • 匹配一个或者多个字符:execution( public int com.mashibing.inter.My*alculator.*(int,int))。
  • 匹配任意一个参数:execution( public int com.mashibing.inter.MyCalculator.*(int,*))。
  • 只能匹配一层路径,如果项目路径下有多层目录,那么*只能匹配一层路径。
  • 权限位置不能使用*,如果想表示全部权限,那么不写即可execution( * com.mashibing.inter.MyCalculator.*(int,*))。

. .通配符

  • 匹配多个参数,任意类型参数execution( * com.mashibing.inter.MyCalculator.*(. .))。
  • 匹配任意多层路径execution( * com.mashibing. .MyCalculator.*(. .))

在写表达式的时候,可以有N多种写法,但是有一种最偷懒和最精确的方式:

  • 最偷懒方式:execution(* *(. .)) 或者 execution(* *.*(. .))
  • 最精确的方式:execution( public int com.mashibing.inter.MyCalculator.add(int,int))。

表达式中还支持 &&、||、!的方式

  • &&:两个表达式同时满足,execution( public int com.mashibing.inter.MyCalculator.*(…)) && execution(* *.*(int,int) )
  • ||:任意满足一个表达式即可,execution( public int com.mashibing.inter.MyCalculator.*(…)) && execution(* *.*(int,int) )。
  • !:只要不是这个位置都可以进行切入,

2、通知方法的执行顺序

  • 正常执行:@Before—>@After—>@AfterReturning
  • 异常执行:@Before—>@After—>@AfterThrowing

3、获取方法的详细信息以及执行结果和异常信息

在上面的案例中,我们并没有获取Method的详细信息,例如方法名、参数列表等信息,想要获取的话其实非常简单,只需要添加JoinPoint参数即可。

  • 通过JoinPoint参数获取方法名,方法参数等信息。
  • 通过returning = "参数名"获取返回值信息
  • 通过throwing = "异常信息名"获取异常信息

LogUtil.java

package com.bobo;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

@Component
@Aspect
public class LogUtil {
    @Before("execution(public int com.bobo.MyCalculator.*(..))")
    public static void start(JoinPoint joinPoint) {
        //参数列表
        Object[] args = joinPoint.getArgs();
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法开始执行,参数是:" + Arrays.toString(args));
    }

    @AfterReturning(value = "execution(public int com.bobo.MyCalculator.*(..))", returning = "result")
    public static void stop(JoinPoint joinPoint, Object result) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行完成,结果是:" + result);

    }

    @AfterThrowing(value = "execution(public int com.bobo.MyCalculator.*(int,int))", throwing = "e")
    public static void logException(JoinPoint joinPoint, Exception e) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法出现异常:" + e);
    }

    @After("execution(public int com.bobo.MyCalculator.*(int,int))")
    public static void end(JoinPoint joinPoint) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束了......");
    }
}

单元测试

public class MyTest {
    @Test
    public void test01() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyCalculator myCalculator=context.getBean( MyCalculator.class);
        myCalculator.add(1, 2);
    }
}

运行输出:

add方法开始执行,参数是:[1, 2]
add方法执行结束了......
add方法执行完成,结果是:3

4、spring对通过方法的要求

spring对于通知方法的要求并不是很高,你可以任意改变方法的返回值和方法的访问修饰符,但是唯一不能修改的就是方法的参数,会出现参数绑定的错误,原因在于通知方法是spring利用反射调用的,每次方法调用得确定这个方法的参数的值。

    @After("execution(public int com.bobo.MyCalculator.*(int,int))")
    private int end(JoinPoint joinPoint) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束了......");
        return 0;
    }

5、表达式的抽取

如果在实际使用过程中,多个方法的表达式是一致的话,那么可以考虑将切入点表达式抽取出来:

  1. 随便声明一个没有实现的返回void的空方法。
  2. 给方法上标注@Potintcut注解。
package com.bobo;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

@Component
@Aspect
public class LogUtil {
    @Pointcut("execution(public int com.bobo.MyCalculator.*(..))")
    private void myPoint(){}

    @Before("myPoint()")
    public static void start(JoinPoint joinPoint) {
        //参数列表
        Object[] args = joinPoint.getArgs();
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法开始执行,参数是:" + Arrays.toString(args));
    }

    @AfterReturning(value = "myPoint()", returning = "result")
    public static void stop(JoinPoint joinPoint, Object result) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行完成,结果是:" + result);

    }

    @AfterThrowing(value = "myPoint()", throwing = "e")
    public static void logException(JoinPoint joinPoint, Exception e) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法出现异常:" + e);
    }

    @After("myPoint()")
    public static void end(JoinPoint joinPoint) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束了......");
    }
}

6、环绕通知的使用

LogUtil.java

package com.bobo;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

@Component
@Aspect
public class LogUtil {
    @Pointcut("execution(public int com.bobo.MyCalculator.*(..))")
    private void myPoint() {
    }

    @Before("myPoint()")
    public static void start(JoinPoint joinPoint) {
        //参数列表
        Object[] args = joinPoint.getArgs();
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法开始执行,参数是:" + Arrays.toString(args));
    }

    @AfterReturning(value = "myPoint()", returning = "result")
    public static void stop(JoinPoint joinPoint, Object result) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行完成,结果是:" + result);

    }

    @AfterThrowing(value = "myPoint()", throwing = "e")
    public static void logException(JoinPoint joinPoint, Exception e) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法出现异常:" + e);
    }

    @After("myPoint()")
    public static void end(JoinPoint joinPoint) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束了......");
    }

    @Around("myPoint()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) {
        //方法名字
        String name = proceedingJoinPoint.getSignature().getName();
        //方法参数
        Object[] args = proceedingJoinPoint.getArgs();
        Object result = null;
        System.out.println("环绕前置通知:" + name + "方法开始,参数是" + Arrays.asList(args));
        try {
            result = proceedingJoinPoint.proceed(args);
            System.out.println("环绕返回通知:" + name + "方法返回,返回值是" + result);
        } catch (Throwable throwable) {
            System.out.println("环绕异常通知" + name + "方法出现异常,异常信息是:" + throwable);
        } finally {
            System.out.println("环绕后置通知" + name + "方法结束");
        }
        return result;
    }
}

单元测试

public class MyTest {
    @Test
    public void test01() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyCalculator myCalculator=context.getBean( MyCalculator.class);
        myCalculator.add(1, 2);
    }
}

运行输出:

环绕前置通知:add方法开始,参数是[1, 2]
add方法开始执行,参数是:[1, 2]
环绕返回通知:add方法返回,返回值是3
环绕后置通知add方法结束
add方法执行结束了......
add方法执行完成,结果是:3

总结:环绕通知的执行顺序是优于普通通知的,具体的执行顺序如下:

环绕前置–>普通前置–>目标方法执行–>环绕正常结束/出现异常–>环绕后置–>普通后置–>普通返回或者异常。

但是需要注意的是,如果出现了异常,那么环绕通知会处理或者捕获异常,普通异常通知是接收不到的,因此最好的方式是在环绕异常通知中向外抛出异常。

7、多切面运行的顺序

LogUtil.java

package com.bobo;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

@Component
@Aspect
public class LogUtil {
    @Pointcut("execution(public int com.bobo.MyCalculator.*(..))")
    private void myPoint() {
    }

    @Before("myPoint()")
    public static void start(JoinPoint joinPoint) {
        //参数列表
        Object[] args = joinPoint.getArgs();
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "Log---方法开始执行,参数是:" + Arrays.toString(args));
    }

    @AfterReturning(value = "myPoint()", returning = "result")
    public static void stop(JoinPoint joinPoint, Object result) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "Log---方法执行完成,结果是:" + result);

    }

    @AfterThrowing(value = "myPoint()", throwing = "e")
    public static void logException(JoinPoint joinPoint, Exception e) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "Log---方法出现异常:" + e);
    }

    @After("myPoint()")
    public static void end(JoinPoint joinPoint) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "Log---方法执行结束了......");
    }
}

SecurityUtil.java

package com.bobo;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect
public class SecurityUtil {
    @Pointcut("execution(public int com.bobo.MyCalculator.*(..))")
    private void myPoint() {
    }

    @Before("myPoint()")
    public static void start(JoinPoint joinPoint) {
        //参数列表
        Object[] args = joinPoint.getArgs();
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "Security---方法开始执行,参数是:" + Arrays.toString(args));
    }

    @AfterReturning(value = "myPoint()", returning = "result")
    public static void stop(JoinPoint joinPoint, Object result) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "Security---方法执行完成,结果是:" + result);

    }

    @AfterThrowing(value = "myPoint()", throwing = "e")
    public static void logException(JoinPoint joinPoint, Exception e) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "Security---方法出现异常:" + e);
    }

    @After("myPoint()")
    public static void end(JoinPoint joinPoint) {
        //方法信息
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "Security---方法执行结束了......");
    }
}

测试类

public class MyTest {
    @Test
    public void test01() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyCalculator myCalculator=context.getBean( MyCalculator.class);
        myCalculator.add(1, 2);
    }
}

运行结果如下:

addLog---方法开始执行,参数是:[1, 2]
addSecurity---方法开始执行,参数是:[1, 2]
addSecurity---方法执行结束了......
addSecurity---方法执行完成,结果是:3
addLog---方法执行结束了......
addLog---方法执行完成,结果是:3

在spring中,默认是按照切面名称的字典顺序进行执行的,但是如果想自己改变具体的执行顺序的话,可以使用@Order注解来解决,数值越小,优先级越高。

在SecurityUtil.java上面添加注解@Order(1),在LogUtil.java上面添加注解@Order(2),运行结果如下:

addSecurity---方法开始执行,参数是:[1, 2]
addLog---方法开始执行,参数是:[1, 2]
addLog---方法执行结束了......
addLog---方法执行完成,结果是:3
addSecurity---方法执行结束了......
addSecurity---方法执行完成,结果是:3

四、基于xml的AOP配置

之前我们讲解了基于注解的AOP配置方式,下面我们看一下基于xml的配置方式,虽然在现在的企业级开发中使用注解的方式比较多,但是你不能不会,因此需要简单的进行配置,注解配置快速简单。

1、将所有的注解都进行删除。

2、添加配置文件。

<?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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.bobo"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <bean id="myCalculator" class="com.bobo.MyCalculator"></bean>
    <bean id="logUtil" class="com.bobo.LogUtil"></bean>

    <aop:config>

        <aop:aspect ref="logUtil">
            <aop:pointcut id="logPoint" expression="execution(public int com.bobo.MyCalculator.*(int,int))"/>
            <aop:before method="start" pointcut-ref="logPoint"></aop:before>
            <aop:after method="end" pointcut-ref="logPoint"></aop:after>
            <aop:after-returning method="stop" pointcut-ref="logPoint" returning="result"></aop:after-returning>
            <aop:after-throwing method="logException" pointcut-ref="logPoint" throwing="e"></aop:after-throwing>
            <aop:around method="myAround" pointcut-ref="logPoint"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃透Java

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值