模拟Sping MVC

在Spring MVC中,将一个普通的java类标注上Controller注解之后,再将类中的方法使用RequestMapping注解标注,那么这个普通的java类就够处理Web请求,示例代码如下:

复制代码
 1 /**
 2  * 使用Controller注解标注LoginUI类
 3  */
 4 @Controller
 5 public class LoginUI {
 6     
 7     //使用RequestMapping注解指明forward1方法的访问路径  
 8     @RequestMapping("LoginUI/Login2")
 9     public View forward1(){
10         //执行完forward1方法之后返回的视图
11         return new View("/login2.jsp");  
12     }
13     
14     //使用RequestMapping注解指明forward2方法的访问路径  
15     @RequestMapping("LoginUI/Login3")
16     public View forward2(){
17         //执行完forward2方法之后返回的视图
18         return new View("/login3.jsp");  
19     } 
20 }
复制代码

  spring通过java annotation就可以注释一个类为action ,在方法上添加上一个java annotation 就可以配置请求的路径了,那么这种机制是如何实现的呢,今天我们使用"自定义注解+Servlet"来简单模拟一下Spring MVC中的这种注解配置方式。

一、编写注解

1.1、Controller注解

  开发Controller注解,这个注解只有一个value属性,默认值为空字符串,代码如下:

复制代码
 1 package me.gacl.annotation;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 /**
 9 * @ClassName: Controller
10 * @Description: 自定义Controller注解
11 * @author: 
12 * @date: 2014-11-16 下午6:16:40
13 *
14 */ 
15 @Retention(RetentionPolicy.RUNTIME)
16 @Target(ElementType.TYPE)
17 public @interface Controller {
18 
19     public String value() default "";
20 }
复制代码

1.2、RequestMapping注解

  开发RequestMapping注解,用于定义请求路径,这个注解只有一个value属性,默认值为空字符串,代码如下:

复制代码
 1 package me.gacl.annotation;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 /**
 9  * 定义请求路径的java annotation
10  */
11 @Target(ElementType.METHOD) 
12 @Retention(RetentionPolicy.RUNTIME)  
13 public @interface RequestMapping {
14 
15     public String value() default "";
16 }
复制代码

  以上就是我们自定义的两个注解,注解的开发工作就算是完成了,有了注解之后,那么就必须针对注解来编写处理器,否则我们开发的注解配置到类或者方法上面是不起作用的,这里我们使用Servlet来作为注解的处理器。

二、编写核心的注解处理器

2.1、开发AnnotationHandleServlet

  这里使用一个Servlet来作为注解处理器,编写一个AnnotationHandleServlet,代码如下:

