- 仅描述 如何创建 以及实现的代码
- 小白理解,欢迎大佬批判.
如何去写一个自定的mvc呢?
- 需要注解 , 通过获取注解的形式来进行一些处理
- 需要自定义映射池
- 一个servlet管理器
自定义注解
这里我是举例说明 --> 例如返回字符串和跳转视图所用的注解
返回字符串的注解
/**
* @Documented 可以使用在用户文档
* @Retention(RetentionPolicy.RUNTIME) 描述此注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
* @Target(ElementType.METHOD) 标记此注解是用在方法上
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
* 注解的作用:
* 被此注解添加的方法 会被用于处理请求
* 方法返回的内容 会以文字的形式返回到客户端
*/
public @interface ResponseBody {
String value();
}
跳转视图所用的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
* 注解的作用:
* 被此注解添加的方法 会被用于处理请求
* 方法返回的内容 会直接重定向
*/
public @interface ResponseView {
String value();
}
针对上面的两个注解去写一个枚举,用于判断是那种类型
public enum ResponseType {
TEXT,VIEW;
}
自定义一个映射池
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 ppt = new Properties();
try {
ppt.load(is);
} catch (IOException e) {
e.printStackTrace();
}
Collection<Object> values = ppt.values();// 获取配置文件中描述的一个个类
// 读取配置文件
for (Object cla : values) {
String className = (String) cla;
try {
Class c = Class.forName(className); // 加载配置文件中每一个描述的类
Object obj = c.getConstructor().newInstance(); // 创建这个类的对象
// 获取类的所有方法
Method[] methods = c.getMethods();
for (Method m : methods) {
Annotation[] as = m.getAnnotations();// 获取所有方法的注解
if (as != null) {
for (Annotation annotation : as) {
if (annotation instanceof ResponseBody) {
// 说明此方法 用于返回字符串
MVCMapping mapping = new MVCMapping(obj, m, ResponseType.TEXT); // 初始话一个MVCMapping
Object o = data.put(((ResponseBody) annotation).value(), mapping);
// 如果已经存在 那么map的put方法返回的就不是空, 如果不存在那么map的put方法返回值是空
if (o != null){
throw new RuntimeException("请求地址重复: "+ ((ResponseBody) annotation).value());
}
} else if (annotation instanceof ResponseView) {
// 说明此方法 用于返回界面给客户端
MVCMapping mapping = new MVCMapping(obj, m, ResponseType.VIEW); // 初始话一个MVCMapping
Object o = data.put(((ResponseView) annotation).value(), mapping);
if (o != null){
throw new RuntimeException("请求地址重复: "+ ((ResponseView) annotation).value());
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 映射对象 --> 每一个对象封装了一个方法 用于处理请求
*/
public static class MVCMapping {
private Object obj; // 处理请求的对象
private Method method; // 处理请求的方法
private ResponseType 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;
}
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;
}
}
}
Servlet管理器
public class DispatcherServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
// config 就是我们的初始化文件
String path = config.getInitParameter("contentConfigLocation");// 通过config 就可以获取配置文件中的参数
InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream(path);
HandlerMapping.load(is);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取用户请求的uri
String uri = req.getRequestURI();
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) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
switch (mapping.getType()) {
case TEXT:
resp.getWriter().write((String) result);
break;
case VIEW:
resp.sendRedirect((String) result);
break;
}
}
}
在 web.xml中配置这个servlet
<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>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>xxx.xxx.xxx.DispatcherServlet</servlet-class>
<init-param> <!-- 设置用于描述配置文件的参数 -->
<param-name>contentConfigLocation</param-name> <!-- 设置配置文件名 -->
<param-value>application.properties</param-value> <!-- 配置文件所在路径 [ 在src目录下创建一个application.properties文件 用来描述之后各个处理类 ] -->
</init-param>
<load-on-startup>0</load-on-startup> <!-- 配置这个servlet为项目开始时就加载 -->
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
思路总结
项目启动 --> 加载 DispatcherServlet --> 调用初始化方法 --> 初始化方法中调用 HandlerMapping.load(is); 加载配置文件 --> 将配置文件中的相应类,类方法,进行封装 --> 接收到请求时 --> 调用 DispatcherServlet 中的service 方法 --> 获取请求的url --> 调用HandlerMapping中的get方法 --> 返回值是否为空,空直接跳转错误页面,不为空继续 --> 获取获取里面的数据 [ 类,类方法,类型 ] --> 使用反射机制,对方法进行传参并调用 --> 使用switch进行匹配 --> TEXT类型 就直接输出字符串 VIEW类型 跳转页面即可
相关类中方法如何创建
HandlerMapping中load加载配置文件中描述的类 [ 下面此类就是 ]
public class UserController {
@ResponseBody("/login.do")
public String login(HttpServletRequest request, HttpServletResponse response){
return "登录成功";
}
@ResponseView("/reg.do")
public String reg(HttpServletRequest request, HttpServletResponse response){
return "success.jsp";
}
}
此类需要在application.properties文件中添加
a= com.xxx.xxx.UserController