Spring-AOP

目录

概述

核心概念

AOP快速上手步骤

AOP执行流程

 AOP切入点表达式

说明 

 标准格式的切入点表达式

使用通配符描述切入点(快速描述)

总结 

 AOP通知类型

说明 

五种通知类型

使用的注意事项:

 通知方法获取原始方法执行时数据

1.获取执行签名信息

2.获取原始方法调用参数

 3.获取返回值

4.获取异常


概述

AOP(Aspect Oriented Programming)面向切面编程

 作用在不惊动原始设计的基础上为其进行功能增强

设计理念无侵入式 

核心概念

 1.连接点(JoinPoint):程序执行过程中的任意位置都可以指为连接点。

2.切入点(PointCut):匹配连接点的式子,指定哪个连接点进行功能增强。

3.通知类:封装通知的类。

4.通知(Advice):在切入点执行的操作,共性功能,也就是增强的方法。

5.切面(Aspect):用来描述切入点和通知之间的关系。例如:用切面来描述通知在原始方法前执行还是原始方法后执行。

看到这些概念,也许还是有点迷糊,举个例子:AOP的核心理念就是无侵入式的代码功能增强 ,现在,我们想给一个类中的所有方法都添加一个功能,就是在方法执行后打印当前时间,打印当前时间就相当于是AOP增强代码。在其中,连接点不用说,就是类中的任意位置切入点就相当于是其中的所有方法,因为方法需要进行功能增强,通知相当于是其中的打印当前时间的方法通知类中包含通知方法切面用来描述切入点和通知之间的关系,也就是在原始方法"之后"执行通知方法。"之后"就是他们之间的关系。

AOP快速上手步骤

AOP测试项目结构

1.导入坐标aspectjweaver

2.定义原始方法进行测试(到时候使用AOP给此原始方法添加功能)

接口 

public interface BookDAO {

    void save();

    void writer();
}

实现类

@Repository
public class BookDAOImpl implements BookDAO{

    @Override
    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("bookDAO...save...");
    }

    @Override
    public void writer() {
        System.out.println("bookDAO...writer...");
    }
}

 3.定义通知类和通知(共性功能)

@Component
@Aspect
public class Advice {  //通知类
public Object getTime(ProceedingJoinPoint joinPoint) throws Throwable {
    //定义通知
    public void getTime(){
        System.out.println(System.currentTimeMillis());
    }
}

 4.设置切入点 (设定哪个方法需要进行功能增强)

@Pointcut("execution(void com.mh.dao.BookDAO.writer())")
private void pt(){} //方法名任意,空参,空方法体

5.设置切面

@Before("pt()")  //设置为before:在原始方法前添加此增强的功能
public void getTime(){
    System.out.println(System.currentTimeMillis());
}

测试代码:

public class app {
public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    BookDAO bean = context.getBean(BookDAO.class);
    bean.writer();

}

测试结果:未使用AOP前结果 

测试结果:使用AOP后结果  

 

在其中值得注意的是:通知类需要加两个注释:@Component @Aspect  ,把通知类加入到IoC容器中,并且告诉IoC容器他是一个关于切面的bean。

还需要在Spring的配置类中添加注解:@EnableAspectJAutoProxy

就是开启切面的自动代理,用于bean加载时使用代理对象执行上增强的方法。

具体的AOP执行流程如下:

AOP执行流程

1.Spring容器启动。

2.读取所有切面配置的切入点(并不是所有切入点都读取)。 

3.初始化bean,检查bean中的方法是否与读取到的切入点匹配上。

3.1:如果匹配上了,说明bean的方法需要进行代码增强(AOP),则创建原始对象(目标对象)的代理对象。

3.2:如果没有匹配上,说明bean中的方法没有配置切入点,则直接创建对象。

4.获取bean,执行方法。

 AOP切入点表达式

说明 

作用:用来描述哪个方法需要进行功能增强。 

在上面的AOP使用步骤中,如果是第一次写AOP,应该会有点迷糊,因为切入点表达式的书写方式并不简单,甚至看着好像有点复杂。下面就来解读AOP切入点表达式的格式。

可以先用上面使用的切入点表达式来说明:

execution(void com.mh.dao.BookDAO.writer())

红色部分就是切入点表达式,

void代表返回值类型

com.mh.dao.BookDAO代表包路径和接口

writer()代表方法

这是简写的表达式,有一些省略掉了,并不是标准的格式。

 标准格式的切入点表达式

