Today, we are going to implement a simple example using spring application events.
Spring application events allow us to throw and listen to specific application events that we can process as we wish. Events are meant for exchanging information between loosely coupled components. As there is no direct coupling between publishers and subscribers, it enables us to modify subscribers without affecting the publishers and vice-versa.
To build our PoC and to execute it, we are going to need just a few classes. We will start with a basic Spring Boot project with the ‘web’ starter. And, once we have that in place (you can use the Spring Initializr) we can start adding our classes.
Let’s start with a very basic ‘User’ model
public class User {
private String firstname;
private String lastname;
public String getFirstname() {
return firstname;
}
public User setFirstname(String firstname) {
this.firstname = firstname;
return this;
}
public String getLastname() {
return lastname;
}
public User setLastname(String lastname) {
this.lastname = lastname;
return this;
}
@Override
public String toString() {
return "User{" +
"firstname='" + firstname + '\'' +
", lastname='" + lastname + '\'' +
'}';
}
}
Nothing out of the ordinary here. Just a couple of properties and some getter and setter methods.
Now, let’s build a basic service that is going to simulate a ‘register’ operation:
...
import org.springframework.context.ApplicationEventPublisher;
...
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
private final ApplicationEventPublisher publisher;
public UserService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void register(final User user) {
logger.info("Registering {}", user);
publisher.publishEvent(new UserRegistered(user));
}
}
Here we have the first references to the event classes the Spring Framework offers us. The ‘ApplicationEventPublished’ that it will allow us to publish the desired event to be consumer by listeners.
The second reference we are going to have to the events framework is when we create and event class we are going to send. In this case, the class ‘UserRegistered’ we can see on the publishing line above.
...
import org.springframework.context.ApplicationEvent;
public class UserRegistered extends ApplicationEvent {
public UserRegistered(User user) {
super(user);
}
}
As we can see, extending the class ‘ApplicationEvent’ we have very easily something we can publish and listen to it.
Now. let’s implements some listeners. The first of them is going to be one implementing the class ‘ApplicationListener’ and, the second one, it is going to be annotation based. Two simple options offered by Spring to build our listeners.
...
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
public class UserListeners {
// Technical note: By default listener events return 'void'. If an object is returned, it will be published as an event
/**
* Example of event listener using the implementation of {@link ApplicationListener}
*/
static class RegisteredListener implements ApplicationListener<UserRegistered> {
private static final Logger logger = LoggerFactory.getLogger(RegisteredListener.class);
@Override
public void onApplicationEvent(UserRegistered event) {
logger.info("Registration event received for {}", event);
}
}
/**
* Example of annotation based event listener
*/
@Component
static class RegisteredAnnotatedListener {
private static final Logger logger = LoggerFactory.getLogger(RegisteredAnnotatedListener.class);
@EventListener
void on(final UserRegistered event) {
logger.info("Annotated registration event received for {}", event);
}
}
}
As we can see, very basic stuff. It is worth it to mention the ‘Technical note’. By default, the listener methods return ‘void’, they are initially design to received an event, do some stuff and finish. But, obviously, they can at the same time publish some messages, we can achieve this easily, returning an object. The returned object will be published as any other event.
Once we have all of this, let’s build a simple controller to run the process:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
@ResponseStatus(HttpStatus.CREATED)
public void register(@RequestParam("firstname") final String firstname,
@RequestParam("lastname") final String lastname) {
Objects.requireNonNull(firstname);
Objects.requireNonNull(lastname);
userService.register(new User().setFirstname(firstname).setLastname(lastname));
}
}
Nothing out of the ordinary, simple stuff.
We can invoke the controller with any tools we want but, a simple way, it is using cURL.
curl -X GET "http://localhost:8080/api/users?firstname=john&lastname=doe"
Once we call the endpoint, we can see the log messages generated by the publisher and the listeners:
Registering User{firstname='john', lastname='doe'}
Annotated registration event received for dev.binarycoders.spring.event.UserRegistered[source=User{firstname='john', lastname='doe'}]
Registration event received for dev.binarycoders.spring.event.UserRegistered[source=User{firstname='john', lastname='doe'}]
As we can see, the ‘register’ action is executed and it publishes the event and, both listeners, the annotated and the implemented, receive and process the message.
As usual you can find the source for this example here, in the ‘spring-events’ module.
For some extra information, you can take a look at one of the videos of the last SpringOne.