【Spring】AOP之面向切面编程

AOP概述

  • AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程 序运行过程。

  • AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理

  • AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程 序的可重用性,同时提高了开发的效率。

 面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。

​ 若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。

​ 例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占 比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大 大干扰了主业务逻辑---转账。

AOP编程术语

  • 切面(Aspect)
    • 切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面 是通知(Advice)。实际就是对主业务逻辑的一种增强。
  • 连接点(JoinPoint)
    • 连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
  • 切入点(Pointcut)
    • 切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
    • 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不 能被增强的。
  • 目标对象(Target)
    • 目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。 上 例 中 的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然, 不被增强,也就无所谓目标不目标了。
  • 通知(Advice)
    • 通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理 解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方 法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
    • 切入点定义切入的位置,通知定义切入的时间。

AspectJ 对 AOP 的实现

  • 对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向 切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便, 而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框 架中。

  • maven依赖的导入

<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.11</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>5.2.5.RELEASE</version>
</dependency>
<build>
	<plugins>
 		<plugin>
			 <artifactId>maven-compiler-plugin</artifactId>
			 <version>3.1</version>
 			<configuration>
				 <source>1.8</source>
				 <target>1.8</target>
			 </configuration>
		 </plugin>
	</plugins>
</build>      

@Before

  • 引入AOP约束

    • 在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签, 均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的。
    • AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。
  • 定义业务类接口和实现类

    • 业务类接口
    public interface SomeService {
        void doSome(String name, Integer age);
    }
    
    • 实现类
    public class SomeServiceImpl implements SomeService {
        @Override
        public void doSome(String name, Integer age) {
            System.out.println("===目标方法doSome执行===");
        }
    }    
    
  • 定义切面类

    • @Aspect:表示当前类是切面类
    • Before:前置通知
      • 属性
        • value:是切入点的表达式,表示切面的功能执行的位置
      • 位置:写在方法的上面
      • 特点:在目标方法之前执行,不会改变、影响目标方法
    • execution(public void service.SomeServiceImpl.doSome(String,Integer))是切入点表达式的完整写法,public是可以省略的,还可以使用通配符*来简写
      • * service.SomeServiceImpl.doSome(String,Integer)这是指返回值为任意的service包下的SomeServiceImpl类中参数为String和Integer的doSome方法
      • * *..SomeServiceImpl.doSome(String,Integer)这是指返回值为任意的任意包下的SomeServiceImpl类中参数为String和Integer的doSome方法
      • * *..SomeServiceImpl.do*(String,Integer)这是指返回值为任意的任意包下的SomeServiceImpl类中参数为String和Integer的do开头的方法
      • * *..SomeServiceImpl.do*(..)这是指返回值为任意的任意包下的SomeServiceImpl类中参数为任意的do开头的方法
@Aspect
public class MyAspect {
    @Before(value = "execution(public void service.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore() {
        System.out.println("前置通知,切面功能:在目标方法执行之前输出执行时间:" + new Date());
    }
}
  • xml文件的配置
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--把对象交给Spring容器,由Spring容器统一管理创建-->
    <!--说明目标对象-->
    <bean id="someService" class="spring.service.SomeServiceImpl"></bean>
    <!--说明切面类对象-->
    <bean id="myAspect" class="spring.MyAspect"></bean>
    <!--自动代理生成器:创建目标对象的代理对象
    创建代理对象是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象
    所以目标对象就是被修改后的代理对象
    会找出Spring容器中所有符合条件的目标,然后生成代理
    -->
    <aop:aspectj-autoproxy />

</beans>
  • 测试
public class AppTest {
    @Test
    public void shouldAnswerWithTrue() {
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标类对象
        SomeService service = (SomeService)ac.getBean("someService");
        //通过代理的类执行方法,实现功能的增强
        //目标类有接口是jdk代理
        service.doSome("admin",12);
    }
}
  • 结果

