Spring08:【aop】Spring AOP介绍与使用

AOP进化过程

需求:在原有代码基础上,添加日志功能

案例演示1:手工添加日志

1、新建maven项目,导入pom依赖

<dependencies>
   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.1</version>
      <scope>test</scope>
   </dependency>
</dependencies>

2、新建接口

package com.lian.service;

public interface Calculator {

    int add(int i,int j);

    int sub(int i,int j);

    int mul(int i,int j);

    int div(int i,int j);
}

3、新建实现类,添加日志内容

package com.lian.service;

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

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

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

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

4、测试类

import com.lian.service.MyCalculator;
import org.junit.Test;

public class MyTest {

    @Test
    public void test1(){
        MyCalculator myCalculator = new MyCalculator();
        System.out.println(myCalculator.add(1, 2));
    }
}

注:如上代码只能在每个方法中添加日志输出,如果需要修改比较麻烦

案例演示2:封装工具类实现

1、导入pom依赖(同上)

2、新建接口(同上)

3、新建LogUtil类

改进:将日志的处理抽象出来,变成工具类来进行实现:

package com.lian.util;

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));
    }
}

4、修改实现类添加日志功能

package com.lian.service;

import com.lian.util.LogUtil;

import java.lang.reflect.Method;

public class MyCalculator implements Calculator{

    public int add(Integer i, Integer j) throws NoSuchMethodException {
        Method add = MyCalculator.class.getMethod("add", Integer.class, Integer.class);
        LogUtil.start(add,i,j);
        int result = i + j;
        LogUtil.stop(add,result);
        return result;
    }

    public int sub(Integer i, Integer j) throws NoSuchMethodException {
        Method sub = MyCalculator.class.getMethod("sub", Integer.class, Integer.class);
        LogUtil.start(sub,i,j);
        int result = i - j;
        LogUtil.stop(sub,result);
        return result;
    }

    public int mul(Integer i, Integer j) throws NoSuchMethodException {
        Method mul = MyCalculator.class.getMethod("mul", Integer.class, Integer.class);
        LogUtil.start(mul,i,j);
        int result = i * j;
        LogUtil.stop(mul,result);
        return result;
    }

    public int div(Integer i, Integer j) throws NoSuchMethodException {
        Method div = MyCalculator.class.getMethod("div", Integer.class, Integer.class);
        LogUtil.start(div,i,j);
        Integer result = i / j;
        LogUtil.stop(div,result);
        return result;
    }
}

5、测试类(同上)

注:基本类型用包装类可避免出错
在这里插入图片描述
按照上述方式抽象之后,代码确实简单很多,但是我们想要在程序运行过程中动态的获取方法的名称、参数、结果等相关信息,此时可以通过使用动态代理的方式来进行实现

案例演示3:动态代理实现

Proxy 核心方法

/**
 * Returns an instance of a proxy class for the specified interfaces
 * that dispatches method invocations to the specified invocation
 * handler.
 * 返回指定接口的代理类的实例,将方法调用分派到指定的调用处理程序。
 */
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

1、导入pom依赖(同上)

2、新建接口(同上)

3、新建实现类

package com.lian.service;

public class MyCalculator implements Calculator{

    public int add(Integer i, Integer j) throws NoSuchMethodException {
        int result = i + j;
        return result;
    }

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

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

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

4、新建LogUtil类

package com.lian.util;

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("方法抛出异常:");
    }

    public static void logFinally(Method method){
        System.out.println("方法执行结束。。。。。over");
    }
}

5、新建CalculatorProxy类

package com.lian.proxy;

import com.lian.service.Calculator;
import com.lian.util.LogUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * 必须要有接口,如果没有接口,不能使用,这种方式是用jdk提供的reflect包下的类
 * 但是在生产环境中我不能保证每个类都有实现的接口,所有有第二种方式cglib
 *
 * cglib在实现的时候有没有接口都无所谓
 *
 * 在spring中使用了两种动态代理的方式,一种是jdk提供的(刚刚完成的)另外一种就是cglib
 */
