sparkjava: Do routes have to be in main method?

2019-03-29 10:11发布

问题:

I am new to sparkjava and like it overall. However, do new routes/endpoints have to be defined in the main method? For any significant web application, this will result in a very long main method or I need to have multiple main methods (and therefore split server resources among multiple instances).

These two sparkjava documentation pages seem to define routes in the main method: http://sparkjava.com/documentation.html#routes and here http://sparkjava.com/documentation.html#getting-started.

Is there another way to do this that I'm not seeing? Cursory google searching hasn't shown me a better way ...

=========

Here is the full solution I did based on the answer from Andrew. In my opinion, adding endpoints outside of the main method should be part of the sparkjava documentation page:

Main Method:

public static void main(String[] args) {
    //Do I need to do something more with the Resource instance so that sparkjava notices it and/or reads the routes?
    Resource resource= new Resource(new Service());
}

My resource:

import static spark.Spark.*;
class Resource{

    private Service service;

    Resource(Service service){
        this.service = service;
        setupEndpoints();
    }

    private void setupEndpoints() {

        get("/user/:id", "application/json",(request, response)
                -> service.find(request.params(":id")), new JsonTransformer());

        get("/users", "application/json", (request, response)
                -> service.findAll(), new JsonTransformer());
    }
}

My service:

public class Service {

    public Object find(String id) {
        return null;
    }

    public Object findAll() {
        return null;
    }
}

My JsonTransformer:

import spark.ResponseTransformer;
public class JsonTransformer implements ResponseTransformer {
    @Override
    public String render(Object model) throws Exception {
        return null;
    }
}

回答1:

You can set routes where you want. You just need call set up method in main thread. e.g.

 public static void main(String[] args){
     Resource resource= new Resource(new Service());
 }

 class Resource{

    private Service service;

    Resource(Service service){
      this.service = service;
      setupEndpoints();
    }

    private void setupEndpoints() {

      get("/user/:id", "application/json",(request, response)
            -> service.find(request.params(":id")), new JsonTransformer());

      get("/users", "application/json", (request, response)
            -> service.findAll(), new JsonTransformer());
    }
 }


回答2:

Here's a design idea you can use when there are multiple endpoints to configure:

First, create a builder interface:

public interface EndpointBuilder {
    void configure(Service spark, String basePath);
}

Now, let's say you have one of many other rest endpoints resources to set up:

public class CustomerEndpoint implements EndpointBuilder {

    private final CustomerService customerService;

    public CustomerEndpoint(CustomerService service) {
        this.customerService = service;
    }

    @Override
    public void configure(Service spark, String basePath) {

        spark.get(basePath + "/customer", (req, res) -> {
            return "hello";
        });
    }
}

Finally, create a RestContext class that will hold the spark instance and will enable you to configure whatever routes you wish:

public class RestContext {

    private static final Logger logger = LoggerFactory.getLogger(RestContext.class);

    private final Service spark;

    private final String basePath;

    public RestContext(int port, String basePath) {
        this.basePath = basePath;
        spark = Service.ignite().port(port); // import spark.Service;
    }

    public void addEndpoint(EndpointBuilder endpoint) {

        endpoint.configure(spark, basePath);
        logger.info("REST endpoints registered for {}.", endpoint.getClass().getSimpleName());
    }

    // Then you can even have some fun:
    public void enableCors() {

        spark.before((request, response) -> {
            response.header("Access-Control-Allow-Origin", "*");
            response.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            response.header("Access-Control-Allow-Headers", "Content-Type, api_key, Authorization");
        });

        logger.info("CORS support enabled.");
    }
}

You should be able to use this context class in your main method (and optionally in your test classes):

public static void main(String... args) {

    RestContext context = new RestContext(8080, "/api");

    context.addEndpoint(new CustomerEndpoint(new CustomerService()));
    context.addEndpoint(new AnotherEndpoint()); // you can add or remove as many as you want.

    context.enableCors();
}

Obs.: Since version 2.5, spark java supports multiple instances through the Service.ignite() api.



回答3:

You can also integrate Spring into your Spark application. This is how I've configured my routes.

@Configuration
public class RoutesConfiguration {
    RoutesConfiguration() {
        get("/hello", (req, res) -> "Hello World!");
    }
}

This allows me to avoid the setup invocation step in the Main method of the SparkApp.



回答4:

Well, spark-java is for people not wanting tons of extra config I guess.

So, I just put and additonal method in the controllers, named declareRoutes where I put the declarations.

For the sample application:

public class LoginController {

    public static void declareRoutes(){
        get(Path.Web.LOGIN, LoginController.serveLoginPage);
        post(Path.Web.LOGIN, LoginController.handleLoginPost);
        post(Path.Web.LOGOUT, LoginController.handleLogoutPost);
    }

    public static Route serveLoginPage = (Request request, Response response) -> {
        Map<String, Object> model = new HashMap<>();
        model.put("loggedOut", removeSessionAttrLoggedOut(request));
        model.put("loginRedirect", removeSessionAttrLoginRedirect(request));
        return ViewUtil.render(request, model, Path.Template.LOGIN);
    };

So, in my application I initialize all routes like this:

 // Routes

 Class[] controllers = {
          IndexController.class,
          LoginController.class,
          SandboxController.class,
          ServicesController.class };

 for (Class controller: controllers) {
         Method m = controller.getMethod("declareRoutes");
         m.invoke(m);
 }

 // Catch all
 declareCatchAllRoute();

The declareCatchAllRoute() is just:

private void declareCatchAllRoute() {
    get("*", ViewUtil.notFound);
}

Works well enough for my needs, does not require any new abstraction.

HTH