自己动手写mvc

    经常做javaee开发的朋友基本上每天与mvc打交道,但是很多朋友却并没有自己写过mvc,甚至只会用,不知道其内部实现原理,同样地,我身边有很多朋友也都是知其然,不知其所以然。其实个人认为mvc实现起来不难,我想事实也是,笔者在兴趣的驱使下,自己写了一个小型的框架,实现了mvcorm。虽然功能不是很强大,但基本可以在简单项目,至少是练手的项目使用,学习的项目使用是很好的。至于ioc目前正在实现中。Aop在酝酿中。

      SpringMVC主要使用注解式,struts2也可以用convention插件来实现注解式。个人认为灵活性很好。不过对于配置文件其实也是不错的选择。不管哪种方式,其实只是初始化访问路径时的来源不一样而已,配置文件只用解析xml,对于注解自然是要扫描注解包。

      当有了访问路径时,其他的事就是访问某一地址(请求路径)时如何转发到具体的方法(也就是对应的注解)。

      注解不难理解,说白了说是一种注释。本身没有任何的意义,只是标记一下。比如:

@Resource @Autoware  等。最多带几个参数:@Table(name=”user”),这种说明一下对应关系或者其他的一些参数。使用不多说。

先上访问地址(也就是action方法)代码:

packagenet.javaing.mvc.annotation;

importjava.lang.annotation.Documented;

importjava.lang.annotation.ElementType;

importjava.lang.annotation.Retention;

importjava.lang.annotation.RetentionPolicy;

importjava.lang.annotation.Target;

/**

对实体属性与数据库字段的一个对应

* @author javaing

*/

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public@interface At {

     

      String value() default "";

     

      String method() default "get";

     

      String suffix() default"servlet";

     

}

 说明:Java中提供了四种元注解,专门负责注解其他的注解,分别如下
1、@Retention元注解,表示需要在什么级别保存该注释信息(生命周期)。可选的RetentionPoicy参数包括:
    RetentionPolicy.SOURCE: 停留在java源文件,编译器被丢掉
    RetentionPolicy.CLASS:停留在class文件中,但会被VM丢弃(默认)
    RetentionPolicy.RUNTIME:内存中的字节码,VM将在运行时也保留注解,因此可以通过反射机制读取注解的信息
2@Target元注解,默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括
    ElementType.CONSTRUCTOR: 构造器声明
    ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
    ElementType.LOCAL_VARIABLE: 局部变量声明
    ElementType.METHOD: 方法声明
    ElementType.PACKAGE: 包声明
    ElementType.PARAMETER: 参数声明
    ElementType.TYPE: 类、接口(包括注解类型)或enum声明
3、@Documented将注解包含在JavaDoc中
4@Inheried允许子类继承父类中的注解

这样首先就完成了action注解。

通过上面的文章,可以看到@At注解有三个属性,value为访问请求地址,method为请求方式,支持get/post,还有个suffix是后缀,也就是通过什么样的后缀访问请求,一个完整的请求地址(也就是浏览器中的址,或post请求控制器地址),由valuesuffix构成。中间用“.”分开。

有了注解,我们当然要扫描到注解,什么时候扫描?其实用spring的朋友都知道,springweb.xml中有这么一句话:

<listener>         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 
</listener>

相信这个大家都不陌生。

那么同样地,我也用这个来实现:

package net.javaing.mvc.listener;
 
import java.lang.reflect.Method;
 
import javax.servlet.ServletContextEvent;
 
import javax.servlet.ServletContextListener;
 
import net.javaing.mvc.annotation.At;
 
import net.javaing.mvc.exception.ExistActionException;
 
import net.javaing.mvc.mapping.ActionMapping;
 
import net.javaing.util.ClassUtil;
 
import net.javaing.util.PropertiesUtil;
 
import org.apache.log4j.Logger;
 
  
 
public class MvcContextListener implements ServletContextListener {  
 
     static Logger logger =Logger.getLogger(MvcContextListener.class.getName());
 
     @Override
 
     public voidcontextDestroyed(ServletContextEvent arg0){      
 
     }
 
  
 
     @Override
 
