Phạm vi tùy chỉnh trong mùa xuân

1. Khái quát chung

Ngoài ra, Spring cung cấp hai phạm vi bean tiêu chuẩn ( “singleton”“nguyên mẫu” ) có thể được sử dụng trong bất kỳ ứng dụng Spring nào, cộng với ba phạm vi bean bổ sung ( “request” , “session”“globalSession” ) để sử dụng chỉ trong các ứng dụng nhận biết web.

Không thể ghi đè các phạm vi bean tiêu chuẩn và thường được coi là một phương pháp không tốt để ghi đè các phạm vi nhận biết web. Tuy nhiên, bạn có thể có một ứng dụng yêu cầu các khả năng khác hoặc bổ sung từ những khả năng được tìm thấy trong phạm vi được cung cấp.

Ví dụ: nếu bạn đang phát triển một hệ thống nhiều người thuê, bạn có thể muốn cung cấp một phiên bản riêng của một bean cụ thể hoặc một tập hợp các bean cho mỗi người thuê. Spring cung cấp một cơ chế để tạo phạm vi tùy chỉnh cho các tình huống như thế này.

Trong hướng dẫn nhanh này, chúng tôi sẽ trình bày cách tạo, đăng ký và sử dụng phạm vi tùy chỉnh trong ứng dụng Spring .

2. Tạo một lớp phạm vi tùy chỉnh

Để tạo phạm vi tùy chỉnh, chúng ta phải triển khai giao diện Phạm vi . Khi làm như vậy, chúng ta cũng phải đảm bảo rằng việc triển khai là an toàn theo luồng vì phạm vi có thể được sử dụng bởi nhiều nhà máy bean cùng một lúc.

2.1. Quản lý các đối tượng theo phạm vi và gọi lại

Một trong những điều đầu tiên cần xem xét khi triển khai lớp Phạm vi tùy chỉnh là cách bạn sẽ lưu trữ và quản lý các đối tượng trong phạm vi và lệnh gọi lại hủy. Điều này có thể được thực hiện bằng cách sử dụng một bản đồ hoặc một lớp chuyên dụng, chẳng hạn.

Đối với bài viết này, chúng tôi sẽ thực hiện việc này theo cách an toàn theo chuỗi sử dụng bản đồ được đồng bộ hóa.

Hãy bắt đầu xác định lớp phạm vi tùy chỉnh của chúng tôi:

public class TenantScope implements Scope { private Map scopedObjects = Collections.synchronizedMap(new HashMap()); private Map destructionCallbacks = Collections.synchronizedMap(new HashMap()); ... }

2.2. Lấy một đối tượng từ phạm vi

Để lấy một đối tượng theo tên từ phạm vi của chúng ta, hãy triển khai phương thức getObject . Như JavaDoc đã nêu, nếu đối tượng được đặt tên không tồn tại trong phạm vi, phương thức này phải tạo và trả về một đối tượng mới .

Trong quá trình triển khai, chúng tôi kiểm tra xem đối tượng được đặt tên có trong bản đồ của chúng tôi hay không. Nếu đúng, chúng tôi trả lại nó, và nếu không, chúng tôi sử dụng ObjectFactory để tạo một đối tượng mới, thêm nó vào bản đồ của chúng tôi và trả lại:

@Override public Object get(String name, ObjectFactory objectFactory) { if(!scopedObjects.containsKey(name)) { scopedObjects.put(name, objectFactory.getObject()); } return scopedObjects.get(name); }

Trong số năm phương thức được xác định bởi giao diện Scope , chỉ phương thức get là cần thiết để triển khai đầy đủ hành vi được mô tả. Bốn phương thức khác là tùy chọn và có thể ném UnsupportedOperationException nếu chúng không cần hoặc không thể hỗ trợ một chức năng.

2.3. Đăng ký Gọi lại Hủy

Chúng ta cũng phải triển khai phương thức registerDestructionCallback . Phương thức này cung cấp một lệnh gọi lại sẽ được thực thi khi đối tượng được đặt tên bị phá hủy hoặc nếu bản thân phạm vi bị phá hủy bởi ứng dụng:

@Override public void registerDestructionCallback(String name, Runnable callback) { destructionCallbacks.put(name, callback); }

2.4. Xóa một đối tượng khỏi phạm vi

Tiếp theo, hãy triển khai phương thức remove , phương thức này sẽ xóa đối tượng được đặt tên khỏi phạm vi và cũng loại bỏ lệnh gọi lại hủy đã đăng ký của nó, trả về đối tượng đã bị loại bỏ:

@Override public Object remove(String name) { destructionCallbacks.remove(name); return scopedObjects.remove(name); }

Lưu ý rằng người gọi có trách nhiệm thực sự thực hiện lệnh gọi lại và hủy đối tượng bị loại bỏ .

2.5. Lấy ID cuộc trò chuyện

Bây giờ, hãy triển khai phương thức getConversationId . Nếu phạm vi của bạn hỗ trợ khái niệm ID hội thoại, bạn sẽ gửi lại nó ở đây. Nếu không, quy ước là trả về null :

@Override public String getConversationId() { return "tenant"; }

2.6. Giải quyết các đối tượng theo ngữ cảnh

