文件结构
1、编写Web.xml
将所有 .do 后缀的请求全都映射到DispatchServlet 中,在DispatchServlet 跳转的时候将配置文件名传递过去,配置文件的编写在后面, 因为测试类还没写。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 以下部分为编写内容 -->
<servlet> <!-- DispatchServlet 对应com.ae.web.DispatchServlet 路径下的文件 -->
<servlet-name>DispatchServlet</servlet-name>
<servlet-class>com.ae.DispatchServlet</servlet-class>
<init-param> <!-- 传递过程中将配置文件名传递过去 -->
<param-name>contentConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>0</load-on-startup> <!-- 项目启动的时候就来加载这个servlet -->
</servlet>
<servlet-mapping><!-- 将所有 *.do 的请求映射到DispatchServlet上 -->
<servlet-name>DispatchServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
2、自定义两个注解
ResponseBody 以及 ResponseView 内容如下,用于区分不同类型的请求。
3、编写ResponseType枚举类
主要作用就是给你自定义的类定义两个不同的类型,之后有其他类型可以直接在这里面进行添加。
4、编写DispatchServlet
该Servlet在初始化的时候会将配置文件的Input流获取,然后传递给HandlerMapping进行加载,结果就行,将所有请求与对应的类和方法建立映射关系。
Service方法进行的时候,应为已经在初始化的时候将所有的url与方法建立了映射关系,所以可以直接可以获取对应的方法,然后执行对应的方法,并且获取返回的结果。
public class DispatchServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
// 首先获取对应的配置文件的名字
String path = config.getInitParameter("contentConfigLocation");
// 获取资源配置文件的读取流InputStream
InputStream is = DispatchServlet.class.getClassLoader().getResourceAsStream(path);
// 将读取流InputStream传递给HandlerMapping,将所有键值进行加载
HandlerMapping.load(is);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
//1.获取用户请求的uri
String uri = req.getRequestURI();
// 根据请求的uri获取对应的映射内容
HandlerMapping.MVCMapping mapping = HandlerMapping.get(uri);
// 为空则不存在该请求
if(mapping == null) {
resp.sendError(404, "MVC映射地址不存在" + uri);
return ;
}
// 获取请求对应的类对象
Object obj = mapping.getObj();
// 获取请求对应的方法
Method method = mapping.getMethod();
// 用于存取请求结果
Object result = null;
try {
// 执行对应的方法返回结果
result = method.invoke(obj, req, resp);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
switch(mapping.getType()){ // 根据对应的内容进行不同的操作
case TEXT: // 返回的文本则直接写入
resp.getWriter().write((String)result);
break;
case VIEW: // 返回的页面文件名则进行跳转
resp.sendRedirect((String) result);
break;
}
}
}
5、HandlerMapping的编写
作用是将配置文件进行加载读取,并且将请求与对应的类的方法建立映射关系,并且存储。
public class HandlerMapping {
// 记录每一个用过的注解值,防止请求地址使用重复
private static Map<String, MVCMapping> data = new HashMap();
public static MVCMapping get(String uri) {
return data.get(uri);
}
public static void load(InputStream is) {
Properties properties = new Properties();
try {
// 加载配置文件中的内容
properties.load(is);
// 获取配置文件中描述的一个个的类
Collection<Object> values = properties.values();
for(Object cla : values) {
// 获取配置文件中第一个类文件名
String className = (String)cla;
try {
// 获取对应的类文件
Class c = Class.forName(className);
// 创建这个类的对象
Object o = c.getConstructor().newInstance();
// 获取这个类的所有方法
Method[] ms = c.getMethods();
// 枚举类中每个方法
for(Method m : ms) {
// 获取当前方法中所有的注解
Annotation[] an = m.getAnnotations();
if(an != null) {
// 枚举当前方法中所有的注解
for(Annotation annotation : an) {
if(annotation instanceof ResponseBody) {
// 说明此方法用于返回字符串给客户端
MVCMapping mapping = new MVCMapping(o, m, ResponseType.TEXT);
Object object = data.put(((ResponseBody) annotation).value(), mapping);
if(object != null) {
// 存在了重复的请求地址
throw new RuntimeException("请求地址重复:" + ((ResponseBody) annotation).value());
}
} else if(annotation instanceof ResponseView) {
// 说明此方法用于返回界面给客户端
MVCMapping mapping = new MVCMapping(o, m, ResponseType.VIEW);
Object object = data.put(((ResponseView) annotation).value(), mapping);
if(object != null) {
// 存在了重复的请求地址
throw new RuntimeException("请求地址重复:" + ((ResponseView) annotation).value());
}
}
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 映射对象,每一个对象都封装一个对象,用于处理请求
*/
public static class MVCMapping {
private Object obj;
private Method method;
private ResponseType type;
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public ResponseType getType() {
return type;
}
public void setType(ResponseType type) {
this.type = type;
}
public MVCMapping() {
}
public MVCMapping(Object obj, Method method, ResponseType type) {
this.obj = obj;
this.method = method;
this.type = type;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
}
6、编写测试类以及添加配置文件
以上已经写好了,测试一下就行了。建立两个测试类。Test1Controller, Test2Controller
然后将两个类的路径添加到配置文件中。这里只需要将所有请求类的路径加进行就行,等号左边的名字随便填就行。
注意资源配置文件跟数据库druid.properties文件问题一样,建在resources文件下,否则无法加载,老版本的项目需要建立在源文件目录下即可。
Test1Controller
success.jsp随便写几个内容就行,进行区分。注意建立在Webapp目录下
public class Test1Controller {
@ResponseBody("/login.do") // 测试返回文本内容的请求
public String login(HttpServletRequest request, HttpServletResponse response) {
return "login success";
}
@ResponseView("/register.do") // 测试返回文件名以及跳转页面的请求
public String register(HttpServletRequest request, HttpServletResponse response) {
return "success.jsp";
}
}
Test2Controller
xxx.html也随便谢谢进行区分即可,注意建立在Webapp目录下
public class Test2Controller {
@ResponseBody("/test1.do") // 测试返回文本的请求
public String test1(HttpServletRequest request, HttpServletResponse response) {
return "ha ha ha";
}
@ResponseView("/test2") // 测试不加后缀是否能使用
public String test2(HttpServletRequest request, HttpServletResponse response) {
return "hei hei hei";
}
@ResponseView("/test3.do") // 测试返回页面文件名以及跳转的请求
public String test3(HttpServletRequest request, HttpServletResponse response) {
return "xxx.html";
}
}