Spring ApplicationEvent Example

0

Spring uses ApplicationEvent as means of communication between container managed beans.

A bean which wants to let other beans know about the occurrence of a certain event will act as publisher to publish the event. Likewise, a bean which is interested to know about the event will implement ApplicationListener and become the listener.

The bean doesn’t have to explicitly subscribe itself. All it needs to do is implement ApplicationListener and register itself in the context XML. Once the application context publishes the event, spring will automatically publish the event to all the ApplicationListener(s). Using events, an event publisher object can communicate with other objects without even knowing which objects are listening. Likewise, an event listener can react to events without knowing which object published the events.

In this article, we will look into an example of spring’s ApplicationEvent, to maintain the login history of users. The bean responsible for recording login history will implement ApplicationListener and register itself in the context XML. When a user successfully logs in, an ApplicationEvent will be published.  The login history bean registered will receive the event and add an entry for the logged in user.

This example uses the following frameworks:

  1. Maven 3.2.3
  2. Spring 4.1.5.RELEASE
  3. Eclipse  as the IDE, version Luna 4.4.1.

Dependencies

Add the following dependencies:

  1. spring-core
  2. spring-context
  3. spring-beans
  4. mysql-connector-java
  5. spring-jdbc

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.javarticles.spring</groupId>
	<artifactId>springListExample</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.26</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>		
	</dependencies>

	<properties>
		<spring.version>3.2.3.RELEASE</spring.version>
	</properties>

</project>

Database initialization

Create schema and sample data. We need two tables, one for user and the other for login history.

db-schema.sql:

DROP TABLE IF EXISTS `USER_DETAILS`;
DROP TABLE IF EXISTS `USER_LOGIN_HISTORY`;