Cuối cùng, chúng ta hãy thực hiện các resolveContextualObject phương pháp. Nếu phạm vi của bạn hỗ trợ nhiều đối tượng theo ngữ cảnh, bạn sẽ liên kết mỗi đối tượng với một giá trị khóa và bạn sẽ trả về đối tượng tương ứng với tham số khóa đã cung cấp . Nếu không, quy ước là trả về null :

@Override public Object resolveContextualObject(String key) { return null; }

3. Đăng ký Phạm vi tùy chỉnh

Để làm cho vùng chứa Spring nhận biết được phạm vi mới của bạn, bạn cần đăng ký nó thông qua phương thức registerScope trên một cá thể ConfigurableBeanFactory . Hãy xem định nghĩa của phương pháp này:

void registerScope(String scopeName, Scope scope);

Tham số đầu tiên, scopeName , được sử dụng để xác định / chỉ định phạm vi bằng tên duy nhất của nó. Tham số thứ hai, phạm vi , là một phiên bản thực tế của việc triển khai Phạm vi tùy chỉnh mà bạn muốn đăng ký và sử dụng.

Hãy tạo một BeanFactoryPostProcessor tùy chỉnh và đăng ký phạm vi tùy chỉnh của chúng tôi bằng cách sử dụng ConfigurableListableBeanFactory :

public class TenantBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException { factory.registerScope("tenant", new TenantScope()); } }

Bây giờ, hãy viết một lớp cấu hình Spring tải triển khai BeanFactoryPostProcessor của chúng ta :

@Configuration public class TenantScopeConfig { @Bean public static BeanFactoryPostProcessor beanFactoryPostProcessor() { return new TenantBeanFactoryPostProcessor(); } }

4. Sử dụng Phạm vi tùy chỉnh

Bây giờ chúng tôi đã đăng ký phạm vi tùy chỉnh của mình, chúng tôi có thể áp dụng nó cho bất kỳ bean nào của chúng tôi giống như chúng tôi làm với bất kỳ bean nào khác sử dụng phạm vi không phải singleton (phạm vi mặc định) - bằng cách sử dụng chú thích @Scope và chỉ định phạm vi tùy chỉnh của chúng tôi bằng tên.

Hãy tạo một lớp TenantBean đơn giản - chúng ta sẽ khai báo các bean phạm vi đối tượng thuê thuộc loại này trong giây lát:

public class TenantBean { private final String name; public TenantBean(String name) { this.name = name; } public void sayHello() { System.out.println( String.format("Hello from %s of type %s", this.name, this.getClass().getName())); } }

Lưu ý rằng chúng tôi đã không sử dụng lớp cấp @Component@Scope chú thích trên lớp này.

Bây giờ, hãy xác định một số bean phạm vi người thuê trong một lớp cấu hình:

@Configuration public class TenantBeansConfig { @Scope(scopeName = "tenant") @Bean public TenantBean foo() { return new TenantBean("foo"); } @Scope(scopeName = "tenant") @Bean public TenantBean bar() { return new TenantBean("bar"); } }

5. Kiểm tra Phạm vi tùy chỉnh

Hãy viết một bài kiểm tra để thực hiện cấu hình phạm vi tùy chỉnh của chúng tôi bằng cách tải lên ApplicationContext , đăng ký các lớp Cấu hình của chúng tôi và truy xuất các bean có phạm vi đối tượng thuê của chúng tôi:

@Test public final void whenRegisterScopeAndBeans_thenContextContainsFooAndBar() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); try{ ctx.register(TenantScopeConfig.class); ctx.register(TenantBeansConfig.class); ctx.refresh(); TenantBean foo = (TenantBean) ctx.getBean("foo", TenantBean.class); foo.sayHello(); TenantBean bar = (TenantBean) ctx.getBean("bar", TenantBean.class); bar.sayHello(); Map foos = ctx.getBeansOfType(TenantBean.class); assertThat(foo, not(equalTo(bar))); assertThat(foos.size(), equalTo(2)); assertTrue(foos.containsValue(foo)); assertTrue(foos.containsValue(bar)); BeanDefinition fooDefinition = ctx.getBeanDefinition("foo"); BeanDefinition barDefinition = ctx.getBeanDefinition("bar"); assertThat(fooDefinition.getScope(), equalTo("tenant")); assertThat(barDefinition.getScope(), equalTo("tenant")); } finally { ctx.close(); } }

Và kết quả từ thử nghiệm của chúng tôi là:

Hello from foo of type org.baeldung.customscope.TenantBean Hello from bar of type org.baeldung.customscope.TenantBean

6. Kết luận

Trong hướng dẫn nhanh này, chúng tôi đã chỉ ra cách xác định, đăng ký và sử dụng phạm vi tùy chỉnh trong Spring.

Bạn có thể đọc thêm về phạm vi tùy chỉnh trong Tham chiếu khung mùa xuân. Bạn cũng có thể xem triển khai Spring của các lớp Scope khác nhau trong kho lưu trữ Spring Framework trên GitHub.

Như thường lệ, bạn có thể tìm thấy các mẫu mã được sử dụng trong bài viết này trên dự án GitHub.