复制代码
  1 package me.gacl.web.controller;
  2 
  3 import java.io.IOException;
  4 import java.lang.reflect.InvocationTargetException;
  5 import java.lang.reflect.Method;
  6 import java.util.Set;
  7 import javax.servlet.ServletConfig;
  8 import javax.servlet.ServletException;
  9 import javax.servlet.http.HttpServlet;
 10 import javax.servlet.http.HttpServletRequest;
 11 import javax.servlet.http.HttpServletResponse;
 12 import me.gacl.annotation.Controller;
 13 import me.gacl.annotation.RequestMapping;
 14 import me.gacl.util.BeanUtils;
 15 import me.gacl.util.RequestMapingMap;
 16 import me.gacl.util.ScanClassUtil;
 17 import me.gacl.web.context.WebContext;
 18 import me.gacl.web.view.DispatchActionConstant;
 19 import me.gacl.web.view.View;
 20 
 21 /**
 22  * <p>ClassName: AnnotationHandleServlet<p>
 23  * <p>Description: AnnotationHandleServlet作为自定义注解的核心处理器以及负责调用目标业务方法处理用户请求<p>
 24  * @author 
 25  * @version 1.0 V
 26  */
 27 public class AnnotationHandleServlet extends HttpServlet {
 28     
 29     private String pareRequestURI(HttpServletRequest request){
 30         String path = request.getContextPath()+"/";
 31         String requestUri = request.getRequestURI();
 32         String midUrl = requestUri.replaceFirst(path, "");
 33         String lasturl = midUrl.substring(0, midUrl.lastIndexOf("."));
 34         return lasturl;
 35     }
 36     
 37     public void doGet(HttpServletRequest request, HttpServletResponse response)
 38             throws ServletException, IOException {
 39         this.excute(request, response);
 40     }
 41 
 42     public void doPost(HttpServletRequest request, HttpServletResponse response)
 43             throws ServletException, IOException {
 44         this.excute(request, response);
 45     }
 46     
 47     private void excute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
 48         //将当前线程中HttpServletRequest对象存储到ThreadLocal中,以便在Controller类中使用
 49         WebContext.requestHodler.set(request);
 50         //将当前线程中HttpServletResponse对象存储到ThreadLocal中,以便在Controller类中使用
 51         WebContext.responseHodler.set(response);
 52         //解析url
 53         String lasturl = pareRequestURI(request);
 54         //获取要使用的类
 55         Class<?> clazz = RequestMapingMap.getRequesetMap().get(lasturl);
 56         //创建类的实例
 57         Object classInstance = BeanUtils.instanceClass(clazz);
 58         //获取类中定义的方法
 59         Method [] methods = BeanUtils.findDeclaredMethods(clazz);
 60         Method method = null;
 61         for(Method m:methods){//循环方法,找匹配的方法进行执行
 62             if(m.isAnnotationPresent(RequestMapping.class)){
 63                 String anoPath = m.getAnnotation(RequestMapping.class).value();
 64                 if(anoPath!=null && !"".equals(anoPath.trim()) && lasturl.equals(anoPath.trim())){
 65                     //找到要执行的目标方法
 66                     method = m;
 67                     break;
 68                 }
 69             }
 70         }
 71         try {
 72             if(method!=null){
 73                 //执行目标方法处理用户请求
 74                 Object retObject = method.invoke(classInstance);
 75                 //如果方法有返回值,那么就表示用户需要返回视图
 76                 if (retObject!=null) {
 77                     View view = (View)retObject;
 78                     //判断要使用的跳转方式
 79                     if(view.getDispathAction().equals(DispatchActionConstant.FORWARD)){
 80                         //使用服务器端跳转方式
 81                         request.getRequestDispatcher(view.getUrl()).forward(request, response);
 82                     }else if(view.getDispathAction().equals(DispatchActionConstant.REDIRECT)){
 83                         //使用客户端跳转方式
 84                         response.sendRedirect(request.getContextPath()+view.getUrl());
 85                     }else{
 86                         request.getRequestDispatcher(view.getUrl()).forward(request, response);
 87                     }
 88                 }
 89             }
 90         } catch (IllegalArgumentException e) {
 91             e.printStackTrace();
 92         } catch (IllegalAccessException e) {
 93             e.printStackTrace();
 94         } catch (InvocationTargetException e) {
 95             e.printStackTrace();
 96         }
 97     }
 98 
 99     @Override
100     public void init(ServletConfig config) throws ServletException {
101         /**
102          * 重写了Servlet的init方法后一定要记得调用父类的init方法,
103          * 否则在service/doGet/doPost方法中使用getServletContext()方法获取ServletContext对象时
104          * 就会出现java.lang.NullPointerException异常
105          */
106         super.init(config); 
107         System.out.println("---初始化开始---");
108         //获取web.xml中配置的要扫描的包
109         String basePackage = config.getInitParameter("basePackage");
110         //如果配置了多个包,例如:<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
111         if (basePackage.indexOf(",")>0) {
112             //按逗号进行分隔
113             String[] packageNameArr = basePackage.split(",");
114             for (String packageName : packageNameArr) {
115                 initRequestMapingMap(packageName);
116             }
117         }else {
118             initRequestMapingMap(basePackage);
119         }
120         System.out.println("----初始化结束---");
121     }
122     
123     /**
124     * @Method: initRequestMapingMap
125     * @Description:添加使用了Controller注解的Class到RequestMapingMap中
126     * @Anthor:孤傲苍狼
127     * @param packageName
128     */ 
129     private void initRequestMapingMap(String packageName){
130         Set<Class<?>> setClasses =  ScanClassUtil.getClasses(packageName);
131         for (Class<?> clazz :setClasses) {
132             if (clazz.isAnnotationPresent(Controller.class)) {
133                 Method [] methods = BeanUtils.findDeclaredMethods(clazz);
134                 for(Method m:methods){//循环方法,找匹配的方法进行执行
135                     if(m.isAnnotationPresent(RequestMapping.class)){
136                         String anoPath = m.getAnnotation(RequestMapping.class).value();
137                         if(anoPath!=null && !"".equals(anoPath.trim())){
138                             if (RequestMapingMap.getRequesetMap().containsKey(anoPath)) {
139                                 throw new RuntimeException("RequestMapping映射的地址不允许重复!");
140                             }
141                             RequestMapingMap.put(anoPath, clazz);
142                         }
143                     }
144                 }
145             }
146         }
147     }
148 }
复制代码

  这里说一下AnnotationHandleServlet的实现思路

  1、AnnotationHandleServlet初始化(init)时扫描指定的包下面使用了Controller注解的类,如下图所示:

  

  2、遍历类中的方法,找到类中使用了RequestMapping注解标注的那些方法,获取RequestMapping注解的value属性值,value属性值指明了该方法的访问路径,以RequestMapping注解的value属性值作为key,Class类作为value将存储到一个静态Map集合中。如下图所示:

  

  当用户请求时(无论是get还是post请求),会调用封装好的execute方法 ,execute会先获取请求的url,然后解析该URL,根据解析好的URL从Map集合中取出要调用的目标类 ,再遍历目标类中定义的所有方法,找到类中使用了RequestMapping注解的那些方法,判断方法上面的RequestMapping注解的value属性值是否和解析出来的URL路径一致,如果一致,说明了这个就是要调用的目标方法,此时就可以利用java反射机制先实例化目标类对象,然后再通过实例化对象调用要执行的方法处理用户请求。服务器将以下图的方式与客户端进行交互  

  

  另外,方法处理完成之后需要给客户端发送响应信息,比如告诉客户端要跳转到哪一个页面,采用的是服务器端跳转还是客户端方式跳转,或者发送一些数据到客户端显示,那么该如何发送响应信息给客户端呢,在此,我们可以设计一个View(视图)类,对这些操作属性进行封装,其中包括跳转的路径 、展现到页面的数据、跳转方式。这就是AnnotationHandleServlet的实现思路。

