专栏首页Coder小黑你真的会用Spring吗?如何在单例Bean中注入原型Bean

你真的会用Spring吗?如何在单例Bean中注入原型Bean

遇到什么问题

假设单例 BeanA 需要使用原型 BeanB(BeanB 可能是 BeanA 的一个属性值)。可是容器仅创建一次单例 BeanA,因此只有一次机会来设置属性 BeanB。

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class OrderService {
}

@Service
public class UserService {

	@Autowired
	private OrderService orderService;

	public OrderService getOrderService() {
		return orderService;
	}
}

@Configuration
@ComponentScan
public class Main {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context =
				new AnnotationConfigApplicationContext(Main.class);
		UserService userService = context.getBean(UserService.class);
		OrderService orderService = userService.getOrderService();
		OrderService orderService1 = userService.getOrderService();
		//ture
		System.out.println(orderService == orderService1);
	}
}

如果直接使用@Autowired注入,容器仅创建一次单例UserService,因此只有一次机会来设置OrderService

那么,如何在单例 Bean 中注入原型 Bean 呢?

解决方案 1:实现 ApplicationContextAware

第一种解决方案,可以让UserService实现ApplicationContextAware接口,然后在每次需要使用原型 BeanOrderService时通过调用容器的getBean方法。

@Service
public class UserService implements ApplicationContextAware {

	private ApplicationContext context;

	public OrderService getOrderService() {
		return context.getBean(OrderService.class);
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		context = applicationContext;
	}
}

Spring 官方并不建议使用这种方式:

The preceding is not desirable, because the business code is aware of and coupled to the Spring Framework. Method Injection, a somewhat advanced feature of the Spring IoC container, lets you handle this use case cleanly. 前面的内容是不理想的,因为业务代码知道并耦合到 Spring 框架。方法注入是 Spring IoC 容器的一项高级功能,使您可以干净地处理此用例。

解决方案 2:使用@Lookup,实现方法注入

@Lookup

先来看一下@Lookup源码

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lookup {

	/**
	 * This annotation attribute may suggest a target bean name to look up.
	 * If not specified, the target bean will be resolved based on the
	 * annotated method's return type declaration.
	 */
	String value() default "";

}

@Lookup默认是通过方法的返回类型声明来解析目标 Bean,也可以通过 value 来指定需要查找的目标 BeanName

介绍

https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/core.html#beans-factory-lookup-method-injection Lookup method injection is the ability of the container to override methods on container-managed beans and return the lookup result for another named bean in the container. The lookup typically involves a prototype bean, as in the scenario described in the preceding section. The Spring Framework implements this method injection by using bytecode generation from the CGLIB library to dynamically generate a subclass that overrides the method. 机器翻译:查找方法注入是容器覆盖容器管理的 Bean 上的方法并返回容器中另一个命名 Bean 的查找结果的能力。查找通常涉及原型 bean,如上一节中所述。Spring 框架通过使用从 CGLIB 库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。

使用限制

For this dynamic subclassing to work, the class that the Spring bean container subclasses cannot be final, and the method to be overridden cannot be final, either. 为了使此动态子类起作用,Spring Bean 容器子类的类也不能是 final,而要覆盖的方法也不能是 final。

Unit-testing a class that has an abstract method requires you to subclass the class yourself and to supply a stub implementation of the abstract method. 对具有抽象方法的类进行单元测试需要您自己对该类进行子类化,并提供该抽象方法的存根实现。

A further key limitation is that lookup methods do not work with factory methods and in particular not with @Bean methods in configuration classes, since, in that case, the container is not in charge of creating the instance and therefore cannot create a runtime-generated subclass on the fly. 另一个关键限制是,查找方法不适用于工厂方法,尤其不适用于配置类中的@Bean 方法,因为在这种情况下,容器不负责创建实例,因此无法创建运行时生成的子类。

根据 Spring 官方文档,我们可以知道:

  1. 方法注入是通过 CGLIB 生成字节码来动态生成覆盖该方法的子类来实现此方法注入
  2. 因为是用 CGLIB 来实现的,所以当前类和当前方法是不能为 final 的
  3. Spring 中使用@Lookup来实现方法注入

使用@Lookup 实现单例 Bean 中注入原型 Bean

@Service
public abstract class UserService {
	@Lookup
	public abstract OrderService getOrderServiceUsingLookup();
}

虽然这个类是抽象的,但是还可以被实例化到 Spring 容器中,因为 Spring 会对当前类生成子类来实现方法注入。至于具体是怎么生成的增强对象,读者可以自行 debug 源码学习。

UserService代理对象

本文分享自微信公众号 - Coder小黑(gh_5c7abf11d864),作者:coder小黑

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring5 源码学习 (6) ConfigurationClassParser 解析配置类

    在ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中创建了Configuration...

    Coder小黑
  • Full @Configuration vs lite @Bean mode

    Full @Configuration和lite @Bean mode 是 Spring Java Config 中两个非常有意思的概念。

    Coder小黑
  • Spring Cache 缺陷,我好像有解决方案了

    在 for 循环中操作 redis。如果数据命中缓存还好,一旦缓存没有命中,则会访问数据库。

    Coder小黑
  • HDOJ 1003

    Max Sum Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (J...

    用户1154259
  • Snapchat专利曝光,致力构建和管理AR数据库

    VRPinea
  • AI 技术讲座精选:数学不好,也可以学习人工智能(七)——自然语言处理的奇妙神奇之处

    机器都能做到吗? 我现在是任由自动化左右吗? 未来AI会让作家失业吗? 请阅读本文。 编译 | AI100 在本系列的第五部分中发现了卷积神经网络...

    AI科技大本营
  • ZOJ 1403&&HDU 1015 Safecracker【暴力】

    Safecracker ---- Time Limit: 2 Seconds      Memory Limit: 65536 KB ---- === Op t...

    Angel_Kitty
  • 如何理解 Spring 的 IOC 思想?

    一份执着✘
  • 【PAT甲级】Sign In and Sign Out

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    喜欢ctrl的cxk
  • I'm Telling the Truth(二分图)- HDU 3729

    二分图:设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点...

    ACM算法日常

扫码关注云+社区

领取腾讯云代金券