前言
标题不严禁,但实在想不到怎么说了,因为在某些情况下才会出错。
起因是我前几天看到的一篇文章,标题为《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;
}