【Spring】AOP的代理默认是Jdk还是Cglib?
菜瓜:你是怎么理解AOP的,经常听说它是动态代理实现的,那它默认是jdk还是cglib的实现
水稻:我觉得吧,AOP是对OOP的补充。通常情况下,OOP代码专注功能的实现,所谓面向切面编程,大多数时候是对某一类对象的方法或者功能进行增强或者抽象
菜瓜:我看你这个理解就挺抽象的
水稻:举个栗子🌰!我要在满足开闭原则的基础下对已有功能进行扩展
- 我现在想对很多个功能增加日志功能,但是代码已经打好包了,不想改。又或者有时候方法调用很慢,想定位问题
- low一点的方法就是每个方法调用之前记录调用开始,之后记录调用结束
菜瓜:你说的这个low一点的方法怎么好像是在说我???
水稻:回到第二个问题,是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
// 注解里面有个属性proxyTargetClass缺省值是false 表示不使用cglib,也就是默认使用jdk动态代理,后面有代码 @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 结束了。。。
菜瓜:这个自定义注解是怎样和动态代理挂上钩的
水稻:这个注解它看上去是从OperatorAspect#around()方法中调到了delete方法,就像代理类Proxy执行invoke方法一样。接下来我们从Spring的角度来分析其执行过程。来理一下思路。
- 如果使用代理模式,那最合适创建代理的地方是在哪?
- 切面类如何被封装到Proxy中的?
- 最后执行调用的逻辑是怎样?
菜瓜:go on ....
水稻:不知道你是否还记得我们之前有聊到过bean创建完毕后会调用一些PostProcessor对其进一步操作,讲道理代理类也非常适合在此处操作,此时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); // ②如果符合切面 创建代理,持有被代理对象的引用,还有Advisor 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; }
-
- 从①进去 - (标星提醒)这里需要用到AOP启动注解@EnableAspectJAutoProxy注入类AnnotationAwareAspectJAutoProxyCreator
-
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { /** * 找到所有的切面类 事务 等 * 开启@EnableAspectJAutoProxy注解会调用@AspectJ的收集类 {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator} * 调用Advisor接口的收集类 {@link BeanFactoryAdvisorRetrievalHelper} 譬如TransactionInterceptor的持有类BeanFactoryTransactionAttributeSourceAdvisor */ List<Advisor> candidateAdvisors = findCandidateAdvisors(); // 匹配beanClass对应的Advisor List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); extendAdvisors(eligibleAdvisors); if (!eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); } return eligibleAdvisors; } @Override protected List<Advisor> findCandidateAdvisors() { // Add all the Spring advisors found according to superclass rules. List<Advisor> advisors = super.findCandidateAdvisors(); // Build Advisors for all AspectJ aspects in the bean factory. if (this.aspectJAdvisorsBuilder != null) { // 注解@AspectJ类搜集入口 advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); } return advisors; } public List<Advisor> buildAspectJAdvisors() { List<String> aspectNames = this.aspectBeanNames; if (aspectNames == null) { synchronized (this) { ... for (String beanName : beanNames) { ... Class<?> beanType = this.beanFactory.getType(beanName); if (beanType == null) { continue; } // 判断是否是@AspectJ修饰的 if (this.advisorFactory.isAspect(beanType)) { aspectNames.add(beanName); AspectMetadata amd = new AspectMetadata(beanType, beanName); //收集 if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); // 获取@Aspect的类 List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory); ... advisors.addAll(classAdvisors); } ... return advisors; }
-
- 从②进去createProxy方法 -> DefaultAopProxyFactory#createAopProxy
-
@Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// isProxyTargetClass 呼应开始的默认配置false 创建Jdk动态代理 - 第一个参数默认为false 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 {
// 默认情况走这里 - 注意SpringBoot自动注入改了proxyTargetClass参数,默认是Cglib代理 return new JdkDynamicAopProxy(config); } }
-
水稻:这段代码划重点
- 默认生成的代理是Jdk。PS:(SpringBoot自动装配修改了参数,默认是Cglib)
- 最后被放入容器(一级缓存)的对象是代理对象
- AOP启动注解的功能在这里被调用,事务启动注解同样也是
- 代理对象持有被代理对象的引用,实际代码执行时会调到增强类的Invoke代码
菜瓜:那动态代理的代码具体是怎么调用的
水稻:由于demo里面被注解对象不是接口调用,这里我们就展示一个Cglib代理的调用过程
- CglibAopProxy$DynamicAdvisedInterceptor#intercept
-
@Nullable public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object oldProxy = null; boolean setProxyContext = false; Object target = null; TargetSource targetSource = this.advised.getTargetSource(); try { ... // demo中此处是ItemOcController实例 target = targetSource.getTarget(); Class<?> targetClass = (target != null ? target.getClass() : null); // 获取拦截器调用链 - 此处包含是Advisor中的advice就是我们定义的注解中的Around方法 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); Object retVal; if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) { ... } else { // proceed调用到around方法 retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); } retVal = processReturnType(proxy, target, method, retVal); return retVal; } ... }
-
菜瓜:终于等到你,还好我没放弃。。。
水稻:是,这里还有一个JDK动态代理调用栗子
菜瓜:停。。。今天饱了,我下去自己断点走一趟再熟悉熟悉
水稻:也好,我下去再给你准备几个栗子🌰
总结:
- Spring默认配置创建的代理是JDK动态代理,SpringBoot默认是Cglib,可自行百度修改配置
- AOP提供了在不侵入代码的前提下动态增强目标对象的途径,让OOP更加专注于实现自己的逻辑
- 而Spring的实现还是老套路,利用PostProcessor在类初始化完成之后替需要的bean创建代理对象
- 配合@Configuration使用的@EnableAspectJAutoProxy注解在启动时会注入其@Import类并执行注册逻辑,后续在找切面时会扫描解析@Aspect注解的类
是谁来自江河湖海,却囿于昼夜厨房与爱