利用Java反射模拟一个Struts2框架 Struts2主要核心设计 手动实现Struts2核心代码

今天用Java的反射机制模拟写一个Struts框架

 

用到的技术大概有Java反射,XML解析,Filter过滤器。

其中Java反射用到的是反射中基本的知识和利用反射内省实现功能的一个apache的工具jar BeanUtils

XML解析用的Dom4j

首先还是讲一下大致的思路和流程

开头先多说一句啊,今天写的这个平常开发中基本上不会用,但是我觉得这东西对理解框架的底层挺有帮助的,有兴趣的可以参考一下,也很欢迎志同道合的朋友一起讨论研究,好了,下边进入正题吧。

接着上边的基本思路讲,基本思路就是解析XML配置文件里的内容,因为咱们是模拟struts2的基本功能,所以咱们的配置文件里的内容大致和struts2的配置文件内容一样,包括action name class method result 这些,下面我会贴出来。读取完配置文件里的内容以后,用map存起来,然后等用户的请求过来以后,利用Filter过滤器截取解析用户的请求,截取出来以后,就去map中找,看能不能找到对应的action,如果能找到就利用配置文件里配置的class信息(类似这种class="com.cj.bean.User")进行反射生成对象,然后调用配置文件里配置的method 方法,然后根据返回结果和配置文件里的result 信息去决定跳转哪个页面。大致思路就是这样,下面开始撸码实现一下。

先看一下整个项目的结构

 

刚才说要贴配置文件,现在贴一下,可以看出来咱们自己定义的配置文件内容和Struts2很像

 

下边开始一步一步进行

第一步建一个Dom4J工具类,用来读取配置文件

 

/**
 * 
 * @author caoju
 *	Dom4J工具类,用来读取配置文件
 */
public class Dom4JUtil {
	
	private static InputStream in;
	
	static{
		in = Dom4JUtil.class.getClassLoader().getResourceAsStream("myStruts.xml");
	}
	
	public static Document getDocument(){
		try {
			SAXReader reader = new SAXReader();
			return reader.read(in);
		} catch (DocumentException e) {
			throw new RuntimeException(e);
		}
	}
	
}

这里边用到了Dom4J的解析,Dom4J是个不错的工具包,它是用SAX解析的方式来读取XML,但是同时又用DOM解析的操作方式来很方便的操作XML中的元素,所以它是将SAX读取省内存的优点和DOM解析操作方便的优点结合了,各取所长,个人理解大概意思是这个。有兴趣的可以大概百度一下,也不难,就不多讲了。

 

 

第二步建Action类和Result类分别用来存配置文件中的action标签信息,和result标签信息

Action类

 

/**
 * @author caoju
 *	配置文件中action标签对应实体类
 */
public class Action implements Serializable {

	private static final long serialVersionUID = 7300850980313493998L;
	
	private String name;
	
	private String className;
	
	private String method = "execute";
	
	private List<Result> results = new ArrayList<Result>();

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getClassName() {
		return className;
	}

	public void setClassName(String className) {
		this.className = className;
	}

	public String getMethod() {
		return method;
	}

	public void setMethod(String method) {
		this.method = method;
	}

	public List<Result> getResults() {
		return results;
	}

	public void setResults(List<Result> results) {
		this.results = results;
	}

	@Override
	public String toString() {
		return "Action [name=" + name + ", className=" + className
				+ ", method=" + method + ", results=" + results + "]";
	}
	
}

Result类

 

 

/**
 * @author caoju
 * 配置文件中action标签中的result标签对应实体类
 */
public class Result implements Serializable {
	
	private static final long serialVersionUID = -4057384816772340611L;
	
	private String name;
	
	private String targetUri;
	
	//配置文件中配了的话就使用配置的值,不配的话给个默认值dispatcher
	private String resultType = "dispatcher";
	
	
	public String getResultType() {
		return resultType;
	}

	public void setResultType(String resultType) {
		this.resultType = resultType;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getTargetUri() {
		return targetUri;
	}

	public void setTargetUri(String targetUri) {
		this.targetUri = targetUri;
	}

	@Override
	public String toString() {
		return "Result [name=" + name + ", targetUri=" + targetUri
				+ ", resultType=" + resultType + "]";
	}
}

 

 

第三步建一个User类来封装用户的请求数据,就是一个bean

 

public class User implements Serializable {

	private static final long serialVersionUID = -7690372108501133937L;

	private String name;
	
	private int age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String addUser(){
		if("caoju".equals(name)){
			return "success";
		}else{
			return "error";
		}
	}
	
}

 

由于时间的关系,我addUser方法里并没有去调业务逻辑层和操作数据库,只是简单的做了一个判断来表示操作成功和失败。

 

第四步编写核心的过滤器类,关键就在这,每一步我都标有详细的注释

 

/**
 * 
 * @author caoju
 *	核心过滤器类
 */
public class CenterFilter implements Filter {
	
	//定义个map来存配置文件里的action,map的key是action中的name value是Action对象
	private Map<String, Action> actions = new HashMap<String, Action>();
	//过滤器配置类,用来获取用户配置的请求结尾后缀 比如 .do .action等,用来决定处理不处理
	private FilterConfig filterConfig;
	
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		initCfg();//初始化配置文件
		this.filterConfig = filterConfig;
	}
	
