Spring AOP 切面编程详解
OOP :
Object Oriented Programming
面向对象编程:代码重用——继承
AOP:
Aspect Oriented Programming
面向切面编程:代码重用——切面
1. Aop 切面编程简介:
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发
然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP
在软件业,面向切面编程,是指通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内
2.1 Aop的特点
AOP的特点:
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
2.2.1 :AOP思想(纵向重复,横向提取)
看下图,取款和显示余额转账前都需要做身份效验,在这里其实取款和显示余额转账就是核心业务功能,而验证用户就是周边功能,这些功能在整个功能流程中是纵向重复的,所以在开始写核心业务工程的时候就可以先不要考虑,核心业务功能完成后,将身份效验等这种周边工程一次"插入"代码, 相当于把重复的代码从核心业务代码中抽取出来,这就是横向提取
参考CSDN文章进行理解:https://blog.csdn.net/guanripeng/article/details/79922787
举例二:
2.2.2 Aop的经典应用
- 事务管理、性能监视、安全检查、缓存 、日志等
- 这些系统服务通常被称为横切关注点
- 想象一下如果每个组件都单独去实现这些系统功能:
- 改变这些关注点的逻辑,修改各个模块当中的实现,方法的调用就会重复出现在各个模块中
- 组件会因为那些与自身核心业务无关的代码而变得混乱
2. Aop的应用:
2.1 建立切面-代理机制实现
aop底层将采用代理机制进行实现。
2.2动态代理的两种机制
A:JDK动态代理
-
接口 + 实现类 :spring采用 jdk 的动态代理Proxy。
-
怎么做的?基于接口去实现。回顾。
-
缺点:如果目标类没有实现任何接口呢?jkd动态代理就无能为力。
B:Cglib 动态代理
- 实现类:spring 采用 cglib字节码增强。
- 怎么做的?基于继承。也可以实现动态代理。
cglib可以对任意类生成代理对象,它的原理是对目标对象进行继承代理,如果目标对象被final修饰,那么该类无法被cglib代理。
SpringAOP的本质就是动态代理,那么Spring到底使用的是JDK代理,还是cglib代理呢?
答:混合使用。如果被代理对象实现了接口,就优先使用JDK代理,如果没有实现接口,就用用cglib代理。
3. Aop实战
三种方式:
- 手动方式
- JDK动态代理
- Cglib动态代理
- 半自动方式(SpringAop)(了解)
- 自动方式,使用Aspectj(掌握)
3.2 方式二:SpringAop
1. 导包(4+1+2)
除了aop的依赖,还要导入Spring的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
2. class
UserService和CustomAdvice
3. 组件的注册
3个组件
UserService的实例
Advice通知的实例(实现MethodInterceptor的接口,通过invoke方法通知在什么之间做什么事情)
UserServiceProxy代理对象的实例(通过目标对象和interceptorName→容器中通知)
4. 测试
5.Spring-Aop实现切面编程的不足之处:
不足之处
- 哪些方法被增强需要写代码判断
- 增强的代码有要求,必须实现一些接口
4. 方式三:Aspectj 实现
AspectJ是什么
-
基于java语言的AOP框架
-
Spring2.0之后支持了基于AspectJ的切入点表达式的支持
-
AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
-
允许直接在POJO里面定义切面类
-
主要用途,不改动现有代码的前提下,自定义开发。植入代码
AspectJ-切入点表达式
- 语法:execution(修饰符 返回值 包 类 方法名(参数类型) throws 异常)
- 修饰符:一般省略,相当于通配
- 可以写 public 表示公共
- *表示任意
- 返回值
- 类的全类名,除了java.lang包下的类和基本类型可以直接写。
- *表示任意
- 包+类名+方法名:
- 可以省略(有条件),除了头和尾,使用..省略中间的部分
- 使用*通配,代表一个单词或者一个单词的一部分,最多通配一级目录。
头和尾都可以进行通配
- 参数
- () 无参
- 特定的参数类型需要写类的全类名。除了java.lang包下的类和基本类型可以直接写
- *进行通配,一个*通配一个任意类型的参数,如果多个参数就多个*
- (..) 参数任意
- throws 可省略 一般不写
举例
<aop:pointcut id="mypoiutcut" expression="execution(public int com.cskaoyan.sercvice.UserServiceImpl.login(String,String ))">
4.1. 导包(5+1+1)
导入Spring-context(5+1) 和Aspectj的织入包(1),织入包使用org版本的
<dependency>
<!--织入包-->
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
4.2.业务逻辑模块组件注册
注册UserService的实例
4.3 使用
4.3.1 方式一:Adviso
-
注册通知类,需要实现 MethodInterceptor 接口
-
在application.xml中配置
测试:
直接获取service实例,执行方法
4.3.2 方式二: Aspect(重点)
aspect:切面
切面类=切入点+通知
4.3.2.1 通知
4.3.2.2 JointPoint 切入点
作为通知方法的形参,可以通过joinPoint拿到代理对象,目标对象,方法,参数
4.3.2.3 Before,After
Before:在委托类(目标类)方法执行前执行,参数类型的校验。 前置通知。
After:在委托类(目标类)方法执行之后,不管发生什么,都会执行到,类似于finally,
4.3.2.5 Around
环绕 目标类的执行方法,通知 在切入点之前和之后都会执行。常用于增加事务
方法返回值是Object 参数是ProceedingJoinPoint,ProceedingJoinPoint对象是JoinPoint的子接口该对象只用在@Around的切面方法中
joinPoint的proceed方法类似于动态代理中的method.invoke方法
4.3.2.5 After-throwing和After-returing
After-throwing:传入参数Throwable 和aop通知标签的throwing属性对应
抛出异常的时候执行通知。正常情况下走不到。只有发生异常的情况下才会去通知。记录一些日志。
After-returning在切入点之后执行后置通知,可以对结果进行检查。增加log
4.4 通过注解去去实现切面AspectJ
4.4.1 AspectJ的注解开关
在application.xml配置文件中添加标签q
<aop:aspectj-autoproxy/>
4.4.2 通过注解去配置Aspect切面类
- 在类上添加@Component @Aspect 注解
- 创建一个方法, 在这个方法上添加@Pointcut(”execution(切入点表达式))
- 通过注解通过@Before,@After等注解,并且value="切入点表达式"
其他通知:
4.4.3 通过自定义注解对指定方法进行增强
- 自定义注解:
-
切入点表达式:
-
容器中组件的方法上增加注解: