SpringBootTutorial–BootstrapaSimpleApplication

编程

This tutorial is a starting point for Boot – a way to get started in a simple manner, with a basic web application.

We"ll go over some core configuration, a front-end, quick data manipulation, and exception handling.

2. Setup

First, let"s use Spring Initializr to generate the base for our project.

The generated project relies on the Boot parent:

1

2

3

4

5

6

<

parent

>

    

<

groupId

>org.springframework.boot</

groupId

>

    

<

artifactId

>spring-boot-starter-parent</

artifactId

>

    

<

version

>2.1.6.RELEASE</

version

>

    

<

relativePath

/>

</

parent

>

The initial dependencies are going to be quite simple:

1

2

3

4

5

6

7

8

9

10

11

12

<

dependency

>

    

<

groupId

>org.springframework.boot</

groupId

>

    

<

artifactId

>spring-boot-starter-web</

artifactId

>

</

dependency

>

<

dependency

>

    

<

groupId

>org.springframework.boot</

groupId

>

    

<

artifactId

>spring-boot-starter-data-jpa</

artifactId

>

</

dependency

>

<

dependency

>

    

<

groupId

>com.h2database</

groupId

>

    

<

artifactId

>h2</

artifactId

>

</

dependency

>

3. Application Configuration

Next, we"ll configure a simple main class for our application:

1

2

3

4

5

6

@SpringBootApplication

public

class

Application {

    

public

static

void

main(String[] args) {

        

SpringApplication.run(Application.

class

, args);

    

}

}

Notice how we"re using @SpringBootApplication as our primary application configuration class; behind the scenes, that"s equivalent to @Configuration, @EnableAutoConfiguration, and @ComponentScan together.

Finally, we"ll define a simple application.properties file – which for now only has one property:

1

server.port=8081

server.port changes the server port from the default 8080 to 8081; there are of course many more Spring Boot properties available.

4. Simple MVC View

Let"s now add a simple front end using Thymeleaf.

First, we need to add the spring-boot-starter-thymeleaf dependency to our pom.xml:

1

2

3

4

<dependency>

    

<groupId>org.springframework.boot</groupId>

    

<artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

That enables Thymeleaf by default – no extra configuration is necessary.

We can now configure it in our application.properties:

1

2

3

4

5

6

spring.thymeleaf.cache=

false

spring.thymeleaf.enabled=

true

spring.thymeleaf.prefix=classpath:/templates/

spring.thymeleaf.suffix=.html

 

spring.application.name=Bootstrap Spring Boot

Next, we"ll define a simple controller and a basic home page – with a welcome message:

1

2

3

4

5

6

7

8

9

10

11

@Controller

public

class

SimpleController {

    

@Value

(

"${spring.application.name}"

)

    

String appName;

 

    

@GetMapping

(

"/"

)

    

public

String homePage(Model model) {

        

model.addAttribute(

"appName"

, appName);

        

return

"home"

;

    

}

}

Finally, here is our home.html:

1

2

3

4

5

6

7

<

html

>

<

head

><

title

>Home Page</

title

></

head

>

<

body

>

<

h1

>Hello !</

h1

>

<

p

>Welcome to <

span

th:text

=

"${appName}"

>Our App</

span

></

p

>

</

body

>

</

html

>

Note how we used a property we defined in our properties – and then injected that so that we can show it on our home page.

5. Security

Next, let"s add security to our application – by first including the security starter:

1

2

3

4

<

dependency

>

    

<

groupId

>org.springframework.boot</

groupId

>

    

<

artifactId

>spring-boot-starter-security</

artifactId

>

</

dependency

>

By now, you"re hopefully noticing a pattern – most Spring libraries are easily imported into our project with the use of simple Boot starters.

Once the spring-boot-starter-security dependency on the classpath of the application – all endpoints are secured by default, using either httpBasic or formLogin based on Spring Security"s content-negotiation strategy.

That"s why, if we have the starter on the classpath, we should usually define our own custom Security configuration by extending the WebSecurityConfigurerAdapter class:

1

2

3

4

5

6

7

8

9

10

11

12

@Configuration

@EnableWebSecurity

public

class

SecurityConfig

extends

WebSecurityConfigurerAdapter {

 

    

@Override

    

protected

void

configure(HttpSecurity http)

throws

Exception {

        

http.authorizeRequests()

            

.anyRequest()

            

.permitAll()

            

.and().csrf().disable();

    

}

}

In our example, we"re allowing unrestricted access to all endpoints.

Of course, Spring Security is an extensive topic and one not easily covered in a couple of lines of configuration – so I definitely encourage you to go deeper into the topic.

6. Simple Persistence

Let"s start by defining our data model – a simple Book entity:

1

2

3

4

5

6

7

8

9

10

11

12

13

@Entity

public

class

Book {

  

    

@Id

    

@GeneratedValue

(strategy = GenerationType.AUTO)

    

private

long

id;

 

    

@Column

(nullable =

false

, unique =

true

)

    

private

String title;

 

    

@Column

(nullable =

false

)

    

private

String author;

}

