手写Struts彻底理解源码设计

个人剖析,不喜勿喷

jar包准备

为什么会用到这两个jar包呢,因为我需要通过这个jar来解析xml配置文件。新建项目

流程梳理

struts配置文件代码语言:txt复制

/index.jsp

/WEB-INF/login.jsp

熟悉struts的朋友都清楚struts.xml配置文件的重要性,这个配置文件名字是可以更改的,这里简单解释下这个配置文件的作用,首先我们找到action这个节点这个action的name是login,就是说前台中请求这个login经过这个配置文件解析就会把这个请求交给action中的class属性,也就是上面的代码语言:txt复制org.zxh.action.LoginAction具体的是交由这个类的login(method)这个方法。这个方法会方法一个string类型的字符串,如果返回的是success就将页面重定向到index.jsp如果是login就重定向到login.jsp。这个配置文件就是这样的作用。因为是自己写的,所以这里并不会想struts框架那样封装了很多东西,这里只是为了让读者更加深入的理解struts的运行机制。

如何将我们写的struts.xml文件在程序中启动呢?

刚入门的同志可能会疑问,写一个配置文件就能处理前后台交互了?答案当然是不能。这里给大家普及一下web基础接触filter的,每次交互需要filter(jsp就是特殊的servlet),所以想实现交互我们就得新建一个servlet,在这个servlet里我们去读我们写的struts.xml文件,通过读到的信息决定下一步的操作。那么如何启动一个filter呢?这个不多说,直接在web项目中的web.xml配置拦截器就会执行filter。新建filter(FilterDispatcher)

这个servlet就是struts的核心过滤器,需要先继承过滤器。代码语言:txt复制public class FilterDispatcher implements Filter{

@Override

public void destroy() {

// TODO Auto-generated method stub

}

@Override

public void doFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain arg2) throws IOException, ServletException {

// TODO Auto-generated method stub

}

@Override

public void init(FilterConfig arg0) throws ServletException {

// TODO Auto-generated method stub

}

}Filter中我们要在初始化函数(init)中对一些参数进行初始化,对那些数据初始化呢,对!当然是拿配置文件的信息啦。配置文件是.xml这里我用dom4j读取.xml配置文件。 把struts.xml配置文件放在src下,(可以放在其他地方,这里的地址填的对应就行了)代码语言:txt复制// 获得xml配置文件

String webRootPath = getClass().getClassLoader()

.getResource("struts.xml").getPath();拿到配置文件路径之后开始读取,这里我讲读到的数据封装到一个map里面。在封装在Map中我们仔细观察一下配置文件其实我们放在Map里面就是这四个属性的值,有了这四个值我们就可以完成一次前后台交互的映射了。所以为了方便这里封装成javabean。代码语言:txt复制package org.zxh.util;

import java.util.HashMap;

import java.util.Map;

/**

* 将action属性封装成类

* @author 87077

*

*/

public class ActionConfig {

//action 给别人调用的名字

private String name;

//action对应程序中的action类

private String clazzName;

//action中的方法

private String method;

//返回结果不知一条 所以用Map

private Map resultMap = new HashMap();

public ActionConfig(){

}

public ActionConfig(String name , String clazzName , String method , Map resultMap){

this.name=name;

this.clazzName=clazzName;

this.method=method;

this.resultMap=resultMap;

}

public String getName() {

return name;

}

public String getClazzName() {

return clazzName;

}

public String getMethod() {

return method;

}

public Map getResultMap() {

return resultMap;

}

public void setName(String name) {

this.name = name;

}

public void setClazzName(String clazzName) {

this.clazzName = clazzName;

}

public void setMethod(String method) {

this.method = method;

}

public void setResultMap(Map resultMap) {

this.resultMap = resultMap;

}

}有了javabean 我们开始解析xml文件代码语言:txt复制package org.zxh.util;

import java.io.File;

import java.util.List;

import java.util.Map;

import org.dom4j.Document;

import org.dom4j.DocumentException;

