Spring AOP

一.回顾AOP

上面的文章说过,AOP就是面向切面编程。

什么是切面?切面就是指某一类特定的问题,在代码里可以代指某个特定的方法。所以AOP也可以认为是面向特定的方法编程。就比如登录拦截器校验,就是一类特定的问题。所以说拦截器的实现就是一种AOP的应用。同样,统一功能处理统一数据返回格式和统一异常处理也是AOP思想的应用

AOP是一种思想,Spring实现了这种思想

简单来说,AOP就是一种思想,是对某一类事情的集中处理。

1.什么是Spring AOP

AOP是一种思想,它的实现方式有很多,Spring AOP 就是其中的一种,还有Aspectj,CGLIB等。

我们学过的拦截器,其实是在URL的维度上实现的AOP(也就是一次请求和响应)。但实际上,AOP的作用维度比这更细,可以根据包,类,参数,方法等进行拦截,从而实现更加复杂度业务。

2.AOP的作用

针对特定方法进行功能的增强,解耦合。

比如一个项目有很多个业务功能,但有一些业务执行效率很低,我们需要对接口进行优化,这就要定位除效率低的业务,也就是要统计一次业务的耗时。拿到要个每一个接口都在执行前后计算一个开始结束时间再相减吗?如果接口很多的话,这种方法就会增加工作量。

而AOP就可以在不敢动这些接口的情况下,写一个特定的方法,对原始方法进行功能的增强。

由于不修改源代码,所以不需要进行重新编译,所以可以解耦合。

二.Spring AOP入门

首先要引入aop依赖

接着来编写AOP程序,实现计算每个方法执行时间的逻辑

接下来运行程序进行访问:

也可以访问其他接口:

对TimeAspect的简单讲解:

1.@Aspect是aspectj包中的一个注解,用于标识这是一个切面类

2.@Around:表示环绕通知,在目标方法前后都会执行

3.ProceedingJoinPoint.proceed():执行目标方法,所以pjp可以被看作是目标方法

4.@Component:表示将切面类交给Spring进行管理

1.AOP编程的优势

由上面的代码,我们可以总结出以下优势:

1.代码无侵入:不用修改原始业务方法,就可以对原始的方法进行一些功能的增强或者功能的改变

2.减少了重复代码

3.提高了开发效率

4.维护方便

三.Spring AOP 详解

1.AOP相关基本概念

切点Pointcut

即切入点,它的作用就是提供一组规则,告诉程序对哪些方法来进行功能的增强。

@Around中的execution函数就是这个切入点。execution(* com.bite.aop.demos.web.controller.*.*(..))就是切入点表达式

连接点(Join Point)

满足切入点表达式规则的方法就是连接点,也就是可以被AOP控制的方法。

execution(* com.bite.aop.demos.web.controller.*.*(..))对于这个表达式来说,controller包下的所有类中的所有方法都是连接点

切点和连接点的关系:连接点是满足切点表达式的元素,切点是保存了众多连接点的集合。

通知(Advice)

通知就是具体要做的工作,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)

比如上述计算执行时间的逻辑

切面

切面=切点+通知

也就是切点+统一功能(三个点加上处在同一平面上的三条线才能组成一个面)

这个整体就是切面

2.通知类型详解

上面已经说过什么是通知,下面我们就来看看都有哪些通知

@Around:环绕通知,此注解标注的通知方法在目标方法前后都执行

@Before:前置通知,此注解标注的通知方法在目标方法前执行

@After:后置通知,在目标方法后执行,无论是否有异常都要执行

@AfterReturning:返回后通知,在目标方法后执行,有异常就不执行

@AfterThrowing:异常后通知,此注解标注的通知方法在发生异常后执行

如下进行测试:

通过postman访问一下:

哎?为什么只执行了一个around方法?其他的呢?

这是因为@Around没有写对,里面应该将目标方法执行!!如下:

这次再运行访问:

如上的执行顺序。且在程序正常没有异常时,AfterThrowing方法不会执行。同时@Around标记的方法的前置通知会在@Before之前执行,后置通知会在@Aftert之后执行

再看看发生异常时:

如上,发生异常时AfterThrowing方法会执行,但是AfteringReturning方法不执行。

3.@PointCut

上面代码有一个问题,就是存在多个重复的切入点表达式execution()。

Spring提供了@PointCut注解,可以将公共的切入点表达式提取出来,需要用到时引用该切入点即可。使用如下:

切点的定义就是@Pointcut注解中加上切入点表达式,然后写一个空方法,不需要有具体的实现。

注意:当切点定义使用private时,只可以在当前类中使用,但当其他切面类也要使用该切点时,就需要将private改成public,并且在其他切面类中引用时要使用全限定类名.方法名()

也就是也就是切点所在的类的类名和切点的方法名。

4.切面优先级@Order

我们在一个项目中可能会定义多个切面类,并且这些切面类的多个切入点可能会匹配到相同的目标方法。当目标方法运行时,这些切面类中的通知方法都会执行到,那执行顺序是什么呢?

AspectDemo3和AspectDemo4也一样

访问如下:

如上可知,当存在多个切面类时,默认按照切面类的类名字母顺序来排序。

@Before通知:字母靠前的先执行

@After通知:字母靠后的先执行

