AOP 详解之一基本概念
什么是 AOP
AOP 即 Aspect Oriented Programming,意为:面向切面编程,通过预编译形式和运行期动静代理实现程序性能的对立保护的一种技术。
AOP 是 OOP 的连续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。
利用 AOP 能够对业务逻辑的各个局部进行隔离,从而使得业务逻辑各局部之间的耦合度升高,进步程序的可重用性,同时进步了开发的效率。
说人话:要在咱们的性能中加一些性能,而不间接批改批改源代码的前提下,为了升高耦合性,就用 AOP 的形式实现。如:日志。
AOP 应用的技术原理次要是 jdk 的动静代理和 cglib 批改字节码两种形式。
在 AOP 中有六个概念:
Joinpoint(连接点):在零碎运行之前,AOP 的功能模块都须要织入到具体的功能模块中。要进行这种织入过程,咱们须要晓得在零碎的哪些执行点上进行织入过程,这些将要在其之上进行织入操作的零碎执行点就称之为 Joinpoint,最常见的 Joinpoint 就是办法调用。
Pointcut(切点):用于指定一组 Joinpoint,代表要在这一组 Joinpoint 中织入咱们的逻辑,它定义了相应 Advice 将要产生的中央。通常应用正则表达式来示意。对于下面的例子,Pointcut 就是示意“所有要退出日志记录的接口”的一个“表达式”。例如:“execution( com.joonwhee.open.demo.service...*(..))”。
Advice(告诉 / 加强):Advice 定义了将会织入到 Joinpoint 的具体逻辑,通过 @Before、@After、@Around 来区别在 JointPoint 之前、之后还是盘绕执行的代码。
Aspect(切面):Aspect 是对系统中的横切关注点逻辑进行模块化封装的 AOP 概念实体。相似于 Java 中的类申明,在 Aspect 中能够蕴含多个 Pointcut 以及相干的 Advice 定义。
Weaving(织入):织入指的是将 Advice 连贯到 Pointcut 指定的 Joinpoint 处的过程,也称为:将 Advice 织入到 Pointcut 指定的 Joinpoint 处。
Target(指标对象):合乎 Pointcut 所指定的条件,被织入 Advice 的对象。
宽泛的说概念很干燥,读者也不能很好的了解,咱们举一个实战中的一个例子。
业务中有个需要,须要在操作一个按钮之前去判断一下,以后按钮是否能够操作。间接批改源代码判断这种形式的确能够,然而耦合性太高了。
咱们就采纳 aop+redis 的形式去实现,看一下咱们的代码。
/**
* @author tcy
* @time 2021-06-22 16:43
*/
@Aspect
@Component
public class OperationAspect {
private static String CHECK_FLAG = "CHECK_FLAG:";
/**
* 定义切入点,切入点为 com.ruoyi.project.medicinemanager.operate.controller 下的所有函数
*/
@Pointcut("execution(public * com.ruoyi.project.medicinemanager.operate.controller.OperateController.insert(..))||" +
"execution(public * com.ruoyi.project.medicinemanager.operate.controller.OperateController.save(..))" +
"||execution(public * com.ruoyi.project.medicinemanager.operate.controller.OperateController.approve(..))")
public void OperationController() {}
/**
* 前置告诉:在操作单之前执行的告诉
*
* @param joinPoint
* @throws Throwable
*/
@Before("OperationController()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 判断标记位状态
String objectId = SecurityUtilsWrapper.getDeptIdStr();
if (RedisUtil.get(CHECK_FLAG + objectId) != null) {if (RedisUtil.get(CHECK_FLAG + objectId).equals(1)) {throw new CustomException("盘点中不容许操作");
}
}
}
}
截图中的办法就是 Joinpoint(连接点)艰深的说就是咱们的 aop 和咱们的业务连贯的中央。
切点就是 @Pointcut(“execution)外面的表达式。
@Before 就是盘绕告诉,能够抉择在业务执行前还是后去执行。
Aspect(切面)就是咱们定义的这个类,外面定义的切点,盘绕告诉。
Weaving(织入)就是咱们判断逻辑的过程,这个也是最形象的。
Target(指标对象)就是咱们的业务逻辑。
如果把 aop 的过程比喻成切肉,Target 就是咱们的肉,切的过程就是 Weaving,切点就是下刀的中央,Advice(告诉 / 加强)就是在什么时候开始切,Joinpoint 就是刀和肉的分割,Aspect(切面)就当咱们刀的大脑吧,外面记录着下刀的中央和肉在什么时候切。
通过这个例子应该对 aop 的概率了然于心了。
Spirng 的 IOC 容器的启动过程是一个大的流程,那么 aop 就是其中的一个局部,那 aop 是什么时候在 IOC 的容器中开始发挥作用的呢?
咱们持续看 refresh 的源码。
@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//1、刷新前的筹备
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//2、将会初始化 BeanFactory、加载 Bean、注册 Bean
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//3、设置 BeanFactory 的类加载器,增加几个 BeanPostProcessor,手动注册几个非凡的 bean
prepareBeanFactory(beanFactory);
try {
//4、模板办法
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 执行 BeanFactory 后置处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 5、Register bean processors that intercept bean creation.
// 注册 bean 后置处理器
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
// 国际化
initMessageSource();
// Initialize event multicaster for this context.
// 初始化事件播送器
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//6、模板办法 --springboot 实现了这个办法
onRefresh();
// Check for listener beans and register them.
//7、注册监听器
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//8、实现 bean 工厂的初始化 ** 办法重要 **********************************************
finishBeanFactoryInitialization(beanFactory);
//9、Last step: publish corresponding event.
// 实现上下文的刷新工作
finishRefresh();}
毫无疑问 aop 的注入过程肯定是在实例化单例 bean 的时候注入的。也就是地位 8,咱们持续深刻该办法。
具体过程很多很多 ….. 省略了,我在 IOC 源码中都有过解析,须要理解的能够移步历史文章。
咱们间接步入正题。
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {if (logger.isTraceEnabled()) {logger.trace("Creating instance of bean'" + beanName + "'");
}
RootBeanDefinition mbdToUse = mbd;
// Make sure bean class is actually resolved at this point, and
// clone the bean definition in case of a dynamically resolved Class
// which cannot be stored in the shared merged bean definition.
// 确保 BeanDefinition 中的 Class 被加载
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// Prepare method overrides.
// 筹备办法覆写,这里又波及到一个概念:MethodOverrides,它来自于 bean 定义中的 <lookup-method />
// 和 <replaced-method />,如果读者感兴趣,回到 bean 解析的中央看看对这两个标签的解析。try {mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
// 让 InstantiationAwareBeanPostProcessor 在这一步有机会返回代理,// 在《Spring AOP 源码剖析》那篇文章中有解释,这里先跳过 aop 入口 *******************************************************
// AOP 外围办法,用来解决应用 @Aspect 注解标识的切面 bean,读取切面 bean 中的信息,增加到 advisorsCache 缓存中,以便前面生成动静代理
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {return bean;}
}
catch (Throwable ex) {throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
try {
// 重头戏,创立实例化 bean
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {logger.trace("Finished creating instance of bean'" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}
AOP 就是在这个办法中开始执行的。
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
明天临时先到这里,下篇文章开始深入分析这个办法。