     public voidcontextInitialized(ServletContextEvent arg0){        
 
         for (Class<?> clazz :ClassUtil.getClasses(PropertiesUtil.readValue("config/mvc.properties","scan-base-package"))) {                           
 
              Method[]methods=clazz.getDeclaredMethods();            
 
              for (Method method :methods) {                
 
                   At at =method.getAnnotation(At.class);
 
                   if (at != null){                     
 
                       StringmethodName="";
 
                       if(!at.value().equals("")){
 
                            methodName=at.value();
 
                       }else{
 
                            methodName=method.getName();                            
 
                       }                      
 
                       if(at.method().equals("get")){                          
 
                            if(ActionMapping.getGetAction(methodName+"."+at.suffix())==null){                                     ActionMapping.addGetActionMapping(methodName+"."+at.suffix(),method);
 
                                 logger.info("findnew action:type:get\t\tname:"+methodName+"."+at.suffix());                           
 
                            }else{                               
 
                                 try{                                     
 
                                     throw newExistActionException(methodName+"."+at.suffix());
 
                                 }catch (ExistActionException e) {                                    e.printStackTrace();
 
                                 }                                
 
                            }                           
 
                       }else{                           if(ActionMapping.getPostAction(methodName+"."+at.suffix())==null){                                 ActionMapping.addPostActionMapping(methodName+"."+at.suffix(),method);                                logger.info("find newaction:type:post\t\tname:"+methodName+"."+at.suffix());                          
 
                            }else{                               
 
                                 try{                                     
 
                                     thrownew ExistActionException(methodName+"."+at.suffix());                                  
 
                                 }catch (ExistActionException e) {
 
                                     e.printStackTrace();                               
 
                                 }                                
 
                            }                           
 
                       }
 
                   }                  
 
              }             
 
         }        
 
     }
 
}

至于使用,同spring一样。只是将类名替换:

<listener>         
    <listener-class>net.javaing.mvc.listener. MvcContextListener.</listener-class>
</listener>


这样也就完成了@At注解的扫描。并且保存到了一个map集合对象ActionMapping中。

有的朋友会问,ActionMapping是个什么东东?

我只能说是个map(事实上他也是),用来存放扫描到的地址。先看看代码:

package net.javaing.mvc.mapping;
 
  
 
import java.lang.reflect.Method;
 
import java.util.HashMap;
 
import java.util.Map;
 
  
 
/***
 
 *       Action映射
 
 */
 
public class ActionMapping {
 
      
 
     private staticMap<String,Method> postMapping = new HashMap<String,Method>();  
 
     private static Map<String,Method>getMapping = new HashMap<String,Method>();
 
      
 
     public static voidaddPostActionMapping(String serletPath,Method method){      
 
         postMapping.put("/"+serletPath,method);       
 
     }
 
      
 
     public static MethodgetPostAction(String servletPath){      
 
         returnpostMapping.get(servletPath);      
 
     }
 
  
 
     public static voidaddGetActionMapping(String serletPath,Method method){       
 
         getMapping.put("/"+serletPath,method);        
 
     }
 
      
 
     public static MethodgetGetAction(String servletPath){       
 
         returngetMapping.get(servletPath);       
 
     }    
 
}

实现的办法就是,把扫描到的请求地址,按get/post分类。按请求地址(前面说过,由value+”.”+suffix构成)存放主键,对应的方法(method对象)为值,这样的方式存放。想法就是,当请求地址请求以后,取得请求地址,寻找对应方法。如果扫描出现地址完全一样(请求方式、请求名称、后缀完全一样)会抛出:ExistActionException。当请求地址找不到的时候(不存在的请求地址,有可能是请求方式不对或名称或后缀不一样)会抛出:NoSuchActionException

以下为两异常定义:

ExistActionException.java

package net.javaing.mvc.exception;
 
public class ExistActionException extends Exception {
 
     private static final longserialVersionUID = -8608538729128495163L;
 
     publicExistActionException(String msg) {
 
         super("重复的请求地址!"+msg);
 
     }
 

NoSuchActionException.java

package net.javaing.mvc.exception;
 
  
 
public class NoSuchActionException extends Exception {
 
     private static final longserialVersionUID = -8608538729128495163L;
 
     publicNoSuchActionException(String msg) {
 
         super("不存在的请求地址!"+msg);
 
     }
 

这样也就完全地完成了地址扫描。


 到现在为止,如果熟悉springmvc的朋友就会发现,笔者自己写的mvc有很多使用方式和spring很像。只不过注解名称不一样而已。多一个了个属性而已,之所以加一个属性,是因为本mvc主要设计为网站形式,尤其文章类型的,给用户呈现的和管理后台有点一样的情况,就是用户呈现的需要页面静态化,常用urlrewrite实现,但是开发的时候一般总是先开发功能,完了再统一做地址静态化,至少我一般这样。这样无形中添加了工作量。如果让action后缀(部分)变成html这样就不用再处理了,而如果直接全用html做后缀,有时感觉又不太方便或有可能冲突(与真实静态文件),而且后台管理方便又一般不那样做,所以,这纯粹是自己新加的一点功能(有些朋友可能不认同这种方式,或感觉没用)。

