利用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

不早了,我要趕緊洗洗睡,安

 

 

 

 

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