基本Seasar2 Web应用工程结构
Seasar2这个框架在日本十分的流行。Seasar2其实就是类似于Spring的一个提供DI功能的开源框架,但比Sping轻量级。并且同“其它轻量级容器”不同的是,“完全不需要书写设定文件”,“就算是应用程序发生改动也无需再次起动即可直接识别变更,因此具有脚本语言的灵活性。
为了不用写设定文件也能够运行,Convention over Configuration的思想得以采用。Conventionover Configuration就是指,“只要遵守一个适当的规约,即使不用进行非常麻烦的设定,框架结构也可以自动替我们搞定的思想”,这一思想是Ruby on Rails中所倡导的。Seasar2的Convention over Configuration是从Ruby on Rails 那里得到的提示而产生的。Seasar2的核心特色,即SuperAgile Struts(超级敏捷开发框架,简称SAStruts),Seasar2实际上就是能够将传统基于Struts的系统开发过程大幅敏捷化的(Super Agile)的技术开发框架。
本开发框架最大的特点是零配置文件支持,在使用传统的开发框架进行系统开发时,配置文件的管理十分的麻烦。同时,每修改一处配置,或者一个代码文件,还至少要重新启动一次服务器。而Seasar2实现零配置支持,可以不写一句配置文件就在框架基础上构建业务应用。开发人员只要遵从本框架约定的命名规范及代码规范根据设计编写业务代码,亦无需关注实现技术细节。
应用程序发生改动之时也无需启动便可立即识别变更的机能在Seasar2里被称为HOT deploy。
使用Seasar2基本功能(S2Container, S2AOP)的时候、CLASSPATH的下面必须包含以下文件。
lib/aopalliance-1.0.jar
lib/commons-logging-1.1.jar
lib/javassist-3.4.ga.jar
lib/ognl-2.6.9-patch-20070624.jar
lib/s2-framework-2.x.x.jar
lib/geronimo-j2ee_1.4_spec-1.0.jar (参考下面)
lib/portlet-api-1.0.jar (任选项)
lib/log4j-1.2.13.jar (任选项)
resources/log4j.properties (任选项)
resources/aop.dicon (任选项)
「lib」文件夹:包含S2Container以及相关库。
「doc」文件夹:S2Container的相关文档。
把下面jar包拷贝到lib目录下,并加入到Build Path里。
(1)使用S2Container必须的文件
s2-framework-2.4.42.jar
commons-logging-1.1.1.jar
javassist-3.4.ga.jar
ognl-2.6.9-patch-20090427.jar
(2)使用S2AOP必须的文件
aopalliance-1.0.jar
(3)使用Java EE服务器以外的Servlet容器(比如Tomcat)时必须的文件
geronimo-jta_1.1_spec-1.0.jar
使用Seasar2的扩张机能(S2JTA, S2DBCP, S2JDBC, S2Unit, S2Tx, S2DataSet)的时候必须要将以下文件追加到CLASSPATH里面。
lib/junit-3.8.2.jar
lib/poi-2.5-final-20040804.jar
lib/s2-extension-2.x.x.jar
lib/geronimo-jta_1.1_spec-1.0.jar (参考下面)
lib/geronimo-ejb_2.1_spec-1.0.jar (参考下面)
resources/jdbc.dicon
根据应用软件所需的执行环境、选择以下需要引用的文件[geronimo-j2ee_1.4_spec-1.0.jar、geronimo-jta_1.0.1B_spec-1.0.jar、geronimo-ejb_2.1_spec-1.0.jar]
环境 geronimo-j2ee_1.4_spec-1.0.jar geronimo-jta_1.1_spec-1.0.jargeronimo-ejb_2.1_spec-1.0.jar
不完全对应J2EE的Servlet container
(Tomcat等) 不要 要
(使用S2JTA,S2Tx的时候) 要
(使用S2Tiger的时候)
完全对应J2EE的应用服务器
配置文件convention.dicon中配置src目录中的RootPackage包,名字自定义。
Passbook系统配置了两个RootPackageName
"jp.bric.passo.web"
"jp.bric.passo.db"
1.Rootpackage下典型Java包
Action
系统中所有Action处理,都在RootPackage.Action下做定义,比如
与http://hostname/projectname/xxx/这样的URL相对应的Action类,就是XxxAction.java (注意大小写)
ActionForm
Action中对应的ActionForm,都在RootPackage.Form下做定义,比如,XxxAction中对应的ActionForm,其名称就对应为XxxForm。(注意大小写)
ActionForm的作用就是接受来自页面提交的输入/输出参数。(虽然这些参数都可以在action中定义,但从维护,可读的角度来要求,我们规定,对于Action处理中需要处理的页面输入输出参数,都要在ActionForm类中相应的变量定义)
Entity
系统中所有Entity类,都在RootPackage.Entity下做定义。Entity是用户储存数据库持久层的数据实体类。对于Entity的命名没有特别的规定。一般情况下可沿用数据库表的名称。
Passbook系统中为了更好的维护Entity放在了DB子项目中。
Service
对Entity操作的类,这里我们称为Service(业务逻辑层)。系统中所有Entity类,都在 RootPackage.Service下做定义。Serivce的类名称形如 XxxService, 因为一个Service通常与一个Entity对应, 所以Xxx部分原则上使用与之对应的Entity名称。(*关于数据持久层的说明,请参考S2JDBC)
Util
工程,项目中会经常调用到的自定义的工具类,可都存放在在RootPackage.Util下,没有特别的命名要求。
2.应用程序体系结构
Seasar2框架从根本上来说,也是基于MVC(ModelView Controller)体系结构衍生而成的, Model对应Entity、View对应JSP、Controller对应Action。
一个Action中,可以包含多个执行方法(函数)。通常,一个UseCase映射一个Action。在B/S系统中也可以认为,业务由多个页面构成,也页面是构成UseCase的基本单位。而数据如何在页面中显示的逻辑,则在Action中做编码定义。
业务逻辑处理一般定义在Service中。也有一种设计观点认为。通过Dao(DataAccess Object),可以进一步把数据访问从业务逻辑中抽出,以应对开发过程中发生的数据访问层框架的变更。但这种情况基本上不会发生,故在Seasar2框 架中,数据访问处理可以直接写在业务逻辑中。
Action详解
服务器端用以响应处理浏览器请求(Request)的类,这里称之为Action。 在Struts和Struts2里,URL和Action的对应关系,需要在struts-config.xml中定义,在Seasar2框架中,这个对应关系通过下述约定的命名规则由框架自动确定,不需要再对struts-config.xml做任何编辑修改。“约定优于配置”原则的体现。
举例来说http://localhost:8080/seasar2-tutorial/login/
首先,seasar2-tutorial是应用名称。
再往后,Seasar2将根据下述规则,将URL转换定位到对应的Action。
* Web应用名之后的是请求的路径,/login/ , 将路径最后的/去掉,并加上Action后缀, 变成 loginAction
* 将首字母转换为大写字母 loginAction --〉LoginAction
* 前置RootPackage.action.
* 通过上面3步的转换,Seasar2最终将、/login/ 这个URL映射到tutorial.action.LoginAction 这个类。
如果请求URL中没有ActionPATH,框架会查找是否存在有RootPackage.action.IndexAction这个类,并加载执行。http://localhost:8080/seasra2-tutorial/ 对应的Action就是 tutorial.action.IndexAction。补充,如果想让系统缺省调用IndexAction,要注意Web的根目录下不要存放有 index.jsp。其执行的优先度高于IndexAction。
ActionPath可以进一步分割子集,如/aaa/bbb/这样的请求url的ActionPath,对应执行RootPackage.action.aaa.BbbAction类。
Seasar2中的Action类,是POJO(最普通的Java类)。 不需要继承Strtus的Action。
Struts2中一般extends ActionSupport .
在Action中通常会要使用到ActionForm,引用ActionForm时,请附加@ActionFormと@Resource注解,引用的ActionForm的实体变量名,固定定义为ActionForm类名的(首字母需转为小写)
Strtuts2中利用Value Stack来获取Action的参数和用户输入的参数。
ActionForm 引用示例
@ActionForm
@Resource
protected AddForm addForm;
向JSP页面输出的值以及而来自JSP页面提交的数据,既可以在Action类中作为属性定义,也可以将其定义在ActionForm中。但原则上请定义都在ActionForm中。
DTO
当有些信息(如Login用户名)需要在Session中管理保存时,将其放入RootPackage.dto.XxxDto。并通过注解@Component声明他将在Session中管理。
@Component(instance = InstanceType.SESSION)
public class UserDto implements Serializable {
private static final long serialVersionUID = 1L;
public String userName;
...
}
在Action中使用上述UserDto时、需要做如下变量定义,前置注解@Resource,变量名为类名称,首字母替换为小写。
@Resource
protected UserDto userDto;
相似的,我们编写的业务逻辑代码,都汇集到RootPackage.service包下,命名为XxxService。
public class XxxService {
...
}
在Action中,需要调用Service时, 按相同的命名规范定义实体,如下:
@Resource
protected XxxService xxxService;
同理,对于HttpServletRequest、HttpServletResponse等Servlet API相关的对象,也可通过前置@Resource注解的方式建立引用。
示例代码:
public class MyAction {
@Resource
protected HttpServletRequest request;
@Resource
protected HttpServletResponse response;
@Resource
protected HttpSession session;
@Resource
protected ServletContext application;
...
}
Method(函数)
Action类中的,对应于Request请求的处理函数,称为执行方法(函数),
方法名(函数名)任意,前置@Execute注解。方法的返回值是String,方法不带任何参数。
@Execute
public String xxx() {
...
return ...;
}
执行方法的返回值,就是处理完成后页面跳转的地址,
如返回值不是以/开头的,系统将以Action所在Path作为相对路径的起始点。
例如,/add/的Action的返回值为index.jsp时,跳转的页面即为/add/index.jsp。在web.xml中有一个VIEW_PREFIX参数,用以确定该jsp文件在工程中的位置。
在seasar2-tutorial工程里,VIEW_PREFIX的值为/WEB-INF/view,所以跳转页面所在的位置就在/WEB-INF/view/add/index.jsp。
如返回值是以/开头的,系统则认为是Web应用的根地址为相对路径的起始点。
例如,返回值是/select/时,跳转的页面就是http://localhost:8080/seasar2-tutorial/select/ 。
页面的跳转方法,缺省为FORWARD,当需要使用重定向方式是,需要在返回路径的后面追加redirect=true的参数。形如,
...
return"xxx.jsp?redirect=true";
...
return"xxx.jsp?key=value&redirect=true";
跨域跳转
...
return "https://hostname/appname/path/?redirect=true";
也有些情况,如文件下载等请求处理,只通过Response直接输出而没有跳转时,执行方法的返回值设为null。
一个Action类中,可以定义多个执行方法。具体到哪一个方法被调用,则是由请求URL,JSP页面提交按钮的Name属性值来确定。
如下的例子、AddAction#index()将被调用执行。
http://localhost:8080/sa-struts-tutorial/add/index
http://localhost:8080/sa-struts-tutorial/add/ (url中没有执行方法名时,index()方法将被缺省调用)
故上述2个URL等效。
再看一个页面提交的例子
JSP页面的按钮定义为
此提交按钮标签的name属性值是comfirm。Action类中的conFirm()方法负责处理通过此按钮提交后的一系列工作。
如希望窗体提交前做必要的输入数据验证,Seaser2提供了比较简易的方法。 将@Execute注解的validator参数置为true。缺省为true。
当验证出错后,页面将跳转到input参数指定的页面。
@Execute(validator = true, input ="edit.jsp")
ActionForm
ActionForm,主要用于管理Request中的输入输出参数,是一个普通的POJO。
输入输出参数的名字对应ActionForm内定义各属性变量。
Seasar2中,Action的生存期缺省为Request周期,如要提升为Session生存周期,需要增加一行注解的描述。
@Component(instance = InstanceType.SESSION)
public class XxxForm implements Serializable {
private static final long serialVersionUID = 1L;
...
}
需要注意的一点,需要归入Session生存周期管理的组件,必须implements Serializable。
进一步,使用对输入对象做数据验证,也是在ActionForm中通过对属性变量前置注解来实现。
@Required
public String arg1;
* 对arg1做必须输入的验证
Ajax
Seasar2支持Ajax调用。最常见的是通过jquery调用。
Ajax调用时,Action的执行方法内,直接使用ResponseUtil.write("字符串"),输出字符文本。也没有页面迁移,所以方法的返回值是null。
@Execute(validator = false) public String hello() { ResponseUtil.write("Hello Ajax"); return null; }
在jsp文件中,使用到jquery,需要包含jquery库。
...
onclick="$('#message').load('hello');"/>
上例中,在jsp页面中,通过$('TAGid').load('执行方法名');的写法,可以将Ajax调用的输出结果赋回页面的指定元素(TAGID)。
3.DB访问配置
S2JDBC持久层框架,快速实现对数据库数据的增删查改和事务控制。
前面的章节提到过,对数据库操作的业务逻辑,可以放在Service层中实现。
Seasar2框架中缺省包含有S2JDBC持久层框架,实现对各类数据库的通用支持,与数据库连接有关的配置信息,都通过配置文件jdbc.dicon统一管理。
S2JDBC支持多种市面常见的数据库,这些信息定义在配置文件s2jdbc.dicon中,根据项目实际情况进行修改。
Passbook系统使用MySQL配置信息如下:
s2jdbc.dicon
mysqlDialect
jdbc.dicon
100
-->
100
-->
class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
"com.mysql.jdbc.Driver"
"jdbc:mysql://localhost:3306/passo_web?characterEncoding=utf8"
"root"
""
class="org.seasar.extension.dbcp.impl.ConnectionPoolImpl">
600
10
true
null
0
class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
在实际代码中,我们通过JdbcManager实例与持久层框架S2JDBC交互。
Service或者Action中,引用JdbcManager的方法如下:
@Resource
protected JdbcManager jdbcManager;
自动事务
在Action或Service中,如需要打开或关闭例外,异常发生后的事务自动回滚,请修改配置文件cutomizer.dicon。
class="org.seasar.framework.container.customizer.CustomizerChain">
class="org.seasar.framework.container.customizer.TxAttributeCustomizer"/>
...
class="org.seasar.framework.container.customizer.CustomizerChain">
class="org.seasar.framework.container.customizer.TxAttributeCustomizer"/>
...
S2JDBC示例
下面的例子,演示了如何通过jdbcmanager,完成对数据库表的CRUD操作。
· SELECT
/*多条结果*/
List results =jdbcManager.from(Employee.class)
.join("department")
.where("id in (? , ?)", 11, 22)
.orderBy("name")
.getResultList();
/*单条结果*/
Employee result =
jdbcManager
.from(Employee.class)
.where("id = ?", 1)
.getSingleResult();
System.out.println(result.name);
* Employee是一个Entity,其属性一般都对应于数据库表的定义,其中没有业
@Entity
public classEmployee {
/** id字段属性 */
@Id
@Column
public Integer id;
/** name 字段 属性 */
@Column
public String name;
/** jobType 字段 属性 */
@Column
public IntegerjobType;
/** salary 字段 属性 */
@Column
public Integer salary;
/** departmentId 字段 属性 */
@Column(nullable =true, unique = false)
public IntegerdepartmentId;
/** addressId 字段 属性 */
@Column(nullable =true, unique = true)
public IntegeraddressId;
}
Select (结果分页)
List < Employee > results =
jdbcManager
. from ( Employee . class )
. orderBy ( "id" )
. limit ( 5 )
. offset ( 4 )
. getResultList ();
for ( Employee e : results ) {
System . out . println ( e . id );
}
Insert
public void testInsertTx () throws Exception {
Employee emp = new Employee ();
emp . name = "test" ;
emp . jobType = JobType . ANALYST ;
emp . salary = 300 ;
jdbcManager . insert ( emp ). execute ();
}
Update
mployee emp =
jdbcManager
. from ( Employee . class )
. where ( "id = ?" , 1 )
. getSingleResult ();
emp . name = "hoge" ;
jdbcManager . update ( emp ). execute ();
Delete
Employee emp =
jdbcManager
.from(Employee.class)
.where("id = ?", 1)
.getSingleResult();
jdbcManager.delete(emp).execute();
emp =
jdbcManager
.from(Employee.class)
.where("id = ?", 1)
.getSingleResult();
对于单表的简单数据表操作,可以通过以上的方式完成。对于多表结合等复杂SQL的操作,JdbaManager提供另外一种直接执行SQL或SQL文件的方法。
代码内SQL定义
private static final String SELECT_SQL =
"select e.*, d.name as department_name"
+ " from employee eleft outer join department d"
+ " one.department_id = d.id"
+ " where d.id =?" ;
...
List < EmployeeDto > results =
jdbcManager
. selectBySql ( EmployeeDto . class , SELECT_SQL , 1 )
. getResultList ();
for ( EmployeeDto e : results ) {
System . out . println ( e . name + " " + e . departmentName );
}
执行外部SQL文件定义
SelectParam param = new SelectParam ();
param . salaryMin = new BigDecimal ( 1200 );
param . salaryMax = new BigDecimal ( 1800 );
List < EmployeeDto > results =
jdbcManager
. selectBySqlFile (
EmployeeDto . class ,
"examples/sql/employee/selectAll.sql" ,
param )
. getResultList ();
看一下sql文件是如何定义的examples/sql/employee/selectAll.sql:( SQL定义文件必须保存为UTF-8编码)。
select * from employeewhere
salary <= /*salaryMin*/ 1000
and salary >= /*salaryMax*/ 2000
SQL中的查询参数,可以是Map,也可以是一个POJO,只要KEY或者属性名和sql文件中定义的参数名称一致即可。
SelectParam .java:
package examples . entity . Employee ;
public class SelectAllParam {
public BigDecimal salaryMin ;
public BigDecimal salaryMax ;
}
复杂SQL的分页,注意,实现分页的SQL定义中,一定要有order by。
jdbcManager
.selectBySql(
EmployeeDto.class,
"select id,name from employee order by name")
.limit(100)
.offset(10)
.getResultList();
通过SQL实现,insert/update/delete
jdbcManager
.updateBySql(
"update employee set salary = ? where id = ?",
BigDecimal.class,
Integer.class)
.params(null, 1)
.execute();
4.Q&A?
Question 1:Web.xml中是否可以定义自己的Servlet?
Answer:可以。Seasar框架本身支持开发者自己编写的Servlet。但主要注意的是。当在Servlet中需要用到Seasar2提供的组件时,
请务必通过SingletonS2ContainerFactory(单例容器工厂)对象建立引用。
例:在servlet中使用jdbcmanager.
final String PATH = "s2jdbc_cloud.dicon";
SingletonS2ContainerFactory.setConfigPath(PATH);
SingletonS2ContainerFactory.init();
S2Container s2Container = SingletonS2ContainerFactory.getContainer();
jdbcmanager = (JdbcManager)s2Container.getComponent("jdbcManager_cloud");
Question 2:s2jdbc是否支持oracle的BLOB字段类型?
Answer:支持。entity类中定义的字段项加注@Lob 注解即可
例:
@Lob
@Column(name = "IMAGEDATA")
public byte [] imagedata ;