And its repository, making good use of Spring Data here:

1

2

3

public

interface

BookRepository

extends

CrudRepository<Book, Long> {

    

List<Book> findByTitle(String title);

}

Finally, we need to of course configure our new persistence layer:

1

2

3

4

5

6

@EnableJpaRepositories

(

"com.baeldung.persistence.repo"

)

@EntityScan

(

"com.baeldung.persistence.model"

)

@SpringBootApplication

public

class

Application {

   

...

}

Note that we"re using:

  • @EnableJpaRepositories to scan the specified package for repositories
  • @EntityScan to pick up our JPA entities

To keep things simple, we"re using an H2 in-memory database here – so that we don"t have any external dependencies when we run the project.

Once we include H2 dependency, Spring Boot auto-detects it and sets up our persistence with no need for extra configuration, other than the data source properties:

1

2

3

4

spring.datasource.driver-class-name=org.h2.Driver

spring.datasource.url=jdbc:h2:mem:bootapp;DB_CLOSE_DELAY=-1

spring.datasource.username=sa

spring.datasource.password=

Of course, like security, persistence is a broader topic than this basic set here, and one you should certainly explore further.

7. Web and the Controller

Next, let"s have a look at a web tier – and we"ll start that by setting up a simple controller – the BookController.

We"ll implement basic CRUD operations exposing Book resources with some simple validation:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

@RestController

@RequestMapping

(

"/api/books"

)

public

class

BookController {

 

    

@Autowired

    

private

BookRepository bookRepository;

 

    

@GetMapping

    

public

Iterable findAll() {

        

return

bookRepository.findAll();

    

}

 

    

@GetMapping

(

"/title/{bookTitle}"

)

    

public

List findByTitle(

@PathVariable

String bookTitle) {

        

return

bookRepository.findByTitle(bookTitle);

    

}

 

    

@GetMapping

(

"/{id}"

)

    

public

Book findOne(

@PathVariable

Long id) {

        

return

bookRepository.findById(id)

          

.orElseThrow(BookNotFoundException::

new

);

    

}

 

    

@PostMapping

    

@ResponseStatus

(HttpStatus.CREATED)

    

public

Book create(

@RequestBody

Book book) {

        

return

bookRepository.save(book);

    

}

 

    

@DeleteMapping

(

"/{id}"

)

    

public

void

delete(

@PathVariable

Long id) {

        

bookRepository.findById(id)

          

.orElseThrow(BookNotFoundException::

new

);

        

bookRepository.deleteById(id);

    

}

 

    

@PutMapping

(

"/{id}"

)

    

public

Book updateBook(

@RequestBody

Book book,

@PathVariable

Long id) {

        

if

(book.getId() != id) {

          

throw

new

BookIdMismatchException();

        

}

        

bookRepository.findById(id)

          

.orElseThrow(BookNotFoundException::

new

);

        

return

bookRepository.save(book);

    

}

}

Given this aspect of the application is an API, we made use of the @RestController annotation here – which equivalent to a @Controller along with @ResponseBody – so that each method marshalls the returned resource right to the HTTP response.

Just one note worth pointing out – we"re exposing our Book entity as our external resource here. That"s fine for our simple application here, but in a real-world application, you will likely want to separate these two concepts.

8. Error Handling

Now that the core application is ready to go, let"s focus on a simple centralized error handling mechanism using @ControllerAdvice:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@ControllerAdvice

public

class

RestExceptionHandler

extends

ResponseEntityExceptionHandler {

 

    

@ExceptionHandler

({ BookNotFoundException.

class

})

    

protected

ResponseEntity<Object> handleNotFound(

      

Exception ex, WebRequest request) {

        

return

handleExceptionInternal(ex,

"Book not found"

,

          

new

HttpHeaders(), HttpStatus.NOT_FOUND, request);

    

}

 

    

@ExceptionHandler

({ BookIdMismatchException.

class

,

      

ConstraintViolationException.

class

,

      

DataIntegrityViolationException.

class

})

    

public

ResponseEntity<Object> handleBadRequest(

      

Exception ex, WebRequest request) {

        

return

handleExceptionInternal(ex, ex.getLocalizedMessage(),

          

new

HttpHeaders(), HttpStatus.BAD_REQUEST, request);

    

}

}

Beyond the standard exceptions we"re handling here, we"re also using a custom exception:

BookNotFoundException:

1

2

3

4

5

6

7

public

class

BookNotFoundException

extends

RuntimeException {

 

    

public

BookNotFoundException(String message, Throwable cause) {

        

super

(message, cause);

    

}

    

// ...

}

This should give you an idea of what"s possible with this global exception handling mechanism. If you"d like to see a full implementation, have a look at the in-depth tutorial.

Note that Spring Boot also provides an /error mapping by default. We can customize its view by creating a simple error.html:

1

2

3

4

5

6

7

8

9

10

<

html

lang

=

"en"

>

<

head

><

title

>Error Occurred</

title

></

head

>

<

body

>

    

<

h1

>Error Occurred!</

h1

>   

    