2.2、在Web.xml文件中注册AnnotationHandleServlet

  在web.xml文件中配置AnnotationHandleServlet和需要扫描的包

复制代码
 1 <servlet>
 2     <servlet-name>AnnotationHandleServlet</servlet-name>
 3     <servlet-class>me.gacl.web.controller.AnnotationHandleServlet</servlet-class>
 4     <init-param>
 5          <description>配置要扫描包及其子包, 如果有多个包,以逗号分隔</description>
 6         <param-name>basePackage</param-name>
 7         <param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
 8         <!-- <param-value>me.gacl.web.controller</param-value> -->
 9     </init-param>
10     <load-on-startup>1</load-on-startup>
11   </servlet>
12 
13   <servlet-mapping>
14     <servlet-name>AnnotationHandleServlet</servlet-name>
15     <!-- 拦截所有以.do后缀结尾的请求 -->
16     <url-pattern>*.do</url-pattern>
17   </servlet-mapping>
复制代码

三、相关代码讲解

3.1、BeanUtils

  BeanUtils工具类主要是用来处理一些反射的操作

复制代码
 1 package me.gacl.util;
 2 
 3 import java.lang.reflect.Constructor;
 4 import java.lang.reflect.Field;
 5 import java.lang.reflect.InvocationTargetException;
 6 import java.lang.reflect.Method;
 7 import java.lang.reflect.Modifier;
 8 
 9 /**
10  * 对java反射中操作的一些封装
11  */
12 public class BeanUtils {
13     
14     /**
15      * 实例化一个class 
16      * @param <T>
17      * @param clazz Person.class
18      * @return
19      */
20     public static <T> T instanceClass(Class<T> clazz){
21         if(!clazz.isInterface()){
22             try {
23                 return clazz.newInstance();
24             } catch (InstantiationException e) {
25                 e.printStackTrace();
26             } catch (IllegalAccessException e) {
27                 e.printStackTrace();
28             }
29         }
30         return null;
31     }
32     
33     /**
34      * 通过构造函数实例化
35      * @param <T>
36      * @param ctor
37      * @param args
38      * @return
39      * @throws IllegalArgumentException
40      * @throws InstantiationException
41      * @throws IllegalAccessException
42      * @throws InvocationTargetException
43      */
44     public static <T> T instanceClass(Constructor<T> ctor, Object... args)
45             throws IllegalArgumentException, InstantiationException, 
46             IllegalAccessException, InvocationTargetException{
47         makeAccessible(ctor);
48         return ctor.newInstance(args);//调用构造方法实例化
49     }
50     
51     /**
52      * 查找某个class的方法
53      * @param clazz
54      * @param methodName
55      * @param paramTypes
56      * @return
57      * @throws SecurityException
58      * @throws NoSuchMethodException
59      */
60     public static  Method findMethod(Class<?> clazz, String methodName, Class<?>... paramTypes){
61         try {
62             return clazz.getMethod(methodName, paramTypes);
63         } catch (NoSuchMethodException e) {
64             return findDeclaredMethod(clazz, methodName, paramTypes);//返回共有的方法
65         }
66     }
67     
68     public static Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>[] paramTypes){
69         try {
70             return clazz.getDeclaredMethod(methodName, paramTypes);
71         }
72         catch (NoSuchMethodException ex) {
73             if (clazz.getSuperclass() != null) {
74                 return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes);
75             }
76             return null;
77         }
78     }
79     
80     public static Method [] findDeclaredMethods(Class<?> clazz){
81             return clazz.getDeclaredMethods();
82     }
83     
84     public static void makeAccessible(Constructor<?> ctor) {
85         if ((!Modifier.isPublic(ctor.getModifiers()) 
86                 || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers()))
87                 && !ctor.isAccessible()) {
88             ctor.setAccessible(true);//如果是私有的 设置为true 使其可以访问
89         }
90     }
91     
92     public static Field[] findDeclaredFields(Class<?> clazz){
93         return clazz.getDeclaredFields();
94     }
95 }
复制代码

