Допустим, у меня есть следующие интерфейсы:
public interface FindExtension {
void findById(long id);
void findAll();
}
public interface SaveExtension {
void save();
}
public interface DeleteExtension {
void deleteById(long id);
}
И у меня есть следующие реализации:
public class FindExtensionImpl implements FindExtension {
@Override
public void findById(long id) {
System.out.println("FindExtensionImpl::findById(" + id + ")");
}
@Override
public void findAll() {
System.out.println("FindExtensionImpl::findAll()");
}
}
public class SaveExtensionImpl implements SaveExtension {
@Override
public void save() {
System.out.println("SaveExtensionImpl::save()");
}
}
public class DeleteExtensionImpl implements DeleteExtension {
@Override
public void deleteById(long id) {
System.out.println("DeleteExtensionImpl::deleteById(" + id + ")");
}
}
И теперь я хочу смешать эти реализации и, допустим, мне нужен этот сервис:
public interface MyService extends FindExtension, SaveExtension, DeleteExtension {
}
Теперь в моем приложении Spring Boot я хотел бы внедрить реализацию MyService в один из моих контроллеров. Обычно я создавал класс MyServiceImpl, реализовывал методы интерфейса и аннотировал @Service. Таким образом, Spring Boot просканирует мой код, создаст экземпляр этой службы и предоставит его мне в любое время, когда мне это нужно.
Однако я бы хотел не создавать MyServiceImpl, а генерировать его фреймворком во время выполнения, другими словами, состоять из маленьких кусочков. Как мне сказать Spring Boot делать это автоматически? Или мне нужно создать свой собственный процессор аннотаций и аннотаций, который каким-то образом сгенерирует реализацию?
Это что-то похожее на репозитории Spring Boot, где у меня будет такой интерфейс:
@Repository
interface IPostRepository implements JpaRepository<Post,Long>, QuerydslPredicateExecutor<Post>, PostCustomRepositoryExtension { }
И Spring Boot "волшебным образом" создаст реализацию этого интерфейса и внедрит методы из JpaRepository, QuerydslPredicateExecutor и моего собственного PostCustomRepositoryExtension...
Поскольку Spring Boot уже выполняет логику, аналогичную моей, мне интересно, могу ли я повторно использовать это и как?
Я думаю, вам придется что-то реализовать самостоятельно — не обязательно процессор аннотаций, но я бы сказал, что это больше похоже на фабрику компонентов, которая может предоставлять экземпляры MyService. Из того, что я знаю, Spring делает это так, то есть создает прокси во время выполнения. Вы можете взглянуть на такие классы, как RepositoryFactorySupport и т. д. - Однако я бы посоветовал вам подумать, действительно ли это стоит усилий, поскольку могут быть более простые решения с несколько другим дизайном.




