如何正确的创建 prototype 类型的 bean

前言

这里把使用 Spring 过程中遇到的问题做个记录,有些给出了解决方案,有些没有,欢迎同道读者斧正!

如何正确的创建 prototype 类型的 Spring bean

这里先尝试的去创建一个 prototypebean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class ProtoTypeComponent {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

private int random;

public ProtoTypeComponent() {
// random 值设置为一个随机值,
this.random = ThreadLocalRandom.current().nextInt(100000);
LOGGER.info("create a new ProtoTypeComponent with random:{}", random);
}

public int getRandom() {
return random;
}
}

然后,我们在Controller 中注入这个 prototype bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class HelloController {

@Resource
private ProtoTypeComponent protoTypeComponent;

@RequestMapping(value = "/hello")
@ResponseBody
public Object hello() {
return "{\"class\": \"" + protoTypeComponent + "\", \"random\":\""
+ protoTypeComponent.getRandom() + "\"}";
}

}

然后我们开始请求这个 http 接口

不断的刷新页面,返回的结果都是一模一样!我们再来看看日志

从上面的日志上我们看到这个 prototype bean 的构造方法只被调用了一次!

到这里,我感到奇怪了,为什么这个 prototype bean 成了 singleton bean 了?

这个时候我想了 《Spring揭秘》 中看到的正解, 之前这个现象出现的原因是 HelloControllersingleton 类型的,这个 bean 只会被初始化一次, 而 HelloController 依赖的 ProtoTypeComponent 也只会被注入一次!
到这里,谜底揭晓:

singleton bean 依赖的 prototype bean 会 “变成” singleton bean

这里,解决这个问题的第一个方案也出现了:

依赖 prototype bean 也必须是 prototype bean

很明显这个方案不能解决很多状况, 我们继续这个问题

再来翻翻 《Spring 揭秘》 找找答案
我们看到了两个答案

“1. 使用方法注入

  1. 继承 ApplicationContextAware 每次去获取bean
使用方法注入

在传统的 xml 配置方式中我们需要这样配置

1
2
3
4
<bean id="protoTypeBean" class="...impl.ProtoTypeService" singleton="false"/>
<bean class = "...impl.SimpleServiceImpl">
<lookup-method name="getProtoTypeBean" bean="protoTypeBean"/>
</bean>

getProtoTypeBean 方法定义如下

1
2
3
4
5
private ProtoTypeService protoTypeBean;

public ProtoTypeService getProtoTypeBean() {
return protoTypeBean;
}

现在我们更倾向于使用注解,那么基于注解怎么实现上面这种方式呢?
我们这里引入 @Lookup 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* An annotation that indicates 'lookup' methods, to be overridden by the container
* to redirect them back to the {@link org.springframework.beans.factory.BeanFactory}
* for a {@code getBean} call. This is essentially an annotation-based version of the
* XML {@code lookup-method} attribute, resulting in the same runtime arrangement.
*
* <p>The resolution of the target bean can either be based on the return type
* ({@code getBean(Class)}) or on a suggested bean name ({@code getBean(String)}),
* in both cases passing the method's arguments to the {@code getBean} call
* for applying them as target factory method arguments or constructor arguments.
*
* <p>Such lookup methods can have default (stub) implementations that will simply
* get replaced by the container, or they can be declared as abstract - for the
* container to fill them in at runtime. In both cases, the container will generate
* runtime subclasses of the method's containing class via CGLIB, which is why such
* lookup methods can only work on beans that the container instantiates through
* regular constructors: i.e. lookup methods cannot get replaced on beans returned
* from factory methods where we cannot dynamically provide a subclass for them.
*
* <p><b>Concrete limitations in typical Spring configuration scenarios:</b>
* When used with component scanning or any other mechanism that filters out abstract
* beans, provide stub implementations of your lookup methods to be able to declare
* them as concrete classes. And please remember that lookup methods won't work on
* beans returned from {@code @Bean} methods in configuration classes; you'll have
* to resort to {@code @Inject Provider&lt;TargetBean&gt;} or the like instead.
*
* @author Juergen Hoeller
* @since 4.1
* @see org.springframework.beans.factory.BeanFactory#getBean(Class, Object...)
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Object...)
*/
@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 "";

从上面的代码能看出:
这个注解只能修饰方法,根据方法的返回值类型创建对应的Bean,底层机制是调用 org.springframework.beans.factory.BeanFactory#getBean(Class, Object...)org.springframework.beans.factory.BeanFactory#getBean(String, Object...)

先来使用 @Lookup 注解改成一下我们尝试创建 有状态的 prototype bean

修正的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Controller
public class HelloController {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

private ProtoTypeComponent protoTypeComponent;

@RequestMapping(value = "/hello")
@ResponseBody
public Object hello() {
ProtoTypeComponent component = getProtoTypeComponent();
return "{\"class\": \"" + component + "\", \"random\":\""
+ component.getRandom() + "\"}";
}

@Lookup
public ProtoTypeComponent getProtoTypeComponent() {
return protoTypeComponent;
}
}

让我们运行起 tomcat 看看结果:


很明显,我们成功的创建了 prototype bean!

继承 ApplicationContextAware 每次去获取bean

这里不推荐使用 ApplicationContextAware 所以不给出方法了。