基于Springboot的Spring AOP学习记录

本文详细介绍了Spring AOP的使用,包括@Aspect、@Pointcut和Advice注解,探讨了Spring AOP的原理,如静态代理、JDK动态代理、Cglib动态代理,并分析了AOP在事务控制、安全控制和缓存控制中的应用,通过实例展示了如何利用AOP进行功能增强。
摘要由CSDN通过智能技术生成

前段时间各种面试,aop问到就蒙逼,所以结合新学的springboot重新理一下这玩意儿,很重要啊

一、AOP概述

AOP(面对切面编程)是对OOP(面向对象编程)的补充,总体来说,编程范式包含:面向过程编程、面向对象编程、函数式编程、事件驱动编程、面向切面编程。

AOP的出现主要是为了解决如下的几个问题:
1.代码重复性的问题
2.关注点的分离(包含了水平分离:展示层->服务层->持久层;垂直分离:模块划分(订单、库存等);切面分离:分离功能性需求与非功能性需求)

AOP使用优势:
1.集中处理某一关注点/横切逻辑
2.可以方便的添加/删除关注点
3.侵入性少,增强代码的可读性和可维护性

AOP的应用场景:
权限控制、缓存控制、事务控制、审计日志、性能监控、分布式追踪、异常处理

所以这么优秀的东西绝不仅仅局限于Java,AOP是支持多语言开发的。

二、SpringAOP的使用

Spring AOP的使用方式包含XML配置和注解方式(比较方便),下面看一下基于注解方式的AOP。
AOP的注解主要包括@Aspect、@Pointcut、Advice三种。在详细记录前先给出一个AOP的简单使用代码(流程:引入依赖–>定义切面类–>在切面类中定义Advice以及前后逻辑),如果要获取方法的一些属性(比如方法名,返回值、参数等等),除了环绕通知需要传入ProceedingJoinPoint对象,其他的Advice则是需要传入JoinPoint对象,两类不同!

 <!-- 添加AOP的依赖 -->
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>
package com.imooc.aspect;

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

@Aspect//表明它是一个切面类
@Component//交给Spring管理
public class HttpAspect {
   

//  //定义拦截GirlController中所有public方法
//  @Before("execution(public * com.imooc.controller.GirlController.*(..))")
//  public void log() {
   
//      System.out.println("******拦截前的逻辑******");
//  }
//  
//  @After("execution(public * com.imooc.controller.GirlController.*(..))")
//  public void doAfter() {
   
//      System.out.println("******拦截后的逻辑******");
//  }

    /*
     * 上面拦截的方式比较繁琐,因为Before和After的匹配规则有重复代码
     * 
     * 可以先定义一个Pointcut,然后直接拦截这个方法即可
     * 
     */
    //这里就定义了一个总的匹配规则,以后拦截的时候直接拦截log()方法即可,无须去重复写execution表达式
    @Pointcut("execution(public * com.imooc.controller.GirlController.*(..))")
    public void log() {

    }

    @Before("log()")
    public void doBefore() {
        System.out.println("******拦截前的逻辑******");
    }

    @After("log()")
    public void doAfter() {
        System.out.println("******拦截后的逻辑******");
    }


}

1.@Aspect

主要用来标注Java类,表明它是一个切面配置的类,通常下面也会加上@Component注解来表明它由Spring管理

2.@Pointcut

主要有pointCutExpression(切面表达式)来表达,用来描述你要在哪些类的哪些方法上注入代码。其中切面表达式包含了designators(指示器,主要描述通过哪些方式去匹配Java类的哪些方法:如execution()等)和wildcards(通配符:如*)以及operators(运算符:如&&、||、!),具体如下所示
切面表达式组成

designators指示器
表示想要通过什么样的方式匹配想要的方法,具体组成如下图,重点是execution()
指示器的组成

1.匹配包/类型within()

//匹配ProductService类种的所有方法
@Poincut("within(com.hhu.service.ProductService)")
public void matchType(){
    ...
}

//匹配com.hhu包及其子包下所有类的方法
@Pointcut("within(com.hhu..*)")
public void matchPackage(){
    ...
}

2.匹配对象(主要有this和target以及bean)

//匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop的代理对象
@Pointcut("this(com.hhu.DemaoDao)")
public void thisDemo() {
    ...
}

//匹配实现IDao接口的目标对象(而不是aop代理后的对象,这里即DemoDao的方法)
@Pointcut("target(com.hhu.Idao)")
public void targetDemo() {
    ...
}

//匹配所有以Service结尾的bean中的方法
@Pointcut("bean(*Service)")
public void beanDemo() {
    ...
}

3.参数匹配(主要有execution()和args()方法)

//过滤出第一个参数是long类型的并且在com.hhu.service包下的方法,如果是第一个参数是Long,第二个参数是String则可以写成args(Long,String),如果匹配第一个为Long,其它任意的话则可以写成args(Long..)
@Pointcut("args(Long) && within(com.hhu.service.*)")
public void matchArgs() {
    ...
}

@Before("mathArgs()")
public void befor() {
    System.out.println("");
    System.out.println("###before");
}

