Friends know that when we use the Spring container, if we encounter some special beans, generally speaking, they can be configured in the following three ways:
- static factory method
- instance factory method
- FactoryBean
However, starting from Spring 5, there is an additional attribute in the AbstractBeandefinition class, and we have more choices for special beans:
/** * Specify a callback for creating an instance of the bean, * as an alternative to a declaratively specified factory method. * <p>If such a callback is set, it will override any other constructor * or factory method metadata. However, bean property population and * potential annotation-driven injection will still apply as usual. * @since 5.0 * @see #setConstructorArgumentValues(ConstructorArgumentValues) * @see #setPropertyValues(MutablePropertyValues) */ public void setInstanceSupplier ( @Nullable Supplier<?> instanceSupplier) { this .instanceSupplier = instanceSupplier; } /** * Return a callback for creating an instance of the bean, if any. * @since 5.0 */ @Nullable public Supplier<?> getInstanceSupplier() { return this .instanceSupplier; }
Next, Brother Song will come and briefly talk with you about this topic.
Contents
1. Traditional solutions
1.1 Question
I don’t know if any of you have ever used OkHttp. This is a tool specialized in making network requests. In the HTTP calling component of the microservice, we can configure the underlying tool to use OkHttp.
Generally speaking, if we want to use OkHttp directly, the code is as follows:
OkHttpClient client = new OkHttpClient .Builder() .connectTimeout( 5 , TimeUnit.SECONDS) .readTimeout( 5 , TimeUnit.SECONDS) .build(); Request getReq = new Request .Builder().get().url( "http://www.javaboy.org" ).build(); Call call = client.newCall(getReq); call.enqueue( new Callback () { @Override public void onFailure ( @NotNull Call call, @NotNull IOException e) { System.out.println( "e.getMessage() = " + e.getMessage()); } @Override public void onResponse ( @NotNull Call call, @NotNull Response response) throws IOException { System.out.println( "response.body().string() = " + response.body().string()); } });
First create an OkHttpClient object through the builder mode, then create the Request object through the builder mode, and then send the request. So for such code, we can hand over the OkHttpClient object to the Spring container for unified management. So how to register the OkHttpClient into the Spring container?
1.2 Static factory method
First, you can use a static factory method, that is, the factory method is a static method, as follows:
public class OkHttpStaticFactory { private static OkHttpClient okHttpClient; static { okHttpClient = new OkHttpClient .Builder() .connectTimeout( 5 , TimeUnit.SECONDS) .readTimeout( 5 , TimeUnit.SECONDS) .build(); } public static OkHttpClient getOkHttpClient () { return okHttpClient; } }
Then inject it in the Spring configuration file:
< bean class = "org.javaboy.bean.OkHttpStaticFactory" factory-method = "getOkHttpClient" id = "httpClient" />
The characteristic of static factory is that static methods can be called directly and an instance of the factory class must be obtained, so you only need to specify it when configuring above factory-method
.
That’s it. In the future, we will search for an object named httpClient in the Spring container, and the one we get is OkHttpClient.
1.3 Instance factory method
Instance factory method means that the factory method is an instance method. as follows:
public class OkHttpInstanceFactory { private volatile static OkHttpClient okHttpClient; public OkHttpClient getInstance () { if (okHttpClient == null ) { synchronized (OkHttpInstanceFactory.class) { if (okHttpClient == null ) { okHttpClient = new OkHttpClient .Builder() .connectTimeout( 5 , TimeUnit.SECONDS) .readTimeout( 5 , TimeUnit.SECONDS) .build(); } } } return okHttpClient; } }
This is a simple singleton pattern. But the factory method here is an instance method. The call to the instance method must first obtain the object and then call the instance method. Therefore, the configuration is as follows:
< bean class = "org.javaboy.bean.OkHttpInstanceFactory" id = "httpInstanceFactory" /> < bean factory-bean = "httpInstanceFactory" factory-method = "getInstance" id = "httpClient" />
Okay, next we can go to the Spring container to get an object named httpClient, and what we get is the OkHttpClient instance.
1.4 FactoryBean
Of course, you can also solve the above problems through FactoryBean. FactoryBean Song Ge just introduced it to you in the previous article. Let’s take a look:
public class OkHttpClientFactoryBean implements FactoryBean <OkHttpClient> { @Override public OkHttpClient getObject () throws Exception { return new OkHttpClient .Builder() .connectTimeout( 5 , TimeUnit.SECONDS) .readTimeout( 5 , TimeUnit.SECONDS) .build(); } @Override public Class<?> getObjectType() { return OkHttpClient.class; } @Override public boolean isSingleton () { return true ; } }
Finally, configure it in Spring:
< bean class = "org.javaboy.bean.OkHttpClientFactoryBean" id = "httpClient" />
I won’t explain too much about this. Friends who are not familiar with it can read the previous articles.
The above three solutions are all traditional solutions.
Especially the first two, we actually use them less. The first two have a flaw, that is, the factory-methods we configure are all called through reflection. If called through reflection, the performance will be affected to some extent.
The source code execution sequence diagram of this factory-method processed in Spring is as follows:
So the final reflection is SimpleInstantiationStrategy#instantiate
executed in the method, which is the reflection code that everyone is very familiar with:
@Override public Object instantiate (RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, @Nullable Object factoryBean, final Method factoryMethod, Object... args) { ReflectionUtils.makeAccessible(factoryMethod); Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get(); try { currentlyInvokedFactoryMethod.set(factoryMethod); Object result = factoryMethod.invoke(factoryBean, args); if (result == null ) { result = new NullBean (); } return result; } finally { if (priorInvokedFactoryMethod != null ) { currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod); } else { currentlyInvokedFactoryMethod.remove(); } } }
Okay, here’s the traditional solution.
2. Spring5 solution
Supplier has been provided in Spring 5, and a Bean instance can be obtained through interface callback. This method obviously has better performance.
as follows:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext (); GenericBeanDefinition definition = new GenericBeanDefinition (); definition.setBeanClass(Book.class); definition.setInstanceSupplier((Supplier<Book>) () -> { Book book = new Book (); book.setName( "Spring Security in simple terms" ); book.setAuthor( "A little rain in Jiangnan" ); return book; }); ctx.registerBeanDefinition( "b1" , definition); ctx.refresh(); Book b = ctx.getBean( "b1" , Book.class); System.out.println( "b = " + b);
The key is to set the callback by calling the setInstanceSupplier method of BeanDefinition. Of course, the above code can be further simplified through Lambda:
public class BookSupplier { public Book getBook () { Book book = new Book (); book.setName( "Spring Security in simple terms" ); book.setAuthor( "A little rain in Jiangnan" ); return book; } }
Then just call this method:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext (); GenericBeanDefinition definition = new GenericBeanDefinition (); definition.setBeanClass(Book.class); BookSupplier bookSupplier = new BookSupplier (); definition.setInstanceSupplier(bookSupplier::getBook); ctx.registerBeanDefinition( "b1" , definition); ctx.refresh(); Book b = ctx.getBean( "b1" , Book.class); System.out.println( "b = " + b);
Doesn’t this feel more like Lambda?
In the Spring source code, when processing the acquisition of Bean instances, there is the following branch, which handles the situation of Supplier:
AbstractAutowireCapableBeanFactory#createBeanInstance
protected BeanWrapper createBeanInstance (String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // Make sure bean class is actually resolved at this point. Class<?> beanClass = resolveBeanClass(mbd, beanName); if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { throw new BeanCreationException (mbd.getResourceDescription(), beanName, "Bean class isn't public, and non- public access not allowed: " + beanClass.getName()); } Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null ) { return obtainFromSupplier(instanceSupplier, beanName); } if (mbd.getFactoryMethodName() != null ) { return instantiateUsingFactoryMethod(beanName, mbd, args); } //... return instantiateBean(beanName, mbd); } @Nullable private Object obtainInstanceFromSupplier (Supplier<?> supplier, String beanName) { String outerBean = this .currentlyCreatedBean.get(); this .currentlyCreatedBean.set(beanName); try { if (supplier instanceof InstanceSupplier<?> instanceSupplier) { return instanceSupplier.get(RegisteredBean.of((ConfigurableListableBeanFactory) this , beanName)); } if (supplier instanceof ThrowingSupplier<?> throwableSupplier) { return throwableSupplier.getWithException(); } return supplier.get(); } }
The obtainFromSupplier method above will eventually call the second method. In the second method, supplier.get();
we actually end up calling the getBook method we wrote ourselves.
Okay, this is a Bean injection method combined with Lamdba starting from Spring 5. Interested friends can try it~