@ Lookup annotation of Spring dependency injection
When understanding the Spring constructor inference, I saw the following source code:
problem analysis
Generally, we will use the @ Autowired annotation to realize the automatic injection of dependencies:
-
Attribute injection
@Component public class Xxx{ @Autowired private Aaa aaa; }
-
Constructor Injection
@Component public class Xxx{ private final Aaa aaa; @Autowired public Xxx(Aaa aaa){ this.aaa = aaa; } }
-
Common method injection
@Component public class Xxx{ private final Aaa aaa; @Autowired public void SetAaa(Aaa aaa){ this.aaa = aaa; } }
The above dependency injection method can cope with most usage scenarios, except for a special usage scenario: a singleton Bean needs to rely on a prototype Bean
@Component public class SingletonBean{ @Autowired private PrototypeBean prototype; public void usePrototype(){ // use prototype bean do something } }
In the above code, SingletonBean is a singleton Bean, and its dependent PrototypeBean is a prototype Bean. Because the PrototypeBean has only one injection opportunity in the SingletonBean's life cycle, the subsequent usePrototype() method actually uses the same PrototypeBean instance, which is obviously problematic: Although we set the PrototypeBean as a prototype Bean, under the current situation, the PrototypeBean has become a de facto singleton Bean.
There are generally two solutions to the above problems:
-
Introduce ApplicationContext dependency in Bean, and then call getBean() method to get prototype Bean.
@Component public class SingletonBean{ @Autowired private ApplicationContext applicationContext; public void usePrototype(){ PrototypeBean prototype = applicationContext.getBean(PrototypeBean.class); // use prototype bean do something } }
-
Use @ Lookup annotation
@Component public class SingletonBean{ public void usePrototype(){ PrototypeBean prototype = getPrototypeBean(); // use prototype bean do something } @Lookup public PrototypeBean getPrototypeBean(){ return null; } }
Methods using method injection need to meet the following syntax requirements:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
- Method access permission must be public or protected: This is related to the underlying principle of @ Lookup - dynamic proxy
- Abstract (optional): if it is an abstract method, the proxy class will implement it; if it is not an abstract method, the proxy class will override it
- Return type: the type of the prototype Bean
- No arguments: parameter list must be empty
Code verification
Declare prototype Bean:
public class LookupConfig { @Bean @Scope(value = BeanDefinition.SCOPE_PROTOTYPE) public PrototypeService prototypeService() { return new PrototypeServiceImpl(); } }
Singleton Bean dependent on prototype Bean:
public class SingletonServiceImpl implements SingletonService { private final PrototypeService prototypeBean; private final ApplicationContext applicationContext; @Autowired public SingletonServiceImpl(PrototypeService prototypeBean, ApplicationContext applicationContext) { this.prototypeBean = prototypeBean; this.applicationContext = applicationContext; } /** * Get prototype Bean * * @return Prototype Bean obtained through @ Autowired */ @Override public PrototypeService getPrototypeBeanThroughAutowiredAnnotation() { return this.prototypeBean; } /** * Get prototype Bean * * @return Prototype Bean obtained through ApplicationContext */ @Override public PrototypeService getPrototypeBeanThroughApplicationContext() { return this.applicationContext.getBean(PrototypeService.class); } /** * Get prototype Bean * * @return Prototype Bean obtained through @ Lookup */ @Lookup @Override public PrototypeService getPrototypeBeanThroughLookupAnnotation() { return null; } }
Test code:
@Slf4j public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(LookupConfig.class); applicationContext.register(SingletonServiceImpl.class); SingletonService singletonService = applicationContext.getBean(SingletonService.class); log.info("Single case Bean: {}", singletonService.toString()); for (int i = 0; i < 3; i++) { log.info("adopt@Autowire Prototype acquired Bean: {}", singletonService.getPrototypeBeanThroughAutowiredAnnotation().toString()); } for (int i = 0; i < 3; i++) { log.info("adopt ApplicationContext Prototype acquired Bean: {}", singletonService.getPrototypeBeanThroughApplicationContext().toString()); } for (int i = 0; i < 3; i++) { log.info("adopt@Lookup Prototype acquired Bean: {}", singletonService.getPrototypeBeanThroughLookupAnnotation().toString()); } } }
Test code execution results:
[main] INFO com.xzy.test.lookup.Main - Single case Bean: com.xzy.service.impl.SingletonServiceImpl$$EnhancerBySpringCGLIB$$a5fddad5@6f96c77 [main] INFO com.xzy.test.lookup.Main - adopt@Autowire Prototype acquired Bean: com.xzy.service.impl.PrototypeServiceImpl@61230f6a [main] INFO com.xzy.test.lookup.Main - adopt@Autowire Prototype acquired Bean: com.xzy.service.impl.PrototypeServiceImpl@61230f6a [main] INFO com.xzy.test.lookup.Main - adopt@Autowire Prototype acquired Bean: com.xzy.service.impl.PrototypeServiceImpl@61230f6a [main] INFO com.xzy.test.lookup.Main - adopt ApplicationContext Prototype acquired Bean: com.xzy.service.impl.PrototypeServiceImpl@3c130745 [main] INFO com.xzy.test.lookup.Main - adopt ApplicationContext Prototype acquired Bean: com.xzy.service.impl.PrototypeServiceImpl@cd3fee8 [main] INFO com.xzy.test.lookup.Main - adopt ApplicationContext Prototype acquired Bean: com.xzy.service.impl.PrototypeServiceImpl@3e2e18f2 [main] INFO com.xzy.test.lookup.Main - adopt@Lookup Prototype acquired Bean: com.xzy.service.impl.PrototypeServiceImpl@470f1802 [main] INFO com.xzy.test.lookup.Main - adopt@Lookup Prototype acquired Bean: com.xzy.service.impl.PrototypeServiceImpl@63021689 [main] INFO com.xzy.test.lookup.Main - adopt@Lookup Prototype acquired Bean: com.xzy.service.impl.PrototypeServiceImpl@703580bf
As can be seen from the execution results, Spring uses CGLIB to create a proxy class for SingletonServiceImpl
matters needing attention
-
Spring will create proxy objects through CGLIB, and then implement or override the methods marked with @ Lookup. Therefore, it doesn't matter what logic is inside the methods marked with @ Lookup. Just return null
-
Back to the beginning of this article, the code dealing with @ Lookup exists in the method of Spring inference constructor:
This means that if declared manually Bean,@Lookup Will not take effect: ```java @Bean @Scope(value = BeanDefinition.SCOPE_SINGLETON) public SingletonService singletonService(PrototypeService prototypeService, ApplicationContext applicationContext) { return new SingletonServiceImpl(prototypeService, applicationContext); }
![Please add a picture description](https://img-blog.csdnimg.cn/930857563378415f8b499ac1d495c64f.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASGVsbG9feHp5X1dvcmQ=,size_20,color_FFFFFF,t_70,g_se,x_16) (Because the manual declaration method has been told Spring Which constructor is used to instantiate Bean,therefore Spring There is no need for constructor inference, so processing@Lookup The code will not be executed, and all proxy classes will not be created, so what is actually called is SingleServiceImple The one in the list returns null The way, so NPE (yes) --- Reference article:[spring in@Lookup Role of](https://www.cnblogs.com/wl20200316/p/12850300.html)