public class CalculatorProxy {
    /**
     * Variable 'calculator' is accessed from within inner class, needs to be declared final
     * 变量calculator从内部类中访问,需要声明为final
     */
    public static Calculator getCalculator(final Calculator calculator){

        //获取被代理对象的类加载器
        ClassLoader loader = calculator.getClass().getClassLoader();
        //被代理对象的所有接口
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        /**
          *  执行目标方法
          * @param proxy 代理对象,给jdk使用,任何时候都不要操作此对象
          * @param method 当前将要执行的目标对象的方法
          * @param args 这个方法调用时外界传入的参数值
          */
        //用来执行被代理类需要执行的方法
        InvocationHandler handler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    //方法1:直接输出打印
                    //System.out.println("invoke方法被执行了");
                    //System.out.println(method.getName()+"方法开始执行,参数列表是"+ Arrays.asList(args));
                    //方法2:使用LogUtil封装类
                    LogUtil.start(method,args);
                    //开始调用被代理类的方法
                    result = method.invoke(calculator,args);
                    System.out.println(method.getName()+"方法执行完成,结果是:"+result);
                } catch (Exception e){
                    //System.out.println(method.getName()+"方法抛出异常:"+e.getMessage());
                    //e.printStackTrace();
                    LogUtil.logException(method,e);
                } finally {
                    //System.out.println(method.getName() + "方法执行结束");
                    LogUtil.logFinally(method);
                }
                return result;
            }
        };

        Object o = Proxy.newProxyInstance(loader, interfaces, handler);
        return (Calculator) o;
    }
}

6、测试类

import com.lian.proxy.CalculatorProxy;
import com.lian.service.Calculator;
import com.lian.service.MyCalculator;
import org.junit.Test;

public class MyTest {

    @Test
    public void test1() throws NoSuchMethodException {
        Calculator calculator = CalculatorProxy.getCalculator(new MyCalculator());
        calculator.add(1,2);
        calculator.sub(1,2);
        calculator.mul(1,2);
        calculator.div(1,1);
        System.out.println(calculator.getClass());
    }
}

很多同学看到上述代码之后可能感觉已经非常完美了,但是要说明的是,这种动态代理的实现方式调用的是jdk的基本实现,如果需要代理的目标对象没有实现任何接口,那么是无法为他创建代理对象的,这也是致命的缺陷。而在Spring中我们可以不编写上述如此复杂的代码,只需要利用AOP,就能够轻轻松松实现上述功能,当然,Spring AOP的底层实现也依赖的是动态代理。

案例演示4:aop增加日志功能

什么是AOP?

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在这里插入图片描述

AOP 和 OOP的关系

AOP:Aspect Oriented Programming 面向切面编程
OOP:Object Oriented Programming 面向对象编程

面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。AOP是Spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC做补充。通俗点说的话就是在程序运行期间,将某段代码动态切入指定方法指定位置进行运行的这种编程方式。

在这里插入图片描述

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框架一样,是在运行时完成织入的。

aop补充理解

切面(Aspect):其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。

