Spring Beanカスタムスコープ

Springカスタムスコープが必要な場合の例を示します。



私たちはB2BおよびSAASの会社であり、各クライアントのいくつかの長いプロセスはタイマーで実行されます。

各クライアントには、いくつかのプロパティ(名前、サブスクリプションのタイプなど)があります。

以前は、サービスプロトタイプビンを作成し、コンストラクターでクライアントと実行中のプロセスのすべての必要なプロパティをコンストラクタに渡しました(フロー-これはOSプロセスではなく論理プロセス、ジョブを意味します):



@Service @Scope("prototype") public class ServiceA { private Customer customer; private ReloadType reloadType; private ServiceB serviceB; @Autowired private ApplicationContext context; public ServiceA(final Customer customer, final ReloadType reloadType) { this.customer = customer; this.reloadType = reloadType; } @PostConstruct public void init(){ serviceB = (ServiceB) context.getBean("serviceB",customer, reloadType); } public void doSomethingInteresting(){ doSomthingWithCustomer(customer,reloadType); serviceB.doSomethingBoring(); } private void doSomthingWithCustomer(final Customer customer, final ReloadType reloadType) { } }
      
      







 @Service @Scope("prototype") public class ServiceB { private Customer customer; private ReloadType reloadType; public ServiceB(final Customer customer, final ReloadType reloadType) { this.customer = customer; this.reloadType = reloadType; } public void doSomethingBoring(){ } }
      
      







  //... ServiceA serviceA = (ServiceA) context.getBean("serviceA",customer, ReloadType.FullReaload); serviceA.doSomethingInteresting(); //...
      
      







これは不便です。まず、ビンを作成するときにパラメーターの数またはタイプを間違える可能性があります。

第二に多くの定型コード



そのため、スコープBean-"customer"を作成しました。



アイデアは次のとおりです。特定の「コンテキスト」を作成します。これは、現在実行中のプロセス(どのクライアント、どのタイプのプロセス-サービスが知る必要があるすべて)に関する情報を保存するオブジェクトです。

スコープのBeanを作成するとき、このコンテキストをそこに注入します。



同じコンテキストで、各ビンがプロセス全体で一度だけ作成されるように、すでに作成されたBeanのリストが保存されます。



プロセスが終了すると、ThreadLocalをクリアし、すべてのBeanがガベージコレクターによって収集されます。



スコープ内のすべてのBeanが何らかのインターフェースを実装する必要があることに注意してください。 これは、コンテキストを注入するためにのみ必要です。



したがって、xmlでスコープを宣言します。



 .. <bean id="customerScope" class="com.scope.CustomerScope"/> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="customer" value-ref="customerScope"/> </map> </property> </bean> ...
      
      







スコープを実装します。

 public class CustomerScope implements Scope { @Override public Object get(String name, ObjectFactory<?> objectFactory) { CustomerContext context = resolve(); Object result = context.getBean(name); if (result == null) { result = objectFactory.getObject(); ICustomerScopeBean syncScopedBean = (ICustomerScopeBean) result; syncScopedBean.setContext(context); Object oldBean = context.setBean(name, result); if (oldBean != null) { result = oldBean; } } return result; } @Override public Object remove(String name) { CustomerContext context = resolve(); return context.removeBean(name); } protected CustomerContext resolve() { return CustomerContextThreadLocal.getCustomerContext(); } @Override public void registerDestructionCallback(String name, Runnable callback) { } @Override public Object resolveContextualObject(String key) { return null; } @Override public String getConversationId() { return resolve().toString(); } }
      
      







ご覧のとおり、同じプロセス(フロー)内で同じbinインスタンスが使用されます(つまり、このスコープは実際には標準ではありません-プロトタイプでは、新しいインスタンスが毎回シングルトンで作成されます-同じもの)。

また、コンテキスト自体はThreadLocalから取得されます。



 public class CustomerContextThreadLocal { private static ThreadLocal<CustomerContext> customerContext = new ThreadLocal<>(); public static CustomerContext getCustomerContext() { return customerContext.get(); } public static void setSyncContext(CustomerContext context) { customerContext.set(context); } public static void clear() { customerContext.remove(); } private CustomerContextThreadLocal() { } public static void setSyncContext(Customer customer, ReloadType reloadType) { setSyncContext(new CustomerContext(customer, reloadType)); }
      
      







すべてのBeanとその抽象実装のインターフェースを作成することは残っています。

 public interface ICustomerScopeBean { void setContext(CustomerContext context); } public class AbstractCustomerScopeBean implements ICustomerScopeBean { protected Customer customer; protected ReloadType reloadType; @Override public void setContext(final CustomerContext context) { customer = context.getCustomer(); reloadType = context.getReloadType(); } }
      
      







そしてその後、私たちのサービスはより美しく見えます:

 @Service @Scope("customer") public class ServiceA extends AbstractCustomerScopeBean { @Autowired private ServiceB serviceB; public void doSomethingInteresting() { doSomthingWithCustomer(customer, reloadType); serviceB.doSomethingBoring(); } private void doSomthingWithCustomer(final Customer customer, final ReloadType reloadType) { } } @Service @Scope("customer") public class ServiceB extends AbstractCustomerScopeBean { public void doSomethingBoring(){ } } //.... CustomerContextThreadLocal.setSyncContext(customer, ReloadType.FullReaload); ServiceA serviceA = context.getBean(ServiceA.class); serviceA.doSomethingInteresting(); //.....
      
      







質問が発生する可能性があります-ThreadLocalを使用します-非同期メソッドを呼び出すとどうなりますか?

主なことは、Beanツリー全体が同期的に作成され、@ Autowiredが正しく機能することです。

また、メソッドのいずれかが@ Asyncで始まる場合、それは恐ろしくありません。Beanはすでに作成されているため、すべてが機能します。



また、スコープが「顧客」であるすべてのBeanがICustomerScopeBeanを実装していること、およびその逆を検証するテストを作成することをお勧めします。

  @ContextConfiguration(locations = {"classpath:beans.xml"}, loader = GenericXmlContextLoader.class) @RunWith(SpringJUnit4ClassRunner.class) public class CustomerBeanScopetest { @Autowired private AbstractApplicationContext context; @Test public void testScopeBeans() throws ClassNotFoundException { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames(); for (String beanDef : beanDefinitionNames) { BeanDefinition def = beanFactory.getBeanDefinition(beanDef); String scope = def.getScope(); String beanClassName = def.getBeanClassName(); if (beanClassName == null) continue; Class<?> aClass = Class.forName(beanClassName); if (ICustomerScopeBean.class.isAssignableFrom(aClass)) assertTrue(beanClassName + " should have scope 'customer'", scope.equals("customer")); if (scope.equals("customer")) assertTrue(beanClassName + " should implement 'ICustomerScopeBean'", ICustomerScopeBean.class.isAssignableFrom(aClass)); } } }
      
      










All Articles