⼿写spring mvc 框架
前言
本文通过手写实现 类似spring mvc的简单框架。
spring mvc 请求流程图
思路分析
要实现自定义的spring mvc,最核心的就是怎么让请求在doGet和doPost方法进行处理时,根据请求的路径找到对应的方法去执行。
maven 引入jar配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-myMvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-myMvc Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<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>
</dependencies>
<build>
<plugins>
<!--编译插件定义编译细节-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>utf-8</encoding>
<!--告诉编译器,编译的时候记录下形参的真实名称-->
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<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>
</build>
</project>
自定义注解
自定义注解类实现容器注入,被注解的类都需要被自定义的容器所管理
MyAutowired 用于属性类类型对象注入
package com.my.test.annotations;
import java.lang.annotation.*;
/**
* @author Administrator
*/
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
/**
* Declares whether the annotated dependency is required.
* <p>Defaults to {@code true}.
*/
boolean required() default true;
String value() default "";
}
MyComponent 用于标识bean是否需要被容器管理
package com.my.test.annotations;
import java.lang.annotation.*;
/**
* @author Administrator
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyComponent {
String value() default "";
}
MyComponent 用于标识被容器管理bean是控制类
/**
* @author Administrator
*/
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@MyComponent
public @interface MyController {
String value() default "";
}
MyRequestMapping 用于维护路径与控制类或者方法的映射关系
import java.lang.annotation.*;
/**
* @author Administrator
*/
@Documented
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
String value() default "";
}
MySecurity 用于对权限的控制
/**
* @author Administrator
* 该注解可以用在类上也可以用在方法上
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MySecurity {
String[] value() default "";
String name() ;
}
MyService 用于标识被容器管理bean是接口的实现类
/**
* @author Administrator
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@MyComponent
public @interface MyService {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
String value() default "";
}
搭建三层架构 持久层不重点处理简单返回
DemoController
package com.my.test.controller;
import com.my.test.annotations.MyAutowired;
import com.my.test.annotations.MyController;
import com.my.test.annotations.MyRequestMapping;
import com.my.test.annotations.MySecurity;
import com.my.test.service.IDemoService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Administrator
*/
@MyController
@MyRequestMapping("/demo")
@MySecurity(name = "username",value ={"lisi","zhangsan","zhaoliu"})
public class DemoController {
@MyAutowired
private IDemoService demoService;
/**
* URL: /demo/query?name=李四
* @param request
* @param response
* @param username
* @return
*/
@MyRequestMapping("/query1")
@MySecurity(name = "username",value ={"zhangsan","zhaoliu"})
public String query1(HttpServletRequest request, HttpServletResponse response,String username) {
return demoService.get(username);
}
/**
* URL: /demo/query?name=张三
* @param request
* @param response
* @param username
* @return
*/
@MyRequestMapping("/query2")
@MySecurity(name = "username",value = {"zhangsan","lisi"})
public String query2(HttpServletRequest request, HttpServletResponse response,String username) {
return demoService.get(username);
}
/**
* URL: /demo/query?name=赵六
* @param request
* @param response
* @param username
* @return
*/
@MyRequestMapping("/query3")
@MySecurity(name = "username",value ={"lisi","zhaoliu"})
public String query(HttpServletRequest request, HttpServletResponse response,String username) {
return demoService.get(username);
}
/**
* 都能访问
* @param request
* @param response
* @param username
* @return
*/
@MyRequestMapping("/query4")
public String query4(HttpServletRequest request, HttpServletResponse response,String username) {
return demoService.get(username);
}
}
接口和实现类的编写
/**
* @author Administrator
*/
public interface IDemoService {
String get(String name);
}
package com.my.test.service.impl;
import com.my.test.annotations.MyService;
import com.my.test.service.IDemoService;
/**
* @author Administrator
*/
@MyService("demoService")
public class DemoServiceImpl implements IDemoService {
@Override
public String get(String name) {
System.out.println("service 实现类中的name参数:" + name) ;
return name+">>welcome";
}
}
自定义MyDispatcherServlet
MyDispatcherServlet类需要做什么的分析
第一步:配置web.xml文件 配置Servlet处理器是我们自定义的MyDispatcherServlet 以及拦截路径的配置和初始化需要加载文件的配置
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>myDispatcherServlet</servlet-name>
<servlet-class>com.my.test.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>springmvc.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>myDispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
第二步:编写配置文件springmvc.properties 配置需要扫描的包路径
scanPackage=com.my.test
第三步:重写init(ServletConfig config) 方法实现 我们需要的逻辑
// 开始重写初始化方法加入自己的逻辑
// 1 加载配置⽂件 springmvc.properties 需要扫描的包路径
String contextConfigLocation = config.getInitParameter(“contextConfigLocation”);
// 拿出配置文件中配置的路径值
String path = doLoadConfig(contextConfigLocation);
// 2. 找到需要扫描的包中需要放入ioc容器中的类的全路径 存放在classPath 集合中
doScan(path);
// 3. 解析出bean并实例化放入自定义ioc容器中
doInstance();
// 4. 维护bean之间的关系 也就是通过set方法进行属性的注入
doAutowired();
// 5. 开始绑定请求路径与方法的对应关系 在后面请求的时候就能拿到需要执行的方法对象 通过反射执行调用。
// 权限校验是就近原则 其实就是在调用方法前 判断传入的参数是否在能定义的数组中存在,任意存在一个就能访问
initHandlerMapping();
// 等待请求 处理请求
MyDispatcherServlet类的编写
在写之前我们要明白,所有的请求都是通过servlet来进行处理的,在spring mvc中是DispatcherServlet来进行请求的处理和转发,DispatcherServlet extends FrameworkServlet 然后 FrameworkServlet extends HttpServletBean 然后HttpServletBean extends HttpServlet所以最终都是落到了HttpServlet上。因此我们需要写一个类继承HttpServlet
package com.my.test;
import com.my.test.annotations.*;
import com.my.test.pojo.Handler;
import com.my.test.pojo.Visit;
import org.apache.commons.lang3.StringUtils;
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.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 自定义 DispatcherServlet 来进行handlerMapping 和容器初始化
* @author HuYuanLiang
* @since 2021/5/29
*/
public class MyDispatcherServlet extends HttpServlet {
/**
* 封装流数据到properties中
*/
Properties properties = new Properties();
/**
* ioc容器
*/
public Map<String,Object> iocMap = new ConcurrentHashMap<>(16);
/**
* 类全路径集合
*/
public List<String> classPath = new ArrayList<>(10);
/**
* handler处理器集合类
*/
public List<Handler> handlerList = new ArrayList<>(10);
@Override
public void init(ServletConfig config) throws ServletException {
try {
// 开始重写初始化方法加入自己的逻辑
// 1 加载配置⽂件 springmvc.properties
String contextConfigLocation =
config.getInitParameter("contextConfigLocation");
String path = doLoadConfig(contextConfigLocation);
// 2. 找到需要扫描的包中的类
doScan(path);
// 3. 解析出bean放入自定义ioc容器中
doInstance();
// 4. 维护bean之间的关系
doAutowired();
// 5. 开始绑定请求路径与方法的对应关系
initHandlerMapping();
// 等待请求 处理请求
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 开始生成请求路径与处理器的映射
* 分析 如果我们直接定义一个map存放 路径与对应方法对象的映射 但是
*/
private void initHandlerMapping() {
// 如果不包含任何元素
if (iocMap.isEmpty()) {
return;
}
for (Object obj : iocMap.values()) {
String[] visit = null;
String visitKey = null;
Class<?> aClass = obj.getClass();
//请求路径
StringBuilder path = new StringBuilder();
// 如果类上面指定了路径
if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
path.append(aClass.getAnnotation(MyRequestMapping.class).value());
}
// 如果类上面有权限 先拿出类中的权限
if (aClass.isAnnotationPresent(MySecurity.class)) {
visit = aClass.getAnnotation(MySecurity.class).value();
visitKey = aClass.getAnnotation(MySecurity.class).name();
}
// 获取到类的所有方法对象
Method[] declaredMethods = aClass.getDeclaredMethods();
for (int i = 0; i < declaredMethods.length; i++) {
Method declaredMethod = declaredMethods[i];
if (declaredMethod.isAnnotationPresent(MyRequestMapping.class)) {
Handler handler = new Handler();
// 获取到方法上面的路径 开始拼接 /demo/query1
String value = declaredMethod.getAnnotation(MyRequestMapping.class).value();
// 获取方法上的权限 如果有就用方法上的为准 没有就用类上面的
if (declaredMethod.isAnnotationPresent(MySecurity.class)){
String[] methodVisitArray = declaredMethod.getAnnotation(MySecurity.class).value();
String methodKey = declaredMethod.getAnnotation(MySecurity.class).name();
Visit methodVisit = new Visit(methodKey,methodVisitArray);
handler.setVisit(methodVisit);
} else if(visit!=null && visitKey!=null){
Visit methodVisit = new Visit(visitKey,visit);
handler.setVisit(methodVisit);
}
// 设置类
handler.setControlObject(obj);
// 设置方法对象
handler.setMethod(declaredMethod);
// 设置路径
handler.setPattern(Pattern.compile(path+value));
Map<String,Integer> paramIndexMapping = new HashMap<>(16);
// 设置参数的角标和值 暂时不考虑非常复杂的情况
// 获取方法中需要传入的参数数组
Parameter[] parameters = declaredMethod.getParameters();
for (int i1 = 0; i1 < parameters.length; i1++) {
Parameter parameter = parameters[i1];
// 检查参数是否是封装后的对象
if (parameter.isAnnotationPresent(MyRequestBody.class) || parameter.getType()== HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
paramIndexMapping.put(parameter.getType().getSimpleName(),i1);
} else {
paramIndexMapping.put(parameter.getName(),i1);
}
}
handler.setParamIndexMapping(paramIndexMapping);
handlerList.add(handler);
}
}
}
}
/**
* 维护类之间关系
*/
private void doAutowired() throws Exception {
// 如果不包含任何元素
if (iocMap.isEmpty()) {
return;
}
for (Object obj : iocMap.values()) {
Class<?> aClass = obj.getClass();
// 获取到类的所有属性对象
Field[] declaredFields = aClass.getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
Field declaredField = declaredFields[i];
// 判断属性对象头上是否包含注解MyAutowired
if (declaredField.isAnnotationPresent(MyAutowired.class)) {
MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class);
// 需要注入
if (annotation.required()) {
String name = annotation.value();
// 如果没有指定名称 按照接口类型注入
if (name.equals("")) {
// 获取到接口的全路径类名
name = declaredField.getType().getName();
}
// 从ioc中拿出来 如果为null则最开始实例化没有实例化完我们所需要的所有类
Object o = iocMap.get(name);
if (o==null) {
throw new RuntimeException("需要类<<"+name+">>不存在!");
}
declaredField.setAccessible(true);
declaredField.set(obj,o);
}
}
}
}
}
/**
* 初始化所有bean
*/
private void doInstance() throws Exception {
// 开始初始化话所有的bean
for (String s : classPath) {
Class<?> aClass = Class.forName(s);
// 如果是MyController 注解 如果有别名就用别名 没有就用首字母小写的类名
if (aClass.isAnnotationPresent(MyController.class)) {
MyController annotation = aClass.getAnnotation(MyController.class);
saveToIoc(annotation.value(),aClass);
} else if (aClass.isAnnotationPresent(MyService.class)) {
MyService annotation = aClass.getAnnotation(MyService.class);
saveToIoc(annotation.value(),aClass);
// 一般service都会实现接口 将接口类名称和实现类存入ioc中方便后面注入和生成代理对象
Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
// 将接口类名称和实现类存入ioc中方便后面注入和生成代理对象
iocMap.put(anInterface.getName(),aClass.getDeclaredConstructor().newInstance());
}
}
}
}
/**
* 获取在ioc容器中的名称
* @param className
* @param aClass
*/
private void saveToIoc(String className, Class<?> aClass) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
if (className.equals("")) {
className = aClass.getSimpleName().substring(0,1).toLowerCase()+aClass.getSimpleName().substring(1);
}
iocMap.put(className, aClass.getDeclaredConstructor().newInstance());
}
/**
* 加载配置并返回配置中的特定值的value
* @param contextConfigLocation
* @return
* @throws IOException
*/
private String doLoadConfig(String contextConfigLocation) throws IOException {
if (contextConfigLocation.equals("")){
return contextConfigLocation;
}
// 获取类加载器
ClassLoader classLoader = this.getClass().getClassLoader();
// 将文件加载到内存中
InputStream resourceAsStream = classLoader.getResourceAsStream(contextConfigLocation);
// 封装流数据到properties中
properties.load(resourceAsStream);
// 读取到需要扫描的包路径
return properties.getProperty("scanPackage");
}
/**
* 根据路径扫描出包下的类
* @param path
* 配置的com.my.test路径 我们需要找到真实的磁盘上的路径 然后查询是不是文件夹 递归进行解析查找
*/
private void doScan(String path) {
if (path.equals("")){
return;
}
// 获取当前加载的磁盘根路径 path=com.my.test
String s = Thread.currentThread().getContextClassLoader().getResource("").getPath() + path.replaceAll("\\.","/");
File file = new File(s);
// 获取所有的文件 开始循环
File[] files = file.listFiles();
for (File file1 : files) {
// 如果是目录开始递归
if (file1.isDirectory()) {
// 开始组装新的路径用于递归
// newPath = com.my.test.annotations
String newPath = path+"."+file1.getName();
doScan(newPath);
} else if (file1.getName().endsWith(".class")) {
// 如果获取到的是class 类
// 获取出类的路径用于后面初始化bean com.my.test.pojo.Handler.class
classPath.add(path+"."+file1.getName().replace(".class",""));
}
}
}
@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 {
// 获取到请求路径 去
String requestURI = req.getRequestURI();
Handler handler = getHandler(requestURI);
if(handler == null) {
resp.getWriter().write("404 not found");
return;
}
// 获取定义好的参数的key和位置
Map<String, Integer> mapping = handler.getParamIndexMapping();
// 获取出方法需要的参数类型数组
Class<?>[] parameters = handler.getMethod().getParameterTypes();
// 创建一个新数组装参数
Object[] objects=new Object[parameters.length];
// 获取出请求的map
Map<String, String[]> parameterMap = req.getParameterMap();
for (String s : parameterMap.keySet()) {
if (mapping.containsKey(s)){
objects[mapping.get(s)] = StringUtils.join(parameterMap.get(s),",");
}
}
// 判断是否能够访问
boolean haveVisit = canVisit(handler,parameterMap);
if (!haveVisit) {
resp.getWriter().write("403 Forbidden");
return;
}
// 放请求对象
objects[mapping.get(HttpServletRequest.class.getSimpleName())] = req;
// 放响应对象
objects[mapping.get(HttpServletResponse.class.getSimpleName())] = resp;
// 执行请求
try {
Object invoke = handler.getMethod().invoke(handler.getControlObject(), objects);
if (invoke!=null){
resp.getWriter().write(invoke.toString());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
*进行权限校验
* @param handler
* @param parameterMap
* @return
*/
private boolean canVisit(Handler handler, Map<String, String[]> parameterMap) {
// 权限对象
Visit visit = handler.getVisit();
// 不需要校验
if (visit==null) {
return true;
}
// 校验值不存在!
if (parameterMap.get(visit.getName())==null) {
return false;
}
// 思路 待校验中有一个匹配上就返回true
// 待校验的
HashSet<String> visitSet=new HashSet<>(Arrays.asList(parameterMap.get(visit.getName())));
// 有权限的
Set<String> visitList = new HashSet<>(Arrays.asList(visit.getVisitArray()));
List<String> collect = visitSet.stream().filter(n -> !visitList.add(n)).collect(Collectors.toList());
// 没匹配上
if (collect.isEmpty()) {
return false;
}
return true;
}
/**
* 根据请求路径获取处理器
* @param requestURI
* @return
*/
private Handler getHandler(String requestURI) {
for (Handler handler : handlerList) {
if (handler.getPattern().matcher(requestURI).matches()) {
return handler;
}
}
return null;
}
}
封装的Handler请求处理对象
package com.my.test.pojo;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 请求处理器
* @author HuYuanLiang
* @since 2021/5/29
*/
public class Handler {
/**
* 匹配的url
*/
private Pattern pattern;
/**
* / 参数顺序,是为了进⾏参数
* 绑定,key是参数名,value代表是第⼏个参数 <name,2>
*/
private Map<String,Integer> paramIndexMapping;
/**
* 反射方法对象
*/
private Method method;
/**
* controlObject 对象
*/
private Object controlObject;
/**
* 能访问的权限对象
*/
private Visit visit;
public Visit getVisit() {
return visit;
}
public void setVisit(Visit visit) {
this.visit = visit;
}
public Pattern getPattern() {
return pattern;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public Map<String, Integer> getParamIndexMapping() {
return paramIndexMapping;
}
public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
this.paramIndexMapping = paramIndexMapping;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Object getControlObject() {
return controlObject;
}
public void setControlObject(Object controlObject) {
this.controlObject = controlObject;
}
}
封装的Visit权限对象
package com.my.test.pojo;
/**
* @author Administrator
*/
public class Visit {
public Visit() {
}
public Visit(String name, String[] visitArray) {
this.name = name;
this.visitArray = visitArray;
}
/**
* 需要检验的key
*/
private String name;
/**
* 能访问的权限 用户名数组
*/
private String[] visitArray;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String[] getVisitArray() {
return visitArray;
}
public void setVisitArray(String[] visitArray) {
this.visitArray = visitArray;
}
}