但是这种方式不方便管理,我们的类名更多的还是具备一定含义的,而不是按照字母顺序起名。这就要用到Spring提供的一个注解@Qrder来控制这些切面通知的执行顺序

5.切点表达式

execution()是最常用的切点表达式,这个我们在上一篇AOP编程的文章中详细讲过,还有常用的就是基于注解的切点表达式,@Annotation

要想使用@Annotation,就要自定义注解,然后将自定义注解放在目标方法上面。如下:

@Target表示了Annotation所修饰的对象的作用范围,即该注解要使用到什么地方,常用取值如下:

ElementType.TYPE:用于描述类、接口(包括注解类型)或enum声明

ElementType.METHOD:用于描述方法

ElementType.PARAMETER:用于描述参数

ElementType.TYPE_USE:可以标注任意类型

@Retention表明了Annotation被保留的时间的长短,标明了注解的生命周期

RetentionPolicy.SOURCE:表名注解仅仅存在于源代码中,编译成字节码后就会被丢弃。

RetentionPolicy.CLASS:编译时注解,表示注解存在于源代码和字节码中,在实际运行时就会被丢弃

RetentionPolicy.RUNTIME:运行时注解,表示注解存在于源代码,字节码和运行时中,这意味着在编译时,字节码中,和运行时都可以看到

定义好注解后就可以写切面类了

给目标方法添加自定义注解:

访问:

四.Spring AOP原理

1.代理模式

代理模式也叫委托模式,这个我在之前的关于Spring AOP的文章中详细讲过。

代理模式可以在不修改原始方法的前提下对原始方法的功能进行增强。

代理模式分为静态代理和动态代理。

静态代理:是由程序员创建的代理类或特定工具自动生成源代码再对其进行编译。在程序运行前代理类的.class文件就已经存在了。

动态代理:在程序运行时,运用反射机制动态创建而成,它没有源代码,也没有对应的.class文件,没有经过编译,而是运行时随用随创建

静态代理:

根据之前的文章,我们知道代理模式首先要有原始方法,还要有接口,原始对象和代理对象要实现相同的接口;同时,代理类是要对原始方法进行增强,所以代理类中还要执行原始方法,所以代理类中还要有原始对象。如下,我们用房东中介来举例:

使用如下:

上述程序就是静态代理。但比如又增加了新的功能:代理房屋出售,那么代码就得进行如下修改

从上述程序可以看出,静态代理很不灵活,代码都是写死的,要想增加功能,还得重新编写代码,给很多类进行功能增加,重新进行编译。

同样,如果有新的接口和业务类,也需要对每一个业务类新增代理。

那怎么就能通过一个代理类进行实现?这就需要了解动态代理技术

动态代理:

在之前的文章中,说过动态代理是在程序运行时由jvm,根据的是动态字节码技术

Java对动态代理的实现由两种方式:JDK动态代理和CGlib动态代理

JDK动态代理

定义JDK动态代理类:

使用代理:

InvocationHandler接口是Java动态代理实现的关键接口之一。它里面有一个方法invoke,用于处理被代理对象的方法调用。这个接口的详解在我写的上一篇关于AOP编程的文章中

Proxy类则是java提供的关于代理的类,最常用的就是newproxyInstance方法,它用于生成一个代理对象。这个方法有三个参数,第一个是类加载器,用哪个类的都可以;第二个是原始类所实现的所有接口(以便于代理对象进行实现);第三个是实现了InvocationHandler接口的对象(用于对原始方法进行增强)

上面的使用代码中target也可以用HouseOwner接收,没有影响(这其实和运行时类型以及编译时类型有关。Rent target=new HouseOwner(),对于target来说,其编译时类型是Rent这个接口,其运行时类型是HouseOwner这个原始类,而在newInstance方法的第二个参数中要使用到target,它是要得到原始类实现的所有接口,所以要得到的是HouseObject的class对象,所以只要new的是HouseObject类型就可以,不管是用Rent接收还是用HouseObject接收。)(同时,变量.getClass()得到的是该变量的运行时类型!!并且,变量访问到的方法也一定是运行时类型中的方法,变量访问属性访问的是编译时类型中的属性)

CGlib动态代理

JDK动态代理最不好的地方就是只能代理实现了接口的类。但有些场景下,我们的业务代码是直接实现的,并没有接口的定义。为了解决这种问题,我们可以使用CGlib实现。详解就在上一篇AOP文章中。

CGlib是通过继承来实现代理(而不是通过实现相同的接口)。

如下:

首先引入依赖

接着自定义方法拦截器实现MethodInterceptor接口并重写intercept方法,用于增强目标方法,和JDK动态代理中InvocationHandler中的invoke方法类似,如下:

最后创建代理类并且使用:

MethodInterceptor接口和JDK动态代理中的InvocationHandler类似,他里面只有一个方法intercept()用于增强目标方法

Enhancer.create()用来生成一个代理对象,第一个参数是目标对象的class,第二个参数是callback即自定义的方法拦截器,其实就是功能的增强。这里为什么没有interfaces,因为CGlib可以实现对所有类的代理,它不是通过与目标类实现相同的接口,而是通过继承目标类,也就是只需要知道目标类的类型即可。

MethodInterceptor继承了Callback,所以第二个参数可以传MethodInceptor的实现类

详细解析请看我的文章:http://t.csdnimg.cn/Mv9LD

  • 15
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值