 execution(public Book com.mh.dao.BookDAO.writer(int)

解释说明:对应关系:

动作关键字(权限修饰符,返回值类型,包名.类/接口名.方法名(参数)异常名

---值得注意的是:权限修饰符和异常名可以省略

使用通配符描述切入点(快速描述)

 支持符号说明:

1. 符号:* 单个独立的任意符号,可以作为前缀或后缀的匹配符出现,表示任意多个字符。

举例execution(public * com.mh.*.BookService.select*(*))

此表达式解释为:权限修饰符为public,任意返回值,com.mh下的所有包下的BookService接口下的select开头的方法,方法参数任意且一个

2.符号:.. 表示多个连续的任意符号,常用于简化包名与参数的书写。

举例:execution(public Book  com.mh..BookService.save(..))

此表达式解释为权限修饰符为public,返回值类型为Book,con.mh包下的任意包下的BookService接口或类的save方法,方法参数为任意且可以一个或多个。

3.符号:+ 专用于匹配子类类型

举例:execution(* *..*Service+.*(..))

此表达式解释为返回值类型任意,任意包下的以Service结尾的类或接口的子类。

总结 

使用切入点表达式,按照自己的需求使用表达式进行描述,并没有什么硬性要求,只要能锁定到自己的目标连接点就行,如果没有头绪,可以参考如下的书写技巧。

书写技巧:

1.所有代码按照标准规范开发,否则以下技巧全部失效

2.描述切入点通常描述接口,而不描述实现类

3.访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)

4.返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述

5.包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配

6.接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名

7.方法名书写以动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAll书写成selectAll

8.参数规则较为复杂,根据业务方法灵活调整

9.通常不使用异常作为匹配规则

 AOP通知类型

说明 

在上面的AOP快速上手中,我们用的通知类型为Before,也就是在原始方法前执行通知(增强的方法)。但是有时候我们的需要并不仅仅想要在“Before之前”,还有之后,中间等。这些东西在AOP中当然存在,下面进行介绍。

五种通知类型

1.@Before:在原始方法前执行通知方法。

2.@After:在原始方法后执行通知方法。

3.@Around:可以让通知方法在原始方法其中的指定位置执行

4.@AfterReturning:通知方法在原始方法正常执行完毕后执行

5.@AfterThrowing:通知方法在原始方法抛出异常后执行。

说明:在5种通知类型中,用法基本一致,常用的是@Around,下面将演示@Around的使用和说明Around使用时的注意事项。

//通知类
@Component
@Aspect
public class Advice {

//切入点
@Pointcut("execution(void com.mh.dao.BookDAO.writer())")
private void pt(){}

//切面和通知
@Around("pt()")
public Object getTime(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println(System.currentTimeMillis());

    Object proceed = joinPoint.proceed();

    System.out.println("after....");
    return  proceed;
	}
}

可以看到,Around的使用方式和Before,After是有些区别的,需要使用Around描述的通知,要有声明ProceedingJoinPoint (程序连接点)类型的参数,用来表示原始方法的,在上面,我们使用joinPoint.proceed() 调用方法代表原始方法的位置。

值得注意的是:调用方法后需要获取返回值,并且将其返回出去。就像上面代码的proceed被返回。

使用的注意事项:

 1.Around环绕通知必须依赖形参ProceedingJoinPoint才能实现原始方法的调用,进而可实现原始方法前后同时添加通知。

2.通知中如果未使用ProceedingJoinPoint对原始方法进行调用,将跳过原始方法的执行,也就是不执行原始方法,只执行增强的方法。

3.对原始方法的调用可以不接收返回值,将通知方法返回值设为void即可,有返回值只可以选择Object类型

4.原始方法的返回值如果是void类型,通知方法的返回值类型可以设成void,也可以设为Object,不必保持一致

5.由于无法预知原始方法执行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象

 

 通知方法获取原始方法执行时数据

1.获取执行签名信息

我们知道可以使用ProceedingJoinPoint来调用原始方法,但是如果方法过多且类型不明确等因素,我们就可以这样调用获取方法名和执行类型:

@Around("pt()")
public Object getTime(ProceedingJoinPoint joinPoint) throws Throwable {
	//获取签名信息
    Signature signature = joinPoint.getSignature();
    //通过签名获取执行类型(接口名)
    String declaringTypeName = signature.getDeclaringTypeName();
    //通过签名获取执行操作名(方法名)
    String name = signature.getName();
    
    System.out.println("执行类型名:" + declaringTypeName);
    System.out.println("执行操作名:" + name);
}

执行结果 

2.获取原始方法调用参数

通过通知方法参数ProceedingJoinPoint调用:

Object[] args = joinPoint.getArgs();

返回的是数组

 3.获取返回值

其实获取返回值很简单,就是调用原始方法时获取返回值就行。例如:

Object proceed = joinPoint.proceed(); 调用了原始方法,获取返回值proceed,此变量就是原始方法的返回值。

但是,还有一种方式,是在@AfterReturning中获取返回值,在切面中声明返回值将之获取。

例如:

@AfterReturning(value = "pt()",returning = "retv")
public void getReturnValue(String retv){
    System.out.println("原始方法返回值:" + retv);
}

4.获取异常

获取异常的方法和获取返回值很相似,都是在切面中声明后调用即可。例如:

@AfterThrowing(value = "pt()",throwing = "t")
public void getReturnValue(Throwable t) {
    System.out.println("原始方法抛出的异常:" + t);
}

除了在@AfterThrowing中获取异常,我们也可以在其他通知类型中获取异常,方式如下:

(调用原始方法时使用try-cat捕获异常)

@Around("pt()")
public Object getTime(ProceedingJoinPoint joinPoint) throws Throwable {
    try {
        Object proceed = joinPoint.proceed();
    }catch (Throwable t){
        t.printStackTrace();
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mao.O

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值