通知(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。

连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但Spring只支持方法级的连接点。

切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。

目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。

代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。

织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。

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

1、Spring AOP的简单配置

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

1、添加pom依赖

	<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--spring-aop-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.2</version>
        </dependency>
        <!--spring-context-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.2</version>
        </dependency>
        <!--cglib-->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.12</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.3</version>
        </dependency>
    </dependencies>

2、配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.lian"/>
    <!--开启aop的注解功能-->
    <aop:aspectj-autoproxy/>

</beans>

3新建接口(同上)

4、新建实现类

package com.lian.service;

import org.springframework.stereotype.Service;

@Service
public class MyCalculator implements Calculator{

    public Integer add(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i + j;
        return result;
    }

    public Integer sub(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i - j;
        return result;
    }

    public Integer mul(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i * j;
        return result;
    }

    public Integer div(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i / j;
        return result;
    }
}

5、编写LogUtil工具类

package com.lian.util;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogUtil {
    /**
     * 通知注解有以下几种类型:
     *
     * @Before:前置通知,在方法执行之前完成
     * @After:后置通知,在方法执行完成之后执行
     * @AfterReturing:返回通知:在返回结果之后运行
     * @AfterThrowing:异常通知:出现异常的时候使用
     * @Around:环绕通知
     *
     * 在方法的参数列表中不要随便添加参数值,会有异常信息
     */
    @Before("execution( public Integer com.lian.service.MyCalculator.add(Integer,Integer))")
    public static void start(){
    System.out.println("方法开始执行:"+"参数是");
    }

    @Before("execution( public Integer com.lian.service.MyCalculator.add(Integer,Integer))")
    public static void stop(){
        System.out.println("方法执行结束:"+"结果是");
    }

    @Before("execution( public Integer com.lian.service.MyCalculator.add(Integer,Integer))")
    public static void logException(){
        System.out.println("方法抛出异常:");
    }

    @Before("execution( public Integer com.lian.service.MyCalculator.add(Integer,Integer))")
    public static void logFinally(){
        System.out.println("方法执行结束。。。。。over");
    }
}

6、测试类

public class MyTest {

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

spring AOP的动态代理方式是jdk自带的方式,容器中保存的组件是代理对象com.sun.proxy.$Proxy对象

2、通过cglib来创建代理对象

1、添加pom依赖(同上)

2、配置applicationContext.xml(同上)

3、修改实现类,不实现接口

package com.lian.service;

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 mult(int i, int j) {
        int result = i * j;
        return result;
    }

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

4、编写LogUtil工具类(同上)

5、测试类

@Test
public void test03() throws NoSuchMethodException {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    //MyCalculator myCalculator = context.getBean(MyCalculator.class);
    MyCalculator myCalculator = context.getBean("myCalculator",MyCalculator.class);
    myCalculator.add(1,1);
    myCalculator.sub(1,1);
    myCalculator.mul(1,1);
    myCalculator.div(1,1);
    System.out.println(myCalculator.getClass());
}

可以通过cglib的方式来创建代理对象,此时不需要实现任何接口,代理对象是

class com.mashibing.service.MyCalculator E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB8e7e4b3b

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

小结

1、切入点表达式

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

1、星号*:

  • 1、匹配一个或者多个字符

    execution( public int com.mashibing.inter.My*alculator.*(int,int))

  • 2、匹配任意一个参数,

    execution( public int com.mashibing.inter.MyCalculator.*(int,*))

  1. 3、只能匹配一层路径,如果项目路径下有多层目录,那么*只能匹配一层路径

  2. 4、权限修饰符位置不能使用*,如果想表示全部权限,那么不写即可

    execution( * com.mashibing.inter.MyCalculator.*(int,*))

  3. 5、返回值可以使用 * 来代替

​ 2、点点 “…” :

1、匹配多个参数,任意类型参数

execution( * com.mashibing.inter.MyCalculator.\*(..))

2、匹配任意多层路径

execution( * com.mashibing..MyCalculator.\*(..))

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

如果表达式以 * 开头,那么可以代替所有

最偷懒的方式:execution(* *(…)) 或者 execution(* *.*(…))

最精确的方式:execution( public int com.mashibing.inter.MyCalculator.add(int,int))

使用通配符的时候,不是越简洁越好,更多的是选择符合要求或者符合规则的匹配方式

3、在表达式中还支持逻辑运算 &&、||、!的方式

  1. &&:两个表达式同时

    execution( public int com.mashibing.inter.MyCalculator.*(…)) && execution(* *.*(int,int) )

  2. ||:任意满足一个表达式即可

    execution( public int com.mashibing.inter.MyCalculator.*(…)) && execution(* *.*(int,int) )

  3. !:只要不是这个位置都可以进行切入

  4. &&:两个表达式同时

    execution( public int com.mashibing.inter.MyCalculator.*(…))

注:有时候 基本数据类型会报错,改为包装类即可

2、通知方法的执行顺序

通知的执行顺序:

1、正常执行:

@Before—>@After—>@AfterReturning

2、异常执行:

@Before—>@After—>@AfterThrowing

3、获取方法的详细信息

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

举个栗子:

1、添加pom依赖(同上)

2、配置applicationContext.xml(同上)

3、修改实现类,不实现接口(同上)

package com.lian.service;

import org.springframework.stereotype.Service;

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

    public Integer sub(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i-j;
        return result;
    }

    public Integer mul(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i*j;
        return result;
    }

    public Integer div(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i/j;
        return result;
    }
}

4、编写LogUtil工具类

package com.mashibing.util;

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;

@Aspect
@Component
public class LogUtil {
    /**
     * 通知注解有以下几种类型:
     *
     * @Before:前置通知,在方法执行之前完成
     * @After:后置通知,在方法执行完成之后执行
     * @AfterReturing:返回通知:在返回结果之后运行
     * @AfterThrowing:异常通知:出现异常的时候使用
     * @Around:环绕通知
     *
     * 在方法的参数列表中不要随便添加参数值,会有异常信息
     *
     * 如果想要在方法中获取对应的参数或者方法名称等信息的时候,必须要使用Joinpoint对象,并且此参数必须是第一个
     * getSignature()
     * getArgs()
     * 如果方法中有返回值,那么必须要在注解中添加 returning = "result",这个result必须要和参数列表中的参数名称保持一致
     * 如果需要添加异常信息,那么在注解中要添加 Throwing="e",这个e的名称必须要和参数列表中的名称保持一致
     * 如果想要添加其他参数,必须要添加args(参数列表),argNames(参数列表)
     */

    @Before(value = "execution( public Integer com.mashibing.service.MyCalculator.add(Integer,Integer)) && args(joinPoint,k)",argNames = "joinPoint,k")
    //@Before("execution( * com.mashibing.service.*.*(..))")
    public static void start(JoinPoint joinPoint,int k){
        //获取方法签名
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        //获取参数信息
        Object[] args = joinPoint.getArgs();
        System.out.println(name+"方法开始执行:参数是"+Arrays.asList(args));
    }
	
	/**
     * 刚刚只是获取了方法的信息,但是如果需要获取结果,还需要添加另外一个方法参数,并且告诉spring使用哪个参数来进行结果接收
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "execution( public Integer com.mashibing.service.MyCalculator.add(Integer,Integer))",returning = "result")
    public static void stop(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法执行结束,结果是:"+result);
    }
	
	/**
     * 也可以通过相同的方式来获取异常的信息
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "execution( public Integer com.mashibing.service.MyCalculator.add(Integer,Integer))",throwing = "e")
    public static void logException(JoinPoint joinPoint,Exception e){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法抛出异常:"+e.getMessage());
    }

    @After("execution( public Integer com.mashibing.service.MyCalculator.add(Integer,Integer))")
    public static void logFinally(){
        System.out.println("方法执行结束。。。。。over");
    }
}

5、测试(同上)

4、spring对通过方法的要求

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

5、表达式的抽取

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

a、随便定义一个没有实现的返回void的空方法

b、给方法上标注@Potintcut注解

package com.lian.util;

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

import java.util.Arrays;

@Component
@Aspect
public class LogUtil {
    
    //没有返回值的空方法,方法上标注@Potintcut注解
    @Pointcut("execution( public int com.lian.service.MyCalculator.*(int,int))")
    public void myPoint(){}
    
    //参数为 方法名
    @Before("myPoint()")
    public static void start(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法开始执行,参数是:"+ Arrays.asList(args));
    }

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

    }

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

    @After("myPoint()")
    private int end(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法执行结束了......");
        return 0;
    }
}

6、环绕通知的使用

package com.lian.util;

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

import java.util.Arrays;

@Component
@Aspect
public class LogUtil {
    @Pointcut("execution( public int com.lian.service.MyCalculator.*(int,int))")
    public void myPoint(){}
    
    /**
     * 环绕通知是spring中功能最强大的通知
     * @param proceedingJoinPoint
     * @return
     */
    @Around("myPoint()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();
        String name = proceedingJoinPoint.getSignature().getName();
        Object proceed = null;
        try {
            System.out.println("环绕前置通知:"+name+"方法开始,参数是"+Arrays.asList(args));
            //利用反射调用目标方法,就是method.invoke()
            proceed = proceedingJoinPoint.proceed(args);
            System.out.println("环绕返回通知:"+name+"方法返回,返回值是"+proceed);
        } catch (Throwable e) {
            System.out.println("环绕异常通知"+name+"方法出现异常,异常信息是:"+e);
        }finally {
            System.out.println("环绕后置通知"+name+"方法结束");
        }
        return proceed;
    }
}

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

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

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

7、多切面运行的顺序

如果有多个切面要进行执行,那么顺序是什么样的呢?

1、LogUtil 类

@Component
@Aspect
@Order(1)
public class LogUtil {
    @Pointcut("execution( public int com.lian.service.MyCalculator.*(int,int))")
    public void myPoint(){}
    
    @Before("myPoint()")
    public static void start(JoinPoint joinPoint){
//        System.out.println("XXX方法开始执行,使用的参数是:"+ Arrays.asList(objects));
//        System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(objects));
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println("Log:"+name+"方法开始执行,参数是:"+ Arrays.asList(args));
    }

    @AfterReturning(value = "myPoint()",returning = "result")
    public static void stop(JoinPoint joinPoint,Object result){
//        System.out.println("XXX方法执行结束,结果是:"+ Arrays.asList(objects));
//        System.out.println(method.getName()+"方法执行结束,结果是:"+ Arrays.asList(objects));
        String name = joinPoint.getSignature().getName();
        System.out.println("Log:"+name+"方法执行完成,结果是:"+result);

    }

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

    @After("myPoint()")
    private int end(JoinPoint joinPoint){
//        System.out.println(method.getName()+"方法执行结束了......");
        String name = joinPoint.getSignature().getName();
        System.out.println("Log:"+name+"方法执行结束了......");
        return 0;
    }

    /**
     * 环绕通知是spring中功能最强大的通知
     * @param proceedingJoinPoint
     * @return
     */
    //@Around("myPoint()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();
        String name = proceedingJoinPoint.getSignature().getName();
        Object proceed = null;
        try {
            System.out.println("环绕前置通知:"+name+"方法开始,参数是"+Arrays.asList(args));
            //利用反射调用目标方法,就是method.invoke()
            proceed = proceedingJoinPoint.proceed(args);
            System.out.println("环绕返回通知:"+name+"方法返回,返回值是"+proceed);
        } catch (Throwable e) {
            System.out.println("环绕异常通知"+name+"方法出现异常,异常信息是:"+e);
        }finally {
            System.out.println("环绕后置通知"+name+"方法结束");
        }
        return proceed;
    }
}