import org.dom4j.Element;

import org.dom4j.io.SAXReader;

/**

* 采用dom4j解析xml配置文件

*

* @author 87077

*

*/

public class ConfigUtil {

/**

* @param fileName

* 待解析的文件

* @param map

* 存放解析的数据

*/

public static void parseConfigFile(String fileName,

Map map) {

SAXReader reader = new SAXReader();

try {

Document doc = reader.read(new File("D:\\zxh\\soft\\apache-tomcat-7.0.70\\apache-tomcat-7.0.70\\webapps\\MyStruts\\WEB-INF\\classes\\struts.xml"));

Element root = doc.getRootElement();

List list = root.selectNodes("package/action");

for (Element element : list) {

// 封装成ActionConfig对象,保存在map中

ActionConfig config = new ActionConfig();

// 获取action中的值

String name = element.attributeValue("name");

String clazzName = element.attributeValue("class");

String method = element.attributeValue("method");

// 将值传入javabean中

config.setName(name);

config.setClazzName(clazzName);

// 如果没有设置执行method 执行默认的

if (method == null || "".equals(method)) {

method = "execute";

}

config.setMethod(method);

// 继续向下获取action中的返回方法

List resultList = element.selectNodes("result");

for (Element resultElement : resultList) {

String resultName = resultElement.attributeValue("name");

String urlPath = resultElement.getTextTrim();

if (resultName == null || "".equals(resultName)) {

resultName = "success";

}

config.getResultMap().put(resultName, urlPath);

}

map.put(name, config);

}

} catch (DocumentException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}现在我们在回到过滤器上,上面两个类就是为了解析xml的。所以在Filter中的init方法里我们就可以将解析的数据放到我们的全局Map中代码语言:txt复制@Override

public void init(FilterConfig arg0) throws ServletException {

// TODO Auto-generated method stub 过滤器的初始化过程

// 获得xml配置文件

String webRootPath = getClass().getClassLoader()

.getResource("struts.xml").getPath();

// 将xml配置文件解析装在到map中

ConfigUtil.parseConfigFile(webRootPath, map);

}过滤器的执行

过滤器真正执行是在doFilter方法开始时。代码语言:txt复制public void doFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain arg2)doFilter()方法类似于Servlet接口的service()方法。当客户端请求目标资源的时候,容器就会调用与这个目标资源相关联的过滤器的 doFilter()方法。其中参数 request, response 为 web 容器或 Filter 链的上一个 Filter 传递过来的请求和相应对象;参数 chain 为代表当前 Filter 链的对象,在特定的操作完成后,可以调用 FilterChain 对象的 chain.doFilter(request,response)方法将请求交付给 Filter 链中的下一个 Filter 或者目标 Servlet 程序去处理,也可以直接向客户端返回响应信息,或者利用RequestDispatcher的forward()和include()方法,以及 HttpServletResponse的sendRedirect()方法将请求转向到其他资源。这个方法的请求和响应参数的类型是 ServletRequest和ServletResponse,也就是说,过滤器的使用并不依赖于具体的协议。

获取请求域和响应域还有Filter链,并设置编码防止乱码代码语言:txt复制//针对http请求,将请求和响应的类型还原为HTTP类型

HttpServletRequest request = (HttpServletRequest) arg0;

HttpServletResponse response = (HttpServletResponse) arg1;

//设置请求和响应的编码问题

request.setCharacterEncoding("UTF-8");

response.setCharacterEncoding("UTF-8");获取请求地址代码语言:txt复制//获取请求路径

String url = request.getServletPath();通过请求去判断知否拦截过滤这个地址的请求,本文默认过滤所有以.action结尾的请求代码语言:txt复制//请求地址过滤,如果不是以.action结尾的

if(!url.endsWith(".action")){

//不是.action的放行

arg2.doFilter(request, response);

return ;

}看我之前将xml文件中数据放入到Map的格式可以看出我是讲整个javabean放入Map中名字是action的name。所以下面我就要去那个name(就是请求中的login)代码语言:txt复制//解析request路径

int start = url.indexOf("/");

int end = url.lastIndexOf(".");

String path=url.substring(start+1,end);

//通过path去匹配到对应的ActionConfig类。在这里已经解析到了所有的action的信息

ActionConfig config = map.get(path);

//匹配不成功就返回找不到页面错误信息

if(config==null){

response.setStatus(response.SC_NOT_FOUND);

return ;

}获取了ActionConfig类了,action的所有信息都存储在这个javabean类中了,下面的事情就好办了。下面的只是会用到反射的知识。我们拿到真正action类的名称后就需要根据名字获取到这个action的实体类。代码语言:txt复制//通过ActionConfig获取完成的类名字

String clazzName=config.getClazzName();

//实例化Action对象,不存在的话就提示错误信息

Object action = getAction(clazzName);

if(action==null){

//说明这个action是错误的,在配置文件中没有占到对应的action类

response.setStatus(response.SC_NOT_FOUND);

return ;

}request参数获取并赋值给action

执行action的方法前很定需要先将request中的参数获取到,进行赋值,这部才是真正的意义上的交互。代码语言:txt复制public static void requestToAction(HttpServletRequest request , Object action )将传进来的action对象进行class话并获取action实体下的属性代码语言:txt复制Class clazzAction = action.getClass();

//获取aciton中所有属性,从前台获取的值很多,只有action属性中有的才会进行反射赋值

Field[] fields = action.getClass().getDeclaredFields();拿到request传过来的值并进行遍历代码语言:txt复制//获取请求中的名字属性值

Enumeration names=request.getParameterNames();代码语言:txt复制String name=names.nextElement();

boolean flag=false;

//需要判断action属性中没有的而请求中有的我们不需要进行反射处理

for (Field field : fields) {

if(name.equals(field.getName())){

flag=true;

}

}

if(!flag){

return;

}

String[] value=request.getParameterValues(name);通过request中的name并且在action中有这个属性之后我们就需要获取action这个字段的属性。代码语言:txt复制Class fieldType=(Class) clazzAction.getDeclaredField(name).getType();获取action的改name字段属性的set方法代码语言:txt复制//通过反射调用该属性的set方法

String setName="set"+name.substring(0,1).toUpperCase()+name.substring(1);

Method method=clazzAction.getMethod(setName, new Class[]{fieldType});下面我们就需要将获取的value按类型代码语言:txt复制private static Object[] transfer(Class fieldType , String[] value){

Object[] os = null;

//fieldType 是[]这种类型的,需要将[]去掉

String type=fieldType.getSimpleName().replace("[]", "");

if("String".equals(type)){

os=value;

}else if("int".equals(type)||"Integer".equals(type)){

os = new Integer[value.length];

for (int i = 0; i < os.length; i++) {

os[i] = Integer.parseInt(value[i]);

}

}else if("float".equals(type)||"Float".equals(type)){

os=new Float[value.length];

for (int i = 0; i < os.length; i++) {

os[i]=Float.parseFloat(value[i]);

}

}else if("double".equals(type)||"Double".equals(type)){

os=new Double[value.length];

for (int i = 0; i < os.length; i++) {

os[i]=Double.parseDouble(value[i]);

}

}

return os;

}获取object数据之后就是讲这个object数据通过反射付给action对应的属性代码语言:txt复制//判断是否是数组属性

if(fieldType.isArray()){

method.invoke(action, new Object[]{object});

}else {

method.invoke(action, new Object[]{object[0]});

}这说一下 method.invoke是将action类中method方法这个方法需要的参数就是object

用户登陆

 姓名:  

 密码:  

 

name="submit">

效果读者自行展示吧,到这里struts的运行机制就讲完了,注意知识运行机制里面还有很多值得我们学习的东西,就好比说这里有很多过滤器,不同过滤器过滤数据程度不同执行效果不同。希望有机会再和大家分享一些其他关于struts的知识!我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!