    同样地,本mvc使用也和spring一样,在web.xml中加入一个servlet

 

<servlet> 
 
      <servlet-name>mvc</servlet-name> 
 
     <servlet-class>net.javaing.mvc.Mvc</servlet-class> 
 
 </servlet> 
 
 <servlet-mapping> 
 
     <servlet-name>mvc</servlet-name> 
 
     <url-pattern>/</url-pattern> 
 
 </servlet-mapping>

 

根据自己的一些功能,配置自己的mvc。和spring比起来简单多了,主要参数少。功能小。

关于地址转发,基本思想是根据请求方式,找到对应的所有方式下的请求地址集合,再从集合中根据请求地址,找到请求方法。然后执行,进行页面返回。中间涉及几个用户不会手动带参的,也就是request这样的参数。但是像springmvc这样直接在后台使用的参数的处理。以下是具体实现:

MvcJava:主要接收地址,进行地址分发,将请求下发

package net.javaing.mvc; 
  
   
  
import java.io.IOException; 
  
import java.lang.reflect.Method; 
  
   
  
import javax.servlet.ServletException; 
  
import javax.servlet.http.HttpServlet; 
  
import javax.servlet.http.HttpServletRequest; 
  
import javax.servlet.http.HttpServletResponse; 
  
   
  
import net.javaing.mvc.mapping.ActionMapping; 
  
   
  
public class Mvc extends HttpServlet { 
  
   
  
     private static Mvcs mvc = new Mvcs(); 
  
   
  
     /** 
  
      *  
  
      */
  
     private static final long serialVersionUID = -6326149137476027612L; 
  
   
  
     @Override
  
     protected void doGet(HttpServletRequest request, 
  
              HttpServletResponse response) throws ServletException, IOException { 
  
   
  
         Method method = ActionMapping.getGetAction(request.getServletPath()); 
  
   
  
         Object obj=mvc.execute(request, response, method); 
  
           
  
         if(obj instanceof JspView){ 
  
                
  
              request.setAttribute("obj", ((JspView) obj).getObject()); 
  
                
  
              request.getRequestDispatcher(((JspView) obj).getPath()+".jsp").forward(request,response); 
  
                
  
         } 
  
   
  
     } 
  
   
  
     @Override
  
     protected void doPost(HttpServletRequest request, HttpServletResponse response) 
  
              throws ServletException, IOException { 
  
   
  
         Method method = ActionMapping.getPostAction(request.getServletPath()); 
  
   
  
         Object obj=mvc.execute(request, response, method); 
  
           
  
         if(obj instanceof JspView){ 
  
                
  
              request.setAttribute("obj", ((JspView) obj).getObject()); 
  
                
  
              request.getRequestDispatcher(((JspView) obj).getPath()+".jsp").forward(request,response); 
  
                
  
         } 
  
   
  
     } 
  
   
  
}

 

在上面的类中可以看到,并没有进行真正的处理,只是转发给mvcs类。

Mvcs.java

package net.javaing.mvc; 
  
   
  
import java.lang.reflect.Field; 
  
import java.lang.reflect.InvocationTargetException; 
  
import java.lang.reflect.Method; 
  
import java.util.List; 
  
   
  
import javax.servlet.http.HttpServletRequest; 
  
import javax.servlet.http.HttpServletResponse; 
  
   
  
import net.javaing.mvc.annotation.CheckSession; 
  
import net.javaing.mvc.exception.NoSuchActionException; 
  
import net.javaing.mvc.exception.NullCheckSessionIdException; 
  
import net.javaing.util.MethodParamNamesScanner; 
  
import net.javaing.util.SetterGetter; 
  
   
  
public class Mvcs { 
  
   
  
