AOP介绍
思想及概念
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程面向切面编程/6016335),通过[预编译]方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
可以做什么?
主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。
主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码
AOP术语
![1560774375748](C:\Users\何进结\AppData\Roaming\Typora\typora-user-images\15607
切面(Aspect)
我们将自己需要插入到目标业务逻辑中的代码模块化, 通过AOP使之可以横切多个类的模块,称之为切面。
在Spring AOP配置中切面通常包含三部分:
- 切面模块本身
- 通知
- 切入点
<!-- 目标业务逻辑代码 -->
<bean id="user" class="testshop2.test_proxy.spring.UserSerivce2Impl2"/>
<!-- 切面模块化对象(代表我们要附加到原始业务逻辑中的代码) -->
<bean id="aspect" class="testshop2.test_proxy.spring.MyAspect2"/>
<!-- 示例说明: 将切面calcAspect中的代码插入到calc原始业务代码中 -->
<aop:config>
<!-- 定义公用的切入点表达式,如果aspect中有多个通知,都可以通过pointcut-ref复用 -->
<aop:pointcut id="h1" expression="execution(* testshop2.test_proxy.spring.UserSerivce2Impl2.*(..))"/>
<!-- 定义公用的切入点表达式,如果aspect中有多个通知,都可以通过pointcut-ref复用 -->
<aop:aspect ref="aspect">
<aop:around method="testProxySpring" pointcut-ref="h1"/>
<aop:before method="beforeMethod" pointcut-ref="h1"/>
<aop:after method="afterMethod" pointcut-ref="h1"/>
<aop:after-returning method="afterReturningMethod" pointcut-ref="h1" returning="obj"/>
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="h1" throwing="throwable"/>
</aop:aspect>
</aop:config>
切入点(pointcut)
在 Spring AOP 中,需要使用 AspectJ 的切点表达式来定义切点
AspectJ 指示器 | 描述 |
---|---|
execution () | 用于匹配连接点的执行方法 常用 |
args () | 限制连接点的指定参数为指定类型的执行方法 |
@args () | 限制连接点匹配参数类型由指定注解标注的执行方法 this () 限制连接点匹配 AOP 代理的 Bean 引用为指定类型的类 |
target () | 限制连接点匹配特定的执行对象,目标对象是指定的类型 |
@target () | 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型注解 |
within() | 限制连接点匹配指定类型,比如哪个包下,或哪个类里面 |
@within() | 限制连接点匹配指定注释所标注的类型(当使用 Spring AOP 时,方法定义在由指定的注解所标注的类里) |
@annotation | 限制匹配带有指定注释的连接点 |
通知(advice)
通知:(增强)要织入的代码
前通知(before)
在目标方法调用前通知切面, 什么参数也无法获取。也不能终止目标方法执行
<bean id="user" class="testshop2.test_proxy.spring.UserSerivce2Impl2"/>
<bean id="aspect" class="testshop2.test_proxy.spring.MyAspect2"/>
<aop:config>
<aop:pointcut id="h1" expression="execution(* testshop2.test_proxy.spring.UserSerivce2Impl2.*(..))"/>
<aop:aspect ref="aspect">
<aop:before method="beforeMethod" pointcut-ref="h1"/>
</aop:aspect>
</aop:config>
环绕通知(around)
- 在目标方法执行前、后被通知, 可以获取连接点对象(ProceedingJoinPoint, 该对象可以获取被拦截方法的签名、参数、返回值、包括是否被调用)
- 该方法的返回值,即代表了真正业务逻辑代码的返回值
- 可以选择终止或正常执行目标方法
<aop:around method="testProxySpring" pointcut-ref="h1"/>
后置通知(after returning)
只有在目标方法 正常 执行结束后才能获取方法返回值的结果!
<aop:after-returning method="afterReturningMethod" pointcut-ref="h1" returning="obj"/>
最终通知(after)
在目标方法执行结束后通知切面,无法获取任何参数。无论目标方法是正常执行结束还是抛出异常终止,都会被通知
<aop:after method="afterMethod" pointcut-ref="h1"/>
异常通知(after throwing)
只有在目标方法 出现异常 才会通知, 在通知方法中可以获取到抛出的异常信息
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="h1" throwing="throwable"/>
连接点(JoinPoint)
- 定义:连接点是一个应用执行过程中能够插入一个切面的点。
- 连接点可以是调用方法时、抛出异常时、甚至修改字段时、
- 切面代码可以利用这些点插入到应用的正规流程中
- 程序执行过程中能够应用通知的所有点。
Spring仅支持方法创建连接点
织入
- 织入是将切面应用到目标对象来创建的代理对象过程。
- 切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可以织入
编译期——切面在目标类编译时期被织入,这种方式需要特殊编译器。AspectJ的织入编译器就是以这种方式织入切面。
类加载期——切面在类加载到
JVM ,这种方式需要特殊的类加载器,他可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的 LTW 就支持这种织入方式运行期——切面在应用运行期间的某个时刻被织入。一般情况下,在织入切面时候,AOP 容器会为目标对象动态的创建代理对象。Spring AOP 就是以这种方式织入切面。
使用步骤
添加依赖包(maven演示)
<!-- Spring IOC最小依赖是beans、context,我们引入context依赖,maven会自动将beans依赖一并引入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<!--aop插件-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
xml方式
添加aop schema摘要
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
<!--此为aop摘要配置
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
-->
使用xml文件配置
- 定义切面类,并且添加到xml配置文件中
配置aop:config
Spring 的 AOP 配置元素简化了基于 POJO 切面声明
AOP 配置元素 | 描述 |
---|---|
aop : advisor | 定义 AOP 通知器 |
aop : after | 定义 AOP 后置通知(不管被通知方法是否执行成功) |
aop : after-returing | 定义 AOP after-returing 通知 |
aop : after-throwing | 定义 AOP after-throwing 通知 |
aop : around | 定义 AOP 环绕通知 |
aop : aspect | 定义切面 |
aop : before | 定义 AOP 前置通知 |
aop : config | 顶层的 AOP 配置元素,大多数 aop : * 元素必须包含在 元素内 |
aop : declare-parents | 为被通知的对象引入额外接口,并透明的实现 |
aop : pointcut | 定义切点 |
引用切面类(配置bean)
<!--代码示例-->
<bean id="user" class="testshop2.test_proxy.spring.UserSerivce2Impl2"/>
<bean id="aspect" class="testshop2.test_proxy.spring.MyAspect2"/>
定义切入点表达式
-
作用
通过表达式的方式定位一个或多个具体的连接点。
-
语法细节
①切入点表达式的语法格式
execution([权限修饰符] [返回值类型] [简单类名/全类名] 方法名)
②在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。
表达式 | 含义 |
---|---|
execution(* .addAfter(int,…)) || execution( *.deleteAfter(int,…)) | 任意类中第一个参数为int类型的addAfter方法或deleteAfter方法 |
③xml配置文件的示例代码
<aop:pointcut id="h1" expression="execution(*testshop2.test_proxy.spring.UserSerivce2Impl2.*(..))"/>
<!--表示这个类的全部方法-->
定义通知
<aop:around method="testProxySpring" pointcut-ref="h1"/>
注解方式(Annotation)
定义Java类
定义切面类,并添加@Aspect注解,@Component注解
@Aspect//表示此类是个切面类
@Component//表示此切面也是一个bean
@Slf4j//logback日志
public class SayMethodAspect {
// 指定该方法是一个环绕通知,通知注解的参数代表引用一个切入点
@Around("testshop2.annotation_proxy.GlobalPointcut.sayMethod()")
public Object testSay(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("大哥来了!");
Object obj=joinPoint.proceed();
System.out.println("大哥慢走!");
log.debug("哈哈哈!哈哈哈!哈哈哈!");
return obj;
}
//要切入的类
@Component
public class IsayImpl implements Isay {
@Override
public void say1(){
System.out.println("HELLO ,world!");
}
@Override
public void say2(String name){
System.out.println("Hello"+name);
}
}
定义切入点
【官方建议从新写个类】
//通过@Pointcut注解定义切入点表达式
//此处表达式含义:拦截testshop2.annotation_proxy包下所有类(包括子包中所有类)中的所有方法
@Component
public class GlobalPointcut {
@Pointcut("execution(* testshop2.annotation_proxy..*.*(..))")
public void sayMethod(){}
}
开启自动织入支持
<!-- 开启aop注解支持 -->
<aop:aspectj-autoproxy />
<!-- 开启注解支持,同时强制指定代理机制为cglib -->
<aop:aspectj-autoproxy proxy-target-class="true" />
@Configuration
// 开启注解支持,同时强制指定代理机制为cglib
@ComponentScan(basePackages = "扫描包路径")
@EnableAspectJAutoProxy(proxyTargetClass = true)
//(proxyTargetClass = true)强制代理机制为cglib
public class MyAOPConf {}
AOP方法上的注解
注解 | 释意 |
---|---|
@Before() | 这个是前置调用,在方法之前掉调用 |
@After() | 这个称为后置调用,不管方法执行是正常还是异常都会调用 |
@Around() | 这个表示的是环绕 在调用方法之前和之后调用 一般用来记录日志,需要joinPoint.proceed()推动目标进行 |
@AfterReturning() | 这个表示的是正常运行后,返回数据的时候调用 |
@AfterThrowing() | 这个表示的是抛出异常的时候调用 |
@Aspect | 告诉容器这是一个切面类 |
@Pointcut() | 切入点表达式的位置 |