@[TOC]
自定义实现Spring Mvc框架
前言
在使用Spring Mvc时候我们需要配置一个中央处理器DispatcherServlet用于分发请求,根据url查找处理器,然后再根据处理器去处理不同的Url去执行不同的方法,最后到处理完数据,再返回给前端;这一过程都是在DispatcherServlet
处理器中去执行的;
可以看到下图是SpringMvc的请求流程图
在这里插入图片描述
当url进来的时候我们就应该去加载完成url和一些方法的对应关系,以及注册和处理好bean的依赖关系;
如需要自己去实现一个这个框架:
可以思考一下:
1 需要提供一个路径去扫描包下的所有类
2 然后解析处理类,注册bean 维护依赖关系
3 处理好url和方法的对应关系
4 在DispatcherServlet中取根据前端请求的url去查找到具体的处理器,然后再根据url去找到具的执行方法,去执行反方,返回给前端;
具体操作:
步骤
-
1 启动tomcat
DisPatcherServlet 加载配置文件SpringMvc.xml -
2 指定包扫描,扫描注解
@Controllelr
@Service
@Autowired
@RequestMapping 维护url映射 -
3 加入IOC容器
初始化对象,维护依赖关系,维护url映射 -
4 SpringMVC相关组件初始化
简历Url和method之间的映射关系---> 映射器 HanderMappping
- 5 请求处理
代码实现
环境搭建
JDK8 ,IDEA 2019 03
依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
插件
<plugins>
<!-- 配置Maven的JDK编译级别 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>8</source>
<target>8</target>
<encoding>UTF-8</encoding>
<!--告诉编译器,编译的时候记录下形参的真实名称-->
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- tomcat7插件 -->
<!-- 注意:目前来说,maven中央仓库还没有tomcat8的插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
自定义注解
Autowired
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String name() default "";
}
Controller
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
}
RequestMapping
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
定义service 和 controller代码
Test控制层
@RequestMapping("/api")
@Controller
public class Test {
@Autowired
private TestService testService;
@RequestMapping("/test")
public String test(HttpServletRequest request, HttpServletResponse response, String name) {
return testService.test(name);
}
}
TestService 接口
public interface TestService {
String test(String name);
}
TestServiceImpl实现类
@Service(value = "testServiceImpl")
public class TestServiceImpl implements TestService {
@Override
public String test(String name) {
System.out.println("-------------------- name = " + name);
return "--------- " + name;
}
}
定义DisPatcherServlet处理器
处理前端所有的请求都走DisPatcherServlet处理器
<!-- 处理请求 -->
<servlet-mapping>
<servlet-name>mvc-servlte</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
DisPatcherServlet 继承 HttpServlet 接口,重写init get post方法
public class DisPatcherServlet extends HttpServlet {
@Override
public void init() throws ServletException {
// 初始化参数 加载配置文件,bean实例化 以及依赖维护
//1 加载配置文件
doLoadConfigFile(resourceAsStream);
// 2 扫描包
doScanPackage();
// 3 bean实例化和依赖维护
doInitBean();
// 4 维护url和method之间的映射关系
doHandlerMapping();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求
}
}
初始化反方里 需要我们去加载bean 实例化和依赖维护,,以及封装好url对应的方法handler
定义方法
//1 加载配置文件
doLoadConfigFile(resourceAsStream);
// 2 扫描包
doScanPackage();
// 3 bean实例化和依赖维护
doInitBean();
// 4 维护url和method之间的映射关系
doHandlerMapping();
加载配置文件
对应到springmvc中就是包扫描配置扫描那些包
这儿配置一个properties文件 mvc.properties
配置需要扫描的包路径
mvc.path=com.udem.edu
通过类加载器去加载配置文件获取到流
//加载流
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("mvc.properties");
然后获取包路径封装文件到 Properties中
/**
* 加载配置文件
*
* @param resourceAsStream
*/
private void doLoadConfigFile(InputStream resourceAsStream) {
try {
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
包扫描
包路径转换成文件路径然后去扫描,判断是否文件夹或文件,然后保存类的全限定命名到集合中,这个没得说;
/**
* 包扫描获取字节码对象
*
* @throws UnsupportedEncodingException
*/
private void doScanPackage() throws UnsupportedEncodingException {
//获取mvc包路径
String packName = properties.getProperty("mvc.path");
if (Objects.isNull(packName) || packName.length() == 0) {
throw new RuntimeException("无效的包路径");
}
packName = packName.replace(".", File.separator);
URL resource = this.getClass().getClassLoader().getResource(packName);
String path = resource.getPath();
//解析中文
String filePath = URLDecoder.decode(path, "UTF-8");
//解析包成java权限定命名com
parseFilePackName(packName, stringSet, filePath);
}
bean实例化和依赖维护
判断类上的注解以及封装到IOC 依赖维护赋值值给含有 Autowired
注解的成员变量
/**
* bean实例化和bean依赖注入关系维护
*
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
*/
private void doInitBean() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
for (String className : stringSet) {
//反射创建对象
Class<?> aClass = Class.forName(className);
//查找 Controller 注解
Controller annotation = aClass.getAnnotation(Controller.class);
if (Objects.nonNull(annotation)) {
//实例化存入ioc
IOC_MAP.put(aClass.getSimpleName(), aClass.newInstance());
}
//查找 service 注解
Service service = aClass.getAnnotation(Service.class);
if (Objects.nonNull(service)) {
//实例化存入ioc
IOC_MAP.put(aClass.getSimpleName(), aClass.newInstance());
//如果实现类接口 按照接口实例化
Class<?>[] interfaces = aClass.getInterfaces();
if (Objects.nonNull(interfaces) && interfaces.length > 0) {
for (Class<?> anInterface : interfaces) {
//按照接口名进行实例化
IOC_MAP.put(anInterface.getSimpleName(), aClass.newInstance());
}
}
}
}
//依赖注入
for (Map.Entry<String, Object> stringObjectEntry : IOC_MAP.entrySet()) {
Class aClass = stringObjectEntry.getValue().getClass();
//获取所有字段
Field[] declaredFields = aClass.getDeclaredFields();
if (Objects.isNull(declaredFields) && declaredFields.length == 0) {
continue;
}
for (Field field : declaredFields) {
//field.get
//字段含有 Autowired 注解的需要被自动装配对象
Autowired autowired = field.getAnnotation(Autowired.class);
if (Objects.nonNull(autowired)) {
//根据当前key获取需要注入示例对象
//先根据名字注入,如果名字获取不到,再根据类型去注入
String beanName = autowired.name();
if (Objects.isNull(beanName) || beanName.length() == 0) {
beanName = field.getType().getSimpleName();
}
//反射设置值
try {
field.setAccessible(true);
//自动装配 线程不安全,Spring中默认单例
field.set(stringObjectEntry.getValue(), IOC_MAP.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
handlerMapper 处理映射器
维护url和method之间的映射关系
这儿需要处理控制层的参数方法,也就是控制层方法的参数顺序,需要和之后 DisPatcherServlet
中请求处理传递进来的参数保持一致
在 DisPatcherServlet 中根据请求url获取到这里封装的 handle 然后传递请求的参数与原方法需要的参数保持一致,反射调用;
封装 handle 对象用于保存参数位置,以及那个方法,那个控制器!
/**
* 封装handler方法相关的信息
* <p>
* handler参数相关信息
*/
public class Handle {
/**
* method.invoke(obj 需要执行得对象
*/
private Object controller;
/**
* url正则匹配对象
*/
private Pattern pattern;
/**
* 需要调用得url对应得方法
*/
private Method method;
/**
* 存储参数顺序,为了进行参数绑定 Integer 第几个参数
*/
private Map<String, Integer> map;
public Handle(Object controller, Pattern pattern, Method method) {
this.controller = controller;
this.pattern = pattern;
this.method = method;
map = new HashMap<>();
}
// get set省略
}
封装handler方法相关的信息
这儿我们目前只处理String类型的传参,其他对象之类的暂时不涉及到...
private void doHandlerMapping() {
if (IOC_MAP.isEmpty()) {
return;
}
//只处理controller层
for (Map.Entry<String, Object> stringObjectEntry : IOC_MAP.entrySet()) {
Class<?> objectEntryValue = stringObjectEntry.getValue().getClass();
//没有包含 注解 跳过
if (!objectEntryValue.isAnnotationPresent(Controller.class)) {
continue;
}
String url = "";
//包含 RequestMapping 注解 就获取url
if (objectEntryValue.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = objectEntryValue.getAnnotation(RequestMapping.class);
url = requestMapping.value();
}
//获取方法
Method[] methods = objectEntryValue.getMethods();
if (methods.length <= 0 || Objects.isNull(methods)) {
continue;
}
for (Method method : methods) {
//如果不包含就跳过
if (!method.isAnnotationPresent(RequestMapping.class)) {
continue;
}
//获取当前方法上 RequestMapping 的注解
RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class);
//构建url
String urls = url + requestMapping.value();
//封装handler
Handle handle = new Handle(stringObjectEntry.getValue(), Pattern.compile(urls), method);
//处理计算参数位置
Parameter[] parameters = method.getParameters();
if (parameters != null && parameters.length > 0) {
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
//如果是 参数名称存本身
handle.getMap().put(parameter.getType().getSimpleName(), i);
}
//如果是tring类型
else {
//String类型存储name
handle.getMap().put(parameter.getName(), i);
}
//其他类型暂时不做判断
}
HANDLE_MAPPING.add(handle);
}
}
}
}
doPost中 处理请求
根据HttpServletRequest 获取到请求参数以及url
String requestURI = req.getRequestURI();
//遍历请求中得参数顺序
Map<String, String[]> reqParameterMaps = req.getParameterMap();
System.out.println("------- 当前请求的url是 : " + requestURI);
if (Objects.isNull(requestURI) || requestURI.length() == 0) {
return;
}
获取这个请求url的处理器handler ,如果不存在返回404
//5.1 获取handle
Handle handler = getHandler(req);
if (Objects.isNull(handler)){
resp.getWriter().println("404....");
return;
}
获取到具体的执行方法以及方法中的参数顺序 也就是封装handler时候封装的map
//获取执行方法
Method method = handler.getMethod();
Parameter[] parameters = handler.getMethod().getParameters();
//5.2 获取型参数
Map<String, Integer> map1 = handler.getMap();
设置请求的url中的请求参数 与 method中的方法参数一致
//设置传递参数
Object[] objects = new Object[parameters.length];
//遍历除reqest 和 response之外的参数 并且填充到参数数组里
for (Map.Entry<String, String[]> stringEntry : reqParameterMaps.entrySet()) {
//判断请求参数和形参是否参数匹配
//多个参数直接 , 拼接
String value = StringUtils.join(stringEntry.getValue(), ","); // 如同 1,2
if (!map1.containsKey(stringEntry.getKey())) {
continue;
}
//获取型参数索引
Integer index = map1.get(stringEntry.getKey());
//赋值参数
objects[index] = value;
}
int index1 = map1.get(HttpServletRequest.class.getSimpleName());
int index2 = map1.get(HttpServletResponse.class.getSimpleName());
//获取ret对象参数
objects[index1] = req;
//获取resp对象参数
objects[index2] = resp;
反射执行方法
try {
String result = (String) method.invoke(handler.getController(), objects);
resp.getWriter().println(result);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
完整的DisPatcherServlet
代码
package com.udem.mvcframework.servlet;
import com.udem.mvcframework.annotation.Autowired;
import com.udem.mvcframework.annotation.Controller;
import com.udem.mvcframework.annotation.RequestMapping;
import com.udem.mvcframework.annotation.Service;
import org.apache.commons.lang3.StringUtils;
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.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 前端请求中央处理器
*/
public class DisPatcherServlet extends HttpServlet {
/**
* 封装配置文件属性
*/
private final static Properties properties = new Properties();
/**
* 存放扫描类的全限定命名
*/
private final static Set<String> stringSet = new HashSet<>();
/**
* .class
*/
public static final String CLASS_STR = ".class";
/**
* 存储bean单例
*/
public final static Map<String, Object> IOC_MAP = new HashMap<>();
/**
* 构建映射信息
*/
public final static List<Handle> HANDLE_MAPPING = new ArrayList<>();
@Override
public void init() throws ServletException {
//加载流
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("mvc.properties");
System.out.println("----------------- 初始化 -------------------");
super.init();
try {
//1 加载配置文件
doLoadConfigFile(resourceAsStream);
// 2 扫描包
doScanPackage();
// 3 bean实例化和依赖维护
doInitBean();
// 4 维护url和method之间的映射关系
doHandlerMapping();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("------------ ioc容器bean \n " + IOC_MAP);
System.out.println("----------------- 初始化完成 -------------------");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//5 判断请求Url和方法url 执行相应方法
String requestURI = req.getRequestURI();
//遍历请求中得参数顺序
Map<String, String[]> reqParameterMaps = req.getParameterMap();
System.out.println("------- 当前请求的url是 : " + requestURI);
if (Objects.isNull(requestURI) || requestURI.length() == 0) {
return;
}
//5.1 获取handle
Handle handler = getHandler(req);
if (Objects.isNull(handler)){
resp.getWriter().println("404....");
return;
}
//获取执行方法
Method method = handler.getMethod();
Parameter[] parameters = handler.getMethod().getParameters();
//5.2 获取型参数
Map<String, Integer> map1 = handler.getMap();
//5.3 获取参数长度
//设置传递参数
Object[] objects = new Object[parameters.length];
//保证参数顺序和方法中的型参数顺序一致
//遍历除reqest 和 response之外的参数 并且填充到参数数组里
for (Map.Entry<String, String[]> stringEntry : reqParameterMaps.entrySet()) {
//判断请求参数和形参是否参数匹配
//多个参数直接 , 拼接
String value = StringUtils.join(stringEntry.getValue(), ","); // 如同 1,2
if (!map1.containsKey(stringEntry.getKey())) {
continue;
}
//获取型参数索引
Integer index = map1.get(stringEntry.getKey());
//赋值参数
objects[index] = value;
}
int index1 = map1.get(HttpServletRequest.class.getSimpleName());
int index2 = map1.get(HttpServletResponse.class.getSimpleName());
//获取ret对象参数
objects[index1] = req;
//获取resp对象参数
objects[index2] = resp;
try {
String result = (String) method.invoke(handler.getController(), objects);
resp.getWriter().println(result);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private Handle getHandler(HttpServletRequest req) {
// 根据url找到对应得method方法 并且执行调用
for (Handle handle : HANDLE_MAPPING) {
Matcher matcher = handle.getPattern().matcher(req.getRequestURI());
if (matcher.find()) {
return handle;
}
}
return null;
}
/**
* 加载配置文件
*
* @param resourceAsStream
*/
private void doLoadConfigFile(InputStream resourceAsStream) {
try {
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* handlerMapper 处理映射器
* 建立url和方法一致
*/
private void doHandlerMapping() {
if (IOC_MAP.isEmpty()) {
return;
}
//只处理controller层
for (Map.Entry<String, Object> stringObjectEntry : IOC_MAP.entrySet()) {
Class<?> objectEntryValue = stringObjectEntry.getValue().getClass();
//没有包含 注解 跳过
if (!objectEntryValue.isAnnotationPresent(Controller.class)) {
continue;
}
String url = "";
//包含 RequestMapping 注解 就获取url
if (objectEntryValue.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = objectEntryValue.getAnnotation(RequestMapping.class);
url = requestMapping.value();
}
//获取方法
Method[] methods = objectEntryValue.getMethods();
if (methods.length <= 0 || Objects.isNull(methods)) {
continue;
}
for (Method method : methods) {
//如果不包含就跳过
if (!method.isAnnotationPresent(RequestMapping.class)) {
continue;
}
//获取当前方法上 RequestMapping 的注解
RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class);
//构建url
String urls = url + requestMapping.value();
//封装handler
Handle handle = new Handle(stringObjectEntry.getValue(), Pattern.compile(urls), method);
//处理计算参数位置
Parameter[] parameters = method.getParameters();
if (parameters != null && parameters.length > 0) {
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
//如果是 参数名称存本身
handle.getMap().put(parameter.getType().getSimpleName(), i);
}
//如果是tring类型
else {
//String类型存储name
handle.getMap().put(parameter.getName(), i);
}
//其他类型暂时不做判断
}
HANDLE_MAPPING.add(handle);
}
}
}
}
/**
* bean实例化和bean依赖注入关系维护
*
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
*/
private void doInitBean() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
for (String className : stringSet) {
//反射创建对象
Class<?> aClass = Class.forName(className);
//查找 Controller 注解
Controller annotation = aClass.getAnnotation(Controller.class);
if (Objects.nonNull(annotation)) {
//实例化存入ioc
IOC_MAP.put(aClass.getSimpleName(), aClass.newInstance());
}
//查找 service 注解
Service service = aClass.getAnnotation(Service.class);
if (Objects.nonNull(service)) {
//实例化存入ioc
IOC_MAP.put(aClass.getSimpleName(), aClass.newInstance());
//如果实现类接口 按照接口实例化
Class<?>[] interfaces = aClass.getInterfaces();
if (Objects.nonNull(interfaces) && interfaces.length > 0) {
for (Class<?> anInterface : interfaces) {
//按照接口名进行实例化
IOC_MAP.put(anInterface.getSimpleName(), aClass.newInstance());
}
}
}
}
//依赖注入
for (Map.Entry<String, Object> stringObjectEntry : IOC_MAP.entrySet()) {
Class aClass = stringObjectEntry.getValue().getClass();
//获取所有字段
Field[] declaredFields = aClass.getDeclaredFields();
if (Objects.isNull(declaredFields) && declaredFields.length == 0) {
continue;
}
for (Field field : declaredFields) {
//field.get
//字段含有 Autowired 注解的需要被自动装配对象
Autowired autowired = field.getAnnotation(Autowired.class);
if (Objects.nonNull(autowired)) {
//根据当前key获取需要注入示例对象
//先根据名字注入,如果名字获取不到,再根据类型去注入
String beanName = autowired.name();
if (Objects.isNull(beanName) || beanName.length() == 0) {
beanName = field.getType().getSimpleName();
}
//反射设置值
try {
field.setAccessible(true);
//自动装配 线程不安全,Spring中默认单例
field.set(stringObjectEntry.getValue(), IOC_MAP.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 包扫描获取字节码对象
*
* @throws UnsupportedEncodingException
*/
private void doScanPackage() throws UnsupportedEncodingException {
//获取mvc包路径
String packName = properties.getProperty("mvc.path");
if (Objects.isNull(packName) || packName.length() == 0) {
throw new RuntimeException("无效的包路径");
}
packName = packName.replace(".", File.separator);
URL resource = this.getClass().getClassLoader().getResource(packName);
String path = resource.getPath();
//解析中文
String filePath = URLDecoder.decode(path, "UTF-8");
//解析包成java权限定命名com
parseFilePackName(packName, stringSet, filePath);
}
/**
* 递归处理路径下文件夹是否包含文件夹,如不包含则获取当前类的权限定命名存入set中
*
* @param packName
* @param classNameSet
* @param path
*/
public static void parseFilePackName(String packName, Set<String> classNameSet, String path) {
File packNamePath = new File(path);
if (!packNamePath.isDirectory() || !packNamePath.exists()) {
return;
}
//递归路径下所有文件和文件夹
for (File file : packNamePath.listFiles()) {
boolean directory = file.isDirectory();
String classNamePath = packName + File.separator + file.getName().replace(File.separator, ".");
if (directory) {
parseFilePackName(classNamePath, classNameSet, file.getPath());
} else if (file.isFile() && file.getName().endsWith(CLASS_STR)) {
//存入set
classNameSet.add(classNamePath.replace(File.separator, ".").replace(CLASS_STR, ""));
}
}
}
}
测试
启动tomcat
首先输入http://localhost:8080/kkkkk
可以看到首次请求容器初始化完成 各种bean实例化以及依赖维护.handler封装完成
请求的这个地址不存在,返回 404....
在这里插入图片描述然后我们再输入自己的地址 http://localhost:8080/api/test?name=123&name=000
可以看到
网友评论