Giter Club home page Giter Club logo

gs-rest-hateoas's Introduction

This guide walks you through the process of creating a “Hello, World” Hypermedia-driven REST web service with Spring.

Hypermedia is an important aspect of REST. It lets you build services that decouple client and server to a large extent and let them evolve independently. The representations returned for REST resources contain not only data but also links to related resources. Thus, the design of the representations is crucial to the design of the overall service.

What You Will Build

You will build a hypermedia-driven REST service with Spring HATEOAS: a library of APIs that you can use to create links that point to Spring MVC controllers, build up resource representations, and control how they are rendered into supported hypermedia formats (such as HAL).

The service will accept HTTP GET requests at http://localhost:8080/greeting.

It will respond with a JSON representation of a greeting that is enriched with the simplest possible hypermedia element, a link that points to the resource itself. The following listing shows the output:

{
  "content":"Hello, World!",
  "_links":{
    "self":{
      "href":"http://localhost:8080/greeting?name=World"
    }
  }
}

The response already indicates that you can customize the greeting with an optional name parameter in the query string, as the following listing shows:

http://localhost:8080/greeting?name=User

The name parameter value overrides the default value of World and is reflected in the response, as the following listing shows:

{
  "content":"Hello, User!",
  "_links":{
    "self":{
      "href":"http://localhost:8080/greeting?name=User"
    }
  }
}

Starting with Spring Initializr

You can use this pre-initialized project and click Generate to download a ZIP file. This project is configured to fit the examples in this tutorial.

To manually initialize the project:

  1. Navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.

  2. Choose either Gradle or Maven and the language you want to use. This guide assumes that you chose Java.

  3. Click Dependencies and select Spring HATEOAS.

  4. Click Generate.

  5. Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.

Note
If your IDE has the Spring Initializr integration, you can complete this process from your IDE.
Note
You can also fork the project from Github and open it in your IDE or other editor.

Create a Resource Representation Class

Now that you have set up the project and build system, you can create your web service.

Begin the process by thinking about service interactions.

The service will expose a resource at /greeting to handle GET requests, optionally with a name parameter in the query string. The GET request should return a 200 OK response with JSON in the body to represent a greeting.

Beyond that, the JSON representation of the resource will be enriched with a list of hypermedia elements in a _links property. The most rudimentary form of this is a link that points to the resource itself. The representation should resemble the following listing:

{
  "content":"Hello, World!",
  "_links":{
    "self":{
      "href":"http://localhost:8080/greeting?name=World"
    }
  }
}

The content is the textual representation of the greeting. The _links element contains a list of links (in this case, exactly one with the relation type of rel and the href attribute pointing to the resource that was accessed).

To model the greeting representation, create a resource representation class. As the _links property is a fundamental property of the representation model, Spring HATEOAS ships with a base class (called RepresentationModel) that lets you add instances of Link and ensures that they are rendered as shown earlier.

Create a plain old java object that extends RepresentationModel and adds the field and accessor for the content as well as a constructor, as the following listing (from src/main/java/com/example/resthateoas/Greeting.java) shows:

link:complete/src/main/java/com/example/resthateoas/Greeting.java[role=include]
  • @JsonCreator: Signals how Jackson can create an instance of this POJO.

  • @JsonProperty: Marks the field into which Jackson should put this constructor argument.

Note
As you will see in later in this guide, Spring will use the Jackson JSON library to automatically marshal instances of type Greeting into JSON.

Next, create the resource controller that will serve these greetings.

Create a REST Controller

In Spring’s approach to building RESTful web services, HTTP requests are handled by a controller. The components are identified by the @RestController annotation, which combines the @Controller and @ResponseBody annotations. The following GreetingController (from src/main/java/com/example/resthateoas/GreetingController.java) handles GET requests for /greeting by returning a new instance of the Greeting class:

link:complete/src/main/java/com/example/resthateoas/GreetingController.java[role=include]

This controller is concise and simple, but there is plenty going on. We break it down step by step.

The @RequestMapping annotation ensures that HTTP requests to /greeting are mapped to the greeting() method.

Note
The above example does not specify GET vs. PUT, POST, and so forth, because @RequestMapping maps all HTTP operations by default. Use @GetMapping("/greeting") to narrow this mapping. In that case you also want to import org.springframework.web.bind.annotation.GetMapping;.

@RequestParam binds the value of the query string parameter name into the name parameter of the greeting() method. This query string parameter is implicitly not required because of the use of the defaultValue attribute. If it is absent in the request, the defaultValue of World is used.

Because the @RestController annotation is present on the class, an implicit @ResponseBody annotation is added to the greeting method. This causes Spring MVC to render the returned HttpEntity and its payload (the Greeting) directly to the response.

