Home > Mobile >  Create a convenience decorator to generate a Controller from a function using BeanPostProcessor?
Create a convenience decorator to generate a Controller from a function using BeanPostProcessor?

Time:09-29

I'm looking to write a decorator that takes a very static function and wraps it inside a controller.

Think of it as a global scope utility callable/runnable, so pathvariable/requestbody has to be injected into the parameters. And then it has to automatically be wrapped inside a bean controller with the appropriate getmapping/postmapping to expose it an endpoint

@AutoGetMapping("/users/{id}")
    public ResponseEntity<User> getById(@PathVariable long id) {
        Optional<User> user = userService.getById(id);
        if (user.isPresent()) {
            return new ResponseEntity<>(user.get(), HttpStatus.OK);
        } else {
            throw new RecordNotFoundException();
        }
    }

gets transformed to

@RestController
public class UserController {
 
    @Autowired
    UserService userService;
 
    @GetMapping("users")
    public ResponseEntity<List<User>> getAll() {
        return new ResponseEntity<>(userService.getAll(), HttpStatus.OK);
    }
 
    @GetMapping("users/{id}")
    public ResponseEntity<User> getById(@PathVariable long id) {
        Optional<User> user = userService.getById(id);
        if (user.isPresent()) {
            return new ResponseEntity<>(user.get(), HttpStatus.OK);
        } else {
            throw new RecordNotFoundException();
        }
    }
}

(maybe even the service layers).

I'm just looking for a place to start. I think im making a mistake in trying to use BeanPostProcessor and BeanDefinitionRegistryPostProcessor to do this. Can someone point me in the right direction on how to start doing this ?

CodePudding user response:

One way to do it could be using the approach described in Interface Driven Controllers article with some additions.

As in the article, we can create an interface with the default annotations. Additionally, we can implement the default methods and enforce the implementation of the certain methods in the service layer using some generics like this:

@RestController
@RequestMapping("/default")
public interface BasicRestController<ID, T, S extends BasicRestService<T, ID>> {
    @NonNull S getService();

    @GetMapping("/{id}")
    default ResponseEntity<T> getById(@PathVariable ID id) {
        return getService().getById(id)
                .map(ResponseEntity::ok)
                .orElseThrow(RecordNotFoundException::new);
    }

    @GetMapping
    default ResponseEntity<List<T>> getAll() {
        List<T> results = getService().getAll();
        return ResponseEntity.ok(results);
    }
}
public interface BasicRestService<T, ID> {
    Optional<T> getById(ID id);

    List<T> getAll();
}

And then use it in the controller like this:

@RestController
@RequestMapping("/bars")
@RequiredArgsConstructor
public class BarController implements BasicRestController<Long, Bar, BarService> {
    private final BarService barService;

    @Override
    public @NonNull BarService getService() {
        return barService;
    }
}

Minimal working example can be found here: https://bitbucket.org/kasptom/stack-73744318-interface-driven-controller

  • Related