2、新增切面SecurityAspect类

@Component
@Aspect
@Order(2)
public class SecurityAspect {

    @Before("com.lian.util.LogUtil.myPoint()")
    public static void start(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println("Security:"+name+"方法开始执行,参数是:"+ Arrays.asList(args));
    }

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

    }

    @AfterThrowing(value = "com.lian.util.LogUtil.myPoint()",throwing = "exception")
    public static void logException(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        System.out.println("Security:"+name+"方法出现异常:"+exception.getMessage());
    }

    @After("com.lian.util.LogUtil.myPoint()")
    private int end(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println("Security:"+name+"方法执行结束了......");
        return 0;
    }

    /**
     * 环绕通知是spring中功能最强大的通知
     * @param proceedingJoinPoint
     * @return
     */
    //@Around("myPoint()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();
        String name = proceedingJoinPoint.getSignature().getName();
        Object proceed = null;
        try {
            System.out.println("环绕前置通知:"+name+"方法开始,参数是"+Arrays.asList(args));
            //利用反射调用目标方法,就是method.invoke()
            proceed = proceedingJoinPoint.proceed(args);
            System.out.println("环绕返回通知:"+name+"方法返回,返回值是"+proceed);
        } catch (Throwable e) {
            System.out.println("环绕异常通知"+name+"方法出现异常,异常信息是:"+e);
        }finally {
            System.out.println("环绕后置通知"+name+"方法结束");
        }
        return proceed;
    }
}

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

