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:
Dependencies
Add the following dependencies:
spring-core
spring-context
spring-beans
- mysql-connector-java
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