Enterprise Java

Java EE Observer Design Pattern Example

This article is about a Java EE Observer Design Pattern Example. The observer pattern is one of the most widely used design patterns in programming. It is implemented in the java.util package of Java SE 8 as Observer and Observable.

1. Introduction

By extending these classes, we can easily implement the observer pattern. But this article is not about that. We will focus on the Java EE implementation of the observer pattern.

In the observer pattern, an object that changes its state can inform other objects that a change in state has happened. The object that changes its state is the subject. The objects that receive the notification of the change of state are the observers. The observer pattern does this in a decoupled manner so that the subject does not know about the observers.

2. Tools and Requirements

  1. Java 8
  2. WildFly 14.0.1
  3. Eclipse Oxygen
  4. Java EE Kickoff App
  5. Eclipse with WildFly and JBoss Tools Example

The source in this is example is based on the Java EE Kickoff App. We will not go through the details of setting up the project, so it is recommended that Eclipse with WildFly and JBoss Tools Example be read before trying out the example. We’ll be using WildFly 14.x because it is a Java EE 8 full platform compatible implementation.

3. Java EE Observer Design Pattern Implementaiton

In Java EE, the subject and observer are decoupled. Many observers can receive notification about a change in the state of the subject. It relies on the Observes annotation. This annotation marks the observers and the subject uses the Event class to create and fire events that observers listen for.

4. @Observes and Event

After downloading WildFly 14.x, we add it as one of our servers in Eclipse. We should have something like below:

Observer Pattern java - WildFly 14.x Servers tab
WildFly 14.x Servers tab

Import the java-ee-observer-pattern project in Eclipse. We will add some code in the AuthBacking class. AuthBacking.java

01
02
03
04
05
06
07
08
09
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
package org.example.kickoff.view.auth;
 
...snipped...
 
public abstract class AuthBacking {
 
    ...snipped...
     
    @Inject
    private Event<User> userLoginEvent;
 
    ...snipped...
 
    protected void authenticate(AuthenticationParameters parameters) throws IOException {
        AuthenticationStatus status = securityContext.authenticate(getRequest(), getResponse(), parameters);
 
        if (status == SEND_FAILURE) {
            addGlobalError("auth.message.error.failure");
            validationFailed();
        }
        else if (status == SEND_CONTINUE) {
            responseComplete(); // Prevent JSF from rendering a response so authentication mechanism can continue.
        }
        else if (activeUser.hasGroup(ADMIN)) {
            userLoginEvent.fire(user);
            redirect("admin/users");
        }
        else if (activeUser.hasGroup(USER)) {
            userLoginEvent.fire(user);
            redirect("user/profile");
        }
        else {
            redirect("");
        }
    }
 
    ...snipped...
 
}

Parts of the code have been snipped. Download the project to get the full contents of the code. We will only be discussing what is relevant to the observer pattern. We will not discuss the other parts of the Java EE Kickoff App.

An Event instance of type User is created by the container and is injected into the subject class. When the fire method is invoked, a notification is fired and any observer listening for User events will receive the user instance. Any method parameter annotated with @Observes and of type User will receive this instance of the user object. This is the observer pattern in its simplest form.

Create the observer classes.

LoginService.java

01
02
03
04
05
06
07
08
09
10
package org.example.kickoff.business.service;
 
import javax.enterprise.event.Observes;
import org.example.kickoff.model.User;
 
public class LoginService {
    public void saveLoginAttempt(@Observes User user) {
        System.out.println("Logged in: " + user.getEmail());
    }
}

EmailLoginService.java

01
02
03
04
05
06
07
08
09
10
package org.example.kickoff.business.service;
 
import javax.enterprise.event.Observes;
import org.example.kickoff.model.User;
 
public class EmailLoginService {
    public void sendLoginAttemptEmail(@Observes User user) {
        System.out.println(user.getEmail() + " has been used to log into Java EE Observer Design Pattern Example.");
    }
}

The above methods with parameters annotated with @Observes will receive a notification when a user logs in. When we log in, we should have the statements above printed.

Observer Pattern java
Java EE Kickoff App Login Page

Try logging as an admin and then as a user. We should see the output below.

Console Output

1
2
3
4
5
6
11:18:31,908 INFO  [org.omnifaces.eventlistener.FacesRequestLogger] (default task-1) GET={url=/login, user={ip=127.0.0.1, login=null, session=zMpJHJSEodlYr0tY8-giDAciJIDcMdEStJT_6lcQ, viewState=null}, action={source=null, event=null, methods=[], validationFailed=false}, params={}, messages={}, timer={0=220ms, 1=3ms, 2=-1ms, 3=-1ms, 4=-1ms, 5=-1ms, 6=217ms}}
11:18:49,943 INFO  [stdout] (default task-1) Logged in: admin@kickoff.example.org
 
11:18:49,943 INFO  [stdout] (default task-1) admin@kickoff.example.org has been used to log into Java EE Observer Design Pattern Example.
 
