前言:
今天用Java的反射机制模拟写一个Struts框架
用到的技术大概有Java反射,XML解析,Filter过滤器。
其中Java反射用到的是反射中基本的知识和利用反射内省实现功能的一个apache的工具jar BeanUtils
XML解析用的Dom4j
首先还是讲一下大致的思路和流程
开头先多说一句啊,今天写的这个平常开发中基本上不会用,但是我觉得这东西对理解MVC框架的底层挺有帮助的,有兴趣的可以参考一下,也很欢迎志同道合的朋友一起讨论研究,好了,下边进入正题吧。
接着上边的基本思路讲,基本思路就是解析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;
//下边是真正的控制器核心部分
//如果web.xml中不配置请求地址的结尾的话
//此处给出默认值 .action .do或者空结尾才真正过滤
String aciontPostFixs [] = {"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