zoukankan      html  css  js  c++  java
  • spring prototype bean 获取处理

    参考链接:http://dolszewski.com/spring/accessing-prototype-bean-in-singleton/amp/

    When to use prototype beans?

    Before we fall into the pitfalls of injecting prototype into singleton beans, stop for a moment to think when you actually need such relation.

    Just because you use dependency injection in your project, it doesn’t mean you should avoid the new keyword like the plague. There is nothing wrong if you instantiate a stateful object on your own.

    Usually, we use the prototype scope for a stateful object when it has dependencies on other Spring beans. Why? Because the framework will autowire these dependencies for us.

    Let’s see this case in a practicle sample.

    Prototype in singleton bean example

    Now consider the following compoistion of Spring beans.

    Let’s start with the right side of the diagram where a prototype bean depends on a singleton. No matter how many instances of MessageBuilders Spring creates, we expect they will always get the reference to the same ContentProcessor object. This part is easy.

    Next, the left side. By contrast, here a singleton bean depends on a prototype. When Spring creates the MessageService bean, it will also create a single instance of MessageBuilder. But just one. That’s all MessageService needs to be created.

    But hang on a minute.

    For this particular graph of objects, Spring creates only one instance for each bean definition. Even though one of them is a prototype. If we had another bean depending on MessageBuilder, it would get another instance of the prototype bean. But in this case, Spring creates only one.

    So what if you want a new instance of the prototype bean on every call to a particular method of the singleton bean? Not only when Spring creates this singleton?

    Don’t worry. There’re multiple solutions.

    You will see all possible options in a moment. But before I share them with you, we need a sample dependency between a prototype and a singleton for demonstration purpose.

    Prototype in singleton dependency demo

    We need a prototype, a singleton which depends on the prototype, and an automated test to verify the number of prototype instances.

    Let’s write each step by step.

    Prototype with instance counter

    First, the prototype.

    We want to know how many objects the Spring framework creates. We’re going to use a simple static counter incremented in the class constructor.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Service
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    class MessageBuilder {
     
        private static final AtomicInteger instanceCounter = newAtomicInteger(0);
     
        MessageBuilder() {
            instanceCounter.incrementAndGet();
        }
     
        static int getInstanceCounter() {
            return instanceCounter.get();
        }
     
    }

    We need the counter only for testing. The main task of the class is to build some immutable Message object. Our prototype will use the Builder pattern as presented below.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class MessageBuilder {
     
        //...
        private String content;
        private String receiver;
     
        MessageBuilder withContent(String content) {
            this.content = content;
            return this;
        }
     
        MessageBuilder withReceiver(String receiver) {
            this.receiver = receiver;
            return this;
        }
     
        Message build() {
            return new Message(content, receiver);
        }
     
    }

    Singleton dependent on prototype bean

    Next, the singleton implementation.

    The MesssageService class is a simple singleton with only one method. It calls all three methods of the prototype.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Service
    class MessageService {
     
        private final MessageBuilder messageBuilder;
     
        MessageService(MessageBuilder messageBuilder) {
            this.messageBuilder = messageBuilder;
        }
     
        Message createMessage(String content, String receiver) {
            return messageBuilder
                    .withContent(content)
                    .withReceiver(receiver)
                    .build();
        }
     
    }

    As a reminder, since Spring 4.3 you do not have to put the @Autowired annotation on a constructor if there is only one in a class. The framework uses it automatically.

    Checking prototype instance count

    Finally, we write a test that will confirm that on every call to the singleton, Spring creates a new prototype instance. That’s our goal. We call the singleton twice so we expect two objects.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class MessageServiceTest {
     
        @Autowired
        private MessageService messageService;
     
        @Test
        public void shouldCreateTwoBuilders() throws Exception {
            //when
            messageService.createMessage("text""alice");
            messageService.createMessage("msg""bob");
            //then
            int prototypeCounter = MessageBuilder.getInstanceCounter();
            assertEquals("Wrong number of instances"2, prototypeCounter);
        }
     
    }

    Now we’re ready to examine all possible solutions to inject prototype bean into a singleton.

    How to inject prototype bean into singleton bean?

    When you work with a prototype bean in a singleton, you have three options to get a new instance of the prototype:

    1. Spring can autowire a single prototype instance when it creates the singleton. It’s the default framework behavior.
    2. Spring can create a new prototype instance on every call to any method of this prototype.
    3. You can programmatically ask the Spring context to create a new prototype instance at the moment you need a new object.

    Option 1: Injecting on singleton construction

    As I already mentioned, injecting on construction is default behavior of the framework. If a singleton bean depends on a prototype, Spring creates a single prototype instance dedicated for this particular singleton.

    The singleton resuses its single prototype instance through its whole lifecycle.

    Let’s run the test we wrote in the previous paragraph and confirm that Spring creates only one prototype.

    Option 2: Injecting on prototype method call

    Another possibility is to force Spring to create a new prototype instance when every call on prototype’s method.

    To achieve this, you need to modify the prototype bean and set its proxy mode. You can do this by modifying the @Scope annotation as follows:

    1
    2
    3
    4
    5
    6
    @Service
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
           proxyMode = ScopedProxyMode.TARGET_CLASS)
    class MessageBuilder {
      // ...
    }

    By default, Spring doesn’t create a proxy object for a bean and uses the reference to the real bean when injection occurs. However, when you change the proxy mode, Spring creates and injects a special proxy object instead of the real object. The proxy decides on its own when to create the real object.

    By setting the proxy mode to ScopedProxyMode.TARGET_CLASS, Spring will create a new instance of the prototype whenever you call its method.

    What is the outcome of our test?

    We didn’t get one instance, as without the proxy, but as many as six.

    Why that happened?

    In our test, we call the createMessage() method on the MessageService object twice and this, in turn, executes three methods of the proxied MessageBuilder. Two times three gives us six in total and that is what our test proved.

    However, that is not what we wanted and it is even worse. Not only got we more instance than we expected, but also the final result is different than wanted because each instance has a separate state.

    The createMessage() method relies on the state that is stored between sequential calls on the prototype. With the given setup, the state is lost on every call to prototype methods. Therefore, setting a proxy mode is a dead end for our problem.

    Option 3: Creating prototype on demand with ObjectFactory

    The last option is the most flexible because it allows you to create a new prototype instance manually. Thanks to bean definition, Spring knows how to create a prototype. You just need to tell the framework when to do it.

    That’s when ObjectFactory comes in.

    ObjectFactory is a functional interface from Spring framework designed to provide a reference to beans managed by the application context. The interface defines only one method which returns an instance of the selected bean.

    How to use ObjectFactory with prototype beans?

    It’s very simple. You just need to define a dependency on the ObjectFactory for the chosen bean. Spring will generate and inject the appropriate implementation of the interface.

    Let’s update the MessageService singleton to see the factory in action.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Service
    class MessageService {
     
        private final ObjectFactory<MessageBuilder> messageBuilder;
     
        MessageService(ObjectFactory<MessageBuilder> messageBuilder) {
            this.messageBuilder = messageBuilder;
        }
     
        Message createMessage(String content, String receiver) {
            return messageBuilder.getObject()
                    .withContent(content)
                    .withReceiver(receiver)
                    .build();
        }
     
    }

    Thanks to ObjectFactory you can create a prototype instance exactly when you need it.

    If we run our test again, it will pass. Every call to the createMessage() method gets its own instance of the prototype.

    ObjectFactory in unit test

    One of the reasons to use dependency injection is to simplify unit testing. It’s really simple to replace a real dependency with a stub or a mock.

    Because ObjectFactory is a function interface, you can easily write its stub implementation with the lambda expression.

    1
    2
    3
    4
    5
    6
    @Test
    public void shouldStubFactory() throws Exception {
        ObjectFactory<MessageBuilder> factory = () -> new MessageBuilder()
        MessageService sut = new MessageService(factory);
        //...
    }

    A few words of conclusion

    At this point, you should know about all the possible options for creating prototype beans. You learned that injecting a prototype bean into singleton may be a bit tricky. After reading the article, you know which injecting option is best for your requirements.

    Do you like the post and find it useful? Consider subscribing to my site. I’ll let you know about similar topics.

  • 相关阅读:
    技术管理之路三、团队建设:怎么带队伍?
    技术管理之路五、管理沟通:管理的必修课!
    职场工作方法论:计划安排,时间管理的“四象限法则”
    职场工作方法论:目标管理SMART原则
    激励方法论1、马斯洛需求模型
    mybatis PageHelper 分页组件优化
    async 和 await
    spring cloud 负载均衡 平滑上下线
    vue 重新渲染组件
    spring cloud 超时整理
  • 原文地址:https://www.cnblogs.com/rongfengliang/p/15178100.html
Copyright © 2011-2022 走看看