Controller被代理后,private方法为什么会报错?

466 阅读3分钟

前言

标题不严禁,但实在想不到怎么说了,因为在某些情况下才会出错。

起因是我前几天看到的一篇文章,标题为《Controller中的请求方法、private和public有什么区别》,文中讲述了Controller被代理后,private修饰的方法下使用其他被Spring注入的对象会报空指针异常,但我觉得他最后还没有总结到点子上,以下是他的总结:

在这里插入图片描述

具体原因

他在文章中说到,因为private修饰的方法,不能进行动态代理,这虽然是对的,但我总觉得说起来怪怪的,真正的原因还不是这个,我们知道private修饰的方法,子类无法重写,同样public final的也一样,那么这就导致cglib创建的子类中没有关于这类方法的实现,那么会直接进入到父类的方法中。

这里的会直接进入到父类的方法中指的是,SpringBoot通过反射直接进入到对应Controller方法,而如果是public方法,此时SpringBoot会兜一圈才会调用源对象中的目标方法。

兜一圈指的是调用你切面下实现的方法,等。

那么在创建代理对象后,代理对象会被Spring放入容器,以后操作都以这个对象为标准, 但问题是,Spring没有为这个代理对象进行属性填充,所以导致虽然方法被调用了,但是内部通过自动注入的所有属性全为空,但你可以手动进行填充。

如下:

@Component
public class Test implements  BeanFactoryAware, BeanPostProcessor {
   private BeanFactory beanFactory;
   @Override
   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
         this.beanFactory =beanFactory;
   }
   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      if (beanName.equals("sampleController")){
         HelloWorldService helloWorldService = beanFactory.getBean(HelloWorldService.class);
         try {
            Field field = bean.getClass().getField("helloWorldService");
            field.setAccessible(true);
            field.set(bean,helloWorldService);
            System.out.println(field);
         } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
         } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
         }
      }
      return bean;
   }
}
@Aspect
@Component
public class ServiceMonitor {

   @Before("execution(* smoketest..*Controller.*(..))")
   public void logServiceAccess(JoinPoint joinPoint) {
      System.out.println("Completed: " + joinPoint);
   }

}
@Controller
public class SampleController {

   @Autowired
   public HelloWorldService helloWorldService;

   @GetMapping("/testA")
   @ResponseBody
   public final String helloWorld() {
      return this.helloWorldService.getHelloMessage();
   }
   @GetMapping("/testB")
   @ResponseBody
   public String test() {
      return this.helloWorldService.getHelloMessage();
   }

}

那么public类的方法是如何被调用的呢?这需要了解cglib的Enhancer,他有一个setCallback方法,用来设置一个回调,这个回调在代理的方法被调用时,会返回这个这回调接口中intercept()的值,如下这段代码是cglib生成的代理类中test方法。

  public final String test() {
    if (this.CGLIB$CALLBACK_0 == null)
      CGLIB$BIND_CALLBACKS(this); 
    return (this.CGLIB$CALLBACK_0 != null) ? (String)this.CGLIB$CALLBACK_0.intercept(this, CGLIB$getString$0$Method, CGLIB$emptyArgs, CGLIB$getString$0$Proxy) : super.test();
  }

而Spring实现的回调类中保存原对象实例,最终会通过反射以这个对象进行调用,原对象走了Spring完整的生命周期,属性也被填充过,所以可以正常调用。

并不仅仅是因为private不能被代理的原因,更因为Spring没有为新对象进行属性填充,源码可以追随到AbstractAutoProxyCreator.wrapIfNecessary,但我现在体会不到Spring在创建代理对象后,把原对象中的属性复制到新代理对象会有什么后果,从而导致Spring不去这样做。

虽然Spring没有这样做,但我们可以通过扩展接口为其填充。

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;
}