最近学习传智播客的巴巴运动网教程,在集成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文件。