理解并手写Spring MVC框架
一、前言
Spring框架是大多Java程序员的必修课程,而SpringMVC是里面的重头戏,它大大的简化了Servlet的繁琐操作,让开发人员得以用更多的时间去处理业务。
SpringMVC是一款经典的三层架构模式,M为Model(模型),V为View(视图),C为Controlle(控制器)。MVC是一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。
二、SpringMVC流程
流程简述:
(1)用户通过浏览器或其他途径发起请求。
(2)请求经过前端控制器DispatcherServlet,并根据请求的URI匹配到对应的处理映射器。
(3)处理映射器返回HandlerExecutionChain实体给DispatcherServlet,内部封装了拦截器HandlerInterceptor、Object类型的具体处理器handler。
(4)系统启动时,初始化了HandlerAdapter的List的集合,此步骤循环List集合,调用HandlerAdapter的support方法,找到handler支持的HandlerAdapter处理适配器。
(5)由HandlerAdapter处理参数并调用处理器的方法。
(6)返回结果是ModelAndView,即视图。其内部封装了view、model、status。
(7)将处理得到的ModelAndView返回给DispatcherServlet。
(8)将ModelAndView交由专业的视图解析器进行解析,得到页面位置。
(9)返回结果给DispatcherServlet。
(10)将数据渲染到页面。
(11)响应请求。
源码核心部分概览
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
- initMultipartResolver(context)
初始化文件上传解析功能,将普通HttpServletRequest包装成MutipartHttpServletRequest,此类可通过getFile获取到文件集合。
- initLocaleResolver(context)
初始化与当地区域有关的设置,如视图解析器与国际化资源的配置。
- initThemeResolver(context)
初始化主题解析,如消息国际话得配置,每个主题对应 一个properties文件。
- initHandlerMappings(context)
初始化HandlerMappings,用来获取对应的handler与interceptor。
- initHandlerAdapters(context)
初始化处理适配器,用来执行具体方法。
- initHandlerExceptionResolvers(context)
对异常情况进行处理。
- initRequestToViewNameTranslator(context)
从请求中获取视图名称。
- initViewResolvers(context)
初始化视图解析器,将ModelAndView渲染成页面。
- initFlashMapManager(context)
主要用处在于传递重定向参数。
三、手写Spring MVC
项目目录
源码地址:
https://github.com/hanguilin/custom-springmvc
环境配置:
maven项目中引入Servlet需要的jar包
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
其他jar包(本人写代码用到的一些包)
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
注解类:
建立SpringMVC中常用注解@Controller、@RequstMapping、@Autowired、@Qualifier、@Service、@RequestParam的替代注解。区别为在自己的注解中给名字添加了Custom前缀。各个注解的区别在于注解地方,如类、方法、字段上不同。
CustomController.java
package com.hgl.mvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 控制层注解
* @author guilin
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface CustomController {
String value() default "";
}
CustomRequestMapping.java
package com.hgl.mvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 请求路径注解
* @author guilin
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface CustomRequestMapping {
String value();
}
CustomAutowired.java
package com.hgl.mvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自动注入
* @author guilin
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface CustomAutowired {
String value() default "";
}
CustomQualifer.java
package com.hgl.mvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 按名称自动注入
* @author guilin
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface CustomQualifer {
String value();
}
CustomService.java
package com.hgl.mvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 业务层注解
* @author guilin
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface CustomService {
String value() default "";
}
CustomRequestParam.java
package com.hgl.mvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 参数注解
* @author guilin
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.PARAMETER)
public @interface CustomRequestParam {
String value() default "";
}
配置文件
在classpath下建立spring-mvc.properties,指定扫描包路径
spring.scanner.base.package=com.hgl
核心配置类
SpringMVC核心配置类DispatcherServlet.java,替代类为CustomDispatcherServlet.java
package com.hgl.mvc.servlet;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hgl.mvc.annotation.CustomController;
import com.hgl.mvc.annotation.CustomQualifer;
import com.hgl.mvc.annotation.CustomRequestMapping;
import com.hgl.mvc.annotation.CustomService;
import com.hgl.mvc.resolver.ArgumentResolver;
public class CustomDispatcherServlet extends HttpServlet{
private static final Logger LOGGER = LoggerFactory.getLogger(CustomDispatcherServlet.class);
private static final long serialVersionUID = 1L;
private Properties contextConfig = new Properties();
// 所有扫描类
private List<String> classes = new ArrayList<String>();
// 存放bean的容器ioc
private Map<String, Object> context = new HashMap<String, Object>();
// 存放参数解析器
private Map<String, ArgumentResolver> argumentResolverMap = new HashMap<String, ArgumentResolver>();
// 根据请求url找到具体的处理器
private List<CustomHandlerMapping> handlerMapping = new ArrayList<CustomHandlerMapping>();
private List<CustomHandlerAdapter> handlerAdapter = new ArrayList<CustomHandlerAdapter>();
public CustomDispatcherServlet() {
LOGGER.info("CustomDispatcherServlet()...");
}
@Override
public void init(ServletConfig config) throws ServletException {
// 加载配置文件
initConfig(config.getInitParameter("spring-mvc"));
// 扫描类
initBaseScanPackage(contextConfig.getProperty("spring.scanner.base.package"));
// 生成bean实例,注入ioc
initContext();
// 初始化参数解析器
initArgumentResolver();
// 为controller层中service对象注入实例
initInstance();
// 建立URI与处理器的映射
initHandlerMapping();
// 处理器适配器
initHandlerAdapter();
}
private void initConfig(String initParameter) {
InputStream in = this.getClass().getClassLoader().getResourceAsStream(initParameter);
try {
contextConfig.load(in);
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
} finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
}
}
}
}
private void initBaseScanPackage(String basePackage) {
URL resource = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));
String packagePath = resource.getFile();
File packageFile = new File(packagePath);
String[] listFiles = packageFile.list();
for (String filepPath : listFiles) {
File file = new File(packagePath + filepPath);
if(file.isDirectory()) {
initBaseScanPackage(basePackage + "." + filepPath);
}else {
classes.add(basePackage + "." + file.getName());
}
}
}
private void initContext() {
if(classes.isEmpty()) {
LOGGER.error("do scan failed.");
return;
}
for (String className : classes) {
String classPath = className.substring(0, className.lastIndexOf(".class"));
try {
Class<?> clazz = Class.forName(classPath);
String simpleName = clazz.getSimpleName();
if(clazz.isAnnotationPresent(CustomController.class)) {
CustomController controller = clazz.getAnnotation(CustomController.class);
String key = controller.value();
if(StringUtils.isBlank(key)) {
key = toLowerCaseFirstOne(simpleName);
}
Object instance = clazz.newInstance();
context.put(key, instance);
} else if(clazz.isAnnotationPresent(CustomService.class)) {
CustomService service = clazz.getAnnotation(CustomService.class);
String key = service.value();
if(StringUtils.isBlank(key)) {
key = toLowerCaseFirstOne(simpleName);
}
Object instance = clazz.newInstance();
context.put(key, instance);
} else {
continue;
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
LOGGER.error(e.getMessage(), e);
}
}
}
private void initArgumentResolver() {
if(context.isEmpty()) {
return;
}
for(Map.Entry<String, Object> entry : context.entrySet()) {
Object bean = entry.getValue();
// 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口
if(ArgumentResolver.class.isAssignableFrom(bean.getClass())) {
argumentResolverMap.put(entry.getKey(), (ArgumentResolver) bean);
}
}
}
private void initInstance() {
if(context.isEmpty()) {
LOGGER.error("no bean is instanced.");
return;
}
for(Map.Entry<String, Object> entry : context.entrySet()) {
Object bean = entry.getValue();
Class<? extends Object> clazz = bean.getClass();
if(clazz.isAnnotationPresent(CustomController.class)) {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
if(field.isAnnotationPresent(CustomQualifer.class)) {
CustomQualifer qualifer = field.getAnnotation(CustomQualifer.class);
String beanName = qualifer.value();
Object value = context.get(beanName);
try {
if(!field.isAccessible()) {
field.setAccessible(true);
}
field.set(bean, value);
} catch (IllegalArgumentException | IllegalAccessException e) {
LOGGER.error(e.getMessage(), e);
}
}
}
}
}
}
private void initHandlerMapping() {
if(context.isEmpty()) {
LOGGER.error("no bean is instanced.");
return;
}
for(Map.Entry<String, Object> entry : context.entrySet()) {
Object bean = entry.getValue();
Class<? extends Object> clazz = bean.getClass();
if(clazz.isAnnotationPresent(CustomController.class)) {
String classRequestMappingVal = "";
if(clazz.isAnnotationPresent(CustomRequestMapping.class)) {
CustomRequestMapping classRequestMapping = clazz.getAnnotation(CustomRequestMapping.class);
classRequestMappingVal = classRequestMapping.value();
}
Method[] declaredMethods = clazz.getDeclaredMethods();
List<String> uris = new ArrayList<String>();
for (Method method : declaredMethods) {
String methodRequestMappingVal = "";
if(method.isAnnotationPresent(CustomRequestMapping.class)) {
CustomRequestMapping methodRequestMapping = method.getAnnotation(CustomRequestMapping.class);
methodRequestMappingVal = classRequestMappingVal + methodRequestMapping.value();
}
if(StringUtils.isNotBlank(methodRequestMappingVal)) {
if(uris.contains(methodRequestMappingVal)) {
throw new RuntimeException("Duplicate mapping for " + methodRequestMappingVal);
}
handlerMapping.add(new CustomHandlerMapping(bean, method, Pattern.compile(methodRequestMappingVal)));
uris.add(methodRequestMappingVal);
}
}
uris = null;
}
}
}
private void initHandlerAdapter() {
if(context.isEmpty()) {
LOGGER.error("no bean is instanced.");
return;
}
for(Map.Entry<String, Object> entry : context.entrySet()) {
Object bean = entry.getValue();
// 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口
if(CustomHandlerAdapter.class.isAssignableFrom(bean.getClass())) {
handlerAdapter.add((CustomHandlerAdapter) bean);
}
}
}
private String toLowerCaseFirstOne(String s){
if(Character.isLowerCase(s.charAt(0)))
return s;
else
return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
CustomHandlerMapping handler = getHandler(request);
CustomHandlerAdapter handlerAdapter = getHandlerAdapter(handler);
CustomModelAndView modelAndView = handlerAdapter.handle(request, response, handler, argumentResolverMap);
}
private CustomHandlerAdapter getHandlerAdapter(CustomHandlerMapping handler) {
for (CustomHandlerAdapter customHandlerAdapter : handlerAdapter) {
if(customHandlerAdapter.support(handler)) {
return customHandlerAdapter;
}
}
throw new RuntimeException("There is no handlerAdapter for " + handler);
}
private CustomHandlerMapping getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
String path = requestURI.replaceAll(request.getContextPath(), "");
for (CustomHandlerMapping handler : handlerMapping) {
Pattern pattern = handler.getPattern();
Matcher matcher = pattern.matcher(path);
if(matcher.matches()) {
return handler;
}
}
throw new RuntimeException("There is no mapping for " + path);
}
}
处理映射器
CustomHandlerMapping.java
package com.hgl.mvc.servlet;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
/**
* 自定义handlerMapping
*
* @author guilin
*
*/
public class CustomHandlerMapping{
/**
* controller对应的bean
*/
private Object controller;
/**
* 具体处理方法
*/
private Method method;
/**
* 用来验证是否是当前url对应的处理方法
*/
private Pattern pattern;
public CustomHandlerMapping(Object controller, Method method, Pattern pattern) {
super();
this.controller = controller;
this.method = method;
this.pattern = pattern;
}
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;
}
}
处理适配器
CustomHandlerAdapter.java,作为接口,由实现类实现。
package com.hgl.mvc.servlet;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.hgl.mvc.resolver.ArgumentResolver;
/**
* @author guilin
* 自定义适配器
*
*/
public interface CustomHandlerAdapter {
/**
* 是否支持处理
* 是则调用本类handle方法
*
* @param handler 处理器
* @return boolean
*/
public boolean support(Object handler);
/**
* 具体处理方法
*
* @param request
* @param response
* @param handler
* @param argumentResolverMap
* @return CustomModelAndView
*/
public CustomModelAndView handle(HttpServletRequest request, HttpServletResponse response, CustomHandlerMapping handler,
Map<String, ArgumentResolver> argumentResolverMap);
}
实现类CustomSimpleHandlerAdapter.java
package com.hgl.mvc.servlet;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hgl.mvc.annotation.CustomService;
import com.hgl.mvc.resolver.ArgumentResolver;
/**
* 对处理适配器的实现
*
* @author guilin
*
*/
@CustomService("customSimpleHandlerAdapter")
public class CustomSimpleHandlerAdapter implements CustomHandlerAdapter{
private static final Logger LOGGER = LoggerFactory.getLogger(CustomDispatcherServlet.class);
@Override
public CustomModelAndView handle(HttpServletRequest request, HttpServletResponse response, CustomHandlerMapping handler,
Map<String, ArgumentResolver> argumentResolverMap) {
Method method = handler.getMethod();
Object controller = handler.getController();
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] args = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterClass = parameterTypes[i];
for (Map.Entry<String, ArgumentResolver> entry : argumentResolverMap.entrySet()) {
ArgumentResolver argumentResolver = entry.getValue();
if(argumentResolver.support(parameterClass, i, method)) {
Object resolver = argumentResolver.argumentResolver(request, response, parameterClass, i, method);
args[i] = resolver;
break;
}
}
}
try {
method.invoke(controller, args);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOGGER.error(e.getMessage(), e);
}
return new CustomModelAndView();
}
@Override
public boolean support(Object handler) {
// 暂定实现为true
return true;
}
}
参数解析器
ArgumentResolver.java,作为上层接口,具体由实现类实现。
package com.hgl.mvc.resolver;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 参数解析器
* @author guilin
*
*/
public interface ArgumentResolver {
public boolean support(Class<?> type, int paramIndex, Method method);
public Object argumentResolver(HttpServletRequest request, HttpServletResponse response,
Class<?> type, int paramIndex, Method method);
}
HttpServletRequestArgumentResolver.java,用于解析HttpServletRequest参数
package com.hgl.mvc.resolver;
import java.lang.reflect.Method;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.hgl.mvc.annotation.CustomService;
/**
* HttpServletRequest参数解析器
* @author guilin
*
*/
@CustomService("httpServletRequestArgumentResolver")
public class HttpServletRequestArgumentResolver implements ArgumentResolver {
@Override
public boolean support(Class<?> type, int paramIndex, Method method) {
return ServletRequest.class.isAssignableFrom(type);
}
@Override
public Object argumentResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type,
int paramIndex, Method method) {
return request;
}
}
HttpServletResponseArgumentResolver.java,用于解析HttpServletResponse参数
package com.hgl.mvc.resolver;
import java.lang.reflect.Method;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.hgl.mvc.annotation.CustomService;
/**
* HttpServletResponse参数解析器
* @author guilin
*
*/
@CustomService("httpServletResponseArgumentResolver")
public class HttpServletResponseArgumentResolver implements ArgumentResolver {
@Override
public boolean support(Class<?> type, int paramIndex, Method method) {
return ServletResponse.class.isAssignableFrom(type);
}
@Override
public Object argumentResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type,
int paramIndex, Method method) {
return response;
}
}
RequestParamArgumentResolver.java,用于解析CustomRequestParam定义的参数
package com.hgl.mvc.resolver;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.hgl.mvc.annotation.CustomRequestParam;
import com.hgl.mvc.annotation.CustomService;
/**
* RequestParam参数解析器
* @author guilin
*
*/
@CustomService("requestParamArgumentResolver")
public class RequestParamArgumentResolver implements ArgumentResolver {
@Override
public boolean support(Class<?> type, int paramIndex, Method method) {
Annotation[][] annotations = method.getParameterAnnotations();
Annotation[] currentField = annotations[paramIndex];
for (Annotation annotation : currentField) {
if(CustomRequestParam.class.isAssignableFrom(annotation.getClass())) {
return true;
}
}
return false;
}
@Override
public Object argumentResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type,
int paramIndex, Method method) {
Annotation[][] annotations = method.getParameterAnnotations();
Annotation[] currentField = annotations[paramIndex];
for (Annotation annotation : currentField) {
if(CustomRequestParam.class.isAssignableFrom(annotation.getClass())) {
CustomRequestParam requestParam = (CustomRequestParam) annotation;
String parameterName = requestParam.value();
String parameterVal = request.getParameter(parameterName);
return parameterVal;
}
}
return null;
}
}