今天用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
不早了,我要趕緊洗洗睡,安