1. 介绍
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。
我们今天就来搭建一个自己的从URL访问到JAVA控制器的简单框架。
附加:在我的 github上有一个初版的 swift-framework 框架(与本节内容无关)
swift是一个轻量级的web框架,实现了 IOC、MVC、ORM 功能,并且已经可以使用,满足基本的开发需要和学习使用,适合了解spring的基本原理。
未来将会逐步实现 aop、安全管理等功能。
2. 实现思路
- 首先,我们要将URL访问请求和我们的后台JAVA方法形成一一对应关系。
- 然后,我们需要在tomcat容器启动网站加载的时候获取上一步骤中记录的一一对应关系,并记录下来。
- 最后,完成URL访问到后台JAVA方法的跳转。
3. 具体实现:
(1)通过注解来标记出URL到后台JAVA方法的映射关系。
A. 定义注解:这个注解可以标注在类和方法上。
/**
* @author 旺旺
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UrlMapping {
public String url() default "";
}
B. JAVA后台中针对URI的相应方法:
package mvc;
/**
* @author 旺旺
*/
@UrlMapping(url="/Say")
public class SayController {
@UrlMapping(url="/Hello")
public String sayHello(){
System.out.println("Hello_________________");
return "Hello";
}
@UrlMapping(url="/Hi")
public String sayHi(){
System.out.println("Hi_____________");
return "Hi";
}
}
通过注解,我们可以看出我们拟定将来用SayController这个类来相应/Say/***的URL。其中sayHello方法相应的URL是/Say/Hello,sayHi方法相应的URL是/Say/Hi。
这样,我们就定义好了URL和JAVA后台方法的对应关系。接下来我们要实现如果完成这种对应关系的跳转
。
(2)实现URL到JAVA后台方法的跳转
A. 定义我们用来存储URL到JAVA后台方法对应关系的基础数据类型:
package mvc;
/**
* @author 旺旺
*/
public class MVCBase {
private String url;//访问路径
private String controller;//类名
private String method;//方法名
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getController() {
return controller;
}
public void setController(String controller) {
this.controller = controller;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
}
B. 通过监听器在tomcat启动的时候收集URL到JAVA后台方法的对应关系并存储起来:
package mvc;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* @author 旺旺
*/
public class UrlMappingCollection implements ServletContextListener {
//被注解了URLMapper的类方法列表
private static List<MVCBase> mvcBases;
//我们要扫描的Controller列表
private final String[] controllerList = {"mvc.SayController"};
public void contextDestroyed(ServletContextEvent arg0) {
}
public void contextInitialized(ServletContextEvent arg0) {
mvcBases = new ArrayList<MVCBase>();
try {
//循环所有需要扫描的Controller
for(String controllerName : controllerList) {
String classUrl = "";
String methodUrl = "";
//获取Controller类
Class<?> clas = Class.forName(controllerName);
//class被标记了URLMapping注解
if(clas.isAnnotationPresent(UrlMapping.class)) {
classUrl = clas.getAnnotation(UrlMapping.class).url();
//获取method列表
Method[] methods = clas.getMethods();
for(Method method : methods) {
if(method.isAnnotationPresent(UrlMapping.class)) {
methodUrl = method.getAnnotation(UrlMapping.class).url();
MVCBase mvc = new MVCBase();
mvc.setUrl(classUrl + methodUrl);
mvc.setController(controllerName);
mvc.setMethod(method.getName());
mvcBases.add(mvc);
}
}
}
}
} catch (Exception e) {
}
}
public static List<MVCBase> getMvcBases() {
return mvcBases;
}
}
-
- mvcBases就是将来我们要将URL到JAVA后台方法对应关系存储到的地方。我们将他定义为静态方法,以便后续我们直接访问获取。
- controllerList是用来告诉监听器,我们要从哪些类中获取URL到JAVA后台方法的对应关系。因为在程序设计期我们就已经知道了,所以定义为final属性。
- 然后在监听器初始化的时候,循环controllerList中的class,根据注解的信息来收集URL到JAVA后台方法的对应关系并存储到mvcBases中。
C. 配置Servlet访问:
首先我们要准备我们的Servlet类,将来所有的URL请求都要跳转到这个Servlet中。
采用注册表单例模式,管理实体类(线程不安全)。
package 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;
/**
* @author 旺旺
*/
public class ServletCenter extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
private static boolean lock = true;
//实例容器,注册表
private static HashMap registry = new HashMap();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
doTransfer(req, resp);
} catch (Exception e) {
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
doTransfer(req, resp);
} catch (Exception e) {
}
}
//通过url执行对应的方法
private void doTransfer(HttpServletRequest req, HttpServletResponse resp)
throws Exception{
//遍历url容器
for(MVCBase mvcBase : UrlMappingCollection.getMvcBases()) {
//判断注解路径
if(req.getRequestURI().equals(req.getContextPath() + mvcBase.getUrl())) {
//反射获取class
Class<?> clas = Class.forName(mvcBase.getController());
//获取方法
Method method = clas.getMethod(mvcBase.getMethod());
//执行方法
if(mvcBase.getController() != null) {
//捕获异常,特殊处理。。。。
//设置单例
if(registry.get(mvcBase.getController()) == null) {
try {
synchronized(lock) { //防止多线程造成冲突
//添加单例放入注册表
registry.put(mvcBase.getController(), Class.forName(mvcBase.getController()).newInstance());
}
} catch(Exception ex) {
ex.printStackTrace();
}
}
//获取单例,执行方法
method.invoke(registry.get(mvcBase.getController()));
} else {
//特殊处理。。。
}
}
}
}
}
可以看到,doGet和doPost里面都执行的是doTransfer方法。
相应的web.xml配置为:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name></display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 配置监听器 -->
<listener>
<listener-class>mvc.UrlMappingCollection</listener-class>
</listener>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>mvc.ServletCenter</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
在web.xml中,我们将所有的请求都跳转到com.mvc.servlet.ServletCenter中。
5. 测试
将MyMVC部署到tomcat容器中,启动tomcat,输入http://127.0.0.1:8080/MyMVC/Say/Hello 以及 http://127.0.0.1:8080/MyMVC/Say/Hi 就可以看到后台输出的Hello和Hi了