Struts2框架入门
前言: 因最近工作项目的需要,所以去学习了struts2框架,本文记录struts2框架学习的过程以及出现的问题,希望可以大家提供帮助.
1 Struts2入门知识
1 Struts2概述
struts2框架是应用JAVAEE的MVC三层架构.
WEB层常用框架:
- Struts2
- SpringMVC
2 Struts2框架入门
1 导入jar包
2 创建action
3 配置action类访问路径
- 创建struts2核心配置文件 (名称和位置固定必须在src下面名称struts.xml)
- 引入dtd约束
访问路径: http:127.0.0.1:8080/struts2_day01/hello.action
(struts2_day01为自己设置项目访问路径)
页面报错: 404
原因: 没有配置过滤器. 配置过滤器可以成功访问.
3 Struts2访问流程
- 1 地址栏访问 http://127.0.0.1:8080/struts2_test1/hello.action
- 2 过滤器进行过滤,获取请求路径,得到路径里面hello值.
- 3 到src下面找到struts.xml,使用dom4j解析得到xml文件中内容,拿着hello值到xml文件找action标签,匹配name属性值是一样.
- 4 匹配name属性值一样,都是hello,找到name属性所在的action标签里面,另外一个属性class值,得到action全路径,使用反射实现功能.
Class clazz = Class.forName(“action全路径”);
Method m = clazz.getMethod("execute);
得到名称是execute的方法
方法执行,有返回值.
Object obj = m.invoke();
- 5 得到action方法的返回值,在struts.xml文件中,找到action标签里面,有result标签,匹配标签的name属性,判断是否一样,一样,跳转到配置页面中.
4 Struts2过滤器源代码查看
1 StrutsPrepareAndExecuteFilter 实现Filter
init方法中主要加载配置文件,包括自己创建的配置文件和struts2自带配置文件(struts-default.xml 等) struts.xml和web.xml
.
5 Struts2核心配置文件Struts.xml分析
1 名称和位置固定的
位置: src目录下
名称: struts.xml
2 标签
1 package
作用: 类似代码包,区别不同的action.
标签属性:
- name: 属性值和功能没有关系,属性值不能相同.
- extends: 固定为struts-default,写了之后,在package里面配置的类具有action功能.
- namespace: 属性值和action标签里面的name构成访问路径 一般为/ ,不写默认为/.
2 action
作用: 配置action访问路径.
标签属性:
- name: package标签的namespace属性值和name构成访问路径,package中可以写多个action,但是action的name不能相同.
- class: action全路径.
- method: action中默认执行execute方法,使用method,可以执行指定的方法,如add,update等.
3 result
作用: 根据action方法的返回值,配置到不同的路径里面.
标签属性:
- name: 等于action的方法的返回值(字符串)
- type: 如何到路径中去(转发或者重定向) 默认做转发操作
4 常量配置
- 1: 在struts.xml中定义
<constant name="Xxx" value="Xxx"></constant>
(如, 设置struts.il8n.encoding为UTF-8) - 2: 在src下创建struts.properties,此文件是框架的默认配置文件
- 3: 在web.xml中进行配置
3 Action的三种编写方式
1 创建普通类,不继承任何类,不实现任何接口.
2 创建类,实现接口Action 重写execute方法,还提供了常量可以使用.
3 创建类,继承ActionSupport ,这个类也实现了Action接口.
4 访问action的常用方法
1 使用action标签的method属性
Action类中每一个方法对应,struts.xml中一个action.
2 使用通配符方式实现(重点)
<action name="book_*" class="..." method="{1}">
ps:
book_add 可以访问 method=add 所以method用{1} 等于 *号
book_update 可以访问 method=update 所以method用{1} 等于 *号
3 动态访问实现 (少用)
1: struts2默认是关闭的,需要开启一个常量
<constant name="struts.enable.DynamicMethod Invocation" value="true"></constant>
2: 动态方法访问的配置:
<action name="userAction" class = "类全名"></action>
3: 页面路径访问:
<a href="${pageContext.request.contextPath}/userAction!save.action"> 添加用户</a>
5 其他问题
1 在action获取表单通过post方式提交.中文乱码问题框架帮忙解决了,不需要自己处理.
2 分模块开发
1 单独写每一个配置文件,
2 把每一个配置文件引入核心配置文件struts.xml中<include file="文件全路径"/>
注意: 导入多个文件报错时, 首先去检查package的name是否有相同的,因为最终引入struts2中,所以不能有相同的.
3 常见错误演示
- 1: 404 No Result defined for action …
原因: 在action中没设置返回值. - 2: 在action里面的方法有返回值,如果有返回值时候类型必须是String类型.
- 3: action里面的方法可以没有返回值,没有返回值时候,在result标签不需要配置
- 把方法上写void
- 让返回值,返回"none"
2 Struts2数据操作
1 结果页面配置
全局结果页面: 在package里面,如果返回结果一样,可以统一处理.
<global-results><result name="success" >/index.jsp</result></global-results>
局部结果页面: 如果全局和局部都存在,以局部为准
<result name="success" >/index2.jsp</result>
result标签type属性:
- 1: 默认值,做转发操作,值是dispatcher
- 2: 做重定向操作,值是redirect
- 配置到其他的action里面: 直接访问action名称
- 3: chain:转发到action,一般不用,缓存问题
- 4: redirectAction: 重定向到action
2 action获取表单提交数据
1 之前web阶段,表单提交到servlet里面,使用getParameter, getParameterMap.
2 提交到表单中,action没有request对象,不能直接使用.
3 action获取表单提交数据的三种方式.
1 使用ActionContext类
说明: 因为方法不是静态方法,需要创建 ,不是new出来的,使用静态方法 ActionContext.getContext获取当前线程的ActionContext(使用getParameters()得到map 根据key得到value,value为数组(因为存在复选框的情况)
)
2 使用ServletActionContext类
调用类里面静态方法,得到request对象ServletActionContext.getRequest()得到request
,request.getParameter(“Xxx”)得到值.
3 使用接口注入方式(很少)
说明:让action实现接口 ServletRequestAware,重写方法得到request,首先定义request,在方法中赋值request.
3 Struts2提供获取表单数据方式
1 属性封装
说明: 直接把表单提交的属性封装到action的属性(成员变量)里面,不能封装到实体类中.
1 action成员变量和表单字段一样
2 生成变量的set和get方法
2 模型驱动封装
说明: 可以直接把表达封装到实体类对象里面(实体类以User为例).
1 action实现接口ModelDrivern<实体类>
2 实现接口里面的方法,getModel方法,把创建对象返回 return user
3 在action里面创建实体类对象 User user = new user()
要求:实体类属性和表单属性名相同
模型驱动和属性封装注意问题: 不能同时使用两者获得同一个表单.同时使用,只会执行模型驱动.
3 表达式封装(会用)
- 1 在action中声明实体类
private User user
- 2 生成实体类变量的set和get方法
setUser()/getUser()
- 3 在表单输入项的name属性值里面写表达式形式
user.username/user.password
比较表达式封装和模型封装:
相同点: 都可以把数据封装到实体类.
不同点: 模型封装只能封装一个实体类,表达式封装可以封装多个实体类.
4 Struts2获取数据封装到集合中
1 封装到list集合
1: 在action声明List变量 List<User> list
2: 生成set和get方法
3: 表达式传入参数 list[0].name list[0].password
2 封装到map集合
1: 声明map集合 Map<String,User> map
2: 生成get和set方法.
3: 表达式传入参数 map['one'].username map['one'].password
3 Struts2值栈
1 引入OGNL
1 OGNL概述
ognl: 表达式,功能更加强大.
- 在struts2里面操作值栈数据
- 一般把ognl在struts2操作,和struts2标签一起使用操作值栈
说明: OGNL不是struts2的一部分,单独的项目,经常和struts2一起使用.(使用ognl首先需要导入jar包,struts2提供jar包)
2 OGNL入门案列
支持对象方法调用:
Q:
- 使用ognl+struts2标签计算字符串长度
A:
- 1 使用struts2标签,引入标签库
<%@ tablib uri="/struts-tags" prefix="s" %>
- 2
<s:property value="'haha'.length()" />
得到字符串长度
2 值栈
1 什么是值栈
1. 引入: 在web阶段,在servlet里面进行操作,把数据放到域对象里面,在页面中使用el表达式获取到,域对象在一定范围内,存值和取值.
2. 值栈: Struts2 里面提供本身一种存取机制,类似于域对象.
3. Servlet和Action区别:
- Servlet:默认在第一次访问时候创建,只创建一次.
- Action:访问时候创建,每次访问action时候,都会创建action对象,创建多次,多实例对象.
4. 值栈存储位置:
- 每次访问action时候,都会创建action对象
- 在每个action对象里面都会有一个值栈对象
5. 获取值栈对象:
使用类ActionContext类里面的方法得到值栈对象ValueStack value = ActionContext.getContext().getValueStack()
(每个action中只有一个值栈)
6. 栈数据结构:
特点: 先进后出 (最上面元素称为栈顶元素)
组成: root 和 context
- root: 结构是List集合,其中CompoundRoot 继承ArrayList.
- context: 结构Map集合,其中OgnlContext 实现Map接口.
context内容:
key (固定) | value |
---|---|
request | request对象引用 |
session | HttpSession对象引用 |
application | ServletContext对象引用 |
parameters | 传递相关的参数 |
attr | 三个域对象值 |
注意: attr中属性名称相同,使用attr操作,获取域对象里面的值,是获取域对象最小的值
(如上面域同时存在相同值,使用attr操作,从request域中获取)
s:dubug: 可以查看栈值结构和存储值,只能由于调试(在页面直接写该标签)
- 在action没有做任何操作,栈顶元素就是action对象
- 值栈对象里面有action引用
2 向值栈存入数据
方法一:
- 1 先获取值栈对象
- 2 调用值栈对象里面的set方法
stack.set("key","value")
方法二:
- 1 先获取值栈对象
- 2 调用值栈对象里面的push方法
stack.push("value")
方法三: (使用较多)
- 1 先定义变量,生成变量get方法
- 2 在执行方法里面向变量设置值
- 3 优点: 在action对象里面创建,减少内存浪费.
向值栈放对象: 存在action对象里面,不会浪费空间
- 1 定义对象变量
- 2 生成变量get方法
- 3 在执行方法里面设置值
向值栈放list对象:
- 1 定义list集合变量
- 2 生成变量的get方法
- 3 在执行的方法里面设置值
3 向值栈获取数据
获取数据: 使用struts2标签+ognl表达式获取值栈
<s:property value="Xxx" />
1 获取字符串: 如 <s:property value="username" />
2 获取获取对象属性: 如 <s:property value="user.username" />
(先使用action的get方法得到对象,再用对象中get方法得到值)
3 获取list集合:
- 方法1: 写固定了,不方便修改
<s:property value=“list[0]” /> 得到对象
<s:property value=“list[0].username” /> 得到属性
- 方法2: struts2标签
<s:iterator>
类似jstl中foreach标签
<s:iterator value ="list">
<!--遍历得到每一个user对象-->
<s:property value="user" />
</s:iterator>
- 方法3:
<s:iterator value ="list" var="user">
1 遍历得到每一个user对象, 优化机制,每次遍历出来的user对象会放到context里面(提高取值效率).
2 临时存放,map -> var值 user对象 (context数据特点Map)
<s:iterator value ="list" var="user">
<-- 写ognl表达式 使用特殊符号# -->
<s:property value="#user.username" />
</s:iterator>
4 其他操作:
- 1 使用set方法设置的 ,获取值
<s:property value="username" />
- 2 使用push方法,获取值,(向值栈中存的数据,会放到数组中,数组名为top),
<s:property value="[0].top" />
,取到数组中第一个值,即为栈顶值.
4 常见问题
1: html注释和jstl注释不一样,html注释不能注释jsp中标签.
- html注释
<--! Xxx -->
- jsp注释
<%-- Xxx --%>
2: EL表达式获取值栈数据(为什么?): (性能低)
- 1 EL表达式获取域对象值
- 2 向与对象里面放值使用setAttribute方法,获取值使用getAttribute方法
- 3 底层增强request对象里面的方法getAttribute方法
- 1 首先从request域获取值,如果获取值,直接返回
- 2 如果从request域获取不到值,到值栈中把值获取出来,把值放到域对象里面
- 4 查看源代码 WrapRequest 增强 (增强: 继承或动态代理)
3: OGNL中# 和% 的使用:
- 1 使用
#
获取context里面参数request.setAttribute("req","");
<!-- context的key名称.域对象 -->
<s:property value="#.request.req" />
- 2
%
使用在struts2标签中表单标签
<!-- 在struts2标签里面直接使用ognl表达式,不能识别,需要使用% -->
<s:textfield name="username" value="%{#.request.req}" ></s"textfield>
4: 标签中使用的c:或s:
- 在页面开头设定的 <%@ tablib uri="/struts-tags" prefix=“s” %> 中prefix有关.
4 Struts2拦截器
1 拦截器概述
1: struts2是框架,封装了很多的功能,struts2里面封装的功能都是在拦截器里面.
2: struts2 有很多拦截器,不是每次都执行这些拦截器,每次执行默认的拦截器.
3: struts2 中默认拦截器位置:struts2-core包中 struts-default.xml中
<!-- 拦截器-->
<interceptor name="Aaa",class="Xxx" >
<!-- 引用拦截器-->
<interceptor-ref name="Aaa" >
4: 拦截器执行时机: 在action对象创建之后,action方法执行之前
2 拦截器底层原理
1 AOP思想
AOP: 面向切面编程 有基本功能,扩展功能,不通过修改源代码方式扩展功能.
2 责任链模式
在java中有很多的设计模式,责任链是其中一种,责任链模式和过滤链很相似的.
过滤链: 一个请求可有多个过滤器进行过滤,每个过滤器只有做放行才能到下一个过滤器.
责任链: 要执行多个操作,有添加,修改,删除操作,每个操作完成之后,放行.
Q: aop思想和责任链模式如何应用到拦截器里面?
A:
- 1 在action创建之后,action的方法执行之前.
- 2 所在action方法执行之前执行默认的拦截器,执行过程使用aop思想, 在action没有直接调用拦截器的方法,使用配置文件方式进行操作.
- 3 在执行拦截器时候,执行很多拦截器,这个过程使用责任链模式.
3 源代码分析
1: doFilter方法中 执行action execute.Action(request,response,mapping)
2: action创建对象 使用动态代理 ActionProxy proxy
3: 执行方法 proxy.execute();
4: 遍历拦截器 if(interceptors.hasNext()){}
, 类似放行操作的方法: return invocation.invoke();
重点区分:
1 过滤器和拦截器区别:
- 过滤器: 理论上可以过滤任意内容,比如html,jsp,图片路径等
- 拦截器: 只可以拦截action
2 Servlet和action区别:
- Servlet 默认第一次访问时候创建,创建一次,单实例对象
- action每次访问创建,创建多次,多实例对象
4 自定义拦截器
1 引入
Q: Why DIY ?
A: 在struts2里面有很多拦截器,但在实际开发中,有些功能没有,需要自己实现.
拦截器结构:
- 1 继承了AbstractInterceptor
- 2 实现Interceptor接口, 有三个方法 init()初始化 , destroy()销毁, interceptor()拦截操作.
2 自定义拦截器的方法
方法1: 可以自定义一个类,继承继承了AbstractInterceptor 也可以直接去实现Interceptor接口,建议继承 ,需要就重写,不需要可以不处理.
(推荐)方法2: 创建类, 继承MethodFilterInterceptor类实现,可以实现让action里面某个方法不进行拦截.
拦截器和action建立关系: 通过配置文件建立关系.
- 1 在要拦截的action里面所在package标签里面申明拦截器
<interceptors>
<interceptor name="adc" class= "Xxx"></interceptor>
</interceptors>
- 2 struts2里面执行很多默认拦截器,但是如果使用自定义的拦截器,就不会使用默认拦截器,需要手动加上默认拦截器.
<!-- action引用拦截器-->
<interceptor-ref name= "adc" ></interceptor-ref>
<!--加上默认拦截器 -->
<interceptor-ref name= "defaultStack" ></interceptor-ref>
- 3 配置的拦截器,对action中所有方法都进行拦截
- 1 像登录的方法,不应该拦截,否则,永远登录不上.
- 2 解决:让login不进行拦截 .
<!-- 直接通过配置不拦截某些方法 -->
<param name="excludeMethods">login <param>
<!-- 多个方法用逗号隔开 如login,logout-->
<interceptor-ref name= "adc" >
<param name="excludeMethods">login,logout<param>
</interceptor-ref>
5 Struts2的标签库
1: s:property:
和ognl表达式在jsp页面获取值栈数据
2: s:iterator:
获取值栈list集合数据,表示list集合
3: s:debug:
查看值栈结构和数据