作为struts2的初学者,有时候理解struts框架的原理,网上看教程会一头雾水,所以要是能自己体会实现struts框架的整个过程,对理解struts思路会更加清晰一些,下面就来尝试自己实现这个过程。
先看一下struts2的官方框架结构图
上面那个图好复杂,看不懂没关系,我们先简化一下过程方便理解,因为作为初学者不能一下子就实现了整个框架的所有功能,只能实现他的核心功能,如下图:
这样图就好看很多了,大概意思是我们发送的request请求先经过过滤器筛选,然后到达ActionInvocation,通过这个类加载拦截器,再去找到对应的action,再找到action要返回的jsp界面,然后响应(response)返回显示数据。
官方的图里有Actionproxy,这是个action代理对象,通过他找到ConfigManager读取配置文件然后发送需要的数据到ActionInvocation中,本文是自己实现struts框架,没必要弄的这么复杂,就把这部分融合到ActionInvocation中去了,其他部分就不介绍了,因为我也不懂是啥
下面实现过程,每一个类希望大家参考这个图边看边实现,这样才好理解
接下来要说的就是需要具备的技术:
- XML解析,Xpath表达式.(dom4j)
- Servlet技术
- java内省(BeanUtils)(参数拦截器)
- ThreadLocal线程本地化类
- 递归调用
需要用到的jar包:
(jar其他版本的自己找了)
commons-beanutils-1.8.3.jar
commons-logging-1.1.1.jar
dom4j-1.6.1.jar
jaxen-1.1-beta-6.jar
步骤1:
创建web项目,然后导入jar包
步骤2:
创建struts.xml文件,放在src目录下
<?xml version="1.0" encoding="UTF-8" ?>
<struts>
<interceptor class="com.test.interceptor.ParamInterceptor" />
<constant name="struts.action.extension" value="action" />
<action name="HelloAction" method="regist" class="com.test.action.HelloAction" >
<result name="success" >/index.jsp</result>
</action>
<action name="registAction" method="regist" class="com.test.action.RegistAction" >
<result name="regist" >/home.jsp</result>
</action>
</struts>
步骤3:
创建读取XML配置文件的解析类(ConfigurationManager)
public class ConfigurationManager {
/**
* 读取Interceptor
* @return
*/
public static List<String> getInterceptors(){
List<String> interceptors = null;
//1创建解析器
SAXReader reader = new SAXReader();
//2.加载配置文件=>document
//获得配置文件流
InputStream is = ConfigurationManager.class.getResourceAsStream("/struts.xml");
Document doc = null;
try{
doc = reader.read(is);
}catch(DocumentException e){
e.printStackTrace();
throw new RuntimeException("配置文件加载失败!");
}
//3.书写xpath
String xpath = "//interceptor";
//4.根据xpath获得拦截器配置
List<Element> list = doc.selectNodes(xpath);
//5.将配置信息封装到list集合中
if(list != null && list.size()>0){
interceptors = new ArrayList<String>();
for(Element ele : list){
String className = ele.attributeValue("class");
interceptors.add(className);
}
}
//返回
return interceptors;
}
/**
* 读取action
* @return
*/
public static Map<String,ActionConfig> getActionConfig() {
Map<String, ActionConfig> actionMap;
Document doc = getDocument();
String xpath = "//action";
List<Element> list = doc.selectNodes(xpath);
if(list == null || list.size() ==0){
return null;
}
actionMap = new HashMap<String,ActionConfig>();
for(Element e : list){
ActionConfig action = new ActionConfig();
action.setName(e.attributeValue("name"));
action.setClassName(e.attributeValue("class"));
String method = e.attributeValue("method");
action.setMethod(method==null||method.trim().equals("")?"execute":method);
List<Element> results = e.elements("result");
for(Element result : results){
action.getResult().put(result.attributeValue("name"), result.getText());
}
actionMap.put(action.getName(),action);
}
return actionMap;
}
private static Document getDocument() {
Map<String,ActionConfig> actionMap =null;
//1创建解析器
SAXReader reader = new SAXReader();
//2.加载配置文件=>document
//获得配置文件流
InputStream is = ConfigurationManager.class.getResourceAsStream("/struts.xml");
Document doc;
try {
doc = reader.read(is);
return doc;
} catch (DocumentException e) {
e.printStackTrace();
throw new RuntimeException("加载配置文件失败!");
}
}
/**
* 读取constant
* @param key
* @return
*/
public static String getConstant(String key) {
Document doc = getDocument();
String path = "//constant[@name='"+key+"']";
Element constant = (Element) doc.selectSingleNode(path);
if(constant!=null){
return constant.attributeValue("value");
}else{
return null;
}
}
}
创建需要的ActionConfig类:
public class ActionConfig {
/**
* <action name="" method=""
class="" >
<result name="success" >/index.jsp</result>
</action>
*/
private String name;//对应的是action中的name
private String method;//对应的是action中的method
private String className;//对应的是action中的class
private Map<String,String> result = new HashMap<String, String>();//对应的是action中的result
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public Map<String, String> getResult() {
return result;
}
public void setResult(Map<String, String> result) {
this.result = result;
}
}
步骤4:
创建Struts2的数据中心(ActionContext)
public class ActionContext implements Serializable{
private static final long serialVersionUID = 1294441883362417229L;
//为了保证能获得ActionContext
public static ThreadLocal<ActionContext> tl = new ThreadLocal<ActionContext>();
///用于存放各个域
private Map<String, Object> context;
public ActionContext(Map<String, Object> context) {
this.context = context;
}
public ActionContext(HttpServletRequest request,
HttpServletResponse response, Object action) {
// 准备域
context = new HashMap<String, Object>();
// 1.request
context.put(Constant.REQUEST, request);
// 2. response
context.put(Constant.RESPONSE, response);
// 3. param
context.put(Constant.PARAM, request.getParameterMap());
// 4.session
context.put(Constant.SESSION, request.getSession());
// 5.application
context.put(Constant.APPLICATION, request.getSession()
.getServletContext());
// ---------------------------------------------------
// 6.valuestack 值栈
ValueStack vs = new ValueStack();
// 将action压入栈顶
vs.push(action);
// 将值栈放入request域
request.setAttribute(Constant.VALUE_STACK, vs);
// 将值栈放入数据中心
context.put(Constant.VALUE_STACK, vs);
// -----------------------------------------------------------------
tl.set(this);
}
//下面提供域的获取方法
public HttpServletRequest getRequest() {
return (HttpServletRequest) context.get(Constant.REQUEST);
}
public HttpServletResponse getResponse() {
return (HttpServletResponse) context.get(Constant.RESPONSE);
}
public HttpSession getSession() {
return (HttpSession) context.get(Constant.SESSION);
}
public ServletContext getApplication() {
return (ServletContext) context.get(Constant.APPLICATION);
}
public Map<String, String[]> getParam() {
return (Map<String, String[]>) context.get(Constant.PARAM);
}
public ValueStack getStack() {
return (ValueStack) context.get(Constant.VALUE_STACK);
}
public static ActionContext getActionContext(){
return tl.get();
}
}
需要用到的类(Constant ):
public class Constant {
/**
* request域
*/
public static final String REQUEST = "com.test.request";
/**
* response域
*/
public static final String RESPONSE = "com.test.response";
/**
* session域
*/
public static final String SESSION = "com.test.session";
/**
* application域
*/
public static final String APPLICATION = "com.test.application";
/**
* param域
*/
public static final String PARAM = "com.test.param";
/**
* value_stack域
*/
public static final String VALUE_STACK = "com.test.stack";
}
需要用到的类(值栈):
public class ValueStack {
private List<Object> list = new ArrayList<Object>();
//弹栈
public Object pop(){
return list.remove(0);
}
//压栈
public void push(Object o){
list.add(0, o);
}
//取出顶部对象
public Object seek(){
return list.get(0);
}
}
需要用到的接口(Interceptor):
public interface Interceptor extends Serializable{
/**
* 初始化
*/
public void init();
/**
* 拦截
* @param invocation
* @return
*/
public String interceptor(ActionInvocation invocation);
/**
* 销毁
*/
public void destory();
}
步骤5:
创建ActionInvocation
ActionInvocation负责完成拦截器链状调用以及action调用,以及数据中心(ActionContext)的提供
public class ActionInvocation {
//过滤器链
private Iterator<Interceptor> interceptors;
//即将调用的action实例
private Object action;
//action配置信息
private ActionConfig config;
//数据中心
private ActionContext ac;
public ActionInvocation(List<String> InterceptorClassNames,ActionConfig config,HttpServletRequest request,HttpServletResponse response) {
//1 准备Interceptor链
List<Interceptor> interceptorList = null;
if(InterceptorClassNames!=null && InterceptorClassNames.size()>0){
interceptorList = new ArrayList<Interceptor>();
for(String className : InterceptorClassNames){
Interceptor interceptor;
try {
//获取实例
interceptor = (Interceptor) Class.forName(className).newInstance();
interceptor.init();//初始化
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("创建Interceptor失败!!"+className);
}
interceptorList.add(interceptor);
}
this.interceptors = interceptorList.iterator();
}
//2 准备action实例
this.config = config;
try {
action = Class.forName(config.getClassName()).newInstance();//获取action对象
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("action创建失败!"+config.getClassName());
}
//3 准备数据中心actionContext
ac = new ActionContext(request, response, action);
}
public ActionContext getActionContext() {
return ac;
}
/**
* 递归调用拦截器链
* @param invocation
* @return
*/
public String invoke(ActionInvocation invocation){
//1 准备一个变量接受action运行结果的路由串
String result = null;
//2 判断拦截器链中是否有下一个拦截器&&变量是否被赋值
if(interceptors!= null && interceptors.hasNext() && result==null ){
//有=>调用下一个拦截的拦截方法
Interceptor it = interceptors.next();
result = it.interceptor(invocation);
}else{
//没有=> 调用action实例的处理方法
//获得将要调用的action方法名称
String methodName = config.getMethod(); // execute
//根据action对象和方法名称获得方法对应的Method对象
try {
Method executeMethod = action.getClass().getMethod(methodName);
//调用目标方法
result = (String) executeMethod.invoke(action);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("您配置的action方法不存在!");
}
}
//3将action的结果路由串返回
return result;
}
步骤6:
创建StrutsPrepareAndExecuteFilter 过滤器类:
public class StrutsPrepareAndExcuteFilter implements Filter{
//配置文件中的过滤器配置信息
private List<String> InterceptorList;
//struts处理的action后缀
private String extension;
// 配置文件中action配置信息
private Map<String, ActionConfig> actionConfigs;
/**
* 初始化
*/
public void init(FilterConfig arg0) throws ServletException {
//1> 准备过滤器链配置
InterceptorList = ConfigurationManager.getInterceptors();
//2> 准备constant配置=> 访问后缀的配置信息
extension = ConfigurationManager.getConstant("struts.action.extension");
//3> 加载action配置
actionConfigs = ConfigurationManager.getActionConfig();
}
/**
* 过滤器处理
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//0 强转request和response为 HttpServletRequest 和 HttpServletResponse
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
//1 获得请求路径
// http://localhost:8080/MyStruts/HellAction.action
String path = req.getServletPath(); // /HellAction.action
//2 判断请求是否需要访问action
if(!path.endsWith(extension)){
// 后缀不以".action"结尾 => 不需要访问action资源 => chain.doFIlter()放行
chain.doFilter(request, response);
return;
}else{
// 后缀以".action"结尾 => 需要访问action
//3 获得需要访问的action名称=>提取需要访问的action名称
path = path.substring(1);// HellAction.action
path = path.replace("."+extension, "");// HellAction
//4 查找action对应的配置信息
ActionConfig config = actionConfigs.get(path);
if(config == null){
//未找到配置信息 => 抛出异常提示访问的action不存在
throw new RuntimeException("访问的action不存在!");
}
//找到配置信息 => 获得到配置信息=>继续
//5 创建actionInvocation实例,完成对拦截器器链以及action的方法
ActionInvocation invocation = new ActionInvocation(InterceptorList,config,req,resp);
//6 获得结果串
String result = invocation.invoke(invocation); //success
//7 从配置信息找到结果串对应的路径
String dispatcherPath = config.getResult().get(result);
//找不到结果路径=> 抛出异常提示返回的路径找不到对应页面
if(dispatcherPath ==null || "".equals(dispatcherPath)){
throw new RuntimeException("您要访问的结果没有找到配置!");
}
//8 将请求转发到配置的路径
req.getRequestDispatcher(dispatcherPath).forward(req, resp);
//释放资源
ActionContext.tl.remove();
}
}
/**
* 销毁
*/
public void destroy() {
// TODO Auto-generated method stub
}
}
步骤7:
将Filter配置到web.xml中
步骤8:
struts经典之参数封装拦截器创建:
以后自己写的每一个拦截器操作步骤都是这样的
public class ParamInterceptor implements Interceptor{
public void init() {
}
public String interceptor(ActionInvocation invocation) {
//1 获得参数
//2 获得action对象
//ActionContext ac = ActionContext.getActionContext().getStack();//第一种获得ActionContext对象
ActionContext ac = invocation.getActionContext();//第二种获得ActionContext对象
ValueStack vs = ac.getStack();
Object action = vs.seek();
//3 封装
try {
BeanUtils.populate(action, ac.getRequest().getParameterMap());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//4 放行
return invocation.invoke(invocation);
}
public void destory() {
}
}
然后在struts.xml里加上:
<interceptor class="com.test.interceptor.ParamInterceptor" />
测试一:
新建一个action:
地址栏测试get请求
public class HelloAction {
private String name;
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String execute(){
System.out.println("hello world!"+name+"==>"+password);
return "success";
}
}
然后在struts.xml里配置对应的action:
<action name="HelloAction" method="execute"
class="com.test.action.HelloAction" >
<result name="success" >/index.jsp</result>
</action>
把项目部署到tomcat运行起来,然后在浏览器地址栏输入
http://localhost:8080/MyStruts2/HelloAction.action?name=marry&password=123456
会看到控制台输出
测试二:
表单(post)提交数据返回界面
新建一个RegistAction:
public class RegistAction implements Serializable{
private static final long serialVersionUID = -793621223468025885L;
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String regist(){
System.out.println("注册的用户名:" + name+" ==> 密码:"+password);
ActionContext.getActionContext().getRequest().setAttribute("name", name);
return "regist";
}
}
然后在struts.xml里配置对于的action:
<action name="registAction" method="regist" class="com.test.action.RegistAction" >
<result name="regist" >/home.jsp</result>
</action>
再创建一个regist.jsp,下面是body的代码:
<body>
<form action="registAction.action" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" id="name" name="name"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" id="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="提交"></td>
</tr>
</table>
</form>
</body>
然后创建一个home.jsp,下面是body的代码:
<body>
欢迎您:${requestScope.name}
</body>
最后部署到tomcat运行起来
在浏览器访问地址:
输入参数后,运行结果如下:
到此说明已经成功实现了
下面是整个项目的结构图:
本文的源码地址:
http://download.csdn.net/detail/u014204541/9842176
写在最后,本文可能存在不足之处,但愿大神看到后有不足之处可以提出,方便后期学习,如果实现过程看不懂,建议多看图,边看边实现这个过程。