Simple Implement of Spring MVC

简单模拟Spring MVC

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 使用Controller注解标注LoginUI类
*/
@Controller
public class LoginUI {

//使用RequestMapping注解指明forward1方法的访问路径
@RequestMapping("LoginUI/Login2")
public View forward1(){
//执行完forward1方法之后返回的视图
return new View("/login2.jsp");
}

//使用RequestMapping注解指明forward2方法的访问路径
@RequestMapping("LoginUI/Login3")
public View forward2(){
//执行完forward2方法之后返回的视图
return new View("/login3.jsp");
}
}

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

一、编写注解

1.1 Controller注解

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package me.gacl.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @ClassName: Controller
* @Description: 自定义Controller注解
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {

public String value() default "";
}

1.2 RequestMapping注解

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package me.gacl.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 定义请求路径的java annotation
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {

public String value() default "";
}

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

编写核心的注解处理器

2.1 开发AnnotationHandleServlet

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package me.gacl.web.controller;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.annotation.Controller;
import me.gacl.annotation.RequestMapping;
import me.gacl.util.BeanUtils;
import me.gacl.util.RequestMapingMap;
import me.gacl.util.ScanClassUtil;
import me.gacl.web.context.WebContext;
import me.gacl.web.view.DispatchActionConstant;
import me.gacl.web.view.View;

