您现在的位置是:首页 > 编程语言学习 > 其他编程语言 > 文章正文 其他编程语言

Spring @Lookup深入分析实现原理

2023-01-03 10:02:27 其他编程语言

简介在使用Spring的时候,往单例bean注入原型bean时,原型bean可能会失效,如下:@ComponentpublicclassPerson{@AutowiredCarcar;publicCargetC...

在使用Spring的时候,往单例bean注入原型bean时,原型bean可能会失效,如下:

  1. @Component 
  2. public class Person { 
  3. @Autowired 
  4. Car car; 
  5. public Car getCar() { 
  6. return car; 
  7. @Component 
  8. @Scope("prototype"
  9. public class Car { 

调用Person#getCar()方法返回的总是同一个Car对象,这也很好理解,因为Person是单例的,Spring在创建Person时只会注入一次Car对象,以后Car都不会再改变了。

怎么解决这个问题呢?Spring提供了多种方式来获取原型bean。

解决方案

解决方案有很多,本文重点分析@Lookup注解的方式。

1、每次从ApplicationContext重新获取bean。

  1. @Component 
  2. public class Person implements ApplicationContextAware { 
  3. private ApplicationContext applicationContext; 
  4. @Lookup 
  5. public Car getCar() { 
  6. return applicationContext.getBean(Car.class); 
  7. @Override 
  8. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 
  9. this.applicationContext = applicationContext; 

2、通过CGLIB生成Car子类代理对象,每次都从容器内获取bean执行。

  1. @Component 
  2. @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) 
  3. public class Car { 

3、通过**@Lookup**注解的方式,方法体已经不重要了,代理对象每次都会从容器中重新获取bean。

  1. @Component 
  2. public class Person { 
  3. @Lookup 
  4. public Car getCar() { 
  5. return null

源码分析

为什么方法上加了@Lookup注解,调用该方法就能拿到原型bean了呢?其实纵观上述三种方式,要想拿到原型bean,底层原理都是一样的,那就是每次都通过ApplicationContext#getBean()方法从容器中重新获取,只要Car本身是原型的,Spring就会保证每次拿到的都是新创建的Car实例。

**@Lookup**注解也是通过生成代理类的方式,重写被标记的方法,每次都从ApplicationContext获取bean。

1、Spring加载Person的时候,容器内不存在该bean,那首先就是要实例化Person对象。Spring会通过SimpleInstantiationStrategy#instantiate()方法去实例化Person。

  1. public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { 
  2. /** 
  3.  * 如果bean没有要重写的方法,直接反射调用构造函数创建对象 
  4.  * 反之,需要通过CGLIB创建增强子类代理对象 
  5.  */ 
  6. if (!bd.hasMethodOverrides()) { 
  7. Constructor<?> constructorToUse; 
  8. synchronized (bd.constructorArgumentLock) { 
  9. constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod; 
  10. if (constructorToUse == null) { 
  11. final Class<?> clazz = bd.getBeanClass(); 
  12. if (clazz.isInterface()) { 
  13. throw new BeanInstantiationException(clazz, "Specified class is an interface"); 
  14. try { 
  15. if (System.getSecurityManager() != null) { 
  16. constructorToUse = AccessController.doPrivileged( 
  17. (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor); 
  18. else { 
  19. constructorToUse = clazz.getDeclaredConstructor(); 
  20. bd.resolvedConstructorOrFactoryMethod = constructorToUse; 
  21. catch (Throwable ex) { 
  22. throw new BeanInstantiationException(clazz, "No default constructor found", ex); 
  23. return BeanUtils.instantiateClass(constructorToUse); 
  24. else { 
  25. /** 
  26.  * 存在 lookup-method 和 replaced-method 
  27.  * 通过CGLIB生成代理类 
  28.  */ 
  29. return instantiateWithMethodInjection(bd, beanName, owner); 

BeanDefinition有一个属性**methodOverrides**,它里面存放的是当前bean里面是否有需要被重写的方法,这些需要被重写的方法可能是**lookup-method****replaced-method**。如果没有需要重写的方法,则直接通过反射调用构造函数来实例化对象;如果有需要重写的方法,这个时候就不能直接实例化对象了,需要通过CGLIB来创建增强子类,把父类的方法给重写掉。

于是会调用instantiateWithMethodInjection()方法来实例化bean,最终是通过CglibSubclassCreator来实例化。

  1. @Override 
  2. protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, 
  3. @Nullable Constructor<?> ctor, Object... args) { 
  4. // Must generate CGLIB subclass... 
  5. return new CglibSubclassCreator(bd, owner).instantiate(ctor, args); 

2、CglibSubclassCreator创建CGLIB子类,重写父类方法。Spring会通过Enhancer来创建增强子类,被@Lookup标记的方法会被LookupOverrideMethodInterceptor拦截。

  1. public Object instantiate(@Nullable Constructor<?> ctor, Object... args) { 
  2. Class<?> subclass = createEnhancedSubclass(this.beanDefinition); 
  3. Object instance; 
  4. if (ctor == null) { 
  5. instance = BeanUtils.instantiateClass(subclass); 
  6. else { 
  7. try { 
  8. Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes()); 
  9. instance = enhancedSubclassConstructor.newInstance(args); 
  10. catch (Exception ex) { 
  11. throw new BeanInstantiationException(this.beanDefinition.getBeanClass(), 
  12. "Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex); 
  13. Factory factory = (Factory) instance; 
  14. factory.setCallbacks(new Callback[] {NoOp.INSTANCE, 
  15. new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner), 
  16. new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)}); 
  17. return instance; 

3、LookupOverrideMethodInterceptor要拦截被@Lookup标记的方法,必然要实现intercept()方法。逻辑很简单,就是每次都调用BeanFactory#getBean()从容器中获取bean。

  1. @Override 
  2. public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { 
  3. // Cast is safe, as CallbackFilter filters are used selectively. 
  4. LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method); 
  5. Assert.state(lo != null"LookupOverride not found"); 
  6. Object[] argsToUse = (args.length > 0 ? args : null);  // if no-arg, don't insist on args at all 
  7. if (StringUtils.hasText(lo.getBeanName())) { 
  8. return (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) : 
  9. this.owner.getBean(lo.getBeanName())); 
  10. else { 
  11. return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) : 
  12. this.owner.getBean(method.getReturnType())); 

4. 总结

一个bean一旦拥有被@Lookup注解标记的方法,就意味着该方法需要被重写掉,Spring在实例化bean的时候会自动基于CGLIB生成增强子类对象重写掉父类方法。此时父类被@Lookup注解标记的方法体已经不重要了,不会被执行了,CGLIB子类会通过LookupOverrideMethodInterceptor拦截掉被@Lookup注解标记的方法。方法体重写的逻辑也很简单,就是每次都通过BeanFactory获取bean,只要bean本身是原型的,每次拿到的都将是不同的实例。

站点信息