CREATE TABLE `USER_DETAILS` (
  `ID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `USER_ID` VARCHAR(15) NOT NULL,
  `PASSWORD` VARCHAR(100) NOT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

CREATE TABLE `USER_LOGIN_HISTORY` (
  `ID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `STATUS` VARCHAR(15) NOT NULL,
  `SESSION_ID` BIGINT(20) DEFAULT NULL,
  `LOGIN_TIME` DATETIME DEFAULT NULL,
  `LOGOUT_TIME` DATETIME DEFAULT NULL,
  `USER_ID` INT(10) DEFAULT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

db-test-data.sql:

insert into user_details(id, user_id, password) values (1, "admin", "admin");

User and Login History Beans

Here is the User bean. We deliberately kept it simple so that our focus remains on ApplicationEvent mechanism.

User:

package com.javarticles.spring;

import java.util.Date;
import java.util.UUID;

public class User {
    private Integer id;
    private String userId;
    private String password;
    private Date loginTime;
    private Long sessionId;

    public User(Integer id, String userId, String password) {
        this.id = id;
        this.userId = userId;
        this.password = password;
        this.sessionId = UUID.randomUUID().getMostSignificantBits();
        this.loginTime = new Date();
    }

    public Integer getId() {
        return id;
    }

    public String getUserId() {
        return userId;
    }

    public String getPassword() {
        return password;
    }

    public Date getLoginTime() {
        return loginTime;
    }

    public Long getSessionId() {
        return sessionId;
    }
    
}

A DAO to find the user given a userId. It extends spring provided JdbcDaoSupport which in turn has the JdbcTemplate class to interact with the database. We will later configure the data source and the beans used.

UserDao:

package com.javarticles.spring;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class UserDao extends JdbcDaoSupport {
    public User findUser(String userId) {
        System.out.println("Find user " + userId);
        return getJdbcTemplate()
                .query("select id, user_id, password from user_details where user_id=?",
                        new Object[] { userId },
                        new ResultSetExtractor() {

                            @Override
                            public User extractData(ResultSet rs)
                                    throws SQLException, DataAccessException {
                                rs.next();
                                return new User(rs.getInt(1), rs.getString(2),
                                        rs.getString(3));
                            }
                        });
    }
}

Here is the LoginHistory bean. It has logoutTime member but we will not use it in this article. It is there just for completeness.

LoginHistory:

package com.javarticles.spring;

import java.util.Date;

public class LoginHistory {
    private int id;
    private User user;
    private Long sessionId;
    private String status;
    private Date loginTime;
    private Date logoutTime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Long getSessionId() {
        return sessionId;
    }

    public void setSessionId(Long sessionId) {
        this.sessionId = sessionId;
    }

    public Date getLoginTime() {
        return loginTime;
    }

    public void setLoginTime(Date loginTime) {
        this.loginTime = loginTime;
    }

    public Date getLogoutTime() {
        return logoutTime;
    }

    public void setLogoutTime(Date logoutTime) {
        this.logoutTime = logoutTime;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
    
    public String toString() {
        return "User: " + user.getUserId() + ", sessionId: " + user.getSessionId() + ", loginTime: " + getLoginTime() + ", status: " + getStatus();
    }

}

A DAO to find and record login history.

LoginHistoryDao:

package com.javarticles.spring;
package com.javarticles.spring;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class LoginHistoryDao extends JdbcDaoSupport {
    public void recordLoginHistory(LoginEvent loginEvent) {
        User user = loginEvent.getUser();
        System.out.println("Add entry to login history for " + user.getUserId());
        getJdbcTemplate().update("insert into user_login_history(status, session_id, login_time, user_id) values(?, ?, ?, ?)",
                "SUCCESS", user.getSessionId(), user.getLoginTime(), user.getId());
    }
    
    public List<LoginHistory> findLoginHistory(final User user) {
        return getJdbcTemplate().query("select id, status, login_time, session_id from user_login_history where user_id=?", 
                new Object[]{user.getId()}, new RowMapper<LoginHistory>(){

            @Override
            public LoginHistory mapRow(ResultSet rs, int rowNum)
                    throws SQLException {
                System.out.println("Found login history");
                LoginHistory loginHistory = new LoginHistory();
                loginHistory.setId(rs.getInt("id"));
                loginHistory.setStatus(rs.getString("status"));
                loginHistory.setLoginTime(rs.getDate("login_time"));
                loginHistory.setSessionId(rs.getLong("session_id"));
                loginHistory.setUser(user);
                return loginHistory;
            }});
    }    
}

ApplicationEvent Publisher

Here is the login manager which will authenticate the user. The implementations are kept super simple as the article’s purpose is not to demonstrate login mechanism. It just finds the user, compares the password. If user is found and the password matches login is successful.
When it is successful, we publish a LoginEvent which is nothing but an implementation of ApplicationEvent holding the User object.

The ApplicationContext interface contains the publishEvent() method which publishes the ApplicationEvents. Any ApplicationListener that is registered in the application context will receive the event in a call to its onApplicationEvent() method. We will see this when we will come to the listener part. For now, all we need to know is in order to publish events, your bean needs to have access to the ApplicationContext. This means that beans will have to be made aware of the container that they’re running in. For this, you need to make the publisher implement ApplicationContextAware interface.

LoginManager:

package com.javarticles.spring;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class LoginManager implements ApplicationContextAware {
    @Autowired
    private UserDao userDao;

    public User login(String userId, String password) {
        try {
            System.out.println("Try Logging in " + userId);
            User user = userDao.findUser(userId);
            if (!user.getPassword().equals(password)) {
                System.out.println("Login failed");
                return null;
            }
            System.out.println("Login succesful");
            System.out.println("Publish successful login event for "
                    + user.getUserId());
            applicationContext.publishEvent(new LoginEvent(user));
            return user;
        } catch (Exception e) {
            System.out.println("Login failed " + e);
            return null;
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.applicationContext = applicationContext;
    }

    private ApplicationContext applicationContext;
}

Create your own ApplicationEvent

Here is the login event. It looks very simple but in reality one can instead pass in a security context object which knows the user’s ip address, locale, user credentials etc.

LoginEvent:

package com.javarticles.spring;

import org.springframework.context.ApplicationEvent;

public class LoginEvent extends ApplicationEvent {
    public LoginEvent(User user) {
        super(user);
    }

    public User getUser() {
        return (User) getSource();
    }

    public static long getSerialversionuid() {
        return serialVersionUID;
    }

    private static final long serialVersionUID = -5594735946614874286L;

}

ApplicationEvent Listener

Now we want to respond to the login event application events. This is possible by making our bean implement the org.springframework.context.ApplicationListener interface. This interface
forces your bean to implement the onApplicationEvent() method, and here is where you will have to add your stuff, for example, delegate the call to LoginHistoryDao to add an entry to login history.

LoginHistoryListener:

package com.javarticles.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;

public class LoginHistoryListener implements ApplicationListener<LoginEvent> {
    @Autowired
    private LoginHistoryDao loginHistoryDao;
   
    @Override
    public void onApplicationEvent(LoginEvent loginEvent) {
        System.out.println("Received login event");
        loginHistoryDao.recordLoginHistory(loginEvent);
    }
}

Configure beans in spring application context

Let’s now configure the login manager, the publisher and listener beans along with the datasource, jdbc template and database scripts.

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<context:annotation-config />
	<jdbc:initialize-database data-source="dataSource"
		enabled="true">
		<jdbc:script location="classpath:db-schema.sql" />
		<jdbc:script location="classpath:db-test-data.sql" />
	</jdbc:initialize-database>

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost/test" />
		<property name="username" value="root" />
		<property name="password" value="mnrpass" />
	</bean>

	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="userDao" class="com.javarticles.spring.UserDao">
		<property name="jdbcTemplate" ref="jdbcTemplate" />
	</bean>
	<bean id="loginHistoryDao" class="com.javarticles.spring.LoginHistoryDao">
		<property name="jdbcTemplate" ref="jdbcTemplate" />
	</bean>
	<bean id="loginManager" class="com.javarticles.spring.LoginManager" />
	<bean id="loginHistoryListener" class="com.javarticles.spring.LoginHistoryListener" />
</beans>

Run the ApplicationEvent example

Let’s test the application event example. We will login twice and then verify that login history has both the entries.

SpringApplicationEventExample:

package com.javarticles.spring;

import java.util.List;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplicationEventExample {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        try {
            String userId = "admin";
            String password = "admin";
            LoginManager loginManager = (LoginManager) context
                    .getBean("loginManager");
            System.out.println("Login userId " + userId);
            User user = loginManager.login(userId, password);
            if (user == null) {
                throw new RuntimeException("User " + userId + " not found");
            }

            
            System.out.println("Log-in again");
            loginManager.login(userId, password);
            
            System.out.println("Login History");
            LoginHistoryDao loginHistoryDao = (LoginHistoryDao) context
                    .getBean("loginHistoryDao");
            List loginHistoryList = loginHistoryDao
                    .findLoginHistory(user);
            for (LoginHistory loginHistory : loginHistoryList) {
                System.out.println(loginHistory);
            }
        } finally {
            context.close();
        }
    }
}

Output:

Login userId admin
Try Logging in admin
Find user admin
Login succesful
Publish successful login event for admin
Received login event
Add entry to login history for admin
Log-in again
Try Logging in admin
Find user admin
Login succesful
Publish successful login event for admin
Received login event
Add entry to login history for admin
Login History
Found login history
Found login history
User: admin, sessionId: 2208187891721454757, loginTime: 2015-05-27, status: SUCCESS
User: admin, sessionId: 2208187891721454757, loginTime: 2015-05-27, status: SUCCESS

Download the source code

This was an example of spring’s ApplicationEvent. You can download the source code here: springApplicationEventExample.zip

Share.

Comments are closed.