<

b

>[<

span

th:text

=

"${status}"

>status</

span

>]

        

<

span

th:text

=

"${error}"

>error</

span

>

    

</

b

>

    

<

p

th:text

=

"${message}"

>message</

p

>

</

body

>

</

html

>

Like most other aspects in Boot, we can control that with a simple property:

1

server.error.path=

/error2

9. Testing

Finally, let"s test our new Books API.

We can make use of @SpringBootTest to load the application context and verify there are no errors when running the app:

1

2

3

4

5

6

7

8

@RunWith

(SpringRunner.

class

)

@SpringBootTest

public

class

SpringContextTest {

 

    

@Test

    

public

void

contextLoads() {

    

}

}

Next, let"s add a JUnit test that verifies the calls to the API we"re written, using RestAssured:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public

class

SpringBootBootstrapLiveTest {

 

    

private

static

final

String API_ROOT

      

=

"http://localhost:8081/api/books"

;

 

    

private

Book createRandomBook() {

        

Book book =

new

Book();

        

book.setTitle(randomAlphabetic(

10

));

        

book.setAuthor(randomAlphabetic(

15

));

        

return

book;

    

}

 

    

private

String createBookAsUri(Book book) {

        

Response response = RestAssured.given()

          

.contentType(MediaType.APPLICATION_JSON_VALUE)

          

.body(book)

          

.post(API_ROOT);

        

return

API_ROOT +

"/"

+ response.jsonPath().get(

"id"

);

    

}

}

First, we can try to find books using variant methods:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

@Test

public

void

whenGetAllBooks_thenOK() {

    

Response response = RestAssured.get(API_ROOT);

  

    

assertEquals(HttpStatus.OK.value(), response.getStatusCode());

}

 

@Test

public

void

whenGetBooksByTitle_thenOK() {

    

Book book = createRandomBook();

    

createBookAsUri(book);

    

Response response = RestAssured.get(

      

API_ROOT +

"/title/"

+ book.getTitle());

     

    

assertEquals(HttpStatus.OK.value(), response.getStatusCode());

    

assertTrue(response.as(List.

class

)

      

.size() >

0

);

}

@Test

public

void

whenGetCreatedBookById_thenOK() {

    

Book book = createRandomBook();

    

String location = createBookAsUri(book);

    

Response response = RestAssured.get(location);

     

    

assertEquals(HttpStatus.OK.value(), response.getStatusCode());

    

assertEquals(book.getTitle(), response.jsonPath()

      

.get(

"title"

));

}

 

@Test

public

void

whenGetNotExistBookById_thenNotFound() {

    

Response response = RestAssured.get(API_ROOT +

"/"

+ randomNumeric(

4

));

     

    

assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());

}

Next, we"ll test creating a new book:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@Test

public

void

whenCreateNewBook_thenCreated() {

    

Book book = createRandomBook();

    

Response response = RestAssured.given()

      

.contentType(MediaType.APPLICATION_JSON_VALUE)

      

.body(book)

      

.post(API_ROOT);

     

    

assertEquals(HttpStatus.CREATED.value(), response.getStatusCode());

}

 

@Test

public

void

whenInvalidBook_thenError() {

    

Book book = createRandomBook();

    

book.setAuthor(

null

);

    

Response response = RestAssured.given()

      

.contentType(MediaType.APPLICATION_JSON_VALUE)

      

.body(book)

      

.post(API_ROOT);

     

    

assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode());

}

Update an existing book:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Test

public

void

whenUpdateCreatedBook_thenUpdated() {

    

Book book = createRandomBook();

    

String location = createBookAsUri(book);

    

book.setId(Long.parseLong(location.split(

"api/books/"

)[

1

]));

    

book.setAuthor(

"newAuthor"

);

    

Response response = RestAssured.given()

      

.contentType(MediaType.APPLICATION_JSON_VALUE)

      

.body(book)

      

.put(location);

     

    

assertEquals(HttpStatus.OK.value(), response.getStatusCode());

 

    

response = RestAssured.get(location);

     

    

assertEquals(HttpStatus.OK.value(), response.getStatusCode());

    

assertEquals(

"newAuthor"

, response.jsonPath()

      

.get(

"author"

));

}

And delete a book:

1

2

3

4

5

6

7

8

9

10

11

@Test

public

void

whenDeleteCreatedBook_thenOk() {

    

Book book = createRandomBook();

    

String location = createBookAsUri(book);

    

Response response = RestAssured.delete(location);

     

    

assertEquals(HttpStatus.OK.value(), response.getStatusCode());

 

    

response = RestAssured.get(location);

    

assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());

}

10. Conclusion

This was a quick but comprehensive intro to Spring Boot.

We of course barely scratched the surface here – there"s a lot more to this framework that we can cover in a single intro article.

That"s exactly why we don"t just have a single article about Boot on the site.

The full source code of our examples here is, as always, over on GitHub.

以上是 SpringBootTutorial–BootstrapaSimpleApplication 的全部内容, 来源链接: utcz.com/z/513019.html

回到顶部