3.2、RequestMapingMap

  该类是用于存储方法的访问路径,AnnotationHandleServlet初始化时会将类(使用Controller注解标注的那些类)中使用了RequestMapping注解标注的那些方法的访问路径存储到RequestMapingMap中。

复制代码
 1 package me.gacl.util;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 
 6 /**
 7 * @ClassName: RequestMapingMap
 8 * @Description: 存储方法的访问路径
 9 * @author: 
10 * @date: 2014-11-16 下午6:31:43
11 *
12 */ 
13 public class RequestMapingMap {
14 
15     /**
16     * @Field: requesetMap
17     *          用于存储方法的访问路径
18     */ 
19     private static Map<String, Class<?>> requesetMap = new HashMap<String, Class<?>>();
20     
21     public static Class<?> getClassName(String path) {
22         return requesetMap.get(path);
23     }
24 
25     public static void put(String path, Class<?> className) {
26         requesetMap.put(path, className);
27     }
28 
29     public static Map<String, Class<?>> getRequesetMap() {
30         return requesetMap;
31     }
32 }
复制代码

3.3、ScanClassUtil

  扫描某个包下面的类的工具类

复制代码
  1 package me.gacl.util;
  2 
  3 import java.io.File;
  4 import java.io.FileFilter;
  5 import java.io.IOException;
  6 import java.net.JarURLConnection;
  7 import java.net.URL;
  8 import java.net.URLDecoder;
  9 import java.util.Enumeration;
 10 import java.util.LinkedHashSet;
 11 import java.util.Set;
 12 import java.util.jar.JarEntry;
 13 import java.util.jar.JarFile;
 14 
 15 /**
 16 * @ClassName: ScanClassUtil
 17 * @Description: 扫描指定包或者jar包下面的class
 18 * @author: 
 19 * @date: 2014-11-16 下午6:34:10
 20 *
 21 */ 
 22 public class ScanClassUtil {
 23 
 24     /**
 25      * 从包package中获取所有的Class
 26      * 
 27      * @param pack
 28      * @return
 29      */
 30     public static Set<Class<?>> getClasses(String pack) {
 31 
 32         // 第一个class类的集合
 33         Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
 34         // 是否循环迭代
 35         boolean recursive = true;
 36         // 获取包的名字 并进行替换
 37         String packageName = pack;
 38         String packageDirName = packageName.replace('.', '/');
 39         // 定义一个枚举的集合 并进行循环来处理这个目录下的things
 40         Enumeration<URL> dirs;
 41         try {
 42             dirs = Thread.currentThread().getContextClassLoader().getResources(
 43                     packageDirName);
 44             // 循环迭代下去
 45             while (dirs.hasMoreElements()) {
 46                 // 获取下一个元素
 47                 URL url = dirs.nextElement();
 48                 // 得到协议的名称
 49                 String protocol = url.getProtocol();
 50                 // 如果是以文件的形式保存在服务器上
 51                 if ("file".equals(protocol)) {
 52                     System.err.println("file类型的扫描");
 53                     // 获取包的物理路径
 54                     String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
 55                     // 以文件的方式扫描整个包下的文件 并添加到集合中
 56                     findAndAddClassesInPackageByFile(packageName, filePath,
 57                             recursive, classes);
 58                 } else if ("jar".equals(protocol)) {
 59                     // 如果是jar包文件
 60                     // 定义一个JarFile
 61                     System.err.println("jar类型的扫描");
 62                     JarFile jar;
 63                     try {
 64                         // 获取jar
 65                         jar = ((JarURLConnection) url.openConnection())
 66                                 .getJarFile();
 67                         // 从此jar包 得到一个枚举类
 68                         Enumeration<JarEntry> entries = jar.entries();
 69                         // 同样的进行循环迭代
 70                         while (entries.hasMoreElements()) {
 71                             // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
 72                             JarEntry entry = entries.nextElement();
 73                             String name = entry.getName();
 74                             // 如果是以/开头的
 75                             if (name.charAt(0) == '/') {
 76                                 // 获取后面的字符串
 77                                 name = name.substring(1);
 78                             }
 79                             // 如果前半部分和定义的包名相同
 80                             if (name.startsWith(packageDirName)) {
 81                                 int idx = name.lastIndexOf('/');
 82                                 // 如果以"/"结尾 是一个包
 83                                 if (idx != -1) {
 84                                     // 获取包名 把"/"替换成"."
 85                                     packageName = name.substring(0, idx)
 86                                             .replace('/', '.');
 87                                 }
 88                                 // 如果可以迭代下去 并且是一个包
 89                                 if ((idx != -1) || recursive) {
 90                                     // 如果是一个.class文件 而且不是目录
 91                                     if (name.endsWith(".class")
 92                                             && !entry.isDirectory()) {
 93                                         // 去掉后面的".class" 获取真正的类名
 94                                         String className = name.substring(
 95                                                 packageName.length() + 1, name
 96                                                         .length() - 6);
 97                                         try {
 98                                             // 添加到classes
 99                                             classes.add(Class
100                                                     .forName(packageName + '.'
101                                                             + className));
102                                         } catch (ClassNotFoundException e) {
103                                             // log
104                                             // .error("添加用户自定义视图类错误 找不到此类的.class文件");
105                                             e.printStackTrace();
106                                         }
107                                     }
108                                 }
109                             }
110                         }
111                     } catch (IOException e) {
112                         // log.error("在扫描用户定义视图时从jar包获取文件出错");
113                         e.printStackTrace();
114                     }
115                 }
116             }
117         } catch (IOException e) {
118             e.printStackTrace();
119         }
120 
121         return classes;
122     }
123     
124     /**
125      * 以文件的形式来获取包下的所有Class
126      * 
127      * @param packageName
128      * @param packagePath
129      * @param recursive
130      * @param classes
131      */
132     public static void findAndAddClassesInPackageByFile(String packageName,
133             String packagePath, final boolean recursive, Set<Class<?>> classes) {
134         // 获取此包的目录 建立一个File
135         File dir = new File(packagePath);
136         // 如果不存在或者 也不是目录就直接返回
137         if (!dir.exists() || !dir.isDirectory()) {
138             // log.warn("用户定义包名 " + packageName + " 下没有任何文件");
139             return;
140         }
141         // 如果存在 就获取包下的所有文件 包括目录
142         File[] dirfiles = dir.listFiles(new FileFilter() {
143             // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
144             public boolean accept(File file) {
145                 return (recursive && file.isDirectory())
146                         || (file.getName().endsWith(".class"));
147             }
148         });
149         // 循环所有文件
150         for (File file : dirfiles) {
151             // 如果是目录 则继续扫描
152             if (file.isDirectory()) {
153                 findAndAddClassesInPackageByFile(packageName + "."
154                         + file.getName(), file.getAbsolutePath(), recursive,
155                         classes);
156             } else {
157                 // 如果是java类文件 去掉后面的.class 只留下类名
158                 String className = file.getName().substring(0,
159                         file.getName().length() - 6);
160                 try {
161                     // 添加到集合中去
162                     //classes.add(Class.forName(packageName + '.' + className));
163                      //经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
164                     classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));  
165                     } catch (ClassNotFoundException e) {
166                     // log.error("添加用户自定义视图类错误 找不到此类的.class文件");
167                     e.printStackTrace();
168                 }
169             }
170         }
171     }
172 }
复制代码

