【学习笔记】SpringAOP的用法全解

Spring的AOP

一、 Spring对AOP的实现包括以下3种方式

  • 第一种方式: Spring框架结合AspectJ框架实现的AOP,基于注解方式。(需要重点掌握, 核心用法
  • 第二种方式: Spring框架结合AspectJ框架实现的AOP,基于XML方式第三种方式:
  • Spring框架自己实现的AOP,基于XML配置方式。(少用不做介绍)

什么是AspectJ?

什么是AspectJ?

(Edlipse组织的一个支持AOP的架。Aspect/架是独立于Spring架之外的一个框架,Spring框架用了AspectJ)

Aspedtu项目起源于洛阿你托 (Palo Ato 研究中心(写为PARC) ,该中由Xeox建团资,reor kczales导,以1997年开力于Aspec开发,1998年第-次发布外部用户,2001年发布1.0 release为了Aspe技术和发展,PAR(在200年3月将Aspec/项目给了dipse织,因为Aspec的发展和受关注度大大超出了PARC的预期,他们已经力继维持它的发展。

二、使用Spring的AOP

1、准备工作

要使用Spring的AOP,需要导入以下依赖

  • spring context 核心依赖 包含 spring-aop
  • spring-aspects 依赖

image-20230405233638103

并在resource目录下新建配置文件

image-20230405234643640

2、尝试写一个简单的AOP demo

思路整理

  1. 写一个业务类模拟实际业务

  2. 定义一个切面类 (切面=通知+切点),需要添加 @Aspect 直接,告诉spring这是一个切面类

  3. 将上述的两个类,纳入Spring容器中 (注解 + 添加扫描配置信息)

  4. 在切面类中定义具体的通知

    • 通知就是增强,就是具体要编写的增强代码
    • 这里通知Advice以方法的形式出现,因为方法中可以写代码
  5. 在该方法上加入 @Before 注解,表示这是一个 前置通知

    • 这个注解注解写 ——> 切点表达式
  6. 在spring配置中,开启aspecj的自动代理 <aop:aspectj-autoproxy />

    • 开启自动dialing后,Spring容器会自动扫描该类上是否带有@Aspect注解,如果有,则生成代理对象。
    • 这里标签可以添加一个属性proxy-target-class
      • 为true时表示强制使用CGLIB
      • 反之,则根据接口情况使用JDK动态代理或者CGLIB
  7. 测试

3、代码如下:

spring.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: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.zhc.aop"/>

    <!--开启aspectj的自动代理-->
    <aop:aspectj-autoproxy  proxy-target-class="true"/>

</beans>

业务类

package com.zhc.aop.service;

import org.springframework.stereotype.Service;

@Service("userService")
public class UserService {
    
    

    public void login(){
    
    
        System.out.println("系统正在登陆。。。");
    }
}

切面类

package com.zhc.aop.aspects;

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

@Component("logAspect")
@Aspect     // 声明这是一个切面
public class LogAspect {
    
    


    @Before("execution(* com.zhc.aop.service..*(..))")
    public void beforePoint(){
    
    
        System.out.println("前置通知");
    }
}

测试类

import com.zhc.aop.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Client {
    
    
    @Test
    public void TestProxy(){
    
    
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
    }
}

测试结果

image-20230406142455120

4、复习切面表达式

切面表达式在这章中比较重要,迅速复习一遍即可,要记得几个常用的表达式

image-20230406135351271

1)所有方法

权限修饰符略,异常略,全限定名略

execution(* *(..))

2)指定路径下某个包及其子包的所有方法

excution(* com.zhc..*(..))

3)限定public的,某个包及其子包的,带login开头的,且参数是Stirng 的 方法

excution(public * com.zhc..login*(String))

5、5种不同的通知如何写?

  • 前置 —— @Before
  • 后置—— @AfterReturning
  • 环绕——@Around
  • 异常——@AfterThrowing
  • 最终——@After

注意 ,后置通知的注解并不是@After哦,别搞混 了。

写法其实差不太多,需要注意是的环绕通知的写法。

image-20230406001825607

