前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你真的会用Spring吗?如何在单例Bean中注入原型Bean

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

作者头像
Coder小黑
发布2019-12-15 21:23:43
1.3K0
发布2019-12-15 21:23:43
举报
文章被收录于专栏:Coder小黑

遇到什么问题

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

代码语言:javascript
复制
@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方法。

代码语言:javascript
复制
@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源码

代码语言:javascript
复制
@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

代码语言:javascript
复制
@Service
public abstract class UserService {
	@Lookup
	public abstract OrderService getOrderServiceUsingLookup();
}

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

UserService代理对象
UserService代理对象
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coder小黑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 遇到什么问题
  • 解决方案 1:实现 ApplicationContextAware
  • 解决方案 2:使用@Lookup,实现方法注入
    • @Lookup
      • 介绍
        • 使用限制
          • 使用@Lookup 实现单例 Bean 中注入原型 Bean
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档