在这里插入图片描述

  • Before的一个参数:JoinPoint

    • JoinPoint的作用是:可以在通知方法中获取方法执行时的信息,如方法名称,方法的实参
    • 如果你的切面功能中需要使用方法的信息,那么就加上JoinPoint
    • 这个JoinPoint参数的值是由框架赋予的,必须是第一个位置的参数
  • 将方法做一些改动

    @Before(value = "execution(* *..SomeServiceImpl.doSome(..))")
    public void myBefore03(JoinPoint joinPoint) {
        //获取方法的完整定义
        //方法的签名(定义):void spring.service.SomeService.doSome(String,Integer)
        System.out.println("方法的签名(定义):" + joinPoint.getSignature());
        //方法的名称:doSome
        System.out.println("方法的名称:" + joinPoint.getSignature().getName());
        //获取参数
        Object args[] = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
        System.out.println("前置通知,切面功能:在目标方法执行之前输出执行时间:" + new Date());
    }
  • 结果

在这里插入图片描述

@AfterReturning

  • @AfterReturning:后置通知

    • 方法的定义要求
      • 是公共方法
      • 没有返回值
      • 方法名称自定义
      • 方法有参数,推荐是Object类型,参数名称自定义
    • 属性
      • value:切入点表达式
      • returning:自定义的变量目标是目标方法的返回值类型,自定义变量名必须和通知方法的形参名一致
    • 特点
      • 在目标方法之后执行
      • 可以获得目标方法的返回值
  • 编写接口和实现方法

    • 接口
    public interface SomeService {
        String doOther(String name, Integer age);
    }
    
    • 实现方法
    public class SomeServiceImpl implements SomeService {
        @Override
        public String doOther(String name, Integer age) {
            System.out.println("===目标方法doOther执行===");
            return "abcd";
        }
    }
    
  • 切面类

    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))", returning = "res")
    public void myAfterReturing(Object res) {
        //res值是目标方法的返回值
        System.out.println("后置通知,是在目标方法之后执行的,目标方法的返回值是:" + res);
    }
  • 根据要求俩处的属性名应该一样

在这里插入图片描述

  • 测试
public class AppTest {
    @Test
    public void shouldAnswerWithTrue() {
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标类对象
        SomeService service = (SomeService)ac.getBean("someService");
        //通过代理的类执行方法,实现功能的增强
        String doOtherResult = service.doOther("admin",12);
        System.out.println(doOtherResult);
    }
}
  • 结果

在这里插入图片描述

@Around

在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法 的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

  • @Around:环绕通知

    • 环绕通知的定义格式
      • 公共方法
      • 必须有一个返回值,推荐使用Object
      • 方法名称自定义
      • 方法有参数,固定的参数,ProceedingJoinPoint
    • 特点
      • 功能最强的通知
      • 在目标方法的前、后增强
      • 可以控制目标方法是否被执行
      • 修改目标方法的结果,影响最后的结果
  • 接口类和实现类

    • 接口
    public interface SomeService {
        String doFirst(String name, Integer age);
    }
    
    • 实现类
    public class SomeServiceImpl implements SomeService {
        @Override
        public String doFirst(String name, Integer age) {
            System.out.println("===目标方法doFirstr执行===");
            return "doFist success";
        }
    }
    
  • 切面类

    @Around(value = "execution(* *..SomeService.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        //实现环绕通知
        //获取第一个参数值,包含有JoinPoint的功能
        Object args[] = pjp.getArgs();
        String name = "";
        if (args != null && args.length > 1) {
            Object arg = args[0];
            name = (String) arg;
        }
        Object result = null;
        //1.目标方法调用
        System.out.println("环绕通知:在目标方法之前加上当前时间:" + new Date());
        if ("admin".equals(name)) {
            //符合条件在调用方法
            result = pjp.proceed();
        }
        //修改返回值
        if (result != null) {
            result = "Hello";
        }
        System.out.println("环绕通知:在目标方法之后加上当前时间:" + new Date());
        //2.在目标方法的前或后加功能
        //返回目标方法的执行结果
        return result;
    }
  • 测试