The most interesting part of the method implementation is how you create the link that points to the controller method and how you add it to the representation model. Both linkTo(…) and methodOn(…) are static methods on ControllerLinkBuilder that let you fake a method invocation on the controller. The returned LinkBuilder will have inspected the controller method’s mapping annotation to build up exactly the URI to which the method is mapped.

Note
Spring HATEOAS respects various X-FORWARDED- headers. If you put a Spring HATEOAS service behind a proxy and properly configure it with X-FORWARDED-HOST headers, the resulting links will be properly formatted.

The call to withSelfRel() creates a Link instance that you add to the Greeting representation model.

Logging output is displayed. The service should be up and running within a few seconds.

Test the Service

Now that the service is up, visit http://localhost:8080/greeting, where you should see the following content:

{
  "content":"Hello, World!",
  "_links":{
    "self":{
      "href":"http://localhost:8080/greeting?name=World"
    }
  }
}

Provide a name query string parameter by visiting the following URL: http://localhost:8080/greeting?name=User. Notice how the value of the content attribute changes from Hello, World! to Hello, User! and the href attribute of the self link reflects that change as well, as the following listing shows:

{
  "content":"Hello, User!",
  "_links":{
    "self":{
      "href":"http://localhost:8080/greeting?name=User"
    }
  }
}

This change demonstrates that the @RequestParam arrangement in GreetingController works as expected. The name parameter has been given a default value of World but can always be explicitly overridden through the query string.

Summary

Congratulations! You have just developed a hypermedia-driven RESTful web service with Spring HATEOAS.

gs-rest-hateoas's People

Contributors

asaikali avatar bclozel avatar btalbott avatar buzzardo avatar cbeams avatar ericdahl avatar gregturn avatar habuma avatar j0ker70 avatar manonthemat avatar michalfotyga avatar nizarbenalla avatar odrotbohm avatar robertmcnees avatar royclarkson avatar sdeleuze avatar spring-operator avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gs-rest-hateoas's Issues

methodOn is undefined

With these codes the "methodOn" is undefined. I need to add the import static to compile:

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

This is normal?

Thanks!

Links switched to hal format

Something has been changed in spring-hateoas, spring-boot or build imports.

While waiting #2 to get fixed I added spring-plugin-core:

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("com.fasterxml.jackson.core:jackson-databind")
    compile("org.springframework.hateoas:spring-hateoas")
    compile("org.springframework.plugin:spring-plugin-core:1.1.0.RELEASE")
    testCompile("junit:junit")
}

Using request:

$ curl http://localhost:8080/greeting

should give links:

{ "links" : [ { "rel" : "self",
                "href" : "http://localhost:8080/greeting?name=World" } ],
  "content" : "Hello, World!" }

Not hal _links:

{"content":"Hello, World!","_links":{"self":{"href":"http://localhost:8080/greeting?name=World"}}}

Update Spring Boot version

Updating the Spring Boot version is a constantly ongoing task, and it's easy to do. That makes it a good spot for beginners to get started by making a PR.

To change the Spring Boot version, you must change it in the following files:

complete/build.gradle
complete/pom.xml
initial/build.gradle
initial/pom.xml

Jayway JsonPath library Error

Following the Guide and adding the JSON library to pom.xml I got the following error