	//初始化配置文件
	@SuppressWarnings("unchecked")
	private void initCfg() {
		//读取XML配置文件:把配置文件中的信息封装到对象中.再放到actions中
		Document document = Dom4JUtil.getDocument();
		Element root = document.getRootElement();
		//得到所有的action元素,创建Action对象,封装信息
		List<Element> actionElements = root.elements("action");
		if(actionElements != null && actionElements.size() > 0){
			for(Element actionElement : actionElements){
				//---------封装action信息到对象中 Start-----------
				Action action = new Action();
				action.setName(actionElement.attributeValue("name"));
				action.setClassName(actionElement.attributeValue("class"));
				String methodXmlAttrValue = actionElement.attributeValue("method");
				//配置文件里如果method不配置就用对象默认的属性值 execute
				if(methodXmlAttrValue != null)
					action.setMethod(methodXmlAttrValue);
				//---------封装action信息到对象中 End-------------
				//得到每个action元素中的result元素,创建Result对象,封装信息
				List<Element> resultElements = actionElement.elements("result");
				if(resultElements != null && resultElements.size() > 0){
					for(Element resultElement : resultElements){
						Result result = new Result();
						result.setName(resultElement.attributeValue("name"));
						String typeXmlValue = resultElement.attributeValue("type");
						//成功或者失败跳转的页面
						result.setTargetUri(resultElement.getText().trim());
						//如果result的type属性不配置的话,则用对象默认的属性值 dispatcher
						if(typeXmlValue != null)
							result.setResultType(typeXmlValue);
						//把result对象放到action
						action.getResults().add(result);
					}
				}
				//把Action对象都放到Map中
				actions.put(action.getName(), action);
			}
		}
		//可以打印确认一下 封装的结构对不对
		System.out.println(actions);
	}
	
	@Override
	public void doFilter(ServletRequest req, ServletResponse resp,
			FilterChain chain) throws IOException, ServletException {
		try {
			
			HttpServletRequest request = (HttpServletRequest)req;
			HttpServletResponse response = (HttpServletResponse)resp;
			
			//下边是真正的控制器核心部分
			String aciontPostFixs [] = {"action","","do"};//如果web.xml中不配置请求地址的结尾的话,此处给出默认值 .action .do或者空结尾才真正过滤
			String aciontPostFix = filterConfig.getInitParameter("aciontPostFix");
			if(aciontPostFix!=null)
				aciontPostFixs = aciontPostFix.split("\\,");
			//解析用户请求的URI
			String uri = request.getRequestURI();//   /MyStruts/addUser.action
			
			//截取后缀名,看看是否需要我们的框架进行处理
			String extendFileName = uri.substring(uri.lastIndexOf(".")+1);
			boolean needProcess = false;
			for(String s:aciontPostFixs){
				if(extendFileName.equals(s)){
					needProcess = true;
					break;
				}
			}
			
			//需要框架处理
			if(needProcess){
				//解析uri中的动作名称
				String requestActionName = uri.substring(uri.lastIndexOf("/")+1, uri.lastIndexOf("."));
				System.out.println("请求动作名是:"+requestActionName);
				//查找actions map中对应的Action对象
				if(actions.containsKey(requestActionName)){
					//根据map中的key得到Action对象
					Action action = actions.get(requestActionName);
					//开始进行反射
					//得到类名称的字节码
					Class clazz = Class.forName(action.getClassName());
					//利用反射生成对象
					Object bean = clazz.newInstance();
					//利用BeanUtils框架把用户提交的数据封装到实体中
					BeanUtils.populate(bean, request.getParameterMap());
					//实例化,调用其中指定的方法名称
					Method m = clazz.getMethod(action.getMethod(), null);
					//根据方法的返回值,遍历结果
					String resultValue = (String)m.invoke(bean, null);
					
					List<Result> results = action.getResults();
					if(results != null && results.size() > 0){
						for(Result result:results){
							if(resultValue.equals(result.getName())){
								//根据结果中的type决定是转发还是重定向
								//重定向的目标就是结果中的targetUri
								if("dispatcher".equals(result.getResultType())){
									//转发
									request.getRequestDispatcher(result.getTargetUri()).forward(request, response);
								}
								if("redirect".equals(result.getResultType())){
									//重定向
									response.sendRedirect(request.getContextPath()+result.getTargetUri());
								}
							}
						}
					}
				}else{
					throw new RuntimeException("对不起,请求: "+requestActionName+",在配置文件中未找到!");
				}
			}else{
				chain.doFilter(request, response);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void destroy() {
		
	}
}


因为每一步我都标有详细的注释,所以就不多啰嗦了

 

 

第五步是配置web.xml中自己写的核心过滤器内容

到这儿基本上就大功告成了。

下面启动项目,访问一下试试


首先输入一个错的,点击保存

可以看到,跳转到了保存失败的页面,注意看一下地址栏的地址的变化,发现地址栏的地址没有变化,并没有变成error.jsp,这是因为咱们myStruts.xml这个核心配置文件里配置了跳转失败页面是用的转发,所以地址栏地址没变化,说明咱们的配置是生效的。

然后再输入一次对的试一下


可以看到,跳转到保存成功的页面了

跳转保存成功页面,再注意看一下地址栏的地址的变化,地址栏上边的地址也变了,成了success.jsp

myStruts.xml里配置的成功跳转就是重定向,说明咱们的配置是生效的。

 

好啦,大概套路就是这样,关于这个就先写这么多吧。

供大家参考,若有错误的地方希望大家包涵并及时指出,3Q

不早了,我要赶紧洗洗睡,安

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章