I have created a post
to describe Reactive programming supports in Spring 5 and its
subprojects, all codes of this article are updated the latest Spring 5
RELEASE, check spring-reactive-sample under my Github account.
In this post, I will create a simple blog system, including:
The source code is hosted on Github, check here.
Open browser, and navigate to http://start.spring.io.
Choose the following stack:
Extract files into your local system, and import it into your favorite IDE.
Create a
To remove getters and setters,
Create a
Let's create a
It is very similar with traditional Servlet based
Now we are almost ready to start up the application. But before this you have to make sure there is a running MongoDB instance.
I have prepared docker-compose.yml in the root folder, utilize it, we can bootstrap a MongoDB instance quickly in Docker container.
Open your terminal, and switch to the root folder of this project, run the following command to start a MongoDB service.
Or click Run action in the project context menu in your IDE.
When it is started, open another terminal window, use
You should see some result like this.
Great! it works.
Next let's add access control rules to protect the
To customize security configuration, create a standalone
In this configuration, we defined a
Create the required
In
Now let's handle authentication.
In the real world, in order to protect REST APIs, token based authentication is mostly used.
Spring Session provides a simple strategy to expose the session id in http response headers and check validation of session id in http request headers.
Currently, Spring Session provides reactive supports for Redis and MongoDB. In this project, we use MongoDB as an example.
Add
To force HTTP BASIC authentication to manage sessions by MongoDB, add the following configuration in
Declare
Expose current user by REST APIs.
When the user is authenticated, the user info can be fetched from an injected
Add the initial users in the
Restart the application and have a try.
As you see, there is a
Try to add
Yeah, it works as expected. You can try more example on other protected path, such as creating and updating posts.
In a webflux based application, we can declare a
For example, if there is no posts found by id, throw a
Declare
Throw an
Declare a
For the bean validations, we can convert the
In the backend codes, I have added some features mentioned at the beginning of this post, such as comment endpoints, and also tried to add pagination, and data auditing feature(when it is ready).
Next let's try to build a simple Angular frontend application to shake hands with the backend APIs.
Make sure it is installed correctly.
Install Angular Material, Angular FlexLayout from Angular team.
Some Material modules depend on
Open polyfills.ts, uncomment the following line,
Then install web-animations polyfill.
To enable gesture in Angular Material, install
Import it in polyfills.ts.
To simplify the coding work, I port my former Angular 2.x work to this project, more about the Angular development steps, check the wiki pages.
Angular 4.x introduced new
Another awesome feature of the
We do not need
Compare the HTTP interceptor provided in
In this post, I will create a simple blog system, including:
- A user can sign in and sign out.
- An authenticated user can create a post.
- An authenticated user can update a post.
- Only the user who has ADMIN role can delete a post.
- All users(including anonymous users) can view post list and post details.
- An authenticated user can add his comments to a certain post.
- Spring Boot 2.0, at the moment the latest version is 2.0.0.M7
- Spring Data MongoDB supports reactive operations for MongoDB
- Spring Session adds reactive support for
WebSession
- Spring Security 5 aligns with Spring 5 reactive stack
The source code is hosted on Github, check here.
Backend APIs
Prerequisites
Make sure you have already installed the following software.- Oracle Java 8 SDK
- Apache Maven
- Gradle
- Your favorite IDE, including :
- NetBeans IDE
- Eclipse IDE (or Eclipse based IDE, Spring ToolSuite is highly recommended)
- Intellij IDEA
Generate the project skeleton
The quickest approach to start a Spring Boot based is using the Spring initializr.Open browser, and navigate to http://start.spring.io.
Choose the following stack:
- Use the default Maven as building tool, and Java as programming language
- Set Spring Boot version to 2.0.0.M7
- In the search dependencies box, search reactive, select Reactive Web, Reactive MongoDB in the search results, and then add Security, Session, Lombok in the same way etc.
Extract files into your local system, and import it into your favorite IDE.
Produces RESTful APIs
Let's start with cooking thePost
APIs, the expected APIs are listed below.URI | request | response | description |
---|---|---|---|
/posts | GET | [{id:'1', title:'title'}, {id:'2', title:'title 2'}] | Get all posts |
/posts | POST {title:'title',content:'content'} | {id:'1', title:'title',content:'content'} | Create a new post |
/posts/{id} | GET | {id:'1', title:'title',content:'content'} | Get a post by id |
/posts/{id} | PUT {title:'title',content:'content'} | {id:'1', title:'title',content:'content'} | Update specific post by id |
/posts/{id} | DELETE | no content | Delete a post by id |
Post
POJO class. Add Spring Data MongoDB specific @Document
annotation on this class to indicate it is a MongoDB document.@Document
@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
class Post implements Serializable {
@Id
private String id;
@NotBlank
private String title;
@NotBlank
private String content;
}
toString
, equals
, hashCode
methods from Post
and make the source code looks clearly, let's use Lombok specific
annotations to generate these required facilities at compile time via
annotation processor tooling support.Create a
PostRepository
interface for Post
document. Make sure it is extended from ReactiveMongoRepository
, which is the reactive variant of MongoRepository
interface and it is ready for reactive operations.interface PostRepository extends ReactiveMongoRepository<Post, String> {
}
PostController
class to expose RESTful APIs.@RestController()
@RequestMapping(value = "/posts")
class PostController {
private final PostRepository posts;
public PostController(PostRepository posts) {
this.posts = posts;
}
@GetMapping("")
public Flux<Post> all() {
return this.posts.findAll();
}
@PostMapping("")
public Mono<Post> create(@RequestBody Post post) {
return this.posts.save(post);
}
@GetMapping("/{id}")
public Mono<Post> get(@PathVariable("id") String id) {
return this.posts.findById(id);
}
@PutMapping("/{id}")
public Mono<Post> update(@PathVariable("id") String id, @RequestBody Post post) {
return this.posts.findById(id)
.map(p -> {
p.setTitle(post.getTitle());
p.setContent(post.getContent());
return p;
})
.flatMap(p -> this.posts.save(p));
}
@DeleteMapping("/{id}")
@ResponseStatus(NO_CONTENT)
public Mono<Void> delete(@PathVariable("id") String id) {
return this.posts.deleteById(id);
}
}
@RestController
, the difference is here we use Reactor specific Mono
and Flux
as return result type.NOTE: Spring 5 also provides functional programming experience, check spring-reactive-sample for more details.Create a
CommandLineRunner
component to insert some dummy data when the application is started.@Component
@Slf4j
class DataInitializer implements CommandLineRunner {
private final PostRepository posts;
public DataInitializer(PostRepository posts) {
this.posts = posts;
}
@Override
public void run(String[] args) {
log.info("start data initialization ...");
this.posts
.deleteAll()
.thenMany(
Flux
.just("Post one", "Post two")
.flatMap(
title -> this.posts.save(Post.builder().title(title).content("content of " + title).build())
)
)
.log()
.subscribe(
null,
null,
() -> log.info("done initialization...")
);
}
}
I have prepared docker-compose.yml in the root folder, utilize it, we can bootstrap a MongoDB instance quickly in Docker container.
Open your terminal, and switch to the root folder of this project, run the following command to start a MongoDB service.
docker-compose up mongodb
NOTE: You can also install a MongoDB server in your local system instead.Now try to execute
mvn spring-boot:run
to start up the application.Or click Run action in the project context menu in your IDE.
When it is started, open another terminal window, use
curl
or httpie
to have a test.curl http://localhost:8080/posts
curl http://localhost:8080/posts
[{"id":"5a584469a5f5c7261cb548e2","title":"Post two","content":"content of Post two"},{"id":"5a584469a5f5c7261cb548e1","title":"Post one","content":"content of Post one"}]
Next let's add access control rules to protect the
Post
APIs.Protect APIs
Like traditional Servlet based MVC, whenspring-security-web
is existed in the classpath of a webflux application, Spring Boot will configure security automatically.To customize security configuration, create a standalone
@Configuration
class.@Configuration
class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
//@formatter:off
return http
.csrf().disable()
.and()
.authorizeExchange()
.pathMatchers(HttpMethod.GET, "/posts/**").permitAll()
.pathMatchers(HttpMethod.DELETE, "/posts/**").hasRole("ADMIN")
.pathMatchers("/posts/**").authenticated()
.pathMatchers("/user").authenticated()
.pathMatchers("/users/{user}/**").access(this::currentUserMatchesPath)
.anyExchange().permitAll()
.and()
.build();
//@formatter:on
}
private Mono<AuthorizationDecision> currentUserMatchesPath(Mono<Authentication> authentication, AuthorizationContext context) {
return authentication
.map(a -> context.getVariables().get("user").equals(a.getName()))
.map(AuthorizationDecision::new);
}
@Bean
public ReactiveUserDetailsService userDetailsService(UserRepository users) {
return (username) -> users.findByUsername(username)
.map(u -> User.withUsername(u.getUsername())
.password(u.getPassword())
.authorities(u.getRoles().toArray(new String[0]))
.accountExpired(!u.isActive())
.credentialsExpired(!u.isActive())
.disabled(!u.isActive())
.accountLocked(!u.isActive())
.build()
);
}
}
SecurityWebFilterChain
bean to change default security path matching rules as expected. And we have to declare a ReactiveUserDetailsService
bean to customize the UserDetailsService
, eg. fetching users from our MongoDB.Create the required
User
document and UserRepository
interface.@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document
class User {
@Id
private String id;
private String username;
@JsonIgnore
private String password;
@Email
private String email;
@Builder.Default()
private boolean active = true;
@Builder.Default()
private List<String> roles = new ArrayList<>();
}
UserRepository
, add a new findByUsername
method to query user by username, it returns Mono<User>
.public interface UserRepository extends ReactiveMongoRepository<User, String> {
Mono<User> findByUsername(String username);
}
In the real world, in order to protect REST APIs, token based authentication is mostly used.
Spring Session provides a simple strategy to expose the session id in http response headers and check validation of session id in http request headers.
Currently, Spring Session provides reactive supports for Redis and MongoDB. In this project, we use MongoDB as an example.
Add
spring-session-data-mongodb
into the project classpath.<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-mongodb</artifactId>
</dependency>
SecurityConfig
.http
.csrf().disable()
.httpBasic().securityContextRepository(new WebSessionServerSecurityContextRepository())
.and()
WebSessionIdResolver
bean to use HTTP header to resolve session id instead of cookie.@Configuration
@EnableMongoWebSession
class SessionConfig {
@Bean
public WebSessionIdResolver webSessionIdResolver() {
HeaderWebSessionIdResolver resolver = new HeaderWebSessionIdResolver();
resolver.setHeaderName("X-AUTH-TOKEN");
return resolver;
}
}
@GetMapping("/user")
public Mono<Map> current(@AuthenticationPrincipal Mono<Principal> principal) {
return principal
.map( user -> {
Map<String, Object> map = new HashMap<>();
map.put("name", user.getName());
map.put("roles", AuthorityUtils.authorityListToSet(((Authentication) user)
.getAuthorities()));
return map;
});
}
Principal
.Add the initial users in the
DataInitializer
bean.this.users
.deleteAll()
.thenMany(
Flux
.just("user", "admin")
.flatMap(
username -> {
List<String> roles = "user".equals(username)
? Arrays.asList("ROLE_USER")
: Arrays.asList("ROLE_USER", "ROLE_ADMIN");
User user = User.builder()
.roles(roles)
.username(username)
.password(passwordEncoder.encode("password"))
.email(username + "@example.com")
.build();
return this.users.save(user);
}
)
)
.log()
.subscribe(
null,
null,
() -> log.info("done users initialization...")
);
curl -v http://localhost:8080/auth/user -u user:password
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'user'
> GET /auth/user HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjpwYXNzd29yZA==
> User-Agent: curl/7.57.0
> Accept: */*
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json;charset=UTF-8
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
< X-AUTH-TOKEN: 39af0166-0f5f-4a1a-a955-21340b0b31b1
<
{"roles":["ROLE_USER"],"name":"user"}* Connection #0 to host localhost left intact
X-AUTH-TOKEN
header in the response headers when the authentication is successful.Try to add
X-AUTH-TOKEN
to HTTP request headers and access the protected APIs.curl -v http://localhost:8080/auth/user -H "X-AUTH-TOKEN: 39af0166-0f5f-4a1a-a955-21340b0b31b1"
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /auth/user HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.57.0
> Accept: */*
> X-AUTH-TOKEN: 39af0166-0f5f-4a1a-a955-21340b0b31b1
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json;charset=UTF-8
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
<
{"roles":["ROLE_USER"],"name":"user"}* Connection #0 to host localhost left intact
Exception Handling
For traditional Servlet stack, we can use@ExceptionHandler
to handle the exceptions and convert them into HTTP friendly messages.In a webflux based application, we can declare a
WebExceptionHanlder
bean to archive this purpose.For example, if there is no posts found by id, throw a
PostNotFoundException
, and finally convert it to a 404 error to HTTP client.Declare
PostNotFoundExcpetion
as a RuntimeException
.public class PostNotFoundException extends RuntimeException {
public PostNotFoundException(String id) {
super("Post:" + id +" is not found.");
}
}
PostNotFoundException
when it is not found, eg. the get
method in PostController
.@GetMapping("/{id}")
public Mono<Post> get(@PathVariable("id") String id) {
return this.posts.findById(id).switchIfEmpty(Mono.error(new PostNotFoundException(id)));
}
WebExceptionHanlder
bean to handle PostNotFoundException
.@Component
@Order(-2)
@Slf4j
public class RestExceptionHandler implements WebExceptionHandler {
private ObjectMapper objectMapper;
public RestExceptionHandler(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof PostNotFoundException) {
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
// marks the response as complete and forbids writing to it
return exchange.getResponse().setComplete();
}
return Mono.error(ex);
}
}
WebExchangeBindException
to a UnprocessableEntity
error to client, please see the complete codes of RestExceptionHandler
for more details.In the backend codes, I have added some features mentioned at the beginning of this post, such as comment endpoints, and also tried to add pagination, and data auditing feature(when it is ready).
Next let's try to build a simple Angular frontend application to shake hands with the backend APIs.
Client
Prerequisites
Make sure you have already installed the following software.- The latest NodeJS, I used NodeJS 9.4.0 at the moment.
- Your favorite code editors, VS Code, Atom Editor, Intellij WebStorm etc.
npm install -g @angular/cli
> ng -v
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI: 1.6.3
Node: 9.4.0
OS: win32 x64
Angular:
Prepare client project skeleton
Open your terminal, execute the following command to generate an Angular project.ng new client
npm install --save @angular/material @angular/cdk @angular/flex-layout
@angular/animations
. Import BrowserAnimationsModule
in AppModule
.import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
@NgModule({
...
imports: [BrowserAnimationsModule],
...
})
export class AppModule { }
import 'web-animations-js';
npm install --save web-animations-js
hammerjs
.npm install --save hammerjs
import 'hammerjs';
Generate the project structure
Follow the official Angular Style Guide, useng
command to generate expected modules and components. We will enrich them later.ng g module home --routing=true
ng g module auth --routing=true
ng g module post --routing=true
ng g module user --routing=true
ng g module core
ng g module shared
ng g c home --module home
ng g c post/post-list --module post
ng g c post/post-form --module post
ng g c post/new-post --module post
ng g c post/edit-post --module post
ng g c post/post-details --module post
ng g c user/profile --module user
ng g c auth/signin --module auth
Angular 4.x introduced new
HttpClientModule
(located in @angular/common/http
) instead of the original @angular/http
module. I updated the original codes to use HttpClientModule
to interact with REST APIs in this project.Interact REST APIs with HttpClientModule
Let's have a look at the refreshedPostService
. Here we use new HttpClient
to replace the legacy Http
, the usage of HttpClient
is similar with Http
, the most difference is its methods return an Observable
by default.const apiUrl = environment.baseApiUrl + '/posts';
@Injectable()
export class PostService {
constructor(private http: HttpClient) { }
getPosts(term?: any): Observable<any> {
const params: HttpParams = new HttpParams();
Object.keys(term).map(key => {
if (term[key]) { params.set(key, term[key]); }
});
return this.http.get(`${apiUrl}`, { params });
}
getPost(id: string): Observable<any> {
return this.http.get(`${apiUrl}/${id}`);
}
savePost(data: Post) {
console.log('saving post:' + data);
return this.http.post(`${apiUrl}`, data);
}
updatePost(id: string, data: Post) {
console.log('updating post:' + data);
return this.http.put(`${apiUrl}/${id}`, data);
}
deletePost(id: string) {
console.log('delete post by id:' + id);
return this.http.delete(`${apiUrl}/${id}`);
}
saveComment(id: string, data: Comment) {
return this.http.post(`${apiUrl}/${id}/comments`, data);
}
getCommentsOfPost(id: string): Observable<any> {
return this.http.get(`${apiUrl}/${id}/comments`);
}
}
HttpClientModule
is it added the long-awaited HttpInterceptor
officially.We do not need
@covalent/http
to get interceptor support now.const TOKEN_HEADER_KEY = 'X-AUTH-TOKEN';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private token: TokenStorage, private router: Router) { }
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
if (this.token.get()) {
console.log('set token in header ::' + this.token.get());
req.headers.set(TOKEN_HEADER_KEY, this.token.get());
}
return next.handle(req).do(
(event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
const token = event.headers.get(TOKEN_HEADER_KEY);
if (token) {
console.log('saving token ::' + token);
this.token.save(token);
}
}
},
(err: any) => {
if (err instanceof HttpErrorResponse) {
console.log(err);
console.log('req url :: ' + req.url);
if (!req.url.endsWith('/auth/user') && err.status === 401) {
this.router.navigate(['', 'auth', 'signin']);
}
}
}
);
}
}
@covalent/http
, the official HttpInterceptor
is more flexible, and it adopts the middleware concept.
评论
Angular JS Online training
Angular JS training in Hyderabad
This information seems to be more unique and interesting.
Thanks for sharing. PHP Training in Chennai | Certification | Online Training Course | Machine Learning Training in Chennai | Certification | Online Training Course | iOT Training in Chennai | Certification | Online Training Course | Blockchain Training in Chennai | Certification | Online Training Course | Open Stack Training in Chennai |
Certification | Online Training Course
SEO Training in Pune
SEO Training in Mumbai
SEO Training in Delhi
SEO Training in Bangalore
SEO Training in Hyderabad
angular js online training
best angular js online training
top angular js online training
scala online training
azure devops online training
tableau online training
SAP BW on Hana online training
sap sd online training
osb online training
Best UI UX Design Course In Bangalore
UI UX Training In Bangalore
Hii
Thank you for the sharing this informative. There is a wealth of information available on cutting-edge software designed for the analysis of vast quantities of unstructured data within a distributed computing framework. Here is sharing some Kofax Totalagility Training course journey information may be its helpful to you.
Kofax Totalagility Training