Самый простой способ - поместить весь интерфейс в один интерфейс:
public interface CommonService<T, Z> {
void findById(long id);
void findAll();
void save();
void deleteById(long id);
}
затем сделайте свой сервис, который расширяет CommonService
public interface ObjectService extends CommonService<Object, long> {
}
и, наконец, вы можете сделать импл, который реализует ObjectService
public class ObjectImpl implements ObjectService {
@Override
public void findById(long id) {
System.out.println("ObjectImpl::findById(" + id + ")");
}
@Override
public void findAll() {
System.out.println("ObjectImpl::findAll()");
}
@Override
public void save() {
System.out.println("ObjectImpl::save()");
}
@Override
public void deleteById(long id) {
System.out.println("ObjectImpl::deleteById(" + id + ")");
}
}
Вам не нужно объединять все эти методы в один интерфейс. MyService расширение нескольких интерфейсов — это нормально. Кроме того, ОП знает о возможности создания единой реализации общего интерфейса, которая в вашем примере будет ObjectImpl. Однако вопрос заключается в том, есть ли способ автоматически создать составную реализацию из фрагментов, каждый из которых реализует только один из меньших интерфейсов.
@Thomas, вот почему он называется CommonService, это просто обычный, самый простой способ.
Что ж, это самый простой способ реализации сервисов, но на самом деле он не отвечает на вопрос ОП, поскольку явно касается композиции уже существующих частичных реализаций.
Отказ от ответственности
Я не эксперт по Spring, особенно в том, что касается создания bean-компонентов и т. д., поэтому примите следующий подход с недоверием. Ваш вопрос заставил меня задуматься, поэтому я поиграл со Spring и смог придумать следующий базовый подход.
Обратите внимание, что это предназначено только для того, чтобы вы начали работу, и далеко не готово к производству. Потребуются дополнительные исследования, и другие могут даже прийти и показать, что это неправильный подход.
Подход
Теперь вот подход:
Код (только дополнительные соответствующие части)
Аннотация интерфейса:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CompositeService {}
Аннотация включения:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({CompositeServiceFactoryRegistrar.class})
public @interface EnableCompositeServices {}
Главный заводской регистратор (со встроенными комментариями):
@Component
public class CompositeServiceFactoryRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Map<Class<?>, Supplier<Object>> fragmentFactories = new HashMap<>();
private Environment environment;
public CompositeServiceFactoryRegistrar() {
//hard coded fragment registration, you'll probably want to do a lookup of all interfaces and their implementation on the classpath instead
fragmentFactories.put(SaveExtension.class, () -> new SaveExtensionImpl());
fragmentFactories.put(DeleteExtension.class, () -> new DeleteExtensionImpl());
fragmentFactories.put(FindExtension.class, () -> new FindExtensionImpl());
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//get the enablement annotation and set up package scan
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(EnableCompositeServices.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("value");
if (basePackages == null || basePackages.length == 0) {
// If value attribute is not set, fallback to the package of the annotated class
basePackages = new String[] {
((StandardAnnotationMetadata) metadata).getIntrospectedClass().getPackage().getName() };
}
// using these packages, scan for interface annotated with MyCustomBean
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
false, environment) {
// Override isCandidateComponent to only scan for interface
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isIndependent() && metadata.isInterface();
}
};
provider.addIncludeFilter(new AnnotationTypeFilter(CompositeService.class));
// Scan all packages
for (String basePackage : basePackages) {
for (BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage)) {
GenericBeanDefinition genericDef = new GenericBeanDefinition(beanDefinition);
//resolve the interface class if not done yet
if ( !genericDef.hasBeanClass()) {
try {
genericDef.resolveBeanClass(getClassLoader());
} catch(ClassNotFoundException e) {
//simple logging, replace that with something more appropriate
e.printStackTrace();
}
}
Class<?> interfaceType = genericDef.getBeanClass();
//add the factory to the bean definition and then register it
genericDef.setInstanceSupplier(() -> createProxy(interfaceType) );
registry.registerBeanDefinition(interfaceType.getSimpleName(), genericDef);
}
}
}
}
/*
* Main factory method
*/
@SuppressWarnings("unchecked")
private <T> T createProxy(Class<T> type) {
//create the factory and set the interface type
ProxyFactory factory = new ProxyFactory();
factory.setInterfaces(type);
//add the advice that actually delegates to the fragments
factory.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method invokedMethod = invocation.getMethod();
Class<?> invokedClass = invokedMethod.getDeclaringClass();
if (invokedClass.isInterface()) {
//create the fragment for this method, if not possible continue with the next interceptor
Supplier<Object> supplier = fragmentFactories.get(invokedClass);
if (supplier == null) {
return invocation.proceed();
}
Object fragment = supplier.get();
//get the fragment method and invoke it
Method targetMethod = fragment.getClass().getDeclaredMethod(invokedMethod.getName(),
invokedMethod.getParameterTypes());
return targetMethod.invoke(fragment, invocation.getArguments());
} else {
return invocation.proceed();
}
}
});
return (T) factory.getProxy(getClassLoader());
}
private ClassLoader getClassLoader() {
return getClass().getClassLoader();
}
}
Обратите внимание, что это очень простая реализация и имеет несколько недостатков, например.
Спасибо за очень хорошее объяснение, я еще не проверял его, но спасибо за указание в правильном направлении.
Забудьте о весне и подумайте о простой Java. Ни одна из трех показанных вами реализаций не реализует MyService, они просто реализуют одну его часть. Если вам нужна пригодная для использования реализация MyService, вам все равно придется инкапсулировать три небольшие реализации в одну большую, которая фактически реализует весь сервис. Так как вы не можете сделать это в простой Java, вы не сможете сделать это в Spring без обработки пользовательских аннотаций.