struts2简介
struts2是在webwork2基础上发展而来的。和struts1一样,struts2也属于MVC框架。不过有一点需要注意的是:struts2和struts2虽然名字很相似,但是在两者在代码编写风格上几乎是不一样的。那么既然有了struts1,为什么还要推出struts2。主要的原因是struts2有以下优点:
1.在软件设计上struts2没有像struts1那样跟servlet API和struts API有着紧密的耦合,struts2的应用可以不依赖于servlet API和struts API。struts2的这种设计属于无侵入式设计,而struts1却属于侵入式设计。
2.struts2提供了拦截器,利用拦截器可以进行AOP编程,实现如权限拦截等功能。
3.struts2提供了类型转换器,可以把特殊的请求参数转化成需要的类型。在struts1中,如果我们要实现同样的功能,就必须向struts1的底层实现BeanUtil注册类型转换器才行。
4.struts2提供支持多种表现层技术,如:jsp、freemarker、velocity等。
5.struts2的输入校验可以对指定的方法进行校验,解决了struts1长久之痛。
6.提供了全局范围、包范围和Action范围的国际化资源文件实现。
struts2开发环境搭建
搭建struts2(这里使用的是2.1.8版的)的开发环境的时,一般都会按如下的步骤:
1.引入struts2需要的jar文件(一般需要commons-fileupload-1.2.1.jar、commons-logging-1.0.4.jar、freemarker-2.3.15.jar、ognl-2.7.3.jar、struts2-core-2.1.8.1.jar和xwork-core-2.1.6.jar这6个jar文件,可以从struts2自带的示例项目中拷贝,粘贴到WebRoot/WEB-INF/lib下面)
2.编写struts2的配置文件(可以从struts2自带的示例项目中拷贝struts.xml,粘贴到src目录下,然后在这个基础上按照自己的需要来更改)
3.在web.xml中加入struts2框架启动配置,具体的方法是在web.xml中加入如下的代码:
struts2
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
struts2
/*
从上面可以看出,struts2框架是通过filter启动的。在StrutsPrepareAndExecuteFilter的init()方法中读取类路径下默认的配置文件struts.xml完成初始化操作。
注意:struts2读取到struts.xml中的内容后,以javabean的形式保存在内存中,以后struts2对用户的每次请求处理将使用内存中的数据,而不是每次都读取struts.xml文件。
struts2配置中的包介绍
/success.jsp
配置包是必须指定name属性,该name可以随意取名,但是必须唯一,它不对应java的类包,如果其他类要继承该包,必须使用该属性进行使用。包的namespace属性用于定义包的命名空间,命名空间作为该action路径的一部分,如访问上面的例子中的action的路径为:/upload/upload.action。namespace属性可以不用配置,如果不指定该属性,默认的命名空间为“”(空字符串)。
通常每个包都必须继承struts-default包,因为struts很多核心的功能都是在这个包中定义的拦截器实现的,如:从请求中把请求参数封装到action、文件上传和数据验证等功能搜是通过拦截器实现的。struts-default包中定义了这些拦截器和result类型。换句话说,当包继承了strtus-default包才能使用struts提供的核心功能。struts-default包是在struts2-core-2.x.x.x.jar文件中的struts-default.xml中定义的。struts-default.xml是struts2的默认配置文件,struts2每次都会自动加载struts-default.xml文件。
包还可以通过abstract=“true”定义为抽象包,抽象包中不能包含action。
注意,在配置文件struts.xml中没有提示的解决办法:window->preference->xml catalog中添加struts-2.0.dtd文件,key type为URI,key为http://struts.apache.org/dtds/struts-2.0.dtd。
action名称的搜索顺序
1.获得请求的URI,例如uri是:http://server/struts2/path1/path2/path3/test.action
2.首先寻找namesp为/path1/path2/path3的package,如果不存在这个package,就转第三步,如果存在这个package,则在这个package中寻找名字为test的action,当在该package中找不到action时就到默认namespace的package中寻找(默认package的命名空间为空字符串“”),如果在默认的package中还找不到该action,页面提示找不到action。
3.寻找namespace为/path1/path2的package,如果不存在这个package,则转第四步,如果存在这个package,则在这个package中寻找名字为test的action,当在该package中找不到action时就到默认namespace的package中寻找(默认package的命名空间为空字符串“”),如果在默认的package中还找不到该action,页面提示找不到action。
4.寻找namespace为/path1的package,如果不存在这个package,则转第五步,如果存在这个package,则在这个package中寻找名字为test的action,当在该package中找不到action时就到默认namespace的package中寻找(默认package的命名空间为空字符串“”),如果在默认的package中还找不到该action,页面提示找不到action。
5.寻找namespace为/的package,如果存在这个package,则在这个package中寻找名字为test的action,当在该package中找不到action或不存在这个package时就到默认namespace的package中寻找(默认package的命名空间为空字符串“”),如果在默认的package中还找不到该action,页面提示找不到action。
action配置中的默认值
department
/employeeAdd.jsp?username=${username}
2.如果没有为action指定method,默认执行action中的execute方法。
3.如果没有为result指定name属性,默认值为success。
action中result的各种转发类型
result配置类似于struts1中的forward,但struts2提供了多种结果类型,常用的类型有dispatcher(默认值)、redirect、redirectAction、plainText。
result中还可以使用${属性名}表达式来访问action中的属性,表达式中的属性名为action中的属性名,如下:
/employeeAdd.jsp?id=4{id}
重定向到同一个包中的action:
add
重定向到别的namespace中的action:
xxx
/redirectAction
/index.jsp
UTF-8
struts2为action的属性提供了依赖注入功能,在struts2的配置文件中,可以很方便的为action中的属性值注入值。注意,属性必须有setter方法。
/upload
/employeeAdd.jsp?username=${username}
上面就是通过节点为action中的savePath注入"/upload"。
常用的常量介绍
default.properties文件中定义了很多常量,下面就说说通常使用到的几个。
struts.i18n.encoding 指定默认编码集
struts.action.extension struts2处理的默认后缀,如果需要定义多个后缀,则用逗号“,"隔开
struts.serve.static.browserCache 设置浏览器是否缓存静态内容,默认为true(生产环境下使用),开发阶段最好关闭
struts.configuration.xml.reload 当struts的配置文件修改后,系统是否自动重新加载该文件,默认值为false(生产环境下),开发阶段最好打开。
struts.devMode 是否为开发模式,默认为false,(生产环境下),开发阶段最好打开,以便打印出更详细的信息
struts.ui.theme 视图主题,一般用simple
struts.objectFactory 于spring集成时,指定由spring负责action对象的创建
struts.enable.DynamicMethodInvocation 是否支持动态方法调用,action!methodname,默认为true
struts.multipart.maxSize 上传文件的大小
struts处理流程
StrutsPrepareAndExecuteFilter是struts2框架的核心控制器,它负责拦截由
为应用指定多个struts配置文件
在大部分应用里,随着应用规模的增加,系统中action的数量也会大量增加,导致struts.xml配置文件变的非常臃肿。为避免struts.xml文件过于庞大、臃肿,提高struts.xml文件的可读性,可以将一个struts.xml文件分解成过个配置文件,然后在struts.xml文件中包含其他的配置文件。下面的struts.xml文件通过include元素指定多个配置文件:
通过这种方式,可以将struts中的action按模块添加在多个
配置文件中。
动态方法调用
在配置文件的action中不指定method,而是在访问的时候通过actionname!methodname这种方式来访问,前提是struts.enable.DynamicMethodInvocation要设置为true。
例如:
/employeeAdd.jsp?username=${username}
使用通配符的定义action
使用通配符*定义action,可以有效的减小配置文件的规模。下面就是一个简单的例子,和上面动态方法调用中的配置文件类似:
employee
/employeeAdd.jsp?username=${username}
假设zhchljr.action.HelloWorldAction类中有add()和update()两个方法,这样定义后,就可以使用”/employee/helloworld_add“来访问add()方法。
接收请求参数
1.采用基本类型接收参数(get/post)
在action类中定义与请求参数同名的属性,并且该属性有set和get方法,struts2就能自动接收请求参数并赋予同名属性。
请求路径:http://localhost:8080/struts2/product/view.action?id=1
public class ProductAction {
private int id;
public int getId() {
return id;
}
public void setId(int id) {//通过反射技术调用与与请求参数同名的属性的setter方法来为该属性设置值
this.id=id;
}
}
请求路径:http://localhost:8080/struts2/product/view.action?product.id=1
public class ProductAction {
private Product product;
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product=product;
}
}
struts2首先通过反射技术调用Product的默认构造器创建product对象,然后再通过反射技术调用product中与请求参数同名的属性的setter方法来设置请求参数值。
struts2.1.6接收中文请求参数乱码的问题是个bug,原因是在获取并使用了请求参数后才调用HttpServletRequest的setCharacterEncoding()方法进行编码设置,导致应用使用的就是乱码参数。这个bug在struts2.1.8中已经被解决,如果使用的是struts2.1.6,要解决这个问题,可以采用下面的方法:
新建一个filter,把这个filter放在struts2的filter前面,然后在doFilter()方法里添加如下的代码:
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws java.io.IOException,ServletException {
HttpServletRequest req = (HttpServletRequest)request;
req.setCharacterEncoding("UTF-8");//根据实际使用的编码替换
chain.doFilter(request,response);
}
1.定义类
public class DateTypeConverter extends DefaultTypeConverter {
@Override
public Object convertValue(Map context, Object value,Class toType) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
try {
if(toType == Date.class) {//字符串向date类型转换
String[] params = (String[])value;
return sdf.parse(params[0]);
} else if(toType == String.class) {//date转换成string类型
Date date = (Date)value;
return sdf.format(date);
}
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
2.将上面的类注册为局部类型转换器
在action所在的包下放置ActionClassName-conversion.properties文件,ActionClassName是action的类名,后面的-conversion.properties是固定写法,在本例中,文件名为HelloWorldAction-conversion.properties。该文件的内容为:
属性名称=类型转换器的全类名
就本例而言,HelloWorldAction-conversion.properties文件中的内容为:
birthday=zhchljr.type.converter.DateTypeConverter
在WEB-INF/classes下放置xwork-conversion.properties文件。该文件中的内容为:
待转换的类型=类型转换器的类全名
对于本例而言,xwork-conversion.properties文件中的内容为:
java.util.Date=zhchljr.type.converter.DateTypeConverter
ActionContext ctx = ActionContext.getContext();
ctx.getApplication().put("app", "应用范围");//相当于往ServletContext中放入app
ctx.getSession().put("ses", "session应用范围");//相当于往session中加入ses
ctx.put("req", "request应用范围");//相当于往request中加入req
ctx.put("names", Arrays.asList("刘明","茫茫大海"));
HttpServletRequest request = ServletActionContext.getRequest();
ServletContext context = ServletActionContext.getServletContext();
request.setAttribute("req", "请求范围属性");
request.getSession().setAttribute("ses", "会话范围属性");
context.setAttribute("app", "应用范围属性");
public class TestAction implements ServletRequestAware, ServletResponseAware, ServletContextAware {
private HttpServletRequest request;
private HttpServletResponse response;
private ServletContext servletContext;
public void setServletRequest(HttpServletRequest request) {
this.request = request;
}
public void setServletResponse(HttpServletResponse response) {
this.response = response;
}
public void setServletContext(ServletContext context) {
this.servletContext = context;
}
}
3.在action类中添加
如下代码中注释的几个属性。public class HelloWorldAction {
private File upload;//得到上传的文件
private String uploadContentType;//得到上传文件的扩展名
private String uploadFileName;//得到上传文件的名称
public File getUpload() {
return upload;
}
public void setUpload(File upload) {
this.upload = upload;
}
public String getUploadContentType() {
return uploadContentType;
}
public void setUploadContentType(String uploadContentType) {
this.uploadContentType = uploadContentType;
}
public String getUploadFileName() {
return uploadFileName;
}
public void setUploadFileName(String uploadFileName) {
this.uploadFileName = uploadFileName;
}
public String upload() throws IOException {
String realpath = ServletActionContext.getServletContext().getRealPath("/upload");
if(upload != null) {
File savefile = new File(realpath,uploadFileName);
if(!savefile.getParentFile().exists()) {
savefile.getParentFile().mkdirs();
}
FileUtils.copyFile(upload, savefile);
ActionContext.getContext().put("msg", "文件上传成功!");
}
return "success";
}
}
3.action中添加的几个属性都是数组形式的。
public class HelloWorldAction {
private File[] upload;//得到上传的文件
private String[] uploadContentType;//得到上传文件的扩展名
private String[] uploadFileName;//得到上传文件的名称
public File[] getUpload() {
return upload;
}
public void setUpload(File[] upload) {
this.upload = upload;
}
public String[] getUploadContentType() {
return uploadContentType;
}
public void setUploadContentType(String[] uploadContentType) {
this.uploadContentType = uploadContentType;
}
public String[] getUploadFileName() {
return uploadFileName;
}
public void setUploadFileName(String[] uploadFileName) {
this.uploadFileName = uploadFileName;
}
public String upload() throws IOException {
String realpath = ServletActionContext.getServletContext().getRealPath("/upload");
if(upload != null) {
for(int i=0; i
public class PermissionInterceptor implements Interceptor {
public void destroy() {
}
public void init() {
}
public String intercept(ActionInvocation invocation) throws Exception {
Object user = ActionContext.getContext().getSession().get("user");
if(user != null) {
return invocation.invoke();
} else {
ActionContext.getContext().put("message", "你没有执行权限!");
}
return "success";
}
}
如果希望包下的所有action都使用自定义的拦截器,可以把拦截器设置为默认拦截器,具体的实现方式是:
public void validate() {//会对action中的所有方法进行校验
if(this.username == null || this.username.trim().equals("")) {
this.addFieldError("username", "用户名不能为空!");
}
if(this.mobile == null || this.mobile.trim().equals("")) {
this.addFieldError("mobile", "手机号不能为空!");
} else {
if(!Pattern.compile("^1[358]\\d{9}{1}quot;).matcher(this.mobile).matches()) {
this.addFieldError("mobile", "手机号格式不正确!");
}
}
}
public void validateUpdate() {//会对action中的update方法进行校验
if(this.username == null || this.username.trim().equals("")) {
this.addFieldError("username", "用户名不能为空!");
}
if(this.mobile == null || this.mobile.trim().equals("")) {
this.addFieldError("mobile", "手机号不能为空!");
} else {
if(!Pattern.compile("^1[358]\\d{9}{1}quot;).matcher(this.mobile).matches()) {
this.addFieldError("mobile", "手机号格式不正确!");
}
}
}
true
用户名不能为空!
true
手机号不能为空!
手机格式不正确!
/WEB-INF/page/message.jsp
/index.jsp
liuming
study
访问包zhchljr.action包下基本名为package的资源文件。
liuming
study
=
不在
在
,价格:
在上面的代码中,直接在集合后跟.{}运算符表明用于取出该集合的子集,{}内的表达式用于获取符合条件的元素,this表示为了从大集合books中筛选数据到小集合,需要对大集合books进行迭代,this代表当前迭代的元素。本例中用与获取集合中价格大于60的书的集合。
姓名:
2.在action中配置token拦截器,如下所示:
/WEB-INF/page/message.jsp
/index.jsp
上面加入了token拦截器和invalid.token result,因为token拦截器在会话状态的token与请求状态的token不一致时,直接返回invalid.token result。