Spring4.x下无法为Feign对象进行Aop增强问题始末

本文探讨了在Spring4.x中使用@FeignClient注解的接口无法被AOP正常代理的问题,通过对比Spring5.x的实现,揭示了解决方案。

目录

动机

问题说明

排错过程


动机

动机很简单,就是想对标注了@FeignClient的feignClient进行一些切入点的配置,实现通用日志控制。

问题说明

像Controller及其子处理器这类Spring自己定义的Bean没问题,对自己的实现的支持还是很完善的,但是到了feign这块,问题就很大了(另外说明一下,当前使用的Spring版本是4.x,且同样的实现,在Spring5.x中完全没问题),通用的aop切片根本无法对标识了@FeignClient的接口api进行处理!!

初步分析了一下,在平常的开发过程中,对feign的各个client调用既然可以使用@Autowired这样的方式引入,那基本可以确定xxFeignClient是会被发布成Spring的容器中的,那么问题所在就只能是spring-cloud-openfeign中对于标识@FeignClient的接口,运行时的生成过程,有可能没有按Spring的标准bean生成方式处理,导致Spring aop没法实现代理。

排错过程

查看feign对于feignClient的生成过程,源码如下

public class ReflectiveFeign extends Feign {

 

  ...

 

  /**

   * creates an api binding to the {@code target}. As this invokes reflection, care should be taken

   * to cache the result.

   */

  @SuppressWarnings("unchecked")

  @Override

  public <T> T newInstance(Target<T> target) {

    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);

    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();

    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

 

    for (Method method : target.type().getMethods()) {

      if (method.getDeclaringClass() == Object.class) {

        continue;

      else if(Util.isDefault(method)) {

        DefaultMethodHandler handler = new DefaultMethodHandler(method);

        defaultMethodHandlers.add(handler);

        methodToHandler.put(method, handler);

      else {

        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));

      }

    }

    InvocationHandler handler = factory.create(target, methodToHandler);

    // 本步骤,通过jdk的动态代理,将标识了@FeignClient的接口进行代理,生成代理对象,并将该对象返回

    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);

 

    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {

      defaultMethodHandler.bindTo(proxy);

    }

    return proxy;

  }

    ...

}

可以看到,通过jdk的动态代理,将标识了@FeignClient的接口进行代理,生成代理对象,并将该对象返回。此步过程看起来也没什么问题。

继续看下Spring生成bean过程中,如何对其进行aop处理的(详细过程可查阅:Spring boot及Spring启动过程及拆解,aop的处理主要是通过BeanPostProcessor【org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator】机制进行处理的)

// 实际该对象类型为AnnotationAwareAspectJAutoProxyCreatorpublic abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {   ...

    /**

     * Create a proxy with the configured interceptors if the bean is

     * identified as one to proxy by the subclass.

     * @see #getAdvicesAndAdvisorsForBean

     */

    @Override

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (bean != null) {

            Object cacheKey = getCacheKey(bean.getClass(), beanName);

            if (this.earlyProxyReferences.remove(cacheKey) != bean) {

                // 本步骤判断该bean是否需要进行aop代理,以便对各切面进行调用

                return wrapIfNecessary(bean, beanName, cacheKey);

            }

        }

        return bean;

    }

    /**

     * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.

     * @param bean the raw bean instance

     * @param beanName the name of the bean

     * @param cacheKey the cache key for metadata access

     * @return a proxy wrapping the bean, or the raw bean instance as-is

     */

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {

        if (beanName != null && this.targetSourcedBeans.contains(beanName)) {

            return bean;

        }

        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {

            return bean;

        }

        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {

            this.advisedBeans.put(cacheKey, Boolean.FALSE);

            return bean;

        }

 

        // Create proxy if we have advice. 首先通过getAdvicesAndAdvisorsForBean获取是否需要进行代理,所以此过程是关键

        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;

    }

     

    ...

}

 

//

public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {

    ...

    @Override

    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {

        // 查找该bean可适配的aop处理器

        List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);

        if (advisors.isEmpty()) {

            // 找不到,返回“无需aop代理”

            return DO_NOT_PROXY;

        }

        return advisors.toArray();

    }

    /**

     * Find all eligible Advisors for auto-proxying this class.

     * @param beanClass the clazz to find advisors for

     * @param beanName the name of the currently proxied bean

     * @return the empty List, not {@code null},

     * if there are no pointcuts or interceptors

     * @see #findCandidateAdvisors

     * @see #sortAdvisors

     * @see #extendAdvisors

     */

    protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {

        // 先找到所有的切点配置

        List<Advisor> candidateAdvisors = findCandidateAdvisors();

        // 找出当前的bean可适配的切点处理

        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);

        extendAdvisors(eligibleAdvisors);

        if (!eligibleAdvisors.isEmpty()) {

            eligibleAdvisors = sortAdvisors(eligibleAdvisors);

        }

        return eligibleAdvisors;

    }

    /**

     * Search the given candidate Advisors to find all Advisors that

     * can apply to the specified bean.

     * @param candidateAdvisors the candidate Advisors

     * @param beanClass the target's bean class

     * @param beanName the target's bean name

     * @return the List of applicable Advisors

     * @see ProxyCreationContext#getCurrentProxiedBeanName()

     */

    protected List<Advisor> findAdvisorsThatCanApply(

            List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

 

        ProxyCreationContext.setCurrentProxiedBeanName(beanName);

        try {

            // 最终,通过Spring提供的AopUtils判断当前bean可适配的切点(其实这步才是核心的实现,它决定了一个bean是否可适配某些aop配置)

            return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);

        }

        finally {

            ProxyCreationContext.setCurrentProxiedBeanName(null);

        }

    }

    ...

}

以下截图是跟踪过程中,对feignClient的bean的实际判读结果

 

好了,不想再陷入这无限的细节中了,既然Spring5.x没问题,那看此步判断是在Spring5.x哪里生效的不就行了!

 

比对成功了!!


比对两个版本实现,org.springframework.aop.aspectj.AspectJExpressionPointcut#getShadowMatch的实现如下,5.x版本增加了对代理类型的判断

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值