mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Use HttpStatus in ErrorPage
This commit is contained in:
parent
dc30add6c5
commit
8bfe07c730
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
|||||||
*.sw?
|
*.sw?
|
||||||
.#*
|
.#*
|
||||||
*#
|
*#
|
||||||
|
*~
|
||||||
/build
|
/build
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
|
177
README.md
177
README.md
@ -1,177 +0,0 @@
|
|||||||
# Spring Bootstrap
|
|
||||||
|
|
||||||
Spring Bootstrap is "Spring for Snowboarders". If you are kewl, or
|
|
||||||
just impatient, and you want to use Spring, then this is the place to
|
|
||||||
be. Spring Bootstrap is a toolkit and runtime platform that will get
|
|
||||||
you up and running with Spring-powered, production-grade applications
|
|
||||||
and services with absolute minimum fuss. It takes an opinionated view
|
|
||||||
of the Spring family so that new and existing users can quickly get to
|
|
||||||
the bits they need. Assumes no knowledge of the Java development
|
|
||||||
ecosystem. Absolutely no code generation and no XML (unless you really
|
|
||||||
want it).
|
|
||||||
|
|
||||||
The goals are:
|
|
||||||
|
|
||||||
* Radically faster and widely accessible getting started experience
|
|
||||||
for Spring development
|
|
||||||
* Be opinionated out of the box, but get out of the way quickly as
|
|
||||||
requirements start to diverge from the defaults
|
|
||||||
* Provide a range of non-functional features that are common to large
|
|
||||||
classes of projects (e.g. embedded servers, security, metrics,
|
|
||||||
health checks, externalized configuration)
|
|
||||||
* First class support for REST-ful services, modern web applications,
|
|
||||||
batch jobs, and enterprise integration
|
|
||||||
* Applications that adapt their behaviour or configuration to their
|
|
||||||
environment
|
|
||||||
* Optionally use Groovy features like DSLs and AST transformations to
|
|
||||||
accelerate the implementation of basic business requirements
|
|
||||||
|
|
||||||
## Installing
|
|
||||||
You need to build from source for now, but when it's done instructions will look like this:
|
|
||||||
|
|
||||||
1) Get Java. Download and install the Java SDK from www.java.com
|
|
||||||
|
|
||||||
2) Get Spring
|
|
||||||
|
|
||||||
$ curl -s try.spring.io | bash
|
|
||||||
|
|
||||||
or use the Windows installer
|
|
||||||
|
|
||||||
3) Get to Work!
|
|
||||||
|
|
||||||
$ cat > app.groovy
|
|
||||||
@Controller
|
|
||||||
class ThisWillActuallyRun {
|
|
||||||
@RequestMapping("/")
|
|
||||||
@ResponseBody
|
|
||||||
String home() {
|
|
||||||
return "Hello World!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$ spring run app.groovy
|
|
||||||
$ curl localhost:8080
|
|
||||||
Hello World!
|
|
||||||
|
|
||||||
|
|
||||||
## What? It's Groovy then? or like Grails? or another Roo?
|
|
||||||
|
|
||||||
There is a command line tool that uses Groovy underneath so that we
|
|
||||||
can present simple snippets that can just run just like the slimline
|
|
||||||
`app.groovy` example above. Groovy makes this really easy.
|
|
||||||
|
|
||||||
If you don't want to use the command line tool, or you would rather
|
|
||||||
work using Java and an IDE you can. Just add a `main()` method that
|
|
||||||
calls `SpringApplication` and add `@EnableAutoConfiguration`:
|
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.stereotype.*;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.bootstrap.context.annotation.*;
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@EnableAutoConfiguration
|
|
||||||
public class SampleController {
|
|
||||||
|
|
||||||
@RequestMapping("/")
|
|
||||||
@ResponseBody
|
|
||||||
String home() {
|
|
||||||
return "Hello World!"
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
|
||||||
SpringApplication.run(SampleController.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
## Spring Bootstrap Themes
|
|
||||||
|
|
||||||
There are a number of themes in Bootstrap. Here are the important
|
|
||||||
ones:
|
|
||||||
|
|
||||||
### The Spring CLI
|
|
||||||
|
|
||||||
The 'spring' command line application compiles and runs Groovy source,
|
|
||||||
making it super easy to write the absolute minimum of code to get an
|
|
||||||
application running. Spring CLI can also watch files, automatically
|
|
||||||
recompiling and restarting when they change.
|
|
||||||
|
|
||||||
### Bootstrap Core
|
|
||||||
|
|
||||||
The main library providing features that support the other parts of
|
|
||||||
Spring Bootstrap. Features include:
|
|
||||||
|
|
||||||
* `SpringApplication` - a class with static convenience methods that
|
|
||||||
make it really easy to write a standalone Spring Application. Its
|
|
||||||
sole job is to create and refresh an appropriate Spring
|
|
||||||
`ApplicationContext`.
|
|
||||||
* Embedded web applications with a choice of container (Tomcat or
|
|
||||||
Jetty for now)
|
|
||||||
* `@EnableAutoConfigure` is an annotation that triggers
|
|
||||||
auto-configuration of the Spring context. Auto-configuration
|
|
||||||
attempts to guess what beans a user might want based on their
|
|
||||||
classpath. For example, If a 'HSQLDB' is on the classpath the user
|
|
||||||
probably wants an in-memory database to be
|
|
||||||
defined. Auto-configuration will back away as the user starts to
|
|
||||||
define their own beans.
|
|
||||||
* `@Conditional` is an annotation in Spring 4.0 that allows you to
|
|
||||||
control which parts of an application are used at runtime. Spring
|
|
||||||
Bootstrap provides some concrete implementations of conditional
|
|
||||||
configuration, e.g. `@ConditionalOnBean`,
|
|
||||||
`@ConditionalOnMissingBean` and `@ConditionalOnClass`.
|
|
||||||
|
|
||||||
### Spring Bootstrap Service
|
|
||||||
|
|
||||||
<!-- FIXME: change the name -->
|
|
||||||
|
|
||||||
Spring Bootstrap Service uses auto-configuration features to decorate
|
|
||||||
your application with features that make it instantly deployable and
|
|
||||||
supportable in production. For instance if you are writing a JSON web
|
|
||||||
service then it will provide a server, security, logging, externalized
|
|
||||||
configuration, management endpoints, an audit abstraction, and more.
|
|
||||||
If you want to switch off the built in features, or extend or replace
|
|
||||||
them, it makes that really easy as well.
|
|
||||||
|
|
||||||
### Spring Bootstrap Applications
|
|
||||||
|
|
||||||
<!-- FIXME: change the name -->
|
|
||||||
|
|
||||||
Spring Bootstrap Applications are a set of convenient dependency
|
|
||||||
descriptors that you can include in your application. You get a
|
|
||||||
one-stop-shop for all the Spring and related technology that you need
|
|
||||||
without having to hunt through sample code and copy paste loads of
|
|
||||||
dependency descriptors. For example, if you want to get started using
|
|
||||||
Spring and JPA for database access just include one dependency in your
|
|
||||||
project, and you are good to go.
|
|
||||||
|
|
||||||
## Building the code
|
|
||||||
Use maven to build the source code.
|
|
||||||
|
|
||||||
$ mvn clean install
|
|
||||||
|
|
||||||
## Importing into eclipse
|
|
||||||
You can use m2e or `maven eclipse:eclipse`.
|
|
||||||
|
|
||||||
Project specific settings are configured for source formatting. If you
|
|
||||||
are using m2e you can follow these steps to install eclipse support
|
|
||||||
for formatting:
|
|
||||||
|
|
||||||
* Select `Install new software` from the `help` menu
|
|
||||||
* Click `Add...` to add a new repository
|
|
||||||
* Click the `Archive...` button
|
|
||||||
* Select `org.eclipse.m2e.maveneclipse.site-0.0.1-SNAPSHOT-site.zip`
|
|
||||||
from the `eclipse` folder in this checkout
|
|
||||||
* Install "Maven Integration for the maven-eclipse-plugin"
|
|
||||||
|
|
||||||
Or if you prefer you can import settings manually from the `/eclipse` folder.
|
|
||||||
|
|
||||||
## Samples
|
|
||||||
The following samples are included. To run use `java -jar target/<archive>.jar`
|
|
||||||
|
|
||||||
* spring-bootstrap-simple-sample - A simple command line application
|
|
||||||
* spring-bootstrap-jetty-sample - Embedded Jetty
|
|
||||||
* spring-bootstrap-tomcat-sample - Embedded Tomcat
|
|
||||||
* spring-bootstrap-service-sample - Simple REST service with production features
|
|
||||||
* spring-batch-sample - Define and run a Batch job in a few lines of code
|
|
||||||
* spring-bootstrap-data-sample - Spring Data JPA + Hibernate + HSQLDB
|
|
||||||
|
|
@ -1,387 +0,0 @@
|
|||||||
<style>
|
|
||||||
table
|
|
||||||
{
|
|
||||||
border-collapse:collapse;
|
|
||||||
}
|
|
||||||
table,th, td
|
|
||||||
{
|
|
||||||
border: 1px solid black;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
# Spring Bootstrap Services
|
|
||||||
|
|
||||||
Minimum fuss for getting RESTful services up and running in
|
|
||||||
production, and in other environments.
|
|
||||||
|
|
||||||
|Feature |Implementation |Notes |
|
|
||||||
|---|---|---|
|
|
||||||
|Server |Tomcat or Jetty | Whatever is on the classpath |
|
|
||||||
|REST |Spring MVC | |
|
|
||||||
|Security |Spring Security | If on the classpath |
|
|
||||||
|Logging |Logback, Log4j or JDK | Whatever is on the classpath. Sensible defaults. |
|
|
||||||
|Database |HSQLDB or H2 | Per classpath, or define a DataSource to override |
|
|
||||||
|Externalized configuration | Properties or YAML | Support for Spring profiles. Bind automatically to @Bean. |
|
|
||||||
|Audit | Spring Security and Spring ApplicationEvent |Flexible abstraction with sensible defaults for security events |
|
|
||||||
|Validation | JSR-303 |If on the classpath |
|
|
||||||
|Management endpoints | Spring MVC | Health, basic metrics, request tracing, shutdown, thread dumps |
|
|
||||||
|Error pages | Spring MVC | Sensible defaults based on exception and status code |
|
|
||||||
|JSON |Jackson 2 | |
|
|
||||||
|ORM |Spring Data JPA | If on the classpath |
|
|
||||||
|Batch |Spring Batch | If enabled and on the classpath |
|
|
||||||
|Integration Patterns |Spring Integration | If on the classpath |
|
|
||||||
|
|
||||||
# Getting Started
|
|
||||||
|
|
||||||
You will need Java (6 at least) and a build tool (Maven is what we use
|
|
||||||
below, but you are more than welcome to use gradle). These can be
|
|
||||||
downloaded or installed easily in most operating systems. For Ubuntu:
|
|
||||||
|
|
||||||
$ sudo apt-get install openjdk-6-jdk maven
|
|
||||||
|
|
||||||
<!--FIXME: short instructions for Mac.-->
|
|
||||||
|
|
||||||
## A basic project
|
|
||||||
|
|
||||||
If you are using Maven create a really simple `pom.xml` with 2 dependencies:
|
|
||||||
|
|
||||||
<project>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.mycompany</groupId>
|
|
||||||
<artifactId>myproject</artifactId>
|
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
<parent>
|
|
||||||
<groupId>org.springframework.bootstrap</groupId>
|
|
||||||
<artifactId>spring-bootstrap-applications</artifactId>
|
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.bootstrap</groupId>
|
|
||||||
<artifactId>spring-bootstrap-web-application</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.bootstrap</groupId>
|
|
||||||
<artifactId>spring-bootstrap-service</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
||||||
|
|
||||||
If you like Gradle, that's fine, and you will know what to do with
|
|
||||||
those dependencies. The first dependency adds Spring Bootstrap auto
|
|
||||||
configuration and the Jetty container to your application, and the
|
|
||||||
second one adds some more opinionated stuff like the default
|
|
||||||
management endpoints. If you prefer Tomcat you can just add the
|
|
||||||
embedded Tomcat jars to your classpath instead of Jetty.
|
|
||||||
|
|
||||||
You should be able to run it already:
|
|
||||||
|
|
||||||
$ mvn package
|
|
||||||
$ java -jar target/myproject-1.0.0-SNAPSHOT.jar
|
|
||||||
|
|
||||||
Then in another terminal
|
|
||||||
|
|
||||||
$ curl localhost:8080/healthz
|
|
||||||
ok
|
|
||||||
$ curl localhost:8080/varz
|
|
||||||
{"counter.status.200.healthz":1.0,"gauge.response.healthz":10.0,"mem":120768.0,"mem.free":105012.0,"processors":4.0}
|
|
||||||
|
|
||||||
`/healthz` is the default location for the health endpoint - it tells
|
|
||||||
you if the application is running and healthy. `/varz` is the default
|
|
||||||
location for the metrics endpoint - it gives you basic counts and
|
|
||||||
response timing data by default but there are plenty of ways to
|
|
||||||
customize it. You can also try `/trace` and `/dump` to get some
|
|
||||||
interesting information about how and what your app is doing.
|
|
||||||
|
|
||||||
What about the home page?
|
|
||||||
|
|
||||||
$ curl localhost:8080/
|
|
||||||
{"status": 404, "error": "Not Found", "message": "Not Found"}
|
|
||||||
|
|
||||||
That's OK, we haven't added any business content yet. But it shows
|
|
||||||
that there are sensible defaults built in for rendering HTTP and
|
|
||||||
server-side errors.
|
|
||||||
|
|
||||||
## Adding a business endpoint
|
|
||||||
|
|
||||||
To do something useful to your business you need to add at least one
|
|
||||||
endpoint. An endpoint can be implemented as a Spring MVC
|
|
||||||
`@Controller`, e.g.
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@EnableAutoConfiguration
|
|
||||||
public class SampleController {
|
|
||||||
|
|
||||||
@RequestMapping("/")
|
|
||||||
@ResponseBody
|
|
||||||
public Map<String, String> helloWorld() {
|
|
||||||
return Collections.singletonMap("message", "Hello World");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
|
||||||
SpringApplication.run(SampleController.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
You can launch that straight away using the Spring Bootstrap CLI
|
|
||||||
(without the `@EnableAutoConfiguration` and even without the import
|
|
||||||
statements that your IDE will add if you are using one), or you can
|
|
||||||
use the main method to launch it from your project jar. Just add a
|
|
||||||
`start-class` in the properties section of the `pom` above pointing to
|
|
||||||
the fully qualified name of your `SampleController`, e.g.
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<start-class>com.mycompany.sample.SampleController</start-class>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
and re-package:
|
|
||||||
|
|
||||||
$ mvn package
|
|
||||||
$ java -jar target/myproject-1.0.0-SNAPSHOT.jar
|
|
||||||
$ curl localhost:8080/
|
|
||||||
{"message": "Hello World"}
|
|
||||||
|
|
||||||
## Running the application
|
|
||||||
|
|
||||||
You can package the app and run it as a jar (as above) and that's very
|
|
||||||
convenient for production usage. Or there are other options, many of
|
|
||||||
which are more convenient at development time. Here are a few:
|
|
||||||
|
|
||||||
1. Use the Maven exec plugin, e.g.
|
|
||||||
|
|
||||||
$ mvn exec:java
|
|
||||||
|
|
||||||
2. Run directly in your IDE, e.g. Eclipse or IDEA let you right click
|
|
||||||
on a class and run it.
|
|
||||||
|
|
||||||
3. Use a different Maven plugin.
|
|
||||||
|
|
||||||
4. Find feature in Gradle that does the same thing.
|
|
||||||
|
|
||||||
5. Use the Spring executable. <!--FIXME: document this maybe.-->
|
|
||||||
|
|
||||||
## Externalizing configuration
|
|
||||||
|
|
||||||
Spring Bootstrap likes you to externalize your configuration so you
|
|
||||||
can work with the same application code in different environments. To
|
|
||||||
get started with this you create a file in the root of your classpath
|
|
||||||
(`src/main/resources` if using Maven) - if you like YAML you can call
|
|
||||||
it `application.yml`, e.g.:
|
|
||||||
|
|
||||||
server:
|
|
||||||
port: 9000
|
|
||||||
management:
|
|
||||||
port: 9001
|
|
||||||
logging:
|
|
||||||
file: target/log.out
|
|
||||||
|
|
||||||
or if you like Java `Properties` files, you can call it
|
|
||||||
`application.properties`, e.g.:
|
|
||||||
|
|
||||||
server.port: 9000
|
|
||||||
management.port: 9001
|
|
||||||
logging.file: target/log.out
|
|
||||||
|
|
||||||
Those examples are properties that Spring Bootstrap itself binds to
|
|
||||||
out of the box, so if you make that change and run the app again, you
|
|
||||||
will find the home page on port 9000 instead of 8080:
|
|
||||||
|
|
||||||
$ curl localhost:9000/
|
|
||||||
{"message": "Hello World"}
|
|
||||||
|
|
||||||
and the management endpoints on port 9001 instead of 8080:
|
|
||||||
|
|
||||||
$ curl localhost:9001/healthz
|
|
||||||
ok
|
|
||||||
|
|
||||||
To externalize business configuration you can simply add a default
|
|
||||||
value to your configuration file, e.g.
|
|
||||||
|
|
||||||
server:
|
|
||||||
port: 9000
|
|
||||||
management:
|
|
||||||
port: 9001
|
|
||||||
logging:
|
|
||||||
file: target/log.out
|
|
||||||
service:
|
|
||||||
message: Awesome Message
|
|
||||||
|
|
||||||
and then bind to it in the application code. The simplest way to do
|
|
||||||
that is to simply refer to it in an `@Value` annotation, e.g.
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@EnableAutoConfiguration
|
|
||||||
public class SampleController {
|
|
||||||
|
|
||||||
@Value("${service.message:Hello World}")
|
|
||||||
private String value = "Goodbye Everypone"
|
|
||||||
|
|
||||||
@RequestMapping("/")
|
|
||||||
@ResponseBody
|
|
||||||
public Map<String, String> helloWorld() {
|
|
||||||
return Collections.singletonMap("message", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
That's a little bit confusing because we have provided a message value
|
|
||||||
in three different places - in the external configuration ("Awesome
|
|
||||||
Message"), in the `@Value` annotation after the colon ("Hello World"),
|
|
||||||
and in the filed initializer ("Goodbye Everyone"). That was only to
|
|
||||||
show you how and you only need it once, so it's your choice (it's
|
|
||||||
useful for unit testing to have the Java initializer as well as the
|
|
||||||
external value). Note that the YAML object is flattened using period
|
|
||||||
separators.
|
|
||||||
|
|
||||||
For simple Strings where you have sensible defaults `@Value` is
|
|
||||||
perfect, but if you want more and you like everything strongly typed
|
|
||||||
then you can have Spring bind the properties and validate them
|
|
||||||
automatically in a separate value object. For instance:
|
|
||||||
|
|
||||||
// ServiceProperties.java
|
|
||||||
@ConfigurationProperties(name="service")
|
|
||||||
public class ServiceProperties {
|
|
||||||
private String message;
|
|
||||||
private int value = 0;
|
|
||||||
... getters and setters
|
|
||||||
}
|
|
||||||
|
|
||||||
// SampleController.java
|
|
||||||
@Controller
|
|
||||||
@EnableAutoConfiguration
|
|
||||||
@EnableConfigurationProperties(ServiceProperties.class)
|
|
||||||
public class SampleController {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ServiceProperties properties;
|
|
||||||
|
|
||||||
@RequestMapping("/")
|
|
||||||
@ResponseBody
|
|
||||||
public Map<String, String> helloWorld() {
|
|
||||||
return Collections.singletonMap("message", properties.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
When you ask to
|
|
||||||
`@EnableConfigurationProperties(ServiceProperties.class)` you are
|
|
||||||
saying you want a bean of type `ServiceProperties` and that you want
|
|
||||||
to bind it to the Spring Environment. The Spring Environment is a
|
|
||||||
collection of name-value pairs taken from (in order of decreasing
|
|
||||||
precedence) 1) the command line, 2) the external configuration file,
|
|
||||||
3) System properties, 4) the OS environment. Validation is done based
|
|
||||||
on JSR-303 annotations by default provided that library (and an
|
|
||||||
implementation) is on the classpath.
|
|
||||||
|
|
||||||
## Adding security
|
|
||||||
|
|
||||||
If you add Spring Security java config to your runtime classpath you
|
|
||||||
will enable HTTP basic authentication by default on all the endpoints.
|
|
||||||
In the `pom.xml` it would look like this:
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.security</groupId>
|
|
||||||
<artifactId>spring-security-javaconfig</artifactId>
|
|
||||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
(Spring Security java config is still work in progress so we have used
|
|
||||||
a snapshot. Beware of sudden changes.)
|
|
||||||
|
|
||||||
<!--FIXME: update Spring Security to full release -->
|
|
||||||
|
|
||||||
Try it out:
|
|
||||||
|
|
||||||
$ curl localhost:8080/
|
|
||||||
{"status": 403, "error": "Forbidden", "message": "Access Denied"}
|
|
||||||
$ curl user:password@localhost:8080/
|
|
||||||
{"message": "Hello World"}
|
|
||||||
|
|
||||||
The default auto configuration has an in-memory user database with one
|
|
||||||
entry. If you want to extend or expand that, or point to a database
|
|
||||||
or directory server, you only need to provide a `@Bean` definition for
|
|
||||||
an `AuthenticationManager`, e.g. in your `SampleController`:
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public AuthenticationManager authenticationManager() throws Exception {
|
|
||||||
return new AuthenticationBuilder().inMemoryAuthentication().withUser("client")
|
|
||||||
.password("secret").roles("USER").and().and().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
Try it out:
|
|
||||||
|
|
||||||
$ curl user:password@localhost:8080/
|
|
||||||
{"status": 403, "error": "Forbidden", "message": "Access Denied"}
|
|
||||||
$ curl client:secret@localhost:8080/
|
|
||||||
{"message": "Hello World"}
|
|
||||||
|
|
||||||
## Adding a database
|
|
||||||
|
|
||||||
Just add `spring-jdbc` and an embedded database to your dependencies:
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-jdbc</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.hsqldb</groupId>
|
|
||||||
<artifactId>hsqldb</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
Then you will be able to inject a `DataSource` into your controller:
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@EnableAutoConfiguration
|
|
||||||
@EnableConfigurationProperties(ServiceProperties.class)
|
|
||||||
public class SampleController {
|
|
||||||
|
|
||||||
private JdbcTemplate jdbcTemplate;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public SampleController(DataSource dataSource) {
|
|
||||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping("/")
|
|
||||||
@ResponseBody
|
|
||||||
public Map<String, String> helloWorld() {
|
|
||||||
return jdbcTemplate.queryForMap("SELECT * FROM MESSAGES WHERE ID=?", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
The app will run (going back to the default security configuration):
|
|
||||||
|
|
||||||
$ curl user:password@localhost:8080/
|
|
||||||
{"error":"Internal Server Error", "status":500, "exception":...}
|
|
||||||
|
|
||||||
but there's no data in the database yet and the `MESSAGES` table
|
|
||||||
doesn't even exist, so there's an error. One easy way to fix it is
|
|
||||||
to provide a `schema.sql` script in the root of the classpath, e.g.
|
|
||||||
|
|
||||||
create table MESSAGES (
|
|
||||||
ID BIGINT NOT NULL PRIMARY KEY,
|
|
||||||
MESSAGE VARCHAR(255)
|
|
||||||
);
|
|
||||||
INSERT INTO MESSAGES (ID, MESSAGE) VALUES (0, 'Hello Phil');
|
|
||||||
|
|
||||||
Now when you run the app you get a sensible response:
|
|
||||||
|
|
||||||
$ curl user:password@localhost:8080/
|
|
||||||
{"ID":0, "MESSAGE":"Hello Phil"}
|
|
||||||
|
|
||||||
Obviously, this is only the start, but hopefully you have a good grasp
|
|
||||||
of the basics and are ready to try it out yourself.
|
|
@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.bootstrap.actuate.autoconfigure;
|
package org.springframework.bootstrap.actuate.autoconfigure;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.HierarchicalBeanFactory;
|
import org.springframework.beans.factory.HierarchicalBeanFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -107,8 +105,7 @@ public class ManagementServerConfiguration implements BeanPostProcessor {
|
|||||||
factory.setAddress(this.configuration.getAddress());
|
factory.setAddress(this.configuration.getAddress());
|
||||||
factory.setContextPath(this.configuration.getContextPath());
|
factory.setContextPath(this.configuration.getContextPath());
|
||||||
|
|
||||||
factory.setErrorPages(Collections
|
factory.addErrorPages(new ErrorPage(this.errorPath));
|
||||||
.singleton(new ErrorPage(this.errorPath)));
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.bootstrap.actuate.autoconfigure;
|
package org.springframework.bootstrap.actuate.autoconfigure;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import javax.servlet.Servlet;
|
import javax.servlet.Servlet;
|
||||||
|
|
||||||
import org.apache.catalina.valves.AccessLogValve;
|
import org.apache.catalina.valves.AccessLogValve;
|
||||||
@ -104,8 +102,7 @@ public class ServerConfiguration implements BeanPostProcessor, BeanFactoryAware
|
|||||||
server);
|
server);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory.setErrorPages(Collections
|
factory.addErrorPages(new ErrorPage(this.errorPath));
|
||||||
.singleton(new ErrorPage(this.errorPath)));
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2013 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.bootstrap.actuate.properties;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.MutablePropertyValues;
|
||||||
|
import org.springframework.bootstrap.bind.RelaxedDataBinder;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Externalized configuration for server properties
|
||||||
|
*
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ServerPropertiesTests {
|
||||||
|
|
||||||
|
private ServerProperties properties = new ServerProperties();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddressBinding() throws Exception {
|
||||||
|
RelaxedDataBinder binder = new RelaxedDataBinder(this.properties, "server");
|
||||||
|
binder.bind(new MutablePropertyValues(Collections.singletonMap("server.address",
|
||||||
|
"127.0.0.1")));
|
||||||
|
assertFalse(binder.getBindingResult().hasErrors());
|
||||||
|
assertEquals(InetAddress.getLocalHost(), this.properties.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPortBinding() throws Exception {
|
||||||
|
new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues(
|
||||||
|
Collections.singletonMap("server.port", "9000")));
|
||||||
|
assertEquals(9000, this.properties.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.springframework.bootstrap.context.embedded;
|
package org.springframework.bootstrap.context.embedded;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,14 +32,14 @@ public class ErrorPage {
|
|||||||
|
|
||||||
private Class<? extends Throwable> exception = null;
|
private Class<? extends Throwable> exception = null;
|
||||||
|
|
||||||
private int status = 0;
|
private HttpStatus status = null;
|
||||||
|
|
||||||
public ErrorPage(String path) {
|
public ErrorPage(String path) {
|
||||||
super();
|
super();
|
||||||
this.path = path;
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ErrorPage(int status, String path) {
|
public ErrorPage(HttpStatus status, String path) {
|
||||||
super();
|
super();
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
@ -68,13 +69,22 @@ public class ErrorPage {
|
|||||||
return this.exception;
|
return this.exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTTP status value that this error page matches.
|
||||||
|
*
|
||||||
|
* @return the status
|
||||||
|
*/
|
||||||
|
public HttpStatus getStatus() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The HTTP status value that this error page matches.
|
* The HTTP status value that this error page matches.
|
||||||
*
|
*
|
||||||
* @return the status value (or 0 for a page that matches any status)
|
* @return the status value (or 0 for a page that matches any status)
|
||||||
*/
|
*/
|
||||||
public int getStatus() {
|
public int getStatusCode() {
|
||||||
return this.status;
|
return this.status == null ? 0 : this.status.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,7 +101,7 @@ public class ErrorPage {
|
|||||||
* types)?
|
* types)?
|
||||||
*/
|
*/
|
||||||
public boolean isGlobal() {
|
public boolean isGlobal() {
|
||||||
return this.status == 0 && this.exception == null;
|
return this.status == null && this.exception == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -100,7 +110,7 @@ public class ErrorPage {
|
|||||||
int result = 1;
|
int result = 1;
|
||||||
result = prime * result + ObjectUtils.nullSafeHashCode(getExceptionName());
|
result = prime * result + ObjectUtils.nullSafeHashCode(getExceptionName());
|
||||||
result = prime * result + ObjectUtils.nullSafeHashCode(this.path);
|
result = prime * result + ObjectUtils.nullSafeHashCode(this.path);
|
||||||
result = prime * result + this.status;
|
result = prime * result + this.getStatusCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +67,8 @@ public class JettyEmbeddedServletContainerFactory extends
|
|||||||
|
|
||||||
private ResourceLoader resourceLoader;
|
private ResourceLoader resourceLoader;
|
||||||
|
|
||||||
|
private WebAppContext context = new WebAppContext();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link JettyEmbeddedServletContainerFactory} instance.
|
* Create a new {@link JettyEmbeddedServletContainerFactory} instance.
|
||||||
*/
|
*/
|
||||||
@ -101,29 +103,29 @@ public class JettyEmbeddedServletContainerFactory extends
|
|||||||
}
|
}
|
||||||
Server server = new Server(new InetSocketAddress(getAddress(), getPort()));
|
Server server = new Server(new InetSocketAddress(getAddress(), getPort()));
|
||||||
|
|
||||||
WebAppContext context = new WebAppContext();
|
|
||||||
if (this.resourceLoader != null) {
|
if (this.resourceLoader != null) {
|
||||||
context.setClassLoader(this.resourceLoader.getClassLoader());
|
this.context.setClassLoader(this.resourceLoader.getClassLoader());
|
||||||
}
|
}
|
||||||
String contextPath = getContextPath();
|
String contextPath = getContextPath();
|
||||||
context.setContextPath(StringUtils.hasLength(contextPath) ? contextPath : "/");
|
this.context.setContextPath(StringUtils.hasLength(contextPath) ? contextPath
|
||||||
configureDocumentRoot(context);
|
: "/");
|
||||||
|
configureDocumentRoot(this.context);
|
||||||
if (getRegisterDefaultServlet()) {
|
if (getRegisterDefaultServlet()) {
|
||||||
addDefaultServlet(context);
|
addDefaultServlet(this.context);
|
||||||
}
|
}
|
||||||
if (getRegisterJspServlet()
|
if (getRegisterJspServlet()
|
||||||
&& ClassUtils.isPresent(getJspServletClassName(), getClass()
|
&& ClassUtils.isPresent(getJspServletClassName(), getClass()
|
||||||
.getClassLoader())) {
|
.getClassLoader())) {
|
||||||
addJspServlet(context);
|
addJspServlet(this.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
|
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
|
||||||
Configuration[] configurations = getWebAppContextConfigurations(context,
|
Configuration[] configurations = getWebAppContextConfigurations(this.context,
|
||||||
initializersToUse);
|
initializersToUse);
|
||||||
context.setConfigurations(configurations);
|
this.context.setConfigurations(configurations);
|
||||||
postProcessWebAppContext(context);
|
postProcessWebAppContext(this.context);
|
||||||
|
|
||||||
server.setHandler(context);
|
server.setHandler(this.context);
|
||||||
return getJettyEmbeddedServletContainer(server);
|
return getJettyEmbeddedServletContainer(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,23 +189,7 @@ public class JettyEmbeddedServletContainerFactory extends
|
|||||||
@Override
|
@Override
|
||||||
public void configure(WebAppContext context) throws Exception {
|
public void configure(WebAppContext context) throws Exception {
|
||||||
ErrorHandler errorHandler = context.getErrorHandler();
|
ErrorHandler errorHandler = context.getErrorHandler();
|
||||||
if (errorHandler instanceof ErrorPageErrorHandler) {
|
addJettyErrorPages(errorHandler, getErrorPages());
|
||||||
ErrorPageErrorHandler handler = (ErrorPageErrorHandler) errorHandler;
|
|
||||||
for (ErrorPage errorPage : getErrorPages()) {
|
|
||||||
if (errorPage.isGlobal()) {
|
|
||||||
handler.addErrorPage(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE,
|
|
||||||
errorPage.getPath());
|
|
||||||
} else {
|
|
||||||
if (errorPage.getExceptionName() != null) {
|
|
||||||
handler.addErrorPage(errorPage.getExceptionName(),
|
|
||||||
errorPage.getPath());
|
|
||||||
} else {
|
|
||||||
handler.addErrorPage(errorPage.getStatus(),
|
|
||||||
errorPage.getPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -269,11 +255,33 @@ public class JettyEmbeddedServletContainerFactory extends
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add {@link Configuration}s that will be applied to the {@link WebAppContext} before
|
* Add {@link Configuration}s that will be applied to the {@link WebAppContext} before
|
||||||
* the server is create.
|
* the server is started.
|
||||||
|
*
|
||||||
* @param configurations the configurations to add
|
* @param configurations the configurations to add
|
||||||
*/
|
*/
|
||||||
public void addConfigurations(Configuration... configurations) {
|
public void addConfigurations(Configuration... configurations) {
|
||||||
Assert.notNull(configurations, "Configurations must not be null");
|
Assert.notNull(configurations, "Configurations must not be null");
|
||||||
this.configurations.addAll(Arrays.asList(configurations));
|
this.configurations.addAll(Arrays.asList(configurations));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addJettyErrorPages(ErrorHandler errorHandler,
|
||||||
|
Collection<ErrorPage> errorPages) {
|
||||||
|
if (errorHandler instanceof ErrorPageErrorHandler) {
|
||||||
|
ErrorPageErrorHandler handler = (ErrorPageErrorHandler) errorHandler;
|
||||||
|
for (ErrorPage errorPage : errorPages) {
|
||||||
|
if (errorPage.isGlobal()) {
|
||||||
|
handler.addErrorPage(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE,
|
||||||
|
errorPage.getPath());
|
||||||
|
} else {
|
||||||
|
if (errorPage.getExceptionName() != null) {
|
||||||
|
handler.addErrorPage(errorPage.getExceptionName(),
|
||||||
|
errorPage.getPath());
|
||||||
|
} else {
|
||||||
|
handler.addErrorPage(errorPage.getStatusCode(),
|
||||||
|
errorPage.getPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ public class TomcatEmbeddedServletContainerFactory extends
|
|||||||
org.apache.catalina.deploy.ErrorPage tomcatPage = new org.apache.catalina.deploy.ErrorPage();
|
org.apache.catalina.deploy.ErrorPage tomcatPage = new org.apache.catalina.deploy.ErrorPage();
|
||||||
tomcatPage.setLocation(errorPage.getPath());
|
tomcatPage.setLocation(errorPage.getPath());
|
||||||
tomcatPage.setExceptionType(errorPage.getExceptionName());
|
tomcatPage.setExceptionType(errorPage.getExceptionName());
|
||||||
tomcatPage.setErrorCode(errorPage.getStatus());
|
tomcatPage.setErrorCode(errorPage.getStatusCode());
|
||||||
context.addErrorPage(tomcatPage);
|
context.addErrorPage(tomcatPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user