spring AOP的介绍和使用

面向切面编程:AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。AOP是Spring中重要的核心点,通俗点说的话就是在程序运行期间,将某段代码动态切入指定方法指定位置进行运行的这种编程方式。

静态代理:代理类必须要实现和被代理类相同的接口。

动态代理:只需要使用reflect包下Proxy类,传入被代理的类,与他的接口。由jdk自动生成一个代理类。

以上的方法都有一个要求,被代理对象必须实现了一个接口。没有接口是不能代理的。

cglic不需要代理对象必须实现接口。

注意:spring在使用动态代理时,优先选择jdk的proxy。如果没有对应接口,则选择cglib。

在这里插入图片描述

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

通知由一下几种类型

  • @Before:前置通知,在方法执行之前完成
  • @After:后置通知,在方法执行完成之后执行
  • @AfterReturing:返回通知,在返回结果之后运行
  • @AfterThrowing:异常通知,在出现异常的时候使用
  • @Around:环绕通知

使用spring的aop注解

** 1、导入相应的jar包依赖**

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</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>

2、在xml中添加命名空间,开启qop的注解功能

添加命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=http://www.springframework.org/schema/aop
           https://www.springframework.org/schema/aop/spring-aop.xsd"
<!--开启注解  , 记得要指定扫描包-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package=" "></context:component-scan>

** 使用注解**

1、添加@Aspect注解,指定切面类

2、添加@Component,扫描。

3、添加把配置文件,指定扫描的包。

4、给切入方法添加通知,指定要切入的点。

@Aspect
@Component
public class LogUtil {
   @Before("execution(public Integer com.mashibing.service.Mycalculator.add(Integer,Integer))")
   public static void start(){
      System.out.println("开始执行:参数是:");
   }
   @AfterReturning("execution(public Integer com.mashibing.service.Mycalculator.add(Integer,Integer))")
   public static void stop(){
      System.out.println("结束执行:结果是:" );
   }
   @AfterThrowing("execution(public Integer com.mashibing.service.Mycalculator.add(Integer,Integer))")
   public static void logException(){
      System.out.println("方法抛出异常");
   }
   @After("execution(public Integer com.mashibing.service.Mycalculator.add(Integer,Integer))")
   public static void logFinally(){
      System.out.println("方法获取到结果");
   }
}

注解的execution表达式

使用完全限定名,精确的匹配太过于死板。可以使用通配符匹配多个方法。

实际生产中,更多的使用完全限定名

使用通配符*
  1、可以用来匹配多个或者一个字符
      @Before("execution(public Integer com.mashibing.service.Mycalculator.*(Integer,Integer))")
  2、匹配任意类型的参数,只能是一个
      @Before("execution(public Integer com.mashibing.service.Mycalculator.*(*,*))")
  3、在包名类名匹配时,只能匹配一层路径
      @Before("execution(public Integer com.mashibing.service.*.*(*,*))")
  4、可以用来代替任意返回值
      @Before("execution(public * com.mashibing.service.*.*(*,*))")
  5、不能用来代替访问修饰符,可以不写访问修饰表示全部

使用..通配符
  1、可以用来匹配零个或多个参数,任意类型
      @Before("execution(public * com.mashibing.*.*(..))")
  2、可以匹配多层路径
      @Before("execution(public * com..*(..))")

偷懒的方式:
      @Before("execution(public * *(..))")
      @Before("execution(public * *.*(..))")  

使用通配符的时候并不是越简洁越好,要选择符合要求或者符合规则的匹配方式。遵循项目规范。

表达式中还可以使用逻辑运算

&&两个都要满足
     @Before(" execution(public * com.mashibing..add ( *, *)) && execution(public * *(..))")
||两个满足一个
	 @Before(" execution(public * com.mashibing..add ( *, *)) || execution(public * com..*.show())")
!除了这都满足
	@Before("! execution(public * com.mashibing..add ( *, *))")

这几种通知的执行顺序

@Before --> @After --> @AfterReturing

@Before --> @After --> @AfterThrowing

通知方法的参数

获取方法中对应的参数或者方法名称,必须使用JoinPoint对象,并且是第一个参数。

@Before("execution(public Integer com.mashibing..*(*,*))")
public static void start(JoinPoint joinPoint){
   //获取方法签名
   Signature signature = joinPoint.getSignature();
   //方法名
   String name = signature.getName();
   //获取参数数组
   Object[] args = joinPoint.getArgs();
   System.out.println(name+"开始执行:参数是:"+Arrays.asList(args));
}

要使用参数返回的结果,需要在注解中添加 returning=" "

@AfterReturning(value="execution(public Integer com.mashibing.service.Mycalculator.add(Integer,Integer))" ,returning="result")
public static void stop(JoinPoint joinPoint,Object result){
   Signature signature = joinPoint.getSignature();
   Object[] args = joinPoint.getArgs();
   System.out.println(signature.getName()+"结束执行:结果是:" +result);
}

如果想要添加其他参数,必须要添加args(参数列表),ArgNames(参数列表)

    @Before(value = "execution(public Integer com.mashibing.service.MyCalculator.*(Integer,Integer)) && args(joinPoint,k)",argNames = "joinPoint,k")

@PointCut封装表达式

如果多个方法的表达式是一致的话,那么可以将切入点表达式抽取出来:随便声明一个没有实现的返回void的空方法,给方法上标注@Potintcut注解

//定义的一个无返回值的空方法
@Pointcut("execution(public * com.mashibing..*(..))")
public void myPointCut(){}

//表达式替换成方法名
@Before(value = "myPointCut()")
public static void start(JoinPoint joinPoint){

@Around环绕通知

@Around通知比其他通知的优先级高,应该套在所有通知最外面。环绕前置–>普通前置–>目标方法执行–>环绕正常结束/出现异常–>环绕后置–>普通后置–>普通返回或者异常。

异常如果不接着抛出,会被环绕通知消化,普通异常通知执行不到。

@Around("myPointCut()")
public static Object around(ProceedingJoinPoint pjp){ // 传入参数 ProceedingJoinPoint
    //通过反射获取目标的方法。
   Signature signature = pjp.getSignature();
   Object[] args = pjp.getArgs();    //方法参数
   Object result = null;             //方法返回结果
   String name = signature.getName(); //方法名
   try{
      System.out.println(name+"环绕通知执行开始,参数是:"+Arrays.asList(args));
      result = pjp.proceed(args); //执行方法。
      result = 200;
   } catch (Throwable throwable) {
      System.out.println(name+"环绕通知异常");
   }finally {
      System.out.println(name+"环绕通知执行结束");
   }
   System.out.println(name+"环绕通知返回结果"+result);

   return result;
}

可以自己修改结果值。功能强大。

** 多个切面类的执行顺序**

如果有多个切面类,默认的情况下是按照首字母比较。
也可以指定执行顺序,使用注解@Order(5),数字小的优先执行。

@Order(5)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值