Caused by: java.lang.NoClassDefFoundError: com/jayway/jsonpath/PathNotFoundException
	at org.springframework.hateoas.mediatype.hal.HalMediaTypeConfiguration.halLinkDisocoverer(HalMediaTypeConfiguration.java:62) ~[spring-hateoas-1.3.2.jar!/:1.3.2]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_302]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_302]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_302]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_302]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.8.jar!/:5.3.8]
	... 28 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.jayway.jsonpath.PathNotFoundException
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382) ~[na:1.8.0_302]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[na:1.8.0_302]
	at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:151) ~[rest-hateoas-complete-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[na:1.8.0_302]
	... 34 common frames omitted

Not using the library outputs no error.

Why it works without the library, i don't know. Just to make it more clear to newcomers.

Thanks!

Run error - ClassNotFoundException

Cloned a repo, did build and ran fat jar.

jvalkealahti@neo:/tmp/foo/gs-rest-hateoas/complete$ ./gradlew clean build
:clean
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:bootRepackage
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build

BUILD SUCCESSFUL

Total time: 6.495 secs
jvalkealahti@neo:/tmp/foo/gs-rest-hateoas/complete$ java -jar build/libs/gs-rest-hateoas-0.1.0.jar

. ____ _ __ _ _
/\ / ' __ _ ()_ __ __ _ \ \ \
( ( )_
| '_ | '| | ' / ` | \ \ \
/ )| |)| | | | | || (| | ) ) ) )
' |
| .**|| ||| |**, | / / / /
=========||==============|/=///_/
:: Spring Boot :: (v1.1.1.RELEASE)

2014-06-16 13:19:15.554 INFO 2785 --- [ main] hello.Application : Starting Application on neo with PID 2785 (/tmp/foo/gs-rest-hateoas/complete/build/libs/gs-rest-hateoas-0.1.0.jar started by jvalkealahti in /tmp/foo/gs-rest-hateoas/complete)
2014-06-16 13:19:15.596 INFO 2785 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@249c38d5: startup date [Mon Jun 16 13:19:15 BST 2014]; root of context hierarchy
2014-06-16 13:19:16.165 INFO 2785 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:53)
at java.lang.Thread.run(Thread.java:724)
Caused by: java.lang.NoClassDefFoundError: org/springframework/plugin/core/support/PluginRegistryFactoryBean
at org.springframework.hateoas.config.LinkBuilderBeanDefinitionRegistrar.registerBeanDefinitions(LinkBuilderBeanDefinitionRegistrar.java:57)
at org.springframework.hateoas.config.HypermediaSupportBeanDefinitionRegistrar.registerBeanDefinitions(HypermediaSupportBeanDefinitionRegistrar.java:93)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:319)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:139)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:116)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:324)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:243)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:254)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:94)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:609)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:464)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:120)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:683)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:313)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:944)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:933)
at hello.Application.main(Application.java:12)
... 6 more
Caused by: java.lang.ClassNotFoundException: org.springframework.plugin.core.support.PluginRegistryFactoryBean
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at org.springframework.boot.loader.LaunchedURLClassLoader.doLoadClass(LaunchedURLClassLoader.java:161)
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:131)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 23 more
2014-06-16 13:19:16.243 INFO 2785 --- [ Thread-2] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@249c38d5: startup date [Mon Jun 16 13:19:15 BST 2014]; root of context hierarchy
2014-06-16 13:19:16.248 WARN 2785 --- [ Thread-2] ationConfigEmbeddedWebApplicationContext : Exception thrown from ApplicationListener handling ContextClosedEvent

java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@249c38d5: startup date [Mon Jun 16 13:19:15 BST 2014]; root of context hierarchy
at org.springframework.context.support.AbstractApplicationContext.getApplicationEventMulticaster(AbstractApplicationContext.java:346)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:333)
at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:880)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.doClose(EmbeddedWebApplicationContext.java:152)
at org.springframework.context.support.AbstractApplicationContext$1.run(AbstractApplicationContext.java:809)

2014-06-16 13:19:16.248 WARN 2785 --- [ Thread-2] ationConfigEmbeddedWebApplicationContext : Exception thrown from LifecycleProcessor on context close

java.lang.IllegalStateException: LifecycleProcessor not initialized - call 'refresh' before invoking lifecycle methods via the context: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@249c38d5: startup date [Mon Jun 16 13:19:15 BST 2014]; root of context hierarchy
at org.springframework.context.support.AbstractApplicationContext.getLifecycleProcessor(AbstractApplicationContext.java:359)
at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:888)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.doClose(EmbeddedWebApplicationContext.java:152)
at org.springframework.context.support.AbstractApplicationContext$1.run(AbstractApplicationContext.java:809)

HATEOAS JSON output doesn't have right links for collection

The json output of the links differ quite a bit from the single entity object. If I use the implementation in this example. I get the json output like this:

{
      "content":"Hello, User!",
      "_links":{
      "self":{
              "href":"http://localhost:8080/greeting?name=User"
       }
   }
}

But moment I change the output to a collection(refer the code below):

  @RequestMapping("/greeting/{name}" )
    public HttpEntity<List<Greeting>> greeting(@PathVariable(value = "name") String name) {

        Greeting greeting = new Greeting(String.format(TEMPLATE, name));
        greeting.add(linkTo(methodOn(AuditHistoryController.class).greeting(name)).withSelfRel());


        List<Greeting> greetings = new ArrayList<>();
        greetings.add(greeting);

        return new ResponseEntity<>(greetings, HttpStatus.OK);
    }

The output json is quite different. Refer the change in links

[
{
    "content": "Hello, User!",
    "links": [
        {
            "rel": "self",
            "href": "http://localhost:8899/risk-portal/api/greeting/User",
            "hreflang": null,
            "media": null,
            "title": null,
            "type": null,
            "deprecation": null
        }
    ]
}
]

Remove unused CI files

The file 'test/run.sh' is no longer required. This file and the 'test' directory can be deleted.

The file was originally used for Jenkins CI. Now that this project has been converted to GitHub Actions, this file can be removed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.