zhogov
zhogov

Reputation: 63

Spring controllers composition instead of inheritance

I develop REST application based on Spring MVC.

I have following hierarhy of Controllers:

UserController extends ImageController
 ImageController extends SuggestController // Controller that has GET-DELETE-POST for image
  SuggestController extends CrudController// Controller that has some suggest methods
   CrudController // CRUD operations

So I had following mappings in runtume:

/users (POST-GET-PUT-DELETE)
/users/suggest (GET, POST(Spring Pageable))
/users/image (POST-GET-DELETE)

It was ok before I realized, that one controller must be able to give me images of its stuff, but cannot implement "suggest" methods:

/stuff (POST-GET-PUT-DELETE)
/stuff/image (POST-GET-DELETE)

And another one does not have "image" functionality, but has "suggest":

/things (POST-GET-PUT-DELETE)
/things/suggest (GET, POST(Spring Pageable))

Java says : "Use composition in such cases":

StuffController {
    @InlineThisController
    ImageController imagedController;
    @InlineThisController
    CrudController crudController;
... //some additional methods
}

So how can I acheive this in Spring MVC without boilerplate for delegation? Annotations? XML?

Upvotes: 2

Views: 2655

Answers (2)

zhogov
zhogov

Reputation: 63

Here are possible approaches:

  1. Use Lombok's @Delegate
  2. Use Kotlin's delegation support
  3. Use Java's default interface methods (looks uglier than 1 and 2)

Upvotes: 0

Serge Ballesta
Serge Ballesta

Reputation: 148890

Spring MVC will not allow you to override method annotated with @RequestMapping, or more exactly it does not allow you to annotate the overriding method with @RequestMapping and will use the mapping in base class.

But you can always define 2 methods in base class : one annotated with @RequestMapping that only delegates to second not annotated. Then you are then free to override the second method in subclasses. Example :

Abstract base class for a CRUD controller

public abstract class AbstractCRUDController<K extends Serializable, X>
// X is the data class, K is the class of the key (int or String)
    @RequestMapping({"/{data}"})
    public String show(@ModelAttribute("data") X data, HttpSession session, Model model,
            @RequestParam(value = "pagesize", required = false, defaultValue = "-1") int pageSize,
            WebRequest request,
            @RequestParam(value = "all", defaultValue = "false") boolean all) {
        return doShow(data, session, model, pageSize, request, all);
    }
    protected String doShow(X data, HttpSession session, Model model,
            int pageSize, WebRequest request, boolean all) {
        return this.getPrefix() + "/view";
    }
    @RequestMapping(value={"/{data}/edit"}, method=RequestMethod.GET)
    public String edit(@ModelAttribute("data") X data, Model model) {
        return doEdit(data, model);
    }

    protected String doEdit(@ModelAttribute("data") X data, Model model) {
        model.addAttribute("title", editTitle);
        return this.getPrefix() + "/edit";
    }

    @RequestMapping(value={"/{data}/edit"}, method=RequestMethod.POST)
    public String update(@ModelAttribute("data") X data, BindingResult result, Model model) {
        if (data == null) {
            throw new NoSuchElementException();
        }
        if (save(data, result, model, SimpleSaveType.UPDATE, null) != null) {
            return "redirect:" + savedUrl(data);
        }
        else {
            model.addAttribute("title", editTitle);
            return getPrefix() + "/edit";
        }
    }

    public K save(X data, BindingResult result, Model model, SaveType saveType, K id) {
        ...
    }

    ...
    public abstract String getPrefix();
}

Concrete implementation for class ProcessQ

@Controller
@RequestMapping(ProcessController.PREFIX)
public class ProcessController extends AbstractCRUDController<Integer, ProcessQ> {
    public static final String PREFIX = "/process";

    @Override
    public String getPrefix() {
        return PREFIX;
    }

    @Override
    protected String doShow(ProcessQ process, HttpSession session, Model model,
            int pageSize, WebRequest request, boolean all) {
        String viewName = super.doShow(process, session, model, pageSize, request, all);
        // specialized processing for class ProcessQ
        ...
        return viewName;
    }
    ...
}

Example is taken from a real program, that's the reason why you can see elements for pagination, error processing and access to underlying request.

Upvotes: 2

Related Questions