11:18:49,946 INFO  [org.omnifaces.eventlistener.FacesRequestLogger] (default task-1) POST={url=/login, user={ip=127.0.0.1, login=admin@kickoff.example.org, session=pnrQwJj3ao-mJoPd3RmEc_I-ompITHeimrs8XvDw, viewState=stateless}, action={source=loginForm:login,

Those were the basics related to the J2EE observer pattern.

5. @Priority

The order in which observers are invoked is not specified. We can use the Priority annotation to specify the invocation order of the observers. This is a new feature in Java EE 8. The lowest value priority is called first. If the priorities are the same then they are invoked in an uncertain order.

LoginService.java

01
02
03
04
05
06
07
08
09
10
package org.example.kickoff.business.service;
 
import javax.enterprise.event.Observes;
import org.example.kickoff.model.User;
 
public class LoginService {
    public void saveLoginAttempt(@Observes @Priority(10) User user) {
        System.out.println("Logged in: " + user.getEmail());
    }
}

EmailLoginService.java

01
02
03
04
05
06
07
08
09
10
package org.example.kickoff.business.service;
 
import javax.enterprise.event.Observes;
import org.example.kickoff.model.User;
 
public class EmailLoginService {
    public void sendLoginAttemptEmail(@Observes @Priority(100) User user) {
        System.out.println(user.getEmail() + " has been used to log into Java EE Observer Design Pattern Example.");
    }
}

With the above priority configuration, the “Logging in…” is printed first followed by “Java EE Observer Design Pattern…”. Try swapping the number so that “Java EE Observer Design Pattern…” is printed first.

6. @Qualifier

So far our observers are listening for User type events. What if we want to distinguish between different types of users? The admin and the user type? At the moment both methods get invoked when a user logs in. What if we only want to save a login attempt if it’s of type user? What if we only want to send an email if the login attempt is of type admin? In order for the observers to distinguish between events, we will use a Qualifier.

UserEvent.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package org.example.kickoff.business.service;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
import javax.inject.Qualifier;
 
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface UserEvent {
    Type value();
    enum Type {ADMIN, USER}
}

We will then use the UserEvent annotation to distinguish which observer to invoke like so.

LoginService.java

01
02
03
04
05
06
07
08
09
10
package org.example.kickoff.business.service;
 
import javax.enterprise.event.Observes;
import org.example.kickoff.model.User;
 
public class LoginService {
    public void saveLoginAttempt(@Observes @Priority(10) @UserEvent(UserEvent.Type.USER) User user) {
        System.out.println("Logged in: " + user.getEmail());
    }
}

EmailLoginService.java

01
02
03
04
05
06
07
08
09
10
package org.example.kickoff.business.service;
 
import javax.enterprise.event.Observes;
import org.example.kickoff.model.User;
 
public class EmailLoginService {
    public void sendLoginAttemptEmail(@Observes @Priority(100) @UserEvent(UserEvent.Type.ADMIN) User user) {
        System.out.println(user.getEmail() + " has been used to log into Java EE Observer Design Pattern Example.");
    }
}

And then edit AuthBacking, adding a new event like so.

AuthBacking.java

01
02
03
04
05
06
07
08
09
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
package org.example.kickoff.view.auth;
 
...snipped...
import org.example.kickoff.business.service.UserEvent;
...snipped...
 
public abstract class AuthBacking {
 
    ...snipped...
     
    @Inject @UserEvent(UserEvent.Type.USER)
    private Event<User> userLoginEvent;
         
        @Inject @UserEvent(UserEvent.Type.ADMIN)
    private Event<User> adminLoginEvent;
 
    ...snipped...
 
    protected void authenticate(AuthenticationParameters parameters) throws IOException {
        AuthenticationStatus status = securityContext.authenticate(getRequest(), getResponse(), parameters);
 
        if (status == SEND_FAILURE) {
            addGlobalError("auth.message.error.failure");
            validationFailed();
        }
        else if (status == SEND_CONTINUE) {
            responseComplete(); // Prevent JSF from rendering a response so authentication mechanism can continue.
        }
        else if (activeUser.hasGroup(ADMIN)) {
            adminLoginEvent.fire(user);
            redirect("admin/users");
        }
        else if (activeUser.hasGroup(USER)) {
            userLoginEvent.fire(user);
            redirect("user/profile");
        }
        else {
            redirect("");
        }
    }
 
    ...snipped...
 
}

With the above code, whenever an admin logs in, an email is sent. Whenever a user logs in, it is saved. Our observers can now distinguish which subject has changed its states.

7. Asynchronous Observer

By default events are synchronous. In CDI 2.0, a new firing method called fireAsync and a corresponding observer annotation ObservesAsync handles asynchronous processing of events. We cannot set a priority because events are observed asynchronously in separate threads. Asynchronous and synchronous observers operate independently from each other. This means the synchronous firing of events are not observed by asynchronous observers and vice versa. We’ll need to change LoginService, EmailLoginService, and AuthBacking like so.

LoginService.java

01
02
03
04
05
06
07
08
09
10
package org.example.kickoff.business.service;
 
import javax.enterprise.event.ObservesAsync;
import org.example.kickoff.model.User;
 
public class LoginService {
    public void saveLoginAttempt(@ObservesAsync @UserEvent(UserEvent.Type.USER) User user) {
        System.out.println("Logged in: " + user.getEmail());
    }
}

EmailLoginService.java

01
02
03
04
05
06
07
08
09
10
package org.example.kickoff.business.service;
 
import javax.enterprise.event.ObservesAsync;
import org.example.kickoff.model.User;
 
public class EmailLoginService {
    public void sendLoginAttemptEmail(@ObservesAsync @UserEvent(UserEvent.Type.ADMIN) User user) {
        System.out.println(user.getEmail() + " has been used to log into Java EE Observer Design Pattern Example.");
    }
}

AuthBacking.java

01
02
03
04
05
06
07
08
09
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
47
48
49
50
51
package org.example.kickoff.view.auth;
 
...snipped...
import java.util.concurrent.CompletionStage;
import org.example.kickoff.business.service.UserEvent;
...snipped...
 
public abstract class AuthBacking {
 
    ...snipped...
     
    @Inject @UserEvent(UserEvent.Type.USER)
    private Event<User> userLoginEvent;
         
        @Inject @UserEvent(UserEvent.Type.ADMIN)
    private Event<User> adminLoginEvent;
 
    ...snipped...
 
    protected void authenticate(AuthenticationParameters parameters) throws IOException {
        AuthenticationStatus status = securityContext.authenticate(getRequest(), getResponse(), parameters);
 
        if (status == SEND_FAILURE) {
            addGlobalError("auth.message.error.failure");
            validationFailed();
        }
        else if (status == SEND_CONTINUE) {
            responseComplete(); // Prevent JSF from rendering a response so authentication mechanism can continue.
        }
        else if (activeUser.hasGroup(ADMIN)) {
            CompletionStage stage = adminLoginEvent.fireAsync(user);
            stage.handle((User event, Throwable e) -> {
                for (Throwable t : e.getSuppressed()) {
                    System.out.println(t.getMessage());
                }
                return event;
            });
            redirect("admin/users");
        }
        else if (activeUser.hasGroup(USER)) {
            userLoginEvent.fireAsync(user, NotificationOptions.ofExecutor(new ForkJoinPool(10)));
            redirect("user/profile");
        }
        else {
            redirect("");
        }
    }
 
    ...snipped...
 
}

We have changed the method fire to fireAsync. We have added notification options to our firing event and specified a thread pool. Our ForkJoinPool allows 10 threads. This means if there are 10 or fewer observers, they will get executed asynchronously. If there are more, the other observers must wait until a thread becomes available.

The fireAsync the method returns an instance of CompletionStage. This instance holds a reference to all the exceptions thrown during observer invocation and can be handled in the same way that you would handle a completion state instance.

8. Java EE Observer Design Pattern Summary

That’s all there is to it. We started with a simple implementation of the observer pattern then moved on to use many of the Java EE features. Reaching to the advanced topic of asynchronous observers. Let us now look at the pros and cons of the J2EE Observer Design pattern.

8.1 Pros and cons of the Observer Design Pattern

In Java, the Observer pattern was introduced in Java 1.0. J2EE Observer design helps in development by using annotations and conventions over configuration. Resources also can be injected by type and using annotations @Inject and @Producer. This design pattern helps in startup behavioral characteristics. The developer has control over concurrency and access timeout. This design results in lesser boilerplate code. Any java object can be injected easily. Loosely coupled design and dependency injection can be achieved easily. Business logic is decoupled from the observer through events.

The disadvantage of this pattern is performance issues related to lazy loading. Eager loading creates memory issues. The overuse of this design pattern results in problems. An observer design pattern is suggested to be used in special cases. Type safety is an issue with named annotations. The creation of objects is not transparent. The execution flow is not visible to the developer due to events triggered by other events.

9. Download the Source Code

This is an example about Java EE Observer Design Pattern.

Download
You can download the source code of this example here: Java EE Observer Design Pattern Example

Last updated on Jun. 22nd, 2020

Joel Patrick Llosa

I graduated from Silliman University in Dumaguete City with a degree in Bachelor of Science in Business Computer Application. I have contributed to many Java related projects at Neural Technologies Ltd., University of Southampton (iSolutions), Predictive Technologies, LLC., Confluence Service, North Concepts, Inc., NEC Telecom Software Philippines, Inc., and NEC Technologies Philippines, Inc. You can also find me in Upwork freelancing as a Java Developer.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Terthan Markov
Terthan Markov
5 years ago
Back to top button