Maven + Struts2 + Spring + Hibernate + Struts2-Convention-Plugin
After spending so many days and nights and days again I finally be able to achieve the basic Architecture. Well, the basic architecture was quite simple and you will be able to get a lot of tutorials online about it but, probably they have few things missing (i.e. Maven or sometimes Struts2-Convention-Plugin).
Reason behind choosing this structure is to make an easy expandable application structure with an isolation in between layers.
Reason Behind:
Maven is just used for dependency management of libraries and easy deployment.
Struts2 is used for UI.
Spring for service layer, as we all know we can easily change implementation which will be helpful for us to entertain the change request.
Hibernate for ORM that will play with the database.
Directory Structure:
In above pom file we have basically two main tags that will be helpful for you one is plugins and second one is dependencies. Well dependencies deal with the libraries that are required for the project, and plugins help for compilation and war creation.
Note: One can easily change the artifact version of the library but make sure the other libraries that are interacting with that library must belong to the same artifacts.
Now, lets proceed towards the deployment descriptor.
We have defined one custom filter that we will use to handle each and every request that will be coming to our application. Secondly other then the struts built-in filter also using a ContextLoaderListener that will load
all the beans of Spring at the time of initialization.
In StrutsPrepareAndExecuteFilter, we have mentioned a config param that will be showing that these files will only be loaded for struts configuration. Here people will get a bit confuse that why we are using configuration while Struts2 offer us a complete annotation based project setup (A.K.A Zero Configuration). Yes we are not going to use configuration for action classes but for interceptors we must need a configuration file that will initiate our interceptors at the time of context-loading.
This is our struts.xml which will be used to declare the interceptors that will be used in our actions.
In above xml we have mentioned few properties that will be used to bound our application to use struts2-convention-plugin and interceptors. We are just declaring our interceptors here that will be initialized at the time of application startup and will be executed whenever we will use it in our action class by using Annotations.
Now, let's move towards our first action class and how we are going to use annotation in that.
Before getting into the depth of this class we must need to tell that what we need to put in @ParanetPackage() in @ParentPackage we need to use the same name that we have used in our struts.xml to represent the package in which our interceptor lies. Then finally use the name of interceptor in InterceptorRef.
We are not extending this class with ActionSupport becuase we have used annotations but implementing ModelDriven that will help use to get a bean entity to be used in our action class to delegate operations to Spring Layer.
This is where we are getting the model by implementing the method of our interface ModelDriven.
Finally here our action comes which will be used for mapping of our action to JSPs
Note: As we have used Struts2-Convention-Plugin so we must need to end our action class with "Action" Because it will only compile all those classes which will have action and defined in action package.
Now, i'll not go into much detail of the classes because it's basic java which you must be aware of that how we can do transformation from one class to another and delegate a class to call the method.
UserBo which we have used in our Action is a BusinessObject which will act as a middle layer that will manage the transformation from Struts->Hibernate
As UserBo is an interface so here it's an implementation for it where we actually will do Dependency Injection a feature offered by Spring.
Entity of UpUser:
resources/config/spring/HibernateSessionFactory.xml
resources/config/SpringBeans.xml
Now wrapping up everything with an AuthenticationFilter and how to write a custom filter that we have just mentioned in our web.xml
AuthenticationFilter.java
And our basic Interceptor that we have just to calculate the time of our action request.
LoggingInterceptor.java
LoginAction.java
This is just a dumb action so will not perform a lot of things for you but, it will give you an idea of using it
resources/config/log4j.xml
So, this is it related to the whole configuration and coding.
I have intentionally didn't add any jsp file in this code because I think JSP can be simply added by anyone by looking into a lot of example available so don't want to waste my time into that but if, anyone have any issues in implementing this then they may contact me.
Reason behind choosing this structure is to make an easy expandable application structure with an isolation in between layers.
Reason Behind:
Maven is just used for dependency management of libraries and easy deployment.
Struts2 is used for UI.
Spring for service layer, as we all know we can easily change implementation which will be helpful for us to entertain the change request.
Hibernate for ORM that will play with the database.
Don't worry about such a big directory tree. One by one I will tell you each and everything about it. Before starting everything we will start telling you about the pom that will be required to setup the artifacts of your project.
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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>TestWar</groupId>
<artifactId>TestWar</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name />
<description />
<dependencies>
<!-- Struts 2 -->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.1.8.1</version>
</dependency>
<!-- Struts 2 End-->
<!-- Struts 2 + Spring plugins -->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-spring-plugin</artifactId>
<version>2.1.8.1</version>
</dependency>
<!-- MySQL database driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!-- Spring framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>2.5.6</version>
</dependency>
<!-- Hibernate core -->
<dependency>
<groupId>asm</groupId>
<artifactId>asm-all</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.7.Final</version>
</dependency>
<!-- Hibernate core library dependency start -->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-convention-plugin</artifactId>
<version>2.1.8.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${basedir}/src</sourceDirectory>
<outputDirectory>${basedir}/WebRoot/WEB-INF/classes</outputDirectory>
<resources>
<resource>
<directory>${basedir}/src</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webappDirectory>${basedir}/WebRoot</webappDirectory>
<warSourceDirectory>${basedir}/WebRoot</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
|
Note: One can easily change the artifact version of the library but make sure the other libraries that are interacting with that library must belong to the same artifacts.
Now, lets proceed towards the deployment descriptor.
<?xml version="1.0"
encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</filter-class>
<init-param>
<param-name>config</param-name>
<param-value>
struts-default.xml,struts-plugin.xml, struts.xml
</param-value>
</init-param>
</filter>
<filter>
<filter-name>AuthenticationFilter</filter-name>
<filter-class>com.test.lab.filter.AuthenticationFilter</filter-class>
<init-param>
<param-name>insecurePages</param-name>
<param-value> /login.action
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>AuthenticationFilter</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/resources/config/SpringBeans.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
</web-app>
|
We have defined one custom filter that we will use to handle each and every request that will be coming to our application. Secondly other then the struts built-in filter also using a ContextLoaderListener that will load
all the beans of Spring at the time of initialization.
In StrutsPrepareAndExecuteFilter, we have mentioned a config param that will be showing that these files will only be loaded for struts configuration. Here people will get a bit confuse that why we are using configuration while Struts2 offer us a complete annotation based project setup (A.K.A Zero Configuration). Yes we are not going to use configuration for action classes but for interceptors we must need a configuration file that will initiate our interceptors at the time of context-loading.
This is our struts.xml which will be used to declare the interceptors that will be used in our actions.
<?xml version="1.0"
encoding="UTF-8"?>
<!DOCTYPE struts
PUBLIC
"-//Apache
Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode"
value="false" />
<constant name="struts.convention.package.locators.basePackage"
value="com.test.lab"
/>
<constant name="struts.convention.exclude.packages"
value="org.apache.struts.*,org.apache.struts2.*,
org.springframework.web.struts.*,
org.springframework.web.struts2.*,
org.hibernate.*,WarFileName.*"
/>
<constant name="struts.convention.action.checkImplementsAction"
value="false"
/>
<constant name="struts.convention.package.locators"
value="action,actions,struts,struts2" />
<package name="interceptions"
extends="struts-default"
namespace="/">
<interceptors>
<interceptor name="mylogging"
class="com.test.lab.interceptor.LoggingInterceptor">
</interceptor>
<interceptor-stack name="loggingStack">
<interceptor-ref name="mylogging"
/>
<interceptor-ref name="defaultStack"
/>
</interceptor-stack>
</interceptors>
</package>
</struts>
|
Now, let's move towards our first action class and how we are going to use annotation in that.
@ParentPackage("interceptions")
@InterceptorRefs({
@InterceptorRef("mylogging")
})
public class
LoginAction implements ModelDriven{
|
We are not extending this class with ActionSupport becuase we have used annotations but implementing ModelDriven that will help use to get a bean entity to be used in our action class to delegate operations to Spring Layer.
UpUser user = new UpUser();
UserBo userBo;
public Object
getModel(){
return user;
}
public void
setUserBo(UserBo userBo) {
this.userBo= userBo;
}
|
Finally here our action comes which will be used for mapping of our action to JSPs
@Action(value="/login",results={@Result(name="success",location="/jsp/successPage.jsp"),
@Result(name="login",location="/jsp/userLogin.jsp")})
public String
execute() {
if(user.getUserScreenName()==null)
return "login";
System.out.println(userBo.verifyUser(user));
return "success";
}
|
Note: As we have used Struts2-Convention-Plugin so we must need to end our action class with "Action" Because it will only compile all those classes which will have action and defined in action package.
From struts.xml:
<constant name="struts.convention.package.locators.basePackage"
value="com.test.lab"
/>
|
Now, i'll not go into much detail of the classes because it's basic java which you must be aware of that how we can do transformation from one class to another and delegate a class to call the method.
UserBo which we have used in our Action is a BusinessObject which will act as a middle layer that will manage the transformation from Struts->Hibernate
package
com.test.lab.bo;
import
java.util.List;
import
com.test.lab.model.UpUser;
public
interface UserBo {
Boolean verifyUser(UpUser user) ;
}
|
As UserBo is an interface so here it's an implementation for it where we actually will do Dependency Injection a feature offered by Spring.
package
com.test.lab.bo.impl;
import
java.util.List;
import
com.test.lab.bo.UserBo;
import
com.test.lab.dao.UserDAO;
import
com.test.lab.model.UpUser;
public class
UserBoImpl implements UserBo{
UserDAO userDAO;
//DI
public void setUserDAO(UserDAO userDAO){
this.userDAO = userDAO;
}
public Boolean verifyUser(UpUser user) {
// TODO Auto-generated method stub
return userDAO.verifyUser(user);
}
}
|
Now we are going to dig UserDAO what exactly it will do. As from the name it's quite clear that it will act as a data layer for us
package
com.test.lab.dao;
import
java.util.List;
import
com.test.lab.model.UpUser;
public
interface UserDAO {
Boolean verifyUser(UpUser user);
}
|
Once again we have created an interface of UserDAO. So, we must need to provide an implementation:
package
com.test.lab.dao.impl;
import
java.util.List;
import
org.hibernate.criterion.DetachedCriteria;
import
org.hibernate.criterion.Restrictions;
import
org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import
com.test.lab.dao.UserDAO;
import
com.test.lab.model.UpUser;
public class
UserDAOImpl extends HibernateDaoSupport
implements
UserDAO{
public Boolean verifyUser(UpUser user) {
// TODO Auto-generated method stub
Boolean result = false;
DetachedCriteria criteria =
DetachedCriteria.forClass(
UpUser.class);
criteria.add(Restrictions.eq("userScreenName",
user.getUserScreenName()));
criteria.add(Restrictions.eq("userPassword",
user.getUserPassword()));
List<UpUser> list =
getHibernateTemplate().findByCriteria(criteria);
if(list.size()>0)
result = true;
return result;
}
}
|
Entity of UpUser:
package
com.test.lab.model;
import
java.util.Date;
import
java.util.HashSet;
import
java.util.Set;
/**
* UpUser entity. @author MyEclipse
Persistence Tools
*/
public class
UpUser implements java.io.Serializable {
// Fields
private Integer userId;
private String userScreenName;
private String userPassword;
// Constructors
/** default constructor */
public UpUser() {
}
/** minimal constructor */
public UpUser(String
userScreenName,String userPassword) {
this.userScreenName =
userScreenName;
this.userPassword = userPassword;
}
// Property accessors
public Integer getUserId() {
return this.userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserScreenName() {
return this.userScreenName;
}
public void setUserScreenName(String
userScreenName) {
this.userScreenName =
userScreenName;
}
public String getUserPassword() {
return this.userPassword;
}
public void setUserPassword(String
userPassword) {
this.userPassword = userPassword;
}
}
|
Hibernate mapping file that we need to keep in the same folder where our hibernate entity will be.
<?xml version="1.0"
encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate
Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by
MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="com.test.lab.model.UpUser"
table="up_user" catalog="test_db">
<id name="userId"
type="java.lang.Integer">
<column name="user_id"
/>
<generator class="identity"
/>
</id>
<property name="userScreenName"
type="java.lang.String">
<column name="user_screen_name"
length="150" not-null="true"
/>
</property>
<property name="userPassword"
type="java.lang.String">
<column name="user_password"
length="25" not-null="true"
/>
</property>
</class>
</hibernate-mapping>
|
Hibernate In the above file user_id, user_password and user_screen_name are the fields that we have in our database and now mapping here with the userId, userPassword and userScreenName in our UpUser.java file.
Here we are going to discuss main Spring file that will manage almost all the interaction between UI and Data layer.
This file need to be placed at: resources/com/test/lab/spring/UpUserBean.xml
<?xml version="1.0"
encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="loginAction"
class="com.test.lab.actions.LoginAction">
<property name="userBo"
ref="userBo" />
</bean>
<bean id="userBo"
class="com.test.lab.bo.impl.UserBoImpl" >
<property name="userDAO"
ref="userDAO" />
</bean>
<bean id="userDAO"
class="com.test.lab.dao.impl.UserDAOImpl"
>
<property name="sessionFactory"
ref="sessionFactory" />
</bean>
</beans>
|
Few more configuration:
resources/config/database/properties/database.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/legal_erp
jdbc.username=root
jdbc.password=root
|
resource/config/spring/DataSource.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>WEB-INF/classes/resources/config/database/properties/database.properties</value>
</property>
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"
value="${jdbc.driverClassName}" />
<property name="url"
value="${jdbc.url}" />
<property name="username"
value="${jdbc.username}" />
<property name="password"
value="${jdbc.password}" />
</bean>
</beans>
|
<?xml version="1.0"
encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!-- Hibernate
session factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.connection.zeroDateTimeBehavior">convertToNull
</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>com/test/lab/model/UpUser.hbm.xml</value>
</list>
</property>
</bean>
</beans>
|
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!--
Database Configuration -->
<import resource="spring/DataSource.xml"/>
<import resource="spring/HibernateSessionFactory.xml"/>
<!-- Beans
Declaration -->
<import resource="../com/test/lab/spring/UpUserBean.xml"/>
</beans>
|
package
com.test.lab.filter;
import
java.io.IOException;
import
java.util.HashSet;
import
java.util.StringTokenizer;
import
javax.servlet.Filter;
import
javax.servlet.FilterChain;
import
javax.servlet.FilterConfig;
import
javax.servlet.RequestDispatcher;
import
javax.servlet.ServletException;
import
javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
org.apache.log4j.Logger;
public class
AuthenticationFilter implements Filter
{
private static Logger logger = Logger.getLogger(
AuthenticationFilter.class );
private HashSet<String>
insecurePages = null;
public void init( FilterConfig config )
throws ServletException
{
System.out.println(
"PUBLIC Authentication Fileter Started." );
logger.info( "PUBLIC Authentication
Fileter Started." );
String strInsecurePages =
config.getInitParameter( "insecurePages" );
StringTokenizer tokenizer = new
StringTokenizer( strInsecurePages, ",", false );
insecurePages = new
HashSet<String>( tokenizer.countTokens() );
while ( tokenizer.hasMoreTokens() )
{
insecurePages.add(
tokenizer.nextToken().trim() );
}
logger.info( "PUBLIC
Authentication Fileter Started." );
}
public void doFilter( ServletRequest
request, ServletResponse response, FilterChain chain )
throws IOException,
ServletException
{
if ( request instanceof
HttpServletRequest )
{
System.out.println("Request
Servlet Request");
HttpServletRequest req =
(HttpServletRequest) request;
HttpServletResponse res =
(HttpServletResponse) response;
logger.info( "Request
received: " + req.getRequestURI()
+ " from user with session id: " +
req.getRequestedSessionId() );
}
chain.doFilter( request, response );
}
public void destroy()
{
insecurePages = null;
}
}
|
package
com.test.lab.interceptor;
import
com.opensymphony.xwork2.ActionInvocation;
import
com.opensymphony.xwork2.interceptor.Interceptor;
public class
LoggingInterceptor implements Interceptor{
private static final long
serialVersionUID = 1L;
public String intercept(ActionInvocation
invocation) throws Exception {
String className =
invocation.getAction().getClass().getName();
long startTime =
System.currentTimeMillis();
System.out.println("Before
calling action: " + className);
String result =
invocation.invoke();
long endTime =
System.currentTimeMillis();
System.out.println("After
calling action: " + className
+ " Time taken:
" + (endTime - startTime) + " ms");
return result;
}
public void destroy() {
System.out.println("Destroying
MyLoggingInterceptor...");
}
public void init() {
System.out.println("Initializing
MyLoggingInterceptor...");
}
}
|
package
com.test.lab.actions;
import
java.util.ArrayList;
import
java.util.List;
import
org.apache.struts2.convention.annotation.Action;
import
org.apache.struts2.convention.annotation.InterceptorRef;
import
org.apache.struts2.convention.annotation.InterceptorRefs;
import
org.apache.struts2.convention.annotation.ParentPackage;
import
org.apache.struts2.convention.annotation.Result;
import
com.opensymphony.xwork2.ActionSupport;
import
com.opensymphony.xwork2.ModelDriven;
import
com.test.lab.bo.UserBo;
import
com.test.lab.model.UpUser;
@ParentPackage("interceptions")
@InterceptorRefs({
@InterceptorRef("mylogging")
})
public class
LoginAction implements ModelDriven{
UpUser user = new UpUser();
UserBo userBo;
@Action(value="/login",results={@Result(name="success",location="/jsp/successPage.jsp"),
@Result(name="login",location="/jsp/userLogin.jsp")})
public String execute() {
if(user.getUserScreenName()==null)
return "login";
System.out.println(userBo.verifyUser(user));
return "success";
}
public Object getModel(){
return user;
}
public void setUserBo(UserBo userBo) {
this.userBo= userBo;
}
public UpUser getUser() {
return user;
}
public void setUser(UpUser user) {
this.user = user;
}
}
|
Very nice and informative post. Keep it up!
ReplyDeletethis looks good, thanks for taking the time.
ReplyDelete