最近学习传智播客的巴巴运动网教程,在集成Struts与Spring时,遇到很多麻烦,在此记录,希望对以后遇到同样问题的人有所帮助。教程中集成的是Struts1,我选择的是Struts2。两者还是有所不同的。比如,默认情况下,Struts1的action使用单例模式,容易引起线程安全问题;Struts2使用的是原型模式,不存在线程安全问题;框架使用起来很方便,前提是你得配置好。
项目框架如下:
附上源码:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<display-name>babasport</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/beans.xml</param-value>
</context-param>
<!-- 对Spring容器进行实例化 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>struts</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
beans.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:component-scan base-package="cn.luecc"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${driverClassName}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}" />
<property name="maxIdle" value="${maxIdle}" />
<property name="minIdle" value="${minIdle}" />
</bean>
<!--
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="luecc"/>
</bean>
-->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="persistenceXmlLocation"
value="classpath:META-INF/persistence.xml" />
<property name="loadTimeWeaver">
<bean
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
struts.xml
<?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>
<!-- 指定Web应用的默认编码集,相当于调用 HttpServletRequest的setCharacterEncoding方法 -->
<constant name="struts.i18n.encoding" value="UTF-8" />
<!-- 该 属性指定需要Struts 2处理的请求后缀,该属性的默认值是action,即 所有匹配*.action的请求都由Struts 2处理。如 果用户需要指定多个请求后缀,则多个后缀之间以英文逗号(,)隔开 -->
<constant name="struts.action.extension" value="do" />
<!-- 设 置浏览器是否缓存静态内容,默认值为true(生产环境下使用),开发阶段最好 关闭 -->
<constant name="struts.serve.static.browserCache " value="false" />
<!-- 当 struts的配置文件修改后,系统是否自动重新加载该文件,默认值为false(生 产环境下使用),开发阶段最好打开 -->
<constant name="struts.configuration.xml.reload" value="true" />
<!-- 开 发模式下使用,这样可以打印出更详细的错误信息 -->
<constant name="struts.devMode" value="true" />
<!-- 默 认的视图主题 -->
<constant name="struts.ui.theme" value="simple" />
<!-- 该 属性指定Struts 2中的action由Spring容器创 建 -->
<constant name="struts.objectFactory" value="spring" />
<package name="productType" extends="struts-default" namespace="/product">
<action name="type" class="cn.luecc.web.action.product.ProductTypeAction">
<result>/test.jsp</result>
</action>
</package>
</struts>
上面的代码应该注意两个地方:
<constant name="struts.action.extension" value="do" />
在加了这个配置后,在访问是action的后缀名为.do,如:/xxx/yyy.do
<constant name="struts.objectFactory" value="spring" />
指定Struts2中的action由Spring容器创建
JDBC.properites
driverClassName=org.gjt.mm.mysql.Driver
url=jdbc:mysql://localhost:3306/babasport?useUnicode=true&characterEncoding=UTF-8
username=root
password=chen
initialSize=1
maxActive=100
maxIdle=8
minIdle=1
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="luecc" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.max_fetch_depth" value="3" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
QueryResult.java
package cn.luecc.bean;
import java.util.List;
public class QueryResult<T> {
private List<T> resultList;
private long totalRecord;
public List<T> getResultList() {
return resultList;
}
public void setResultList(List<T> resultList) {
this.resultList = resultList;
}
public long getTotalRecord() {
return totalRecord;
}
public void setTotalRecord(long totalRecord) {
this.totalRecord = totalRecord;
}
}
ProductType.java
package cn.luecc.bean.product;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
@Entity
public class ProductType implements Serializable{
private static final long serialVersionUID = 1105925869147292968L;
/** 类别id **/
private int typeid;
/** 类别名称 **/
private String name;
/** 备注,用于google搜索页面描述 **/
private String note;
/** 是否可见 **/
private Boolean visible = true;
/** 子类别 **/
private Set<ProductType> childTypes = new HashSet<ProductType>();
/** 所属父类别 **/
private ProductType parentType;
@ManyToOne(cascade=CascadeType.REFRESH)
@JoinColumn(name="parentid")
public ProductType getParentType() {
return parentType;
}
public void setParentType(ProductType parentType) {
this.parentType = parentType;
}
@OneToMany(cascade={CascadeType.REFRESH,CascadeType.REMOVE},mappedBy="parentType")
public Set<ProductType> getChildTypes() {
return childTypes;
}
public void setChildTypes(Set<ProductType> childTypes) {
this.childTypes = childTypes;
}
@Column(length=36,nullable=false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Column(length=200)
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
@Column(nullable=false)
public Boolean getVisible() {
return visible;
}
public void setVisible(Boolean visiable) {
this.visible = visiable;
}
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public int getTypeid() {
return typeid;
}
public void setTypeid(int typeid) {
this.typeid = typeid;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + typeid;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ProductType other = (ProductType) obj;
if (typeid != other.typeid)
return false;
return true;
}
}
DAO.java
package cn.luecc.service.base;
import java.util.LinkedHashMap;
import cn.luecc.bean.QueryResult;
public interface DAO {
/**
* 保存实体
* @param entity
*/
public void save(Object entity);
/**
* 更新实体
* @param entity
*/
public void update(Object entity);
/**
* 删除实体
* @param entityid
*/
public <T> void delete(Class<T> entityClass,Object entityid);
/**
* 删除实体
* @param entityids 实体id数组
*/
public <T> void delete(Class<T> entityClass,Object[] entityids);
/**
* 获取实体
* @param <T>
* @param entityClass
* @param entityId
* @return
*/
public <T> T find(Class<T> entityClass,Object entityId);
/**
* 获取分页数据
* @param <T>
* @param entityClass 实体类
* @param firstIndex 分页的开始点
* @param maxResult 设置当前分页的记录数量
* @return
*/
public <T> QueryResult<T> getScrollData(Class<T> entityClass,int firstIndex,int maxResult,String optionalSql,Object[] params,LinkedHashMap<String, String> strOrderby);
DaoSupport.java
package cn.luecc.service.base;
import java.util.LinkedHashMap;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import cn.luecc.bean.QueryResult;
@Transactional
public abstract class DaoSupport implements DAO {
@PersistenceContext protected EntityManager em;
public void save(Object entity) {
em.persist(entity);
}
public void update(Object entity) {
em.merge(entity);
}
public <T> void delete(Class<T> entityClass,Object entityid) {
delete(entityClass,new Object[]{entityid});
}
public <T> void delete(Class<T> entityClass,Object[] entityids) {
for(Object id : entityids) {
em.remove(em.getReference(entityClass, id));
}
}
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public <T> T find(Class<T> entityClass, Object entityId) {
return em.find(entityClass, entityId);
}
@SuppressWarnings("unchecked")
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public <T> QueryResult<T> getScrollData(Class<T> entityClass,
int firstIndex, int maxResult,String optionalSql,Object[] params,LinkedHashMap<String, String> strOrderby) {
QueryResult<T> qr = new QueryResult<T>();
String entityName = getEntityName(entityClass);
Query query = em.createQuery("select o from "+entityName+" o "+(optionalSql==null?"":"where "+optionalSql) + bulidOrderbyString(strOrderby));
setQueryParams(query,params);
query.setFirstResult(firstIndex).setMaxResults(maxResult);
qr.setResultList(query.getResultList());
query = em.createQuery("select count(o) from "+entityName+" o "+(optionalSql==null?"":"where "+optionalSql));
setQueryParams(query,params);
qr.setTotalRecord((Long)query.getSingleResult());
return qr;
}
/**
* 获取实体名称
* @param <T>
* @param entityClass 实体类
* @return
*/
protected <T> String getEntityName(Class<T> entityClass) {
String entityName = entityClass.getSimpleName();
Entity entity = entityClass.getAnnotation(Entity.class);
if(entity.name()!=null &&!"".equals(entity.name())) {
entityName = entity.name();
}
return entityName;
}
/**
* 取得orderby子句
*/
protected String bulidOrderbyString(LinkedHashMap<String, String> strOrderby) {
StringBuffer sb = new StringBuffer();
if(strOrderby!=null && strOrderby.size()>0) {
sb.append(" order by ");
for(String key : strOrderby.keySet()) {
sb.append("o.").append(key).append(" ").append(strOrderby.get(key)).append(",");
}
sb.deleteCharAt(sb.length()-1);
}
return sb.toString();
}
protected void setQueryParams(Query query,Object[] params){
if(params!=null && params.length>0) {
for(int i=0;i<params.length;i++) {
query.setParameter(i+1, params[i]);
}
}
}
}
ProductTypeService.java
package cn.luecc.service.product;
import cn.luecc.service.base.DAO;
public interface ProductTypeService extends DAO{
}
ProductTypeServiceBean.java
package cn.luecc.service.product.impl;
import javax.persistence.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import cn.luecc.service.base.DaoSupport;
import cn.luecc.service.product.ProductTypeService;
/**
@Service相当于配置一个bean
@Transactional采用spring默认事物管理机制
**/
@Service
@Transactional
public class ProductTypeServiceBean extends DaoSupport implements ProductTypeService {
@Override
public <T> void delete(Class<T> entityClass, Object[] entityids) {
StringBuffer jpql = new StringBuffer();
if(entityids!=null && entityids.length>0) {
for(int i=0; i<entityids.length; i++) {
jpql.append("?").append(i+2).append(",");
}
jpql.deleteCharAt(jpql.length()-1);
Query query = em.createQuery("update ProductType pt set pt.visible = ?1 where pt.typeid in("+ jpql.toString() +")").setParameter(1, false);
for(int i=0; i<entityids.length; i++) {
query.setParameter(i+2, entityids[i]);
}
query.executeUpdate();
}
}
}
ProductTypeAction.java
package cn.luecc.web.action.product;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.ServletActionContext;
import org.springframework.stereotype.Controller;
import cn.luecc.bean.product.ProductType;
import cn.luecc.service.product.ProductTypeService;
import com.opensymphony.xwork2.ActionSupport;
@Controller
public class ProductTypeAction extends ActionSupport {
private static final long serialVersionUID = -1290944953355785357L;
/*
private static ApplicationContext cxt;
private static ProductTypeService productTypeService;
@Override
public String execute() throws Exception {
cxt = new ClassPathXmlApplicationContext("beans.xml");
productTypeService = (ProductTypeService)cxt.getBean("productTypeServiceBean");
ProductType productType = productTypeService.find(ProductType.class, 3);
HttpServletRequest request = ServletActionContext.getRequest();
request.setAttribute("producttype", productType);
return SUCCESS;
}
*/
@Resource(name="productTypeServiceBean")
private ProductTypeService productTypeService;
private ProductType productType;
@Override
public String execute() throws Exception {
productType = productTypeService.find(ProductType.class, 3);
HttpServletRequest request = ServletActionContext.getRequest();
request.setAttribute("producttype", productType);
return SUCCESS;
}
}
这里应该注意两点,如果不采用@Resource方式注入bean的话,请参考注释中的代码,重写构造一个bean。使用@Controller注解,Spring将扫描指定的路径获得需要的资源,这里注意如果@Controller使用默认配置,那么struts.xml文件中action的class属性应该与这里的ProductTypeAction.java名字一致。如果这里不使用注解的方式,可以参考注释中的代码,
相应的在配置文件struts.xml 中也不需要那么复杂,具体请自己思考减少配置代码。
test.jsp(这里有好多中方法,可以使用el表达式测试,下面使用基本的jsp代码)
<%@ page language="java" import="java.util.*" pageEncoding="GB18030"%>
<%@ page import="cn.luecc.bean.product.*" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
ProductType type = (ProductType)request.getAttribute("producttype");
%>
<html>
<head>
<base href="<%=basePath%>">
<title>产品列表</title>
</head>
<body>
<%=type.getName()%>
</body>
</html>
在实际测试中遇到很多问题,最主要的就是下面两个:
出现异常:
Cannot locate the chosen ObjectFactory implementation: spring
看到网上说原因是
没有添加struts2-spring-plugin-XXXX.jar
但是很多人早就已经添加了struts2-spring-plugin-2.0.11.1.jar包,还是会出现上面的异常,在这里请把这个包复制到/WebRoot/WEB-INT/lib目录下即可解决。
还有一个最常见的异常就是:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'productTypeServiceBean': Injection of persistence fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [beans.xml]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Cannot parse persistence unit from class path resource.......
出现这种错误应该是没有指定Struts2中的factory由Spring容器创建,而你在程序中使用了各种Spring的注解,所以应该仔细检查程序中的配置文件,具体可以参考上面的struts.xml文件。