菜瓜:你觉得AOP是啥
水稻:我觉得吧,AOP是对OOP的补充。通常情况下,OOP代码专注功能的实现,所谓面向切面编程,大多数时候是对某一类对象的方法或者功能进行增强或者抽象
菜瓜:我看你这个理解就挺抽象的
水稻:举个栗子🌰!我要在满足开闭原则的基础下对已有功能进行扩展
- 我现在想对很多个功能增加日志功能,但是代码已经打好包了,不想改。又或者有时候方法调用很慢,想定位问题
- low一点的方法就是每个方法调用之前记录调用开始,之后记录调用结束
菜瓜:你说的这个low一点的方法怎么好像是在说我???
水稻:建议看一下动态代理设计模式【DP-动态代理】JDK&Cglib,我当然知道你不会看,所以我还准备了自定义注解的栗子
package com.hb.merchant.config.aop; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * @author QuCheng on 2020/6/23. */ @Configuration @EnableAspectJAutoProxy public class AopConfig { } package com.hb.merchant.config.aop; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author QuCheng on 2020/6/23. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OperatorLog { } package com.hb.merchant.config.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; /** * * @author QuCheng on 2020/6/23. */ @Aspect @Component @Slf4j public class OperatorAspect { @Around("@annotation(OperatorLog)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { //获取要执行的方法 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); //记录方法执行前日志 log.info("startLog: {} 开始了。。。" , methodSignature.getName()); //获取方法信息 String[] argNames = methodSignature.getParameterNames(); // 参数值: final Object[] argValues = joinPoint.getArgs(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < argNames.length; i++) { String value = argValues[i] == null ? "null" : argValues[i].toString(); sb.append(argNames[i]).append("=").append(value).append(","); } String paramStr = sb.length() > 0 ? sb.toString().substring(0, sb.length() - 1) + "]" : ""; log.info("参数信息为:[{}", paramStr); //执行方法 Object result; try { result = joinPoint.proceed(); } catch (Exception e) { log.error("errorLog", e); return null; } //记录方法执行后日志 log.info("endLog: {} 结束了。。。" , methodSignature.getName()); return result; } } package com.hb.merchant.controller.icbc.item.oc; import com.hb.merchant.config.aop.OperatorLog; import lombok.extern.slf4j.Slf4j; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author QuCheng on 2020-06-23. */ @RestController @RequestMapping("/item") @Slf4j public class ItemOcController { @OperatorLog @GetMapping("/delete") public String delete(Long itemId) { Assert.notNull(itemId,"itemId不能为空"); return "delete finished ..."; } }
// 后台打印
startLog: delete 开始了。。。
参数信息为:[itemId=1]
endLog: delete 结束了。。。
菜瓜:这个自定义注解又是怎么实现的呢?
水稻:不愧是你,没有源码看来是满足不了你的好奇心了!!不知道你是否还记得我们之前有聊到过bean创建完毕后会调用一些PostProcessor对其进一步操作
菜瓜:有印象,@PostConstruct注解就是InitDestroyAnnotationBeanPostProcessor在这里调用的,还自定义过BeanPostProcessorT对象打印输出过bean信息
水稻:你猜Spring是怎么操作的
菜瓜:let me try try。结合刚刚的栗子和提示,大胆猜测应该是用PostProcessor在bean创建完成之后生成代理对象。实际调用代理的invoke方法实现对被代理bean的增强
水稻:思路正确。看脉络
- 入口在AbstractAdvisorAutoProxyCreator#initializeBean
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { 。。。 // BeanNameAware BeanFactoryAware ... invokeAwareMethods(beanName, bean); 。。。 // BeanPostProcessorBefore @PostConstruct wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); 。。。 // initMethod InitializingBean接口 invokeInitMethods(beanName, wrappedBean, mbd); 。。。 if (mbd == null || !mbd.isSynthetic()) { // aop wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }
- 从aop入口跟下去
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { 。。。 // 收集切面信息匹配被代理对象 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); // 如果符合切面 创建代理,被代理对象被代理引用 Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
- 跟createProxy方法 -> DefaultAopProxyFactory#createAopProxy
@Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { // jdk动态代理类 return new JdkDynamicAopProxy(config); } // cglib return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
- 此处省略了切面类搜集和匹配的过程。可以简单理解成搜集到所有的切面类信息获取pointcut的目录或者注解信息,匹配当前bean是否属于pointcut目标范围
- 另外我们可以看到最后返回的bean已经不是原始bean了,而是代理对象。也就是说getBean("xxx")返回的对象实际是代理对象,被代理对象被其成员变量直接引用
菜瓜:然后代理类中都有invoke方法,那些advice(@Around,@Before...)在invoke中找到适当时机调用对吧
水稻:是的,这里我想结合@Transactional注解会更容易理解,你肯定用过这个注解吧,它其实。。。
菜瓜:停。。。今天获取的知识量已经够了,我下去自己断点走一趟再熟悉熟悉。下次请结合Transactional注解再敲打我吧
水稻:也好,我下去再给你准备几个栗子🌰
总结:
- AOP提供了在不侵入代码的前提下动态增强目标对象的途径,让OOP更加专注于实现自己的逻辑
- 而Spring的实现还是老套路,利用PostProcessor在类初始化完成之后替需要的bean创建代理对象
- 这里还有一些细节没有照顾到,譬如说AOP解析类是什么时候注册到IOC容器的(偷偷告诉你从@EnableAspectJAutoProxy注解下手)