3.4、WebContext

  WebContext主要是用来存储当前线程中的HttpServletRequest和HttpServletResponse,当别的地方需要使用HttpServletRequest和HttpServletResponse,就可以通过requestHodler和responseHodler获取,通过WebContext.java这个类 ,我们可以在作为Controller的普通java类中获取当前请求的request、response或者session相关请求类的实例变量,并且线程间互不干扰的,因为用到了ThreadLocal这个类。

复制代码
 1 package me.gacl.web.context;
 2 
 3 import javax.servlet.ServletContext;
 4 import javax.servlet.http.HttpServletRequest;
 5 import javax.servlet.http.HttpServletResponse;
 6 import javax.servlet.http.HttpSession;
 7 
 8 /**
 9  * WebContext主要是用来存储当前线程中的HttpServletRequest和HttpServletResponse
10  * 当别的地方需要使用HttpServletRequest和HttpServletResponse,就可以通过requestHodler和responseHodler获取
11  **/
12 public class WebContext {
13 
14     public static ThreadLocal<HttpServletRequest> requestHodler = new ThreadLocal<HttpServletRequest>();
15     public static ThreadLocal<HttpServletResponse> responseHodler = new ThreadLocal<HttpServletResponse>();
16    
17     public HttpServletRequest getRequest(){
18            return requestHodler.get();
19     }
20    
21     public HttpSession getSession(){
22            return requestHodler.get().getSession();
23     }
24    
25     public ServletContext getServletContext(){
26            return requestHodler.get().getSession().getServletContext();
27     }
28     
29     public HttpServletResponse getResponse(){
30         return responseHodler.get();
31     }
32 }
复制代码

3.5、View

  一个视图类,对一些客户端响应操作进行封装,其中包括跳转的路径 、展现到页面的数据、跳转方式

复制代码
 1 package me.gacl.web.view;
 2 
 3 /**
 4  * 视图模型
 5  **/
 6 public class View {
 7 
 8     private String url;//跳转路径
 9     
10     private String dispathAction = DispatchActionConstant.FORWARD;//跳转方式
11 
12     public View(String url) {
13         this.url = url;
14     }
15     
16     public View(String url,String name,Object value) {
17         this.url = url;
18         ViewData view = new ViewData();
19         view.put(name, value);
20     }
21     
22     
23     public View(String url,String name,String dispathAction ,Object value) {
24         this.dispathAction = dispathAction;
25         this.url = url;
26         ViewData view = new ViewData();//请看后面的代码
27         view.put(name, value);
28     }
29     
30     
31     public String getUrl() {
32         return url;
33     }
34     
35     
36     public void setUrl(String url) {
37         this.url = url;
38     }
39 
40     public String getDispathAction() {
41         return dispathAction;
42     }
43 
44     public void setDispathAction(String dispathAction) {
45         this.dispathAction = dispathAction;
46     }
47 }
复制代码

3.6、ViewData

  request范围的数据存储类,当需要发送数据到客户端显示时,就可以将要显示的数据存储到ViewData类中。使用ViewData.put(String name,Object value)方法往request对象中存数据。

复制代码
 1 package me.gacl.web.view;
 2 
 3 import javax.servlet.http.HttpServletRequest;
 4 
 5 import me.gacl.web.context.WebContext;
 6 
 7 /**
 8  * 需要发送到客户端显示的数据模型
 9  */
10 public class ViewData {
11     
12     private HttpServletRequest request;
13     
14     public ViewData() {
15         initRequest();
16     }
17 
18     private void initRequest(){
19         //从requestHodler中获取request对象
20         this.request = WebContext.requestHodler.get();
21     }
22     
23     public void put(String name,Object value){
24         this.request.setAttribute(name, value);
25     }
26 }
复制代码

3.7、DispatchActionConstant

  一个跳转方式的常量类

复制代码
 1 package me.gacl.web.view;
 2 
 3 /**
 4  * 跳转常量
 5  */
 6 public class DispatchActionConstant {
 7 
 8     public static String FORWARD = "forward";//服务器跳转
 9     
10     public static String REDIRECT = "redirect";//客户端跳转
11 }
复制代码

四、Controller注解和RequestMapping注解测试

4.1、简单测试

  编写一个LoginUI类,用于跳转到具体的jsp页面,代码如下:

复制代码
 1 package me.gacl.web.UI;
 2 
 3 import me.gacl.annotation.Controller;
 4 import me.gacl.annotation.RequestMapping;
 5 import me.gacl.web.view.View;
 6 /**
 7  * 使用Controller注解标注LoginUI类
 8  */
 9 @Controller
10 public class LoginUI {
11     
12     //使用RequestMapping注解指明forward1方法的访问路径  
13     @RequestMapping("LoginUI/Login2")
14     public View forward1(){
15         //执行完forward1方法之后返回的视图
16         return new View("/login2.jsp");  
17     }
18     
19     //使用RequestMapping注解指明forward2方法的访问路径  
20     @RequestMapping("LoginUI/Login3")
21     public View forward2(){
22         //执行完forward2方法之后返回的视图
23         return new View("/login3.jsp");  
24     } 
25 }
复制代码

  运行结果如下所示:

  

4.2、复杂测试

  编写用于处理用户登录请求的Controller,代码如下:

复制代码
 1 package me.gacl.web.controller;
 2 
 3 import java.io.IOException;
 4 import javax.servlet.http.HttpServletRequest;
 5 import javax.servlet.http.HttpServletResponse;
 6 import me.gacl.annotation.Controller;
 7 import me.gacl.annotation.RequestMapping;
 8 import me.gacl.web.context.WebContext;
 9 import me.gacl.web.view.View;
10 import me.gacl.web.view.ViewData;
11 
12 /**
13 * 
14 * @ClassName: LoginServlet2
15 * @Description:处理用户登录的Servlet,
16 * LoginServlet现在就是一个普通的java类,不是一个真正的Servlet
17 * @author: 
18 * @date: 2014-10-8 上午12:07:58
19 *
20 */
21 @Controller //使用Controller注解标注LoginServlet2
22 public class LoginServlet2 {
23 
24     /**
25     * @Method: loginHandle
26     * @Description:处理以普通方式提交的请求
27     * @Anthor:
28     *
29     * @return View
30     */
31     //使用RequestMapping注解标注loginHandle方法,指明loginHandle方法的访问路径是login/handle
32     @RequestMapping("login/handle")
33     public View loginHandle(){
34         //创建一个ViewData对象,用于存储需要发送到客户端的响应数据
35         ViewData viewData = new ViewData();
36         //通过WebContext类获取当前线程中的HttpServletRequest对象
37         HttpServletRequest request = WebContext.requestHodler.get();
38         //接收提交上来的参数
39         String username =request.getParameter("usename");
40         String pwd = request.getParameter("pwd");
41         if (username.equals("gacl") && pwd.equals("xdp")) {
42             request.getSession().setAttribute("usename", username);
43             //将响应数据存储到ViewData对象中
44             viewData.put("msg", "欢迎您!"+username);
45             //返回一个View对象,指明要跳转的视图的路径
46             return new View("/index.jsp");
47         }else {
48             //将响应数据存储到ViewData对象中
49             viewData.put("msg", "登录失败,请检查用户名和密码是否正确!");
50             //返回一个View对象,指明要跳转的视图的路径
51             return new View("/login2.jsp");
52         }
53     }
54     
55     /**
56     * @Method: ajaxLoginHandle
57     * @Description: 处理以AJAX方式提交的请求
58     * @Anthor:
59     *
60     * @throws IOException
61     */ 
62     //使用RequestMapping注解标注ajaxLoginHandle方法,指明ajaxLoginHandle方法的访问路径是ajaxLogin/handle
63     @RequestMapping("ajaxLogin/handle")
64     public void ajaxLoginHandle() throws IOException{
65         //通过WebContext类获取当前线程中的HttpServletRequest对象
66         HttpServletRequest request = WebContext.requestHodler.get();
67         //接收提交上来的参数
68         String username =request.getParameter("usename");
69         String pwd = request.getParameter("pwd");
70         //通过WebContext类获取当前线程中的HttpServletResponse对象
71         HttpServletResponse response = WebContext.responseHodler.get();
72         if (username.equals("gacl") && pwd.equals("xdp")) {
73             request.getSession().setAttribute("usename", username);
74             response.getWriter().write("success");
75         }else {
76             response.getWriter().write("fail");
77         }
78     }
79 }
复制代码

  编写用于测试的jsp页面,代码如下所示:

  Login2.jsp登录页面

复制代码
 1 <%@ page language="java" pageEncoding="UTF-8"%>
 2 <!DOCTYPE HTML>
 3 <html>
 4   <head>
 5     <title>login2.jsp登录页面</title>
 6   </head>
 7   
 8   <body>
 9     <fieldset>
10         <legend>用户登录</legend>
11         <form action="${pageContext.request.contextPath}/login/handle.do" method="post">
12             用户名:<input type="text" value="${param.usename}" name="usename">
13             <br/>
14             密码:<input type="text" value="${param.pwd}" name="pwd">
15             <br/>
16             <input type="submit" value="登录"/>
17         </form>
18     </fieldset>
19     <hr/>
20     <label style="color: red;">${msg}</label>
21   </body>
22 </html>
复制代码

  login3.jsp登录页面

复制代码
 1 <%@ page language="java" pageEncoding="UTF-8"%>
 2 <!DOCTYPE HTML>
 3 <html>
 4   <head>
 5     <title>login3登录页面</title>
 6     <script type="text/javascript" src="${pageContext.request.contextPath}/ajaxUtil.js"></script>
 7     <script type="text/javascript" src="${pageContext.request.contextPath}/js/Utils.js"></script>
 8     <script type="text/javascript">
 9     
10         function login(){
11              Ajax.request({
12                  url : "${pageContext.request.contextPath}/ajaxLogin/handle.do",
13                  data : {
14                      "usename" : document.getElementById("usename").value,
15                      "pwd" : document.getElementById("pwd").value
16                  },
17                  success : function(xhr) {
18                      onData(xhr.responseText);
19                  },
20                  error : function(xhr) {
21                      
22                  }
23              });
24         }
25         
26         function onData(responseText){
27             if(responseText=="success"){
28                 //window.location.href="index.jsp";//改变url地址
29                 /*
30                 window.location.replace("url"):将地址替换成新url,
31                 该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,
32                 你不能通过“前进”和“后 退”来访问已经被替换的URL,这个特点对于做一些过渡页面非常有用!
33                 */
34                 location.replace(g_basePath+"/index.jsp");
35             }else{
36                 alert("用户名和密码错误");
37             }
38         }
39     </script>
40   </head>
41   
42   <body>
43     <fieldset>
44         <legend>用户登录</legend>
45         <form>
46             用户名:<input type="text" name="usename" id="usename">
47             <br/>
48             密码:<input type="text" name="pwd" id="pwd">
49             <br/>
50             <input type="button" value="登录" onclick="login()"/>
51         </form>
52     </fieldset>
53   </body>
54 </html>
复制代码

  index.jsp页面代码如下:

复制代码
 1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
 2 
 3 <!DOCTYPE HTML>
 4 <html>
 5   <head>
 6     <title>首页</title>
 7   </head>
 8   
 9   <body>
10         登录的用户名:${usename}
11     <br/>
12     ${msg}
13   </body>
14 </html>
复制代码

  jsp页面中使用到的Utils.js代码如下:

复制代码
 1 //立即执行的js
 2 (function() {
 3     //获取contextPath
 4     var contextPath = getContextPath();
 5     //获取basePath
 6     var basePath = getBasePath();
 7     //将获取到contextPath和basePath分别赋值给window对象的g_contextPath属性和g_basePath属性
 8     window.g_contextPath = contextPath;
 9     window.g_basePath = basePath;
10 })();
11 
12 /**
13  * @author 
14  * 获得项目根路径,等价于jsp页面中
15  *  <%
16         String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
17     %>
18  * 使用方法:getBasePath();
19  * @returns 项目的根路径
20  *  
21  */
22 function getBasePath() {
23     var curWwwPath = window.document.location.href;
24     var pathName = window.document.location.pathname;
25     var pos = curWwwPath.indexOf(pathName);
26     var localhostPath = curWwwPath.substring(0, pos);
27     var projectName = pathName.substring(0, pathName.substr(1).indexOf('/') + 1);
28     return (localhostPath + projectName);
29 }
30 
31 /**
32  * @author 
33  * 获取Web应用的contextPath,等价于jsp页面中
34  *  <%
35         String path = request.getContextPath();
36     %>
37  * 使用方法:getContextPath();
38  * @returns /项目名称(/EasyUIStudy_20141104)
39  */
40 function getContextPath() {
41     return window.document.location.pathname.substring(0, window.document.location.pathname.indexOf('\/', 1));
42 };
复制代码

  测试结果如下:

  

  

  以上就是对Spring MVC的简单模拟。

posted @ 2015-12-09 14:48  江飞劫  阅读(164)  评论(0编辑  收藏  举报