了解SpringMVC的执行流程以及应用后,我们可以自己自定义一个MVC框架
一、手写MVC框架
1.1 自定义MVC框架分析步骤
- tomcat加载web.xml,前端控制器DispatcherServlet加载指定的配置文件springmvc.xml
- 进行包扫描,扫描注解@Controller,@Service,@Autowired,@RequestMapping
- 初始化ioc容器,bean的初始化,以及维护依赖关系
- SpringMVC相关组件的初始化,建立url和Handler方法之间的映射关系-HandlerMapping(处理器映射器)
- 等待请求,处理请求
1.2 环境准备
- pom.xml
<?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>com.lagou.edu</groupId>
<artifactId>springMVC</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>springMVC Maven Webapp</name>
<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>
<!--servlet 依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</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>
<!--tomcat7 插件-->
<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>
- LagouAutowried注解
package com.lagou.edu.mvcframework.Annotaions;
import java.lang.annotation.*;
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouAutowried {
String value() default "";
}
- LagouController注解
package com.lagou.edu.mvcframework.Annotaions;
import java.lang.annotation.*;
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouController {
String value() default "";
}
- LagouRequestMapping注解
package com.lagou.edu.mvcframework.Annotaions;
import java.lang.annotation.*;
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouRequestMapping {
String value() default "";
}
- LagouService注解
package com.lagou.edu.mvcframework.Annotaions;
import java.lang.annotation.*;
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouService {
String value() default "";
}
- springmvc.properties配置文件
scanPackage=com.lagou.edu.demo
- web.xml(配置自定义前端控制器)
<!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>lagoumvc</servlet-name>
<servlet-class>com.lagou.edu.mvcframework.servlet.LgDispatcherServlet</servlet-class>
<!--加载配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>springmvc.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<!--配置进入前端控制器的请求-->
<servlet-name>lagoumvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
1.3 自定义DispatcherServlet编写
先编写一些空壳方法,然后我们再一点点的填充里面的实现
package com.lagou.edu.mvcframework.servlet;
import com.lagou.edu.mvcframework.Annotaions.LagouController;
import com.lagou.edu.mvcframework.Annotaions.LagouService;
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.util.*;
/**
* 自定义前端控制器
*/
public class LgDispatcherServlet extends HttpServlet {
private Properties properties = new Properties();
private List<String> classNames = new ArrayList<>();
private Map<String, Object> ioc = new HashMap<>();
/**
* 初始化
*
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
//1. 加载配置文件
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
doLoadConfig(contextConfigLocation);
//2. 扫描相关的类,扫描注解
doScan(properties.getProperty("scanPackage"));
//3. 初始化ioc容器,基于注解的形式
doInstance();
//4. 维护bean之间的依赖关系
doAutoWired();
//5. 构造一个HandlerMapping处理器适配器,将配置好的url和handler方法建立映射
initHandlerMapping();
System.out.println("lagoumvc 初始化完成 ==============");
}
/**
* 构造一个HandlerMapping处理器适配器
*/
private void initHandlerMapping() {
}
/**
* 依赖注入
*/
private void doAutoWired() {
}
/**
* 实例化ioc容器
*/
private void doInstance() {
}
/**
* 扫描包
*
* @param scanPackage
*/
private void doScan(String scanPackage) {
}
/**
* 加载配置文件
*
* @param contextConfigLocation
*/
private void doLoadConfig(String contextConfigLocation) {
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
/**
* 处理具体业务逻辑
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
- doScan方法
通过配置文件拿到的文件路径,访问目录下所有文件,以.class结尾的获取全限定类名存储起来
/**
* 扫描包
*
* @param scanPackage
*/
private void doScan(String scanPackage) {
//获取到路径
String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
File pack = new File(scanPackagePath);
File[] files = pack.listFiles();
for (File file : files) {
if (file.isDirectory()) {
//递归
doScan(scanPackage + "." + file.getName());
} else if (file.getName().endsWith(".class")) {
//class文件 获取到类的全限定类名,存储起来
String className = scanPackage + "." + file.getName().replaceAll(".class", "");
classNames.add(className);
}
}
}
- doInstance方法
通过全限定类名进行实例化,获取其类中注解,Controller以类名首字母小写放入容器,Service则以类名和类型放入容器方便匹配
/**
* 实例化ioc容器
*/
private void doInstance() {
if (classNames.isEmpty()) {
return;
}
for (String className : classNames) {
try {
//通过全限定类名获取字节码文件,并实例化
Class<?> aClass = Class.forName(className);
Object obj = aClass.newInstance();
if (aClass.isAnnotationPresent(LagouController.class)) {
//controller,简单点只取类名的首字母小写作为id,存入ioc容器中
String simpleName = aClass.getSimpleName();
//首字母小写
simpleName = lowerFirst(simpleName);
ioc.put(simpleName, obj);
}else if (aClass.isAnnotationPresent(LagouService.class)){
//service
LagouService annotation = aClass.getAnnotation(LagouService.class);
//获取注解的值
String value = annotation.value();
if (!"".equals(value.trim())) {
//注解值作为id
ioc.put(value, obj);
}else {
//类名首字母小写作为id
value = lowerFirst(aClass.getSimpleName());
ioc.put(value, obj);
}
//service往往是有接口的,还需根据接口名注入
Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
ioc.put(anInterface.getName(), obj);
}
}else {
continue;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 首字母转小写
*
* @param str
* @return
*/
private String lowerFirst(String str) {
char[] chars = str.toCharArray();
if ('A' <= chars[0] && chars[0] <= 'Z') {
chars[0] += 32;
}
return String.valueOf(chars);
}
- doAutoWired
遍历容器中所有对象,获取属性值,以及属性值的注解,进行属性赋值,完成依赖维护
/**
* 依赖注入
*/
private void doAutoWired() {
if (ioc.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
Class<?> aClass = value.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (!declaredField.isAnnotationPresent(LagouAutowried.class)) {
//没有注解
continue;
}
LagouAutowried annotation = declaredField.getAnnotation(LagouAutowried.class);
//注解上的值
String annValue = annotation.value();
if ("".equals(annValue.trim())) {
//没有配置注解值,根据类型获取名称
annValue = declaredField.getType().getName();
}
//暴力反射
declaredField.setAccessible(true);
//给属性赋值
try {
declaredField.set(value, ioc.get(annValue));
ioc.put(key, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
- initHandlerMapping
同样的对容器进行遍历,获取类上的url和方法上的url,方法中需要的信息比较多我们创建一个handler来存储method相关信息
/**
* 构造一个HandlerMapping处理器适配器
*/
private void initHandlerMapping() {
if (ioc.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Object value = entry.getValue();
Class<?> aClass = value.getClass();
//没有controller注解
if (!aClass.isAnnotationPresent(LagouController.class)) {
continue;
}
String baseUrl = "";
if (aClass.isAnnotationPresent(LagouRequestMapping.class)) {
//获取到controller上的url
baseUrl = aClass.getAnnotation(LagouRequestMapping.class).value();
}
//获取handler方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (!declaredMethod.isAnnotationPresent(LagouRequestMapping.class)) {
//没有注解不进行处理
continue;
}
LagouRequestMapping annotation = declaredMethod.getAnnotation(LagouRequestMapping.class);
String methodUrl = annotation.value();
//最终拼接的url
String url = baseUrl + methodUrl;
//把method所有信息和url封装成一个Handler
Handler handler = new Handler(value, declaredMethod, Pattern.compile(url));
//计算方法的参数位置信息
Parameter[] parameters = declaredMethod.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
Class<?> type = parameter.getType();
if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
//原生api,参数名称直接写类名
handler.getParamIndexMapping().put(type.getSimpleName(), i);
} else {
//参数名
handler.getParamIndexMapping().put(parameter.getName(), i);
}
}
//建立起url和method的映射关系,并存储起来
handlerMapping.add(handler);
}
}
}
package com.lagou.edu.mvcframework.pojo;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
public class Handler {
/**
* 实例化的Controller
*/
private Object controller;
/**
* Controller中的方法
*/
private Method method;
/**
* spring中url是支持正则的,方便到时候匹配url
*/
private Pattern pattern;
/**
* 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2>
*/
private Map<String, Integer> paramIndexMapping;
public Handler(Object controller, Method method, Pattern pattern) {
this.controller = controller;
this.method = method;
this.pattern = pattern;
this.paramIndexMapping = new HashMap<>();
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
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;
}
}
- doPost
这里也是执行业务方法的地方
/**
* 处理具体业务逻辑
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取handler
Handler handler = getHandler(req);
//handler为空,直接404
resp.setHeader("Content-type", "text/html;charset=UTF-8");
if (handler == null) {
resp.getWriter().write("404 not found");
return;
}
//获取需要执行的handler方法
Method method = handler.getMethod();
//获取执行方法的参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
//创建一个长度一致的数组,存放执行方法需要的参数
Object[] parameterList = new Object[parameterTypes.length];
//获取请求中的所有参数
Map<String, String[]> parameterMap = req.getParameterMap();
//之前存好的参数与位置映射
Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
//将多个参数通过逗号拼接在一起
String value = String.join(",", entry.getValue());
if (paramIndexMapping.containsKey(entry.getKey())) {
//获取位置关系映射,并赋值
Integer index = paramIndexMapping.get(entry.getKey());
parameterList[index] = value;
}
}
//HttpServletRequest和HttpServletResponse单独处理
int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
parameterList[requestIndex] = req;
int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
parameterList[responseIndex] = resp;
try {
//执行方法
Object invoke = method.invoke(handler.getController(), parameterList);
resp.getWriter().write(invoke.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据请求url从HandlerMapping获取handler
*
* @param req
* @return
*/
private Handler getHandler(HttpServletRequest req) {
if (handlerMapping.isEmpty()) {
return null;
}
String url = req.getRequestURI();
for (Handler handler : handlerMapping) {
Matcher matcher = handler.getPattern().matcher(url);
if (matcher.matches()) {
return handler;
}
}
return null;
}
总结: 这里的难点主要是在url和handler方法之间的映射,需要考虑的东西较多.参数之间的处理较为麻烦