public class AppTest {
    /**
     * Rigorous Test :-)
     */
    @Test
    public void shouldAnswerWithTrue() {
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标类对象
        SomeService service = (SomeService) ac.getBean("someService");
        //通过代理的类执行方法,实现功能的增强
        String doFirstResult = service.doFirst("admin", 12);
        System.out.println(doFirstResult);
    }
}
  • 结果
    • 目标方法的返回值已经被更改
    • 如果我们更改在切面类的条件还可以不让目标方法执行

在这里插入图片描述

@AfterThrowing

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的 名称,表示发生的异常对象。

  • @AfterThrowing:异常通知

    • 异常通知的定义格式
      • 公共方法
      • 没有返回值
      • 方法名自定义
      • 有一个Exception参数,如果还有的话就是JoinPoint
    • 属性
      • value:切入点的表达式
      • throwing:自定义变量,表示抛出的异常对象,变量名必须和方法的参数名一样
    • 特点
      • 在方法抛出异常时执行
      • 可以做异常的监控程序,在监控目标执行是是否有异常,有异常可以进行通知
  • 接口和实现类

    • 接口
    package spring.service;
    
    public interface SomeService {
        void doException();
    }
    
    • 实现类
      • 因为是抛出异常,所以我们这里自己创造出一个异常
    public class SomeServiceImpl implements SomeService {
        @Override
        public void doException() {
            //自定义异常
            int a = 0;
            int b = 1/a;
            System.out.println("===目标方法doException执行===");
        }
    }
    
  • 切面类

    @AfterThrowing(value = "execution(* *..SomeService.doException(..))", throwing = "ex")
    public void myAfterThrowing(Exception ex) {
        System.out.println("异常通知:发生异常时,执行:" + ex.getMessage());
    }
  • 根据要求红框处要一致

在这里插入图片描述

  • 测试类
public class AppTest {
    @Test
    public void shouldAnswerWithTrue() {
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标类对象
        SomeService service = (SomeService) ac.getBean("someService");
        //通过代理的类执行方法,实现功能的增强
        service.doException();
    }
}
  • 结果
    • 返回出了异常信息

在这里插入图片描述

@After

  • @After:最终通知

    • 最终通知定义格式
      • 公共方法
      • 没有返回值
      • 方法名称自定义
      • 方法没有参数,如果有就是JoinPoint
    • 属性
      • value:切入点表达式
    • 特点
      • 总是会执行,就算是异常也会执行,类似于finally
      • 在目标方法之后执行
      • 一般用于资源的释放和清除
  • 接口和实现类

    • 接口
    public interface SomeService {
        void doAfter();
    }
    
    • 实现类
    public class SomeServiceImpl implements SomeService {
        @Override
        public void doAfter() {
            System.out.println("===目标方法doAfter执行===");
        }
    }
    
  • 切面类

    @After(value = "execution(* *..SomeService.doAfter(..))")
    public void doAfter() {
        System.out.println("最终通知:在目标方法执行之后执行,并且总是会被执行");
    }
  • 测试类
public class AppTest {
    /**
     * Rigorous Test :-)
     */
    @Test
    public void shouldAnswerWithTrue() {
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标类对象
        SomeService service = (SomeService) ac.getBean("someService");
        //通过代理的类执行方法,实现功能的增强
        service.doAfter();
    }
}
  • 结果

在这里插入图片描述

  • 如果实现类出现了异常

    • 修改后的实现类
    public class SomeServiceImpl implements SomeService {
        @Override
        public void doAfter() {
            int a = 0;
            int b = 1 / a;
            System.out.println("===目标方法doAfter执行===");
        }
    }
    
    • 结果
      • 发现虽然有异常但是最终通知还是执行了

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值