如果需要添加环绕通知呢,具体的执行顺序又会是什么顺序呢?
因为环绕通知在进行添加的时候,是在切面层引入的,所以在哪个切面添加环绕通知,那么就会在哪个切面执行。

基于xml配置AOP

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

1、导入pom依赖(同上)

2、新建 切入点类 MyCalculator(同上,无注解)

3、新建切面类,日志和安全类(同上,无注解)

LogUtil 类

package com.lian.util;

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

import java.util.Arrays;

public class LogUtil {

    public static void start(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println(name+"方法开始执行,参数是"+ Arrays.asList(args));
    }

    public static void stop(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println("LogUtil-----"+name+"方法执行结束:"+"结果是"+result);
    }


    public static void logException(JoinPoint joinPoint,Exception e){
        String name = joinPoint.getSignature().getName();
        System.out.println("LogUtil-----"+name+"方法抛出异常:"+e);
    }


    public static void logFinally(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println("LogUtil-----"+name+"方法执行结束。。。。。over");
    }


    public static Object around(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();
        String name = proceedingJoinPoint.getSignature().getName();
        Object proceed = null;
        try {
            System.out.println("环绕前置通知:"+name+"方法开始,参数是"+ Arrays.asList(args));
            //利用反射调用目标方法,就是method.invoke()
            proceed = proceedingJoinPoint.proceed(args);
            System.out.println("环绕返回通知:"+name+"方法返回,返回值是"+proceed);
        } catch (Throwable e) {
            System.out.println("环绕异常通知"+name+"方法出现异常,异常信息是:"+e);
        }finally {
            System.out.println("环绕后置通知"+name+"方法结束");
        }
        return proceed;
    }
}