4.匹配注解

//匹配方法标注有AdminOnly注解的方法
@Pointcut("@annotation(com.hhu.demo.security.AdminOnly)")
public void annoDemo() {
    ...
}

//匹配标注有Beta的类下的方法,要求annotation的RetentionPplicy级别为CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void annoWithinDemo() {
    ...
}

//匹配标注有Repository类下的方法,要求的annotation的RetentionPolicy级别为RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void annoTargetDemo() {
    ...
}

//匹配传入的参数类型标注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void annoArgsDemo() {
    ...
}

5.execution()
标准的execution表达式如下,共有5个参数:

execution(
//权限修饰符(如public、private)
[modifier-pattern],
//返回值(如*表示任意返回值,void表示无返回值)
ret-type-pattern,
//包名(如com.hhu.service.*Service.*(..)表示对应包com.hhu.service中以Service结尾的类中的任意方法(该方法可以带任意参数),如果匹配无参的则可以写成(),如果像拦截产生异常的方法,则可以写成(..) throws+具体异常 )
[declaring-type-pattern],
name-pattern(或者是param-pattern),
[throws-pattern]
)

其中带有方括号的参数表示可以缺省。比如

@Pointcut("execution(* *..find*(Long)) && within(com.imooc..*) ")

wildcard主要包括常用的三种:
*表示匹配任意数量的字符
+表示匹配制定类及其子类
..表示一般用于匹配任意数的子包或参数。

operators主要包括如下的三种:
&&表示与操作
||表示或操作
!表示非操作

3.Advice注解

表示代码织入的时机,如执行之前、执行之后等等,主要有5种,通常是进行注解切面注解后会紧接着写Advice,格式为Advice(“切面拦截方法”)。比如下面的

//切面注解
@Pointcut("@annotation(com.imooc.anno.AdminOnly) && within(com.imooc..*)")
    public void matchAnno(){}

//Advice注解
@After("matchAnno()")
    public void after() {
        System.out.println("###after");
    }
  1. @Before,前置通知
  2. @After,后置通知,不管代码是成功还是抛出异常,都会织入
  3. @AfterReturning,返回通知,当且仅当方法成功执行
  4. @AfterThrowing,异常通知,当且仅当方法抛出异常
  5. @Around,环绕通知,基本包含了上述所有的植入位置

获取参数的话一般跟Before的Advice结合在一起的,代码如下

@Before("matchLongArg() && args(productId)")
//这里是因为知道参数的类型是Long
public void before(Long productId) {
    System.out.println("###before,get args:" + productId);
}

其中@AfterReturning注解可以获取方法的返回值,比如下面的

//切面注解
@Pointcut("@annotation(com.imooc.anno.AdminOnly) && within(com.imooc..*)")
    public void matchAnno(){}

//Advice注解,returning的值result代表的就是返回值,形参Object类表示
@AfterReturning(value="matchAnno()",returning="result")
    public void after(java.lang.Object result) {
        System.out.println("###after");
    }

@Around注解比较强大,一般有它的存在就可以不用Before或者After这类注解了,如下

//由于这里可能有返回值所以必须有Object,而且必须获取Proceeding的上下文才能让方法得以继续,所以会有此形参
@Around("matchAnno()")
public java.lang.Object after(ProceedingJoinPoint joinPoint) {
    System.out.println("###before");
    //定义返回值
    java.lang.Object result = null;
    try {
        //获取返回值
        result = joinPoint.proceed(joinPoint.getArgs());
        System.out.println("###after returning");
    } catch(Throwable e) {
        System.out.println("###ex");
        e.printStackTrace();
    } finally {
        System.out.println("###finally");
    }
    return result;
}

三、Spring AOP的原理

AOP的设计主要用到的设计模式包含了代理模式、责任链模式;AOP的实现方式包含了两种:基于JDK实现、基于CGlib实现。

织入时机:主要有以下的3种
1. 编译期(如AspectJ)
2. 类加载时(如AspectJ5以上版本)
3. 运行时(如Spring AOP)

Spring AOP属于运行时织入,这种织入方式是基于代理实现的,这样代理才能在实际方法执行前后进行一些逻辑处理,可以有两种:从静态代理到动态代理(动态代理包含基于接口代理与基于继承代理)。

1.代理模式(简单AOP的模拟)–静态代理

//1.首先定义一个接口
package com.imooc.pattern;

public interface Subject {
   
    void request();
}

//2.让实际对象和代理对象都实现这个接口
//2.1实际对象
package com.imooc.pattern;

public class RealSubject implements Subject {
   
    @Override
    public void request() {
        System.out.println("real subject execute request");
    }
}

//2.2织入逻辑对象,持有实际对象
package com.imooc.pattern;

public class Proxy implements Subject{
   

    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        System.out.println("before");
        try {
            realSubject.request();
        } catch (Exception e) {
            System.out.println("ex:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("after");
        }

    }
}

//3.客户端调用
package com.imooc.pattern;

public class Client {
   
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        Proxy proxy = new Proxy(realSubject);
        realSubject.reques
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值