思考1,5种通知都存在时,执行的顺序是什么样的?如果抛出异常,执行的顺序是什么样的?

引用老杜的案例:无异常的执行顺序如下

image-20230406002226310

一旦捕获异常,后置通知和后环绕就被抛弃了!

  • 后环绕也有特殊之处——如果是try…catch内捕获的话,后环绕也会执行

image-20230406001952142

思考2:切面的先后顺序(如果一个业务存在多个切面,如何排序?如何执行

在实际业务中,往往存在多个切面,如何做好排序呢?

其实Spring的开发者已经想到了这种情况,解决方法也很简单——

在切面类上加入注解@Order(int num),Order注解的数字越小,优先级越高!

  • 这里老杜没有提到的细节,比如同数字或者都无Order注解的情况,以及不同优先级的切面先后执行顺序是怎么样的?(比如环绕通知的先后执行顺序是什么样的?

测试2:同数字或者都无Order注解的情况,是如何执行的

结论:不是随机的,而是按照某个顺序进行执行,规律不明但是固定

测试2:不同优先级的切面先后执行顺序

测试设置 log的order为0,auth的order为1,测试结果如下:

结论:不同切面的执行顺序的:

  • 优先级高的前置/前环绕通知 会先执行
  • 优先级高的后置/后环绕通知 会后执行

image-20230406144853905

6、写法优化(切点优化pointcut)

写的切面多了,发现切面表达式重复写了很多,这里老杜介绍了 如何优化这个问题,具体实现如下

  • 在切面类中定义一个方法(任意名称)
  • 在该方法上加入@Pointcut注解,并在注解内写入 切面表达式
  • 在其他的切面方法上使用注解,就可以直接传入该方法名即可

这个简化的写法也可以跨类使用,只需要写上全限定类名(包名+类名)即可

优化后的切面类

package com.zhc.aop.aspects;

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;

@Component("logAspect")
@Aspect     // 声明这是一个切面
public class LogAspect {
    
    

    // 优化写法
    @Pointcut("execution(* com.zhc.aop.service.UserService.*(..))")
    public void pointcut(){
    
    

    }


    @Before("pointcut()")   // 前置通知
    public void beforePoint(JoinPoint joinPoint){
    
    
        System.out.println("前置通知");
//        System.out.println(joinPoint);  // 打印结果:execution(void com.zhc.aop.service.UserService.loginA())
    }

    @AfterReturning("pointcut()")
    public void afterPoint(){
    
    
        System.out.println("后置通知");
    }

    @Around("pointcut()")
    public void aroundPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        System.out.println("环绕前。。。");
        joinPoint.proceed();
        System.out.println("环绕后。。。");
    }


    @AfterThrowing("pointcut()")
    public void throwPoint(){
    
    
        System.out.println("异常通知");
    }

    @After("pointcut()")
    public void finallyPoint(){
    
    
        System.out.println("最终通知");
    }
}

7、拓展用法,JointPoint

前面的环绕通知有用到了一个 ProcessJoinPoint ,其实在编写其他类型的通知类时,也可以通过JoinPoint来获取目标方法

  • 比如直接尝试打印该joinpoint对象——可以看出来打印的结果就是一个切面表达式:

    打印结果:execution(void com.zhc.aop.service.UserService.loginA())
    

8、全注解开发优化

可以优化掉配置文件spring.xml

声明一个配置类,配置思路如下

  1. 声明一个配置类 加上@Configuration
  2. 声明组件扫描
  3. 声明aspectj的自动扫描
  4. 修改对应的测试/调用代码
修改后的配置类
package com.zhc.aop.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.zhc.aop")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
    
    
}

测试类
@Test
public void TestProxyByAnno(){
    
    
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserService userService = applicationContext.getBean("userService", UserService.class);
    userService.loginA();

}

9、基于XML开发的AOP

略,作为了解。主要是就是xml文件的书写和阅读,截图老杜写好的xml文件

image-20230406151405523

依然推荐大家去看动力节点的视频,更有助于理解

猜你喜欢

转载自blog.csdn.net/Xcong_Zhu/article/details/129990306