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 定义中的
// 和 ,如果读者感兴趣,回到 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);
今天暂时先到这里,下篇文章开始深入分析这个方法。