小伙伴们晓得,当咱们应用 Spring 容器的时候,如果遇到一些非凡的 Bean,一般来说能够通过如下三种形式进行配置:
- 动态工厂办法
- 实例工厂办法
- FactoryBean
不过从 Spring5 开始,在 AbstractBeandefinition 类中多了一个属性,对于非凡的 Bean 咱们有了更多的抉择:
/** * 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 */@Nullablepublic Supplier<?> getInstanceSupplier() { return this.instanceSupplier;}
接下来松哥就来和大家简略聊一聊这个话题。
1. 传统解决方案
1.1 问题
不晓得各位小伙伴们有没有用过 OkHttp,这是一个专门做网络申请的工具,在微服务的 HTTP 调用组件中,咱们能够配置底层应用 OkHttp 这个工具。
一般来说,如果咱们想间接应用 OkHttp,代码如下:
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()); }});
先通过建造者模式创立进去一个 OkHttpClient 对象,而后还是建造者模式创立进去 Request 对象,接下来去发送申请就能够了。那么对于这样的代码,咱们能够将 OkHttpClient 对象交由 Spring 容器对立治理,那么该如何将 OkHttpClient 注册到 Spring 容器中呢?
1.2 动态工厂办法
首先能够采纳动态工厂办法,也就是工厂办法是一个静态方法,如下:
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; }}
而后在 Spring 配置文件中进行注入:
<bean class="org.javaboy.bean.OkHttpStaticFactory" factory-method="getOkHttpClient" id="httpClient"/>
动态工厂的特点是静态方法能够间接调用,并必须要获取到工厂类的实例,所以下面配置的时候只须要指定 factory-method
就能够了。
这就能够了,未来咱们去 Spring 容器中查找一个名为 httpClient 的对象,拿到手的就是 OkHttpClient 了。
1.3 实例工厂办法
实例工厂办法意思就是说工厂办法是一个实例办法。如下:
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; }}
这是一个简略的单例模式。然而这里的工厂办法是一个实例办法,实例办法的调用必须得先获取到对象而后能力调用实例办法,因而配置形式如下:
<bean class="org.javaboy.bean.OkHttpInstanceFactory" id="httpInstanceFactory"/><bean factory-bean="httpInstanceFactory" factory-method="getInstance" id="httpClient"/>
好啦,接下来咱们就能够去 Spring 容器中获取一个名为 httpClient 的对象了,拿到手的就是 OkHttpClient 实例。
1.4 FactoryBean
当然,也能够通过 FactoryBean 来解决上述问题,FactoryBean 松哥在之前的文章中刚刚和大家介绍过,咱们来看下:
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; }}
最初在 Spring 中配置即可:
<bean class="org.javaboy.bean.OkHttpClientFactoryBean" id="httpClient"/>
这个就不做过多解释了,不相熟的小伙伴能够翻看后面的文章。
下面这三种计划都是传统计划。
特地是前两种,其实咱们用的比拟少,前两种有一个缺点,就是咱们配置的的 factory-method 都是通过反射来调用的,通过反射调用的话,多多少少性能受点影响。
这种 factory-method 在 Spring 中解决的源码执行时序图如下:
所以最终反射是在 SimpleInstantiationStrategy#instantiate
办法中执行的,就是大家十分相熟的反射代码了:
@Overridepublic 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(); } }}
好了,这是传统的解决方案。
2. Spring5 解决方案
Spring5 中开始提供了 Supplier,能够通过接口回调获取到一个 Bean 的实例,这种形式显然性能更好一些。
如下:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();GenericBeanDefinition definition = new GenericBeanDefinition();definition.setBeanClass(Book.class);definition.setInstanceSupplier((Supplier<Book>) () -> { Book book = new Book(); book.setName("深入浅出 Spring Security"); book.setAuthor("江南一点雨"); return book;});ctx.registerBeanDefinition("b1", definition);ctx.refresh();Book b = ctx.getBean("b1", Book.class);System.out.println("b = " + b);
要害就是通过调用 BeanDefinition 的 setInstanceSupplier 办法去设置回调。当然,下面这段代码还能够通过 Lambda 进一步简化:
public class BookSupplier { public Book getBook() { Book book = new Book(); book.setName("深入浅出 Spring Security"); book.setAuthor("江南一点雨"); return book; }}
而后调用这个办法即可:
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);
这是不是更有一点 Lambda 的感觉了~
在 Spring 源码中,解决获取 Bean 实例的时候,有如下一个分支,就是解决 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);}@Nullableprivate 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(); }}
下面 obtainFromSupplier 这个办法,最终会调用到第二个办法。第二个办法中的 supplier.get();
其实最终就调用到咱们本人写的 getBook 办法了。
好啦,这是从 Spring5 开始联合 Lamdba 的一种 Bean 注入形式,感兴趣的小伙伴能够试试哦~