     public Object execute(HttpServletRequest request, 
  
              HttpServletResponse response, Method method) { 
  
   
  
         if (method == null) { 
  
   
  
              try { 
  
   
  
                   throw new NoSuchActionException(""); 
  
   
  
              } catch (NoSuchActionException e) { 
  
   
  
                   e.printStackTrace(); 
  
   
  
              } 
  
                
  
              return null; 
  
   
  
         } else { 
  
   
  
              try { 
  
                     
  
                   if(checkSession(request, method)){                       
  
   
  
                       Class<?>[] parameterTypes = method.getParameterTypes(); 
  
   
  
                       List<String> paramNames = MethodParamNamesScanner 
  
                                 .getParamNames(method); 
  
   
  
                       Object[] parameterObjects = new Object[parameterTypes.length]; 
  
   
  
                       for (int index = 0; index < parameterTypes.length; index++) { 
  
   
  
                            Class<?> clazz = parameterTypes[index]; 
  
   
  
                            String paramName = paramNames.get(index); 
  
   
  
                            if (clazz.getName().equals( 
  
                                     HttpServletRequest.class.getName())) { 
  
   
  
                                 parameterObjects[index] = request; 
  
   
  
                            } else if (clazz.getName().equals( 
  
                                     HttpServletResponse.class.getName())) { 
  
   
  
                                 parameterObjects[index] = response; 
  
   
  
                            } else if (clazz.getName().equals(Integer.class.getName())) { 
  
   
  
                                 parameterObjects[index] = Integer.parseInt(request 
  
                                          .getParameter(paramName)); 
  
   
  
                            } else if (clazz.getName().equals(Double.class.getName())) { 
  
   
  
                                 parameterObjects[index] = Double.parseDouble(request 
  
                                          .getParameter(paramName)); 
  
   
  
                            } else if (clazz.getName().equals(Float.class.getName())) { 
  
   
  
                                 parameterObjects[index] = Float.parseFloat(request 
  
                                          .getParameter(paramName)); 
  
   
  
                            } else if (clazz.getName().equals(String.class.getName())) { 
  
   
  
                                 parameterObjects[index] = request 
  
                                          .getParameter(paramName); 
  
   
  
                            } else { 
  
   
  
                                 Object obj = clazz.newInstance(); 
  
   
  
                                 for (Field filed : clazz.getDeclaredFields()) { 
  
   
  
                                     Method setMethod = clazz.getDeclaredMethod( 
  
                                               SetterGetter.setter(filed.getName()), 
  
                                               filed.getType()); 
  
   
  
                                     setMethod.invoke(obj, 
  
                                               request.getParameter(filed.getName())); 
  
   
  
                                } 
  
   
  
                                 parameterObjects[index] = obj; 
  
   
  
                            } 
  
   
  
                       } 
  
   
  
                       return method.invoke(method.getDeclaringClass().newInstance(), 
  
                                 parameterObjects); 
  
                         
  
                   } 
  
   
  
              } catch (IllegalArgumentException e) { 
  
   
  
                   e.printStackTrace(); 
  
   
  
              } catch (IllegalAccessException e) { 
  
   
  
                   e.printStackTrace(); 
  
   
  
              } catch (InvocationTargetException e) { 
  
   
  
                   e.printStackTrace(); 
  
   
  
              } catch (InstantiationException e) { 
  
   
  
                   e.printStackTrace(); 
  
   
  
              } catch (SecurityException e) { 
  
   
  
                   e.printStackTrace(); 
  
   
  
              } catch (NoSuchMethodException e) { 
  
   
  
                   e.printStackTrace(); 
  
   
  
              } 
  
                
  
              return null; 
  
   
  
         } 
  
   
  
     } 
  
       
  
       
  
     public boolean checkSession(HttpServletRequest request, Method method){ 
  
           
  
         CheckSession checkSession = method.getAnnotation(CheckSession.class); 
  
           
  
         if(checkSession!=null){ 
  
                
  
              if(checkSession.name()==null){ 
  
                     
  
                   try { 
  
                         
  
                       throw new NullCheckSessionIdException("\"\""); 
  
                         
  
                   } catch (NullCheckSessionIdException e) { 
  
   
  
                       e.printStackTrace(); 
  
                         
  
                   } 
  
                     
  
                   return false; 
  
                     
  
              }else if((checkSession.value()==null&&request.getSession().getAttribute(checkSession.name())!=null)||checkSession.value().equals(request.getSession().getAttribute(checkSession.name()))){ 
  
   
  
                   return true; 
  
                     
  
              }else{ 
  
                     
  
                   //不符合指定值当然是要返回指定页面了。 
  
                   return false; 
  
                     
  
              } 
  
                
  
         }else{ 
  
                
  
              return true; 
  
                
  
         } 
  
           
  
     } 
  
   
  
}

这个类会进行真正的处理,将请求转发给真正的我们要使用的控制器。主要处理:

1、地址分发

2、参数处理,主要表现为requestresponse

3、将参数对象化,主要模仿spring的方式,自动装配对象,而不是像struts2一样,要通过user.username这种方式处理。

4、添加了一个过滤器,会检查会话状态。下次会讲到。

这样也就完成了从页面请求到控制器的处理。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值