SecurityAspect类(和LogUtil 类内容相同)

4、配置aop.xml文件

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

    <!--将组件注入到spring容器中-->
    <!--切面-->
    <bean id="logUtil" class="com.lian.util.LogUtil"/>
    <bean id="securityAspect" class="com.lian.util.SecurityAspect"/>
    <!--切入点-->
    <bean id="myCalculator" class="com.lian.service.MyCalculator"/>

    <!--配置aop-->
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="logUtil">
            <!--将通用的表达式抽取出来-->
            <aop:pointcut id="mypoint" expression="execution(public int com.lian.service.MyCalculator.*(..))"/>
            <!--定义通知在哪些方法上使用-->
            <aop:before method="start" pointcut-ref="mypoint"></aop:before>
            <aop:after method="logFinally" pointcut-ref="mypoint"></aop:after>
            <aop:after-returning method="stop" pointcut-ref="mypoint" returning="result"></aop:after-returning>
            <aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="e"></aop:after-throwing>
            <aop:around method="around" pointcut-ref="mypoint"></aop:around>
        </aop:aspect>
        <aop:aspect ref="securityAspect">
            <aop:before method="start" pointcut="execution(public int com.lian.service.MyCalculator.*(..))"></aop:before>
            <aop:after method="logFinally" pointcut="execution(public int com.lian.service.MyCalculator.*(..))"></aop:after>
            <aop:after-returning method="stop" pointcut="execution(public int com.lian.service.MyCalculator.*(..))" returning="result"></aop:after-returning>
            <aop:after-throwing method="logException" pointcut="execution(public int com.lian.service.MyCalculator.*(..))" throwing="e"></aop:after-throwing>
            <aop:around method="around" pointcut="execution(public int com.lian.service.MyCalculator.*(..))"></aop:around>
        </aop:aspect>
    </aop:config>
    
</beans>

5、测试(同上)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值