Reputation: 588
I have following architecture:
Abstract Controller:
public abstract class AbstractController<E> {
private final AbstractService<E> abstractService;
public AbstractController(AbstractService<E> abstractService) {
this.abstractService = abstractService;
}
@PostMapping("blabla")
public List<Something> getList(@RequestBody whatever) {
return abstractService.method(whatever);
}
}
Abstract Service:
public abstract class AbstractService<E> {
@Autowired
private SomeConcreteRepository concreteRepository;
private final EntityTypeAwareRepository<E> entityRepository;
@Autowired(required = false)
public AbstractService(EntityTypeAwareRepository<E> entityRepository) {
this.entityRepository = entityRepository;
}
List<Something> method() {
String tableName = extractTableName(this.entityRepository.getEntityClass());
return this.concreteRepository.someJdbcCall(tableName);
}
}
Entity A Repository:
@Repository
public interface EntityARepository extends EntityTypeAwareRepository<A>, JpaRepository<A, Long>, JdbcRepository {
// JdbcRepository has implementation for A entity and its methods are called with EntityARepository in service layer
}
Entity A Controller:
@RestController
@RequestMapping("base/mapping")
public class AController extends AbstractController<A> {
private final AService aService;
@Autowired
public AController(AService aService) {
super(aService);
this.aService = aService;
}
// concrete endpoints methods
}
Entity A Service:
@Service
@PreAuthorize(someRole)
@Transactional
public class AService extends AbstractService<A> {
private final ARepository aRepository;
@Autowired
public AService(ARepository aRepository) {
super(aRepository);
this.aRepository = aRepository;
}
//methods using Spring-JPA aRepository methods
//and
//methods using JdbcRepository interface implementation (via same reference)
}
And it is scaled for any entity you like. EntityTypeAwareRepository
is NoBeanRepository
with defualt method for finding concrete repository entity. To the point. Abstract controller is getting calls from client for different kind of entities but regarding the same thing. So the goal is that one abstract controller will handle calls for N sub-controllers.
SomeConcreteRepository
is concrete class handling this logic.
The problem is that it is not autowiring, together with entityRepository
in abstract service. causing NPE. Actually the latter is passed from sub-controller but resolves to null.
And in fact in this configuration I had it working somehow, but next day autowiring was not working properly. So I am not sure if there is some problem with this architecture or I just had some building problems that it magically worked. Do you have idea what can cause problem? Can it be connected with proxies from @Transactional?
I am sure everything is properly scanned and all beans are visible.
Upvotes: 1
Views: 565
Reputation:
In your abstract controller I would make an abstract AbstractService<E> getService()
method. Then in the concrete controller, implement that getter:
public abstract class AbstractController<E> {
abstract AbstractService<E> getService();
@GetMapping("/{id}")
public E findById(Long id) {
return getService().findById(id);
}
@PostMapping
public E save(E entity) {
return getService().save(entity);
}
}
The concrete controller then looks as follows:
@RestController
@RequestMapping("/a")
public AController extends AbstractController<A> {
@AutoWired
AService service;
public AbstractService<A> getService() {
return service;
}
}
The service would implement the same pattern with a abstract EntityRepository<E> getRepository()
public abstract class AbstractService<E> {
public abstract EntityRepository<E> getRepository();
public E findById(Long id) {
return getRepository().findById(id);
}
public E save(E entity) {
return getRepository().save(entity);
}
}
The concrete service appears as follows:
@Service
public class AService extends AbstractService<A> {
@Autowired
ARepository repository;
public EntityRepository<A> getRepository() {
return repository;
}
}
The repository is the last layer. The "abstract-ish" repository appears as follows:
@NoBeanRepository
public interface EntityRepository<E> extends EntityTypeAwareRepository<E>, JpaRepository<E, Long>, JdbcRepository {
// Implementations are inherited... for findById(Long id), save(E entity), etc
}
We then extend this with the "concrete-ish" repository:
@Repository
public interface ARepository extends EntityRepository<A> {
}
We get something like the following:
This way, we do not have instance variables for the service or repository at the "abstract" layer. We can only access the concrete version via abstract getters. The concrete implementations of services and repositories are the only classes that autowire implementations of the specific repository and service, respectively.
My original answer to this is here
Upvotes: 1