1、什么是AOP?
AOP(Aspect Oriented Programming):面向切面编程,降低业务逻辑各部分之间的耦合度,提高程序可重用性,提高开发效率。
2、AOP能干什么?
日志记录、性能统计、性能调优、安全控制、权限管理、事务处理、异常处理、资源池管理、缓存处理、同步持久化等。
3、我太难了!看了没懂!
好吧!以记录日志为例。
原始时代:在做日志处理的时候,在每个方法中添加日志处理。
石器时代:为了代码复用,把日志处理抽离成一个新的方法。仍然手动插入这些方法。
牛逼时代:通过动态代理,将一些横向的功能抽离成一个独立的模块,在指定位置插入这些功能。在指定位置执行对应流程。这样的思想,亦即AOP。
4、这么牛逼怎么玩耍的?
一言不合丢代码。。。。。。
POM需要依赖
Spring需要引入,SpringBoot不需要
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
-
Spring使用XML装配方式
- 首先定义注解。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckVersion {
}
- 保存订单业务逻辑,添加注解。
public class OrderService {
// 使用CheckVersion注解,saveOrder是切点。
@CheckVersion
public OrderVO saveOrder(OrderVO order) {
...
}
}
- AOP增强方法。
/**
* 版本号检查增强方法
*/
public class CheckVersionAdvice {
@Autowired
private OrderDAO orderDAO;
/**
* 保存之前做订单版本校验,不一致不能保存
* @param param
*/
public void checkVersion(OrderVO param) {
if (StringUtils.isBlank(param.getOrderId())) {
return;
}
Optional<Order> optional = orderDAO.findById(param.getOrderId());
Order order = optional.get();
if (!order.getVersion().equals(param.getVersion())) {
throw new BusinessException(getConstantValue("不是最新订单!"));
}
}
}
- spring-aop.xml可以参照如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
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">
<!--可以使用注解自动装配-->
<aop:aspectj-autoproxy/>
<!--如下XML配置AOP,如果类中使用@Aspect如下配置可以省略 -->
<bean id="checkVersionAdvice" class="com.xyz.demo.CheckVersionAdvice"/>
<aop:config>
<!--AOP通知增强方法-->
<aop:aspect id="versionCheck" ref="checkVersionAdvice" order="1">
<!--配置AOP切点及增强类型-->
<aop:before method="checkVersion"
pointcut="execution(* com.xyz.demo..service..*Service.*(..))
and args(param) and @annotation(com.xyz.demo.CheckVersion)" />
</aop:aspect>
</aop:config>
</beans>
-
SpringBoot 用注解自动装配
- 测试类:TestController
@RestController
public class TestController {
@RequestMapping("/{msg}")
public String hello(@PathVariable("msg") String message) {
System.out.println(message);
return message;
}
}
启动SpringBoot,在浏览器执行http://localhost:8080/Hello,SpringBoot!
返回结果:
Hello,SpringBoot!
- 切面类:LogAspect
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.example.demo.controller..*(..))")
private void pointCut() { }
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
System.out.println(className + "." + methodName + "() 开始!");
}
@After("pointCut()")
public void after(JoinPoint joinPoint) {
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
System.out.println(className + "." + methodName + "() 结束!");
}
}
加人AOP输出结果:
com.example.demo.controller.TestController.hello() 开始!
Hello,SpringBoot!
com.example.demo.controller.TestController.hello() 结束!
哇塞!不用配置XML喽!我的Controller里一点日志代码也没加耶!!!
5、这些注解都是什么鬼?
让我们先了解下边这些名词:
- TargetObject目标对象
指被切入的对象。
例子中,TestController是目标对象。
例子中,LogAspect就是切面。 - JoinPoint连接点
程序执行中的某个点、某个位置。
例子中,hello()是连接点。 - PointCut切点
切面匹配连接点的点,与切点表达式相关,切面如何切点。
例子中,@PointCut注解就是切点表达式,匹配对应的连接点。 - Aspect切面
一个关注点的模块。 - Advice通知
指在切面的某个特定的连接点上执行的动作。
例子中,before()与after()方法中的代码。 - Weave织入
将Advice作用在JoinPoint的过程。
看着有点蒙圈,这么名词直接有啥关系呢?
6、怎么才能玩的溜?
要想玩的溜,继续往下看!
Aop核心类大致分为三类:
- advisorCreator:默认情况下只使用一种代理机制,主要用来扫描获取advisor。
- advisor顾问:切面的体现形式,封装AOP切点和通知。LogAspect中的@Pointcut、@before和@after就是Advisor。
- advice通知:AOP中增强的方法。LogAspect中before()和after()方法是通知。
切面提供 5 种通知类型:
Before:在方法调用之前调用通知。
After:在方法完成之后调用通知,无论方法执行成功与否。
After-returning:在方法执行成功之后调用通知。
After-throwing:在方法抛出异常后进行通知。
Around:包裹了被通知方法,在方法调用之前和调用之后执行自定义的行为。
切点表达式分类:
指示器 | 描述 |
---|---|
arg () | 限制连接点的指定参数为指定类型的执行方法 |
@args () | 限制连接点匹配参数由指定注解标注的执行方法 |
execution() | 用于匹配连接点的执行方法 |
this () | 限制连接点匹配 AOP 代理的 Bean 引用为指定类型的类 |
target() | 限制连接点匹配特定执行对象,这些对象对应类要具备指定类型注解 |
within() | 限制连接点匹配指定类型 |
@within() | 限制连接点匹配指定注释所标注的类型 |
@annotation | 限制匹配带有指定注释的连接点 |
让你牛掰让你飞
execution指示器简介
- 开头* 号标识了我们不关心的方法返回值的类型。
- 后我们指定了类名和方法名。
- (..)标识切点选择任意的 play( ) 方法,无论入参是什么。
execution指示器进阶
假设仅匹配 com.Springinaction.springidol 包。
可以使用 within()注意 && 是将 execution()和 within()连接起来,形成的 and 关系。同理也可以使用 || 或关系、!非关系。
7、我要飞得更高
获取参数的值和方法名称
AspectJ使用JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。
我们先来了解一下这两个接口的主要方法:
- JoinPoint
- Object[] getArgs():获取连接点方法运行时的入参列表;
- Signature getSignature() :获取连接点的方法签名对象;
- Object getTarget() :获取连接点所在的目标对象;
- Object getThis() :获取代理对象本身;
- ProceedingJoinPoint
继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:
- Object proceed() throws Throwable:通过反射执行目标对象的连接点处的方法;
- Object proceed(Object[] args) throws Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。
切点表达式
execute表达式
- 拦截任意公共方法
execution(public * *(..))- 拦截以set开头的任意方法
execution(* set*(..))- 拦截类或者接口中的方法
execution(* com.xyz.service.AccountService.*(..))
拦截AccountService(类、接口)中定义的所有方法- 拦截包中定义的方法,不包含子包中的方法
execution(* com.xyz.service..(..))
拦截com.xyz.service包中所有类中任意方法,不包含子包中的类- 拦截包或者子包中定义的方法
execution(* com.xyz.service...(..))
拦截com.xyz.service包或者子包中定义的所有方法
within表达式
- 拦截包中任意方法,不包含子包中的方法
within(com.xyz.service.*)
拦截service包中任意类的任意方法- 拦截包或者子包中定义的方法
within(com.xyz.service..*)
拦截service包及子包中任意类的任意方法
this表达式
代理对象为指定的类型会被拦截
this(com.xyz.service.AccountService)
被spring代理之后生成的对象必须为ServiceImpl才会被拦截。
target表达式
目标对象为指定的类型被拦截
target(com.xyz.service.AccountService)
目标对象为AccountService类型的会被代理。
args 表达式
匹配方法中的参数
- 匹配只有一个参数,且类型UserModel
@Pointcut("args(com.xyz.demo.UserModel)")- 匹配多个参数
args(type1,type2,typeN)- 匹配任意多个参数
@Pointcut("args(com.xyz.demo.UserModel,..)")
匹配第一个参数类型为UserModel的所有方法, .. 表示任意个参数。
@target表达式
匹配的目标对象的类有一个指定的注解
@target(com.xyz.demo.Annotation1)
目标对象中包含Annotation1注解,调用该目标对象的任意方法都会被拦截。
@within表达式
指定匹配必须包含某个注解的类里的所有连接点
@within(com.xyz.demo.Annotation1)
声明有Annotation1注解的类中的所有方法都会被拦截。
@annotation表达式
匹配有指定注解的方法
@annotation(com.xyz.demo.Annotation1)
被调用的方法包含指定的注解。
@args表达式
方法参数所属的类型上有指定的注解,被匹配
注意:是方法参数所属的类型上有指定的注解,不是方法参数中有注解
- 匹配1个参数,且第1个参数所属的类中有Anno1注解
@args(com.xyz.demo.Anno1)- 匹配多个参数,且多个参数所属的类型上都有指定的注解
@args(com.xyz.demo.Anno1,com.xyz.demo.Anno2)- 匹配多个参数,且第一个参数所属的类中有Anno1注解
@args(com.ms.aop.jargs.demo2.Anno1,..)
8、修仙秘籍
Spring AOP的流程
- Spring加载自动代理器AnnotationAwareAspectJAutoProxyCreator,当作一个系统组件。spring aop 开启注解方式之后,该类会扫描所有@Aspect()标准的类,生成对应的adviosr。目前SpringBoot框架中默认支持的方式,自动配置。
- 当一个bean加载到Spring中时,会触发自动代理器中的bean后置处理
- bean后置处理,会先扫描bean中所有的Advisor
- 然后用这些Adviosr和其他参数构建ProxyFactory
- ProxyFactory会根据配置和目标对象的类型寻找代理的方式(JDK动态代理或CGLIG代理)
- 然后代理出来的对象放回context中,完成Spring AOP代理。