手写自己的springmvc- 2020-12-13 09:40- springmvc: 手写自己的springmvc
springmvc
概念
springmvc属于表现层的框架,它是Spring框架的一部分,我们可以从Spring的整体结构中看得出来:
处理流程
框架流程
流程图
流程解析
1、 用户发送请求至前端控制器DispatcherServlet(配置在web.xml中)
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器(HandlerMapping处理器映射器存储请求的url和对应的方法之间的映射关系,方法上的注解@ RequestMapping来建立这种映射关系)。
3、 处理器映射器根据请求url找到具体的处理器,返回处理器执行链HandlerExecutionChain,执行链包含生成的所有处理器对象handler及处理器拦截器(如果有则生成),一并返回给DispatcherServlet。
4、 DispatcherServlet将处理器执行链HandlerExecutionChain交给HandlerAdapter处理器适配器,HandlerAdapter处理器适配器根据handler的类型(类型可以是注解,配置文件等)调用不同的处理器适配器(例如专门处理注解类型的适配器)。
5、 对应类型的处理器适配器会执行controller中url对应的handler,也就是方法执行了 。(handler是方法,Controller是指表现层处理的类)
6、 Controller执行完成返回ModelAndView
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9、 ViewReslover根据ModelAndView中设置的视图的类型(类型可以是jsp,报表,pdf等),使用对应类型的视图解析器进行解析,解析后返回具体View
10、 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中,将后台数据填充到前台页面)。
11、 DispatcherServlet响应用户
组件说明
以下组件通常使用框架提供实现:
DispatcherServlet:前端控制器
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
HandlerMapping:处理器映射器
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式(实际开发中只用注解的方式)等。
DispatcherServlet.properties配置文件中HandlerMapping默认加载DefaultAnnotationHandlerMapping和BeanNameUrlHandlerMapping。
DefaultAnnotationHandlerMapping用来解析Spring MVC里面的annotation对应的Controller,也就是通过这个类,给Annotation设置映射关系,如@RequestMapping。
Handler:处理器
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。而Controller是指表现层处理的类
由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。
HandlAdapter:处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
补充:
DispatcherServlet.properties配置文件中HandlAdapter默认加载AnnotationMethodHandlerAdapter
AnnotationMethodHandlerAdapter对Annotation设置的方法进行处理的类,通过此类,解析annotation设置的类的处理(解析url和方法之间的映射关系),也就是有请求时,通过此类,可以调用annotation设置controller的方法,主要处理方法,handle(HttpServletRequest req, HttpServletResponse resp, Object handler)
View Resolver:视图解析器
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。
DispatcherServlet.properties配置文件中ViewResolver默认加载InternalResourceViewResolver
View:视图**
springmvc框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是jsp。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
说明:在springmvc的各个组件中,处理器映射器、处理器适配器、视图解析器称为springmvc的三大组件。
需要用户开发的组件有handler、view
手写自己的springmvc
设计思路
配置阶段
1.替换web.xml中配置的DispatcherServlet 为我们自己实现的DispatcherServlet
2.实现自己的@Controller @Service @Autowired@RequestMapping
初始化阶段
1.加载配置文件
加载配置的spirngmvc.xml或者application.properties
2.扫描配置的相关类
根据加载的配置文件内中配置的扫描包路径,查找对应包路径下的类
3.实例化扫描到的类,并放入到IOC容器中
将扫描到的类 实例化 并放入IOC容器中
4.依赖注入
对已经实例化的类中,有@Autowired注解的属性进行动态注入实例引用
5.整理requertMapping,把url和method对应起来放在一个k-v的Map中,在运行阶段取出
**
运行阶段
1.获取请求中的url ,从requestMapping中获取对应的方法
2.参数处理
3.执行方法
4.返回结果
demo
annotation:注解类
import java.lang.annotation.*;
/**
* 实现自己的Autowired
* @author Bai
* @date 2020/12/10 22:46
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
String value() default "";
}
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyController {
String value() default "";
}
import java.lang.annotation.*;
/**
* @author Bai
* @date 2020/12/10 20:59
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface MyRequestMapping {
String value() default "";
}
import java.lang.annotation.*;
/**
* @author Bai
* @date 2020/12/10 22:36
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
通用controller or service
/**
* @author Bai
* @date 2020/12/11 21:54
*/
@MyController
@MyRequestMapping(value = "/login")
public class LoginController {
@MyAutowired
private LoginService loginService;
@MyRequestMapping(value = "/userLogin")
public String login(HttpServletRequest request,
@RequestParam(value = "name") String name,
@RequestParam(value = "level", defaultValue = "123") Integer level) {
return request.getParameter("name") + loginService.login() + level;
}
}
/**
* @author Bai
* @date 2020/12/11 21:55
*/
@MyService
public class LoginService {
public String login() {
return "登录成功";
}
}
V1版本
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ValueConstants;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
/**
* 实现自己的springmvc 仿写 v1版本
*
* @author Bai
* @date 2020/12/10 21:03
*/
public class MyDispatcherServlet extends HttpServlet {
/**
* 上下文
*/
private static Properties applicationContext = new Properties();
/**
* IOC容器 包路径:实例
*/
private Map<String, Object> IOC = new HashMap<>();
/**
* 方法与url之间的映射
* key:url value:requestMapping
*/
private Map<String, Method> methodRequestMapping = new HashMap<>();
/**
* 扫描到的类路径
*/
private static List<String> classPathList = new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解析requestMapping
String requestURI = req.getRequestURI();
if (!methodRequestMapping.containsKey(requestURI)) {
resp.getWriter().write("url not exit!");
}
//获取对应method
Method method = methodRequestMapping.get(requestURI);
if (null == method) {
return;
}
//获取方法所在类的实例
Object o = IOC.get(classNameToLower(method.getDeclaringClass().getSimpleName()));
try {
//封装入参
Object[] params = getParams(req, resp, method);
//执行方法
Object invokeResult = method.invoke(o, params);
if (null != invokeResult) {
PrintWriter writer = resp.getWriter();
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html; charset=utf-8");
writer.write(JSONObject.toJSONString(invokeResult));
}
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("error 500" + e.getMessage());
}
}
private Object[] getParams(HttpServletRequest request, HttpServletResponse response, Method method) {
//获取方法上的参数列表
Parameter[] methodParameters = method.getParameters();
if (null == methodParameters || methodParameters.length < 1) {
return new Object[0];
}
//获取入参参数列表
Map<String, String[]> requestParameterMap = request.getParameterMap();
Object[] params = new Object[methodParameters.length];
for (int i = 0; i < methodParameters.length; i++) {
Parameter methodParameter = methodParameters[i];
if (methodParameter.getType() == HttpServletRequest.class) {
params[i] = request;
continue;
} else if (methodParameter.getType() == HttpServletResponse.class) {
params[i] = response;
continue;
}
//参数判断是否必选
RequestParam requestParam = methodParameter.getAnnotation(RequestParam.class);
String[] requestParameter = requestParameterMap.get(requestParam.value());
//如果没有给默认值,同时没有给入参 则报错
if (requestParam.required()
&& (null == requestParameter || requestParameter.length < 1)
//RequestParam的默认值是ValueConstants.DEFAULT_NONE
&& ValueConstants.DEFAULT_NONE.equals(requestParam.defaultValue())) {
throw new RuntimeException("error 500 " + requestParam.name() + " not null!");
}
//处理String类型 的参数
if (methodParameter.getType() == String.class) {
String value = null;
//先取默认值
if (!ValueConstants.DEFAULT_NONE.equals(requestParam.defaultValue())) {
value = requestParam.defaultValue();
}
if (null != requestParameter && requestParameter.length > 0) {
StringBuilder sb = new StringBuilder();
for (String s : requestParameter) {
sb.append(s);
}
value = sb.toString();
}
params[i] = value;
}
//处理Inter类型 的参数
if (methodParameter.getType() == Integer.class) {
Integer value = null;
//先取默认值
if (!ValueConstants.DEFAULT_NONE.equals(requestParam.defaultValue())) {
value = Integer.valueOf(requestParam.defaultValue());
}
if (null != requestParameter && requestParameter.length > 0) {
StringBuilder sb = new StringBuilder();
for (String s : requestParameter) {
sb.append(s);
}
value = Integer.valueOf(sb.toString());
}
params[i] = value;
}
}
return params;
}
@Override
public void init(ServletConfig config) throws ServletException {
//1.加载配置文件
doLoadContextConfig(config.getInitParameter("contextConfigLocation"));
//2.扫描相关类
doComponentScan(String.valueOf(applicationContext.get("componentScan")));
//3.实例化并且放入ioc容器内
doInstance();
//4.依赖注入
doAutowired();
//5.初始化handlerMapping
initHandlerMapping();
}
/**
* 存储 url 与方法之间的映射
*/
private void initHandlerMapping() {
if (IOC.isEmpty()) {
return;
}
for (Object object : IOC.values()) {
//非MyController注释类不处理
final Class<?> aClass = object.getClass();
if (!aClass.isAnnotationPresent(MyController.class)) {
continue;
}
//获取类 requestMapping
String name = aClass.getAnnotation(MyRequestMapping.class).value();
//获取所有被MyRequestMapping注解的方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(MyRequestMapping.class)) {
continue;
}
//获取方法 requestMapping
String requestMapping = name + method.getAnnotation(MyRequestMapping.class).value();
if (methodRequestMapping.containsKey(requestMapping)) {
throw new RuntimeException(requestMapping + " is exist");
}
methodRequestMapping.put(requestMapping, method);
}
}
}
/**
* 依赖注入
*/
private void doAutowired() {
if (IOC.isEmpty()) {
return;
}
for (Object object : IOC.values()) {
Class<?> aClass = object.getClass();
//MyAutowired一般是放在成员属性上
//获取所有成员属性
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 判断是否有MyAutowired注解
if (declaredField.isAnnotationPresent(MyAutowired.class)) {
String name = declaredField.getName();
if (IOC.containsKey(name)) {
try {
declaredField.setAccessible(true);
declaredField.set(object, IOC.get(name));
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException(name + " not in IOC!");
}
}
}
}
}
}
/**
* 实例化对象并且放入IOC容器中
*/
private void doInstance() {
if (classPathList.isEmpty()) {
return;
}
for (String clazzPath : classPathList) {
//通过反射获取class对象
Class aClass = getClass(clazzPath);
//非MyController MyService注解不实例化
if (!aClass.isAnnotationPresent(MyController.class)
&& !aClass.isAnnotationPresent(MyService.class)) {
continue;
}
if (IOC.containsKey(aClass.getName())) {
throw new RuntimeException(aClass.getName() + " is exits in IOC!");
}
try {
IOC.put(classNameToLower(aClass.getSimpleName()), aClass.newInstance());
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
private String classNameToLower(String simpleName) {
char[] chars = simpleName.toCharArray();
chars[0] += 32;
return new String(chars);
}
private Class getClass(String clazzPath) {
try {
return Class.forName(clazzPath);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 根据配置包路径扫描类
*
* @param componentScan 配置的扫描路径
*/
private void doComponentScan(String componentScan) {
//将路径转换为相对路径
String path = componentScan.replaceAll("\\.", "/");
//加载系统文件路径
URL url = this.getClass().getClassLoader().getResource(path);
System.out.println(url.getFile());
File classPath = new File(url.getFile());
for (File file : classPath.listFiles()) {
//如果是文件夹 则继续递归
if (file.isDirectory()) {
doComponentScan(componentScan + "." + file.getName());
} else {
//只加载class文件
if (!file.getName().endsWith(".class")) {
continue;
}
String className = (componentScan + "." + file.getName()).replace(".class", "");
classPathList.add(className);
}
}
}
/**
* 加载applicationContext
*
* @param initParameter
*/
private void doLoadContextConfig(String initParameter) {
InputStream inputStream = null;
try {
inputStream = this.getClass().getClassLoader().getResourceAsStream(initParameter);
applicationContext.load(inputStream);
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
**