/**
* <p>ClassName: AnnotationHandleServlet<p>
* <p>Description: AnnotationHandleServlet作为自定义注解的核心处理器以及负责调用目标业务方法处理用户请求<p>
*/
public class AnnotationHandleServlet extends HttpServlet {

private String pareRequestURI(HttpServletRequest request){
String path = request.getContextPath()+"/";
String requestUri = request.getRequestURI();
String midUrl = requestUri.replaceFirst(path, "");
String lasturl = midUrl.substring(0, midUrl.lastIndexOf("."));
return lasturl;
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.excute(request, response);
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.excute(request, response);
}

private void excute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
//将当前线程中HttpServletRequest对象存储到ThreadLocal中,以便在Controller类中使用
WebContext.requestHodler.set(request);
//将当前线程中HttpServletResponse对象存储到ThreadLocal中,以便在Controller类中使用
WebContext.responseHodler.set(response);
//解析url
String lasturl = pareRequestURI(request);
//获取要使用的类
Class<?> clazz = RequestMapingMap.getRequesetMap().get(lasturl);
//创建类的实例
Object classInstance = BeanUtils.instanceClass(clazz);
//获取类中定义的方法
Method [] methods = BeanUtils.findDeclaredMethods(clazz);
Method method = null;
for(Method m:methods){//循环方法,找匹配的方法进行执行
if(m.isAnnotationPresent(RequestMapping.class)){
String anoPath = m.getAnnotation(RequestMapping.class).value();
if(anoPath!=null && !"".equals(anoPath.trim()) && lasturl.equals(anoPath.trim())){
//找到要执行的目标方法
method = m;
break;
}
}
}
try {
if(method!=null){
//执行目标方法处理用户请求
Object retObject = method.invoke(classInstance);
//如果方法有返回值,那么就表示用户需要返回视图
if (retObject!=null) {
View view = (View)retObject;
//判断要使用的跳转方式
if(view.getDispathAction().equals(DispatchActionConstant.FORWARD)){
//使用服务器端跳转方式
request.getRequestDispatcher(view.getUrl()).forward(request, response);
}else if(view.getDispathAction().equals(DispatchActionConstant.REDIRECT)){
//使用客户端跳转方式
response.sendRedirect(request.getContextPath()+view.getUrl());
}else{
request.getRequestDispatcher(view.getUrl()).forward(request, response);
}
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}

@Override
public void init(ServletConfig config) throws ServletException {
/**
* 重写了Servlet的init方法后一定要记得调用父类的init方法,
* 否则在service/doGet/doPost方法中使用getServletContext()方法获取ServletContext对象时
* 就会出现java.lang.NullPointerException异常
*/
super.init(config);
System.out.println("---初始化开始---");
//获取web.xml中配置的要扫描的包
String basePackage = config.getInitParameter("basePackage");
//如果配置了多个包,例如:<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
if (basePackage.indexOf(",")>0) {
//按逗号进行分隔
String[] packageNameArr = basePackage.split(",");
for (String packageName : packageNameArr) {
initRequestMapingMap(packageName);
}
}else {
initRequestMapingMap(basePackage);
}
System.out.println("----初始化结束---");
}

/**
* @Method: initRequestMapingMap
* @Description:添加使用了Controller注解的Class到RequestMapingMap中
* @param packageName
*/
private void initRequestMapingMap(String packageName){
Set<Class<?>> setClasses = ScanClassUtil.getClasses(packageName);
for (Class<?> clazz :setClasses) {
if (clazz.isAnnotationPresent(Controller.class)) {
Method [] methods = BeanUtils.findDeclaredMethods(clazz);
for(Method m:methods){//循环方法,找匹配的方法进行执行
if(m.isAnnotationPresent(RequestMapping.class)){
String anoPath = m.getAnnotation(RequestMapping.class).value();
if(anoPath!=null && !"".equals(anoPath.trim())){
if (RequestMapingMap.getRequesetMap().containsKey(anoPath)) {
throw new RuntimeException("RequestMapping映射的地址不允许重复!");
}
RequestMapingMap.put(anoPath, clazz);
}
}
}
}
}
}
}

简单说一下AnnotationHandleServlet的实现思路

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


  1. 遍历类中的方法,找到类中使用了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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<servlet>
<servlet-name>AnnotationHandleServlet</servlet-name>
<servlet-class>me.gacl.web.controller.AnnotationHandleServlet</servlet-class>
<init-param>
<description>配置要扫描包及其子包, 如果有多个包,以逗号分隔</description>
<param-name>basePackage</param-name>
<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
<!-- <param-value>me.gacl.web.controller</param-value> -->
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>AnnotationHandleServlet</servlet-name>
<!-- 拦截所有以.do后缀结尾的请求 -->
<url-pattern>*.do</url-pattern>
</servlet-mapping>

三、相关代码讲解

3.1 BeanUtils

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package me.gacl.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
* 对java反射中操作的一些封装
*/
public class BeanUtils {

/**
* 实例化一个class
* @param <T>
* @param clazz Person.class
* @return
*/
public static <T> T instanceClass(Class<T> clazz){
if(!clazz.isInterface()){
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return null;
}

/**
* 通过构造函数实例化
* @param <T>
* @param ctor
* @param args
* @return
* @throws IllegalArgumentException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public static <T> T instanceClass(Constructor<T> ctor, Object... args)
throws IllegalArgumentException, InstantiationException,
IllegalAccessException, InvocationTargetException{
makeAccessible(ctor);
return ctor.newInstance(args);//调用构造方法实例化
}

/**
* 查找某个class的方法
* @param clazz
* @param methodName
* @param paramTypes
* @return
* @throws SecurityException
* @throws NoSuchMethodException
*/
public static Method findMethod(Class<?> clazz, String methodName, Class<?>... paramTypes){
try {
return clazz.getMethod(methodName, paramTypes);
} catch (NoSuchMethodException e) {
return findDeclaredMethod(clazz, methodName, paramTypes);//返回共有的方法
}
}

public static Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>[] paramTypes){
try {
return clazz.getDeclaredMethod(methodName, paramTypes);
}
catch (NoSuchMethodException ex) {
if (clazz.getSuperclass() != null) {
return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes);
}
return null;
}
}

public static Method [] findDeclaredMethods(Class<?> clazz){
return clazz.getDeclaredMethods();
}

public static void makeAccessible(Constructor<?> ctor) {
if ((!Modifier.isPublic(ctor.getModifiers())
|| !Modifier.isPublic(ctor.getDeclaringClass().getModifiers()))
&& !ctor.isAccessible()) {
ctor.setAccessible(true);//如果是私有的 设置为true 使其可以访问
}
}

public static Field[] findDeclaredFields(Class<?> clazz){
return clazz.getDeclaredFields();
}
}

3.2 RequestMapingMap

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package me.gacl.util;

import java.util.HashMap;
import java.util.Map;

/**
* @ClassName: RequestMapingMap
* @Description: 存储方法的访问路径
* @author: 孤傲苍狼
* @date: 2014-11-16 下午6:31:43
*
*/
public class RequestMapingMap {

/**
* @Field: requesetMap
* 用于存储方法的访问路径
*/
private static Map<String, Class<?>> requesetMap = new HashMap<String, Class<?>>();

public static Class<?> getClassName(String path) {
return requesetMap.get(path);
}

public static void put(String path, Class<?> className) {
requesetMap.put(path, className);
}

public static Map<String, Class<?>> getRequesetMap() {
return requesetMap;
}
}

3.3. ScanClassUtil

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package me.gacl.util;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* @ClassName: ScanClassUtil
* @Description: 扫描指定包或者jar包下面的class
* @author: 孤傲苍狼
* @date: 2014-11-16 下午6:34:10
*
*/
public class ScanClassUtil {

/**
* 从包package中获取所有的Class
*
* @param pack
* @return
*/
public static Set<Class<?>> getClasses(String pack) {

// 第一个class类的集合
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(
packageDirName);
// 循环迭代下去
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
System.err.println("file类型的扫描");
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath,
recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
System.err.println("jar类型的扫描");
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection())
.getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx)
.replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class")
&& !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(
packageName.length() + 1, name
.length() - 6);
try {
// 添加到classes
classes.add(Class
.forName(packageName + '.'
+ className));
} catch (ClassNotFoundException e) {
// log
// .error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}

return classes;
}

/**
* 以文件的形式来获取包下的所有Class
*
* @param packageName
* @param packagePath
* @param recursive
* @param classes
*/
public static void findAndAddClassesInPackageByFile(String packageName,
String packagePath, final boolean recursive, Set<Class<?>> classes) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("用户定义包名 " + packageName + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory())
|| (file.getName().endsWith(".class"));
}
});
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "."
+ file.getName(), file.getAbsolutePath(), recursive,
classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0,
file.getName().length() - 6);
try {
// 添加到集合中去
//classes.add(Class.forName(packageName + '.' + className));
//经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// log.error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}

3.4 WebContext

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package me.gacl.web.context;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
* WebContext主要是用来存储当前线程中的HttpServletRequest和HttpServletResponse
* 当别的地方需要使用HttpServletRequest和HttpServletResponse,就可以通过requestHodler和responseHodler获取
**/
public class WebContext {

public static ThreadLocal<HttpServletRequest> requestHodler = new ThreadLocal<HttpServletRequest>();
public static ThreadLocal<HttpServletResponse> responseHodler = new ThreadLocal<HttpServletResponse>();

public HttpServletRequest getRequest(){
return requestHodler.get();
}

public HttpSession getSession(){
return requestHodler.get().getSession();
}

public ServletContext getServletContext(){
return requestHodler.get().getSession().getServletContext();
}

public HttpServletResponse getResponse(){
return responseHodler.get();
}
}

3.5 View

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package me.gacl.web.view;

/**
* 视图模型
**/
public class View {

private String url;//跳转路径

private String dispathAction = DispatchActionConstant.FORWARD;//跳转方式

public View(String url) {
this.url = url;
}

public View(String url,String name,Object value) {
this.url = url;
ViewData view = new ViewData();
view.put(name, value);
}


public View(String url,String name,String dispathAction ,Object value) {
this.dispathAction = dispathAction;
this.url = url;
ViewData view = new ViewData();//请看后面的代码
view.put(name, value);
}


public String getUrl() {
return url;
}


public void setUrl(String url) {
this.url = url;
}

public String getDispathAction() {
return dispathAction;
}

public void setDispathAction(String dispathAction) {
this.dispathAction = dispathAction;
}
}

3.6 ViewData

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package me.gacl.web.view;

import javax.servlet.http.HttpServletRequest;

import me.gacl.web.context.WebContext;

/**
* 需要发送到客户端显示的数据模型
*/
public class ViewData {

private HttpServletRequest request;

public ViewData() {
initRequest();
}

private void initRequest(){
//从requestHodler中获取request对象
this.request = WebContext.requestHodler.get();
}

public void put(String name,Object value){
this.request.setAttribute(name, value);
}
}

3.7 DispatchActionConstant

一个跳转方式的常量类

1
2
3
4
5
6
7
8
9
10
11
package me.gacl.web.view;

/**
* 跳转常量
*/
public class DispatchActionConstant {

public static String FORWARD = "forward";//服务器跳转

public static String REDIRECT = "redirect";//客户端跳转
}

四、Controller注解和RequestMapping注解测试

4.1 简单测试

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package me.gacl.web.UI;

import me.gacl.annotation.Controller;
import me.gacl.annotation.RequestMapping;
import me.gacl.web.view.View;
/**
* 使用Controller注解标注LoginUI类
*/
@Controller
public class LoginUI {

//使用RequestMapping注解指明forward1方法的访问路径
@RequestMapping("LoginUI/Login2")
public View forward1(){
//执行完forward1方法之后返回的视图
return new View("/login2.jsp");
}

//使用RequestMapping注解指明forward2方法的访问路径
@RequestMapping("LoginUI/Login3")
public View forward2(){
//执行完forward2方法之后返回的视图
return new View("/login3.jsp");
}
}

运行结果如下



4.2 复杂测试

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package me.gacl.web.controller;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.annotation.Controller;
import me.gacl.annotation.RequestMapping;
import me.gacl.web.context.WebContext;
import me.gacl.web.view.View;
import me.gacl.web.view.ViewData;

/**
*
* @ClassName: LoginServlet2
* @Description:处理用户登录的Servlet,
* LoginServlet现在就是一个普通的java类,不是一个真正的Servlet
* @author: 孤傲苍狼
* @date: 2014-10-8 上午12:07:58
*
*/
@Controller //使用Controller注解标注LoginServlet2
public class LoginServlet2 {

/**
* @Method: loginHandle
* @Description:处理以普通方式提交的请求
* @Anthor:孤傲苍狼
*
* @return View
*/
//使用RequestMapping注解标注loginHandle方法,指明loginHandle方法的访问路径是login/handle
@RequestMapping("login/handle")
public View loginHandle(){
//创建一个ViewData对象,用于存储需要发送到客户端的响应数据
ViewData viewData = new ViewData();
//通过WebContext类获取当前线程中的HttpServletRequest对象
HttpServletRequest request = WebContext.requestHodler.get();
//接收提交上来的参数
String username =request.getParameter("usename");
String pwd = request.getParameter("pwd");
if (username.equals("gacl") && pwd.equals("xdp")) {
request.getSession().setAttribute("usename", username);
//将响应数据存储到ViewData对象中
viewData.put("msg", "欢迎您!"+username);
//返回一个View对象,指明要跳转的视图的路径
return new View("/index.jsp");
}else {
//将响应数据存储到ViewData对象中
viewData.put("msg", "登录失败,请检查用户名和密码是否正确!");
//返回一个View对象,指明要跳转的视图的路径
return new View("/login2.jsp");
}
}

/**
* @Method: ajaxLoginHandle
* @Description: 处理以AJAX方式提交的请求
* @Anthor:孤傲苍狼
*
* @throws IOException
*/
//使用RequestMapping注解标注ajaxLoginHandle方法,指明ajaxLoginHandle方法的访问路径是ajaxLogin/handle
@RequestMapping("ajaxLogin/handle")
public void ajaxLoginHandle() throws IOException{
//通过WebContext类获取当前线程中的HttpServletRequest对象
HttpServletRequest request = WebContext.requestHodler.get();
//接收提交上来的参数
String username =request.getParameter("usename");
String pwd = request.getParameter("pwd");
//通过WebContext类获取当前线程中的HttpServletResponse对象
HttpServletResponse response = WebContext.responseHodler.get();
if (username.equals("gacl") && pwd.equals("xdp")) {
request.getSession().setAttribute("usename", username);
response.getWriter().write("success");
}else {
response.getWriter().write("fail");
}
}
}

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

Login2.jsp登录页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>login2.jsp登录页面</title>
</head>

<body>
<fieldset>
<legend>用户登录</legend>
<form action="${pageContext.request.contextPath}/login/handle.do" method="post">
用户名:<input type="text" value="${param.usename}" name="usename">
<br/>
密码:<input type="text" value="${param.pwd}" name="pwd">
<br/>
<input type="submit" value="登录"/>
</form>
</fieldset>
<hr/>
<label style="color: red;">${msg}</label>
</body>
</html>

login3.jsp登录页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>login3登录页面</title>
<script type="text/javascript" src="${pageContext.request.contextPath}/ajaxUtil.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/Utils.js"></script>
<script type="text/javascript">

function login(){
Ajax.request({
url : "${pageContext.request.contextPath}/ajaxLogin/handle.do",
data : {
"usename" : document.getElementById("usename").value,
"pwd" : document.getElementById("pwd").value
},
success : function(xhr) {
onData(xhr.responseText);
},
error : function(xhr) {

}
});
}

function onData(responseText){
if(responseText=="success"){
//window.location.href="index.jsp";//改变url地址
/*
window.location.replace("url"):将地址替换成新url,
该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,
你不能通过“前进”和“后 退”来访问已经被替换的URL,这个特点对于做一些过渡页面非常有用!
*/
location.replace(g_basePath+"/index.jsp");
}else{
alert("用户名和密码错误");
}
}
</script>
</head>

<body>
<fieldset>
<legend>用户登录</legend>
<form>
用户名:<input type="text" name="usename" id="usename">
<br/>
密码:<input type="text" name="pwd" id="pwd">
<br/>
<input type="button" value="登录" onclick="login()"/>
</form>
</fieldset>
</body>
</html>

index.jsp页面代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<!DOCTYPE HTML>
<html>
<head>
<title>首页</title>
</head>

<body>
登录的用户名:${usename}
<br/>
${msg}
</body>
</html>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//立即执行的js
(function() {
//获取contextPath
var contextPath = getContextPath();
//获取basePath
var basePath = getBasePath();
//将获取到contextPath和basePath分别赋值给window对象的g_contextPath属性和g_basePath属性
window.g_contextPath = contextPath;
window.g_basePath = basePath;
})();

/**
* @author 孤傲苍狼
* 获得项目根路径,等价于jsp页面中
* <%
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
* 使用方法:getBasePath();
* @returns 项目的根路径
*
*/
function getBasePath() {
var curWwwPath = window.document.location.href;
var pathName = window.document.location.pathname;
var pos = curWwwPath.indexOf(pathName);
var localhostPath = curWwwPath.substring(0, pos);
var projectName = pathName.substring(0, pathName.substr(1).indexOf('/') + 1);
return (localhostPath + projectName);
}

/**
* @author 孤傲苍狼
* 获取Web应用的contextPath,等价于jsp页面中
* <%
String path = request.getContextPath();
%>
* 使用方法:getContextPath();
* @returns /项目名称(/EasyUIStudy_20141104)
*/
function getContextPath() {
return window.document.location.pathname.substring(0, window.document.location.pathname.indexOf('\/', 1));
};

测试结果如下:





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

文章作者: Monad Kai
文章链接: onlookerliu.github.io/2018/03/26/Simple-Implement-of-Spring-MVC